@shware/workflow 3.2.0 → 4.0.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 +100 -29
- package/dist/apns.cjs +113 -26
- package/dist/apns.cjs.map +1 -1
- package/dist/apns.d.cts +52 -6
- package/dist/apns.d.cts.map +1 -1
- package/dist/apns.d.mts +52 -6
- package/dist/apns.d.mts.map +1 -1
- package/dist/apns.mjs +111 -27
- package/dist/apns.mjs.map +1 -1
- package/dist/aws/engine-step.cjs +5 -5
- package/dist/aws/engine-step.cjs.map +1 -1
- package/dist/aws/engine-step.d.cts +8 -2
- package/dist/aws/engine-step.d.cts.map +1 -1
- package/dist/aws/engine-step.d.mts +8 -2
- package/dist/aws/engine-step.d.mts.map +1 -1
- package/dist/aws/engine-step.mjs +5 -5
- package/dist/aws/engine-step.mjs.map +1 -1
- package/dist/aws/index.cjs +47 -6
- package/dist/aws/index.cjs.map +1 -1
- package/dist/aws/index.d.cts +2 -2
- package/dist/aws/index.d.cts.map +1 -1
- package/dist/aws/index.d.mts +2 -2
- package/dist/aws/index.d.mts.map +1 -1
- package/dist/aws/index.mjs +47 -6
- package/dist/aws/index.mjs.map +1 -1
- package/dist/aws/instances.cjs +29 -6
- package/dist/aws/instances.cjs.map +1 -1
- package/dist/aws/instances.d.cts +11 -1
- package/dist/aws/instances.d.cts.map +1 -1
- package/dist/aws/instances.d.mts +11 -1
- package/dist/aws/instances.d.mts.map +1 -1
- package/dist/aws/instances.mjs +27 -4
- package/dist/aws/instances.mjs.map +1 -1
- package/dist/client.cjs +4 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +2 -2
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.mts +2 -2
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +4 -1
- package/dist/client.mjs.map +1 -1
- package/dist/cloudflare/bindings.cjs +28 -25
- package/dist/cloudflare/bindings.cjs.map +1 -1
- package/dist/cloudflare/bindings.d.cts +17 -129
- package/dist/cloudflare/bindings.d.cts.map +1 -1
- package/dist/cloudflare/bindings.d.mts +17 -129
- package/dist/cloudflare/bindings.d.mts.map +1 -1
- package/dist/cloudflare/bindings.mjs +28 -23
- package/dist/cloudflare/bindings.mjs.map +1 -1
- package/dist/cloudflare/d1-env.cjs +6 -6
- package/dist/cloudflare/d1-env.cjs.map +1 -1
- package/dist/cloudflare/d1-env.d.cts +5 -5
- package/dist/cloudflare/d1-env.d.mts +5 -5
- package/dist/cloudflare/d1-env.mjs +6 -6
- package/dist/cloudflare/d1-env.mjs.map +1 -1
- package/dist/cloudflare/index.cjs +28 -18
- package/dist/cloudflare/index.d.cts +8 -4
- package/dist/cloudflare/index.d.mts +8 -4
- package/dist/cloudflare/index.mjs +9 -5
- package/dist/cloudflare/runner.cjs +29 -14
- package/dist/cloudflare/runner.cjs.map +1 -1
- package/dist/cloudflare/runner.d.cts +9 -2
- package/dist/cloudflare/runner.d.cts.map +1 -1
- package/dist/cloudflare/runner.d.mts +9 -2
- package/dist/cloudflare/runner.d.mts.map +1 -1
- package/dist/cloudflare/runner.mjs +28 -13
- package/dist/cloudflare/runner.mjs.map +1 -1
- package/dist/cloudflare/schema.sql +27 -6
- package/dist/cloudflare/worker.cjs +19 -9
- package/dist/cloudflare/worker.cjs.map +1 -1
- package/dist/cloudflare/worker.d.cts +2 -2
- package/dist/cloudflare/worker.d.cts.map +1 -1
- package/dist/cloudflare/worker.d.mts +2 -2
- package/dist/cloudflare/worker.d.mts.map +1 -1
- package/dist/cloudflare/worker.mjs +19 -9
- package/dist/cloudflare/worker.mjs.map +1 -1
- package/dist/config.cjs +21 -5
- package/dist/config.cjs.map +1 -1
- package/dist/config.mjs +21 -5
- package/dist/config.mjs.map +1 -1
- package/dist/dsl/action.cjs +3 -6
- package/dist/dsl/action.cjs.map +1 -1
- package/dist/dsl/action.d.cts +12 -11
- package/dist/dsl/action.d.cts.map +1 -1
- package/dist/dsl/action.d.mts +12 -11
- package/dist/dsl/action.d.mts.map +1 -1
- package/dist/dsl/action.mjs +3 -6
- package/dist/dsl/action.mjs.map +1 -1
- package/dist/dsl/bundle.cjs +35 -31
- package/dist/dsl/bundle.cjs.map +1 -1
- package/dist/dsl/bundle.d.cts.map +1 -1
- package/dist/dsl/bundle.d.mts.map +1 -1
- package/dist/dsl/bundle.mjs +35 -31
- package/dist/dsl/bundle.mjs.map +1 -1
- package/dist/dsl/condition.cjs +13 -1
- package/dist/dsl/condition.cjs.map +1 -1
- package/dist/dsl/condition.d.cts +9 -1
- package/dist/dsl/condition.d.cts.map +1 -1
- package/dist/dsl/condition.d.mts +9 -1
- package/dist/dsl/condition.d.mts.map +1 -1
- package/dist/dsl/condition.mjs +13 -2
- package/dist/dsl/condition.mjs.map +1 -1
- package/dist/dsl/flow.cjs +33 -11
- package/dist/dsl/flow.cjs.map +1 -1
- package/dist/dsl/flow.d.cts +9 -1
- package/dist/dsl/flow.d.cts.map +1 -1
- package/dist/dsl/flow.d.mts +9 -1
- package/dist/dsl/flow.d.mts.map +1 -1
- package/dist/dsl/flow.mjs +34 -13
- package/dist/dsl/flow.mjs.map +1 -1
- package/dist/dsl/workflow.cjs +2 -3
- package/dist/dsl/workflow.cjs.map +1 -1
- package/dist/dsl/workflow.d.cts.map +1 -1
- package/dist/dsl/workflow.d.mts.map +1 -1
- package/dist/dsl/workflow.mjs +3 -4
- package/dist/dsl/workflow.mjs.map +1 -1
- package/dist/email-sender.cjs +24 -0
- package/dist/email-sender.cjs.map +1 -1
- package/dist/email-sender.d.cts +18 -0
- package/dist/email-sender.d.cts.map +1 -1
- package/dist/email-sender.d.mts +18 -0
- package/dist/email-sender.d.mts.map +1 -1
- package/dist/email-sender.mjs +24 -1
- package/dist/email-sender.mjs.map +1 -1
- package/dist/engine/actions.cjs +1 -8
- package/dist/engine/actions.cjs.map +1 -1
- package/dist/engine/actions.d.cts +1 -12
- package/dist/engine/actions.d.cts.map +1 -1
- package/dist/engine/actions.d.mts +1 -12
- package/dist/engine/actions.d.mts.map +1 -1
- package/dist/engine/actions.mjs +1 -8
- package/dist/engine/actions.mjs.map +1 -1
- package/dist/engine/condition.cjs +25 -5
- package/dist/engine/condition.cjs.map +1 -1
- package/dist/engine/condition.d.cts +12 -3
- package/dist/engine/condition.d.cts.map +1 -1
- package/dist/engine/condition.d.mts +12 -3
- package/dist/engine/condition.d.mts.map +1 -1
- package/dist/engine/condition.mjs +25 -6
- package/dist/engine/condition.mjs.map +1 -1
- package/dist/engine/index.cjs +3 -0
- package/dist/engine/index.d.cts +5 -5
- package/dist/engine/index.d.mts +5 -5
- package/dist/engine/index.mjs +4 -4
- package/dist/engine/interpreter.cjs +27 -20
- package/dist/engine/interpreter.cjs.map +1 -1
- package/dist/engine/interpreter.d.cts.map +1 -1
- package/dist/engine/interpreter.d.mts.map +1 -1
- package/dist/engine/interpreter.mjs +27 -20
- package/dist/engine/interpreter.mjs.map +1 -1
- package/dist/engine/ports.d.cts +21 -6
- package/dist/engine/ports.d.cts.map +1 -1
- package/dist/engine/ports.d.mts +21 -6
- package/dist/engine/ports.d.mts.map +1 -1
- package/dist/engine/retry.cjs +13 -0
- package/dist/engine/retry.cjs.map +1 -1
- package/dist/engine/retry.d.cts +12 -0
- package/dist/engine/retry.d.cts.map +1 -1
- package/dist/engine/retry.d.mts +12 -0
- package/dist/engine/retry.d.mts.map +1 -1
- package/dist/engine/retry.mjs +13 -1
- package/dist/engine/retry.mjs.map +1 -1
- package/dist/engine/time-scale.cjs +90 -15
- package/dist/engine/time-scale.cjs.map +1 -1
- package/dist/engine/time-scale.d.cts +9 -6
- package/dist/engine/time-scale.d.cts.map +1 -1
- package/dist/engine/time-scale.d.mts +9 -6
- package/dist/engine/time-scale.d.mts.map +1 -1
- package/dist/engine/time-scale.mjs +90 -16
- package/dist/engine/time-scale.mjs.map +1 -1
- package/dist/fcm.cjs +111 -38
- package/dist/fcm.cjs.map +1 -1
- package/dist/fcm.d.cts +45 -5
- package/dist/fcm.d.cts.map +1 -1
- package/dist/fcm.d.mts +45 -5
- package/dist/fcm.d.mts.map +1 -1
- package/dist/fcm.mjs +110 -39
- package/dist/fcm.mjs.map +1 -1
- package/dist/hash.cjs +6 -6
- package/dist/hash.cjs.map +1 -1
- package/dist/hash.mjs +6 -6
- package/dist/hash.mjs.map +1 -1
- package/dist/host/auth.cjs +49 -0
- package/dist/host/auth.cjs.map +1 -0
- package/dist/host/auth.d.cts +21 -0
- package/dist/host/auth.d.cts.map +1 -0
- package/dist/host/auth.d.mts +21 -0
- package/dist/host/auth.d.mts.map +1 -0
- package/dist/host/auth.mjs +47 -0
- package/dist/host/auth.mjs.map +1 -0
- package/dist/host/channels.cjs +118 -40
- package/dist/host/channels.cjs.map +1 -1
- package/dist/host/channels.d.cts +27 -1
- package/dist/host/channels.d.cts.map +1 -1
- package/dist/host/channels.d.mts +27 -1
- package/dist/host/channels.d.mts.map +1 -1
- package/dist/host/channels.mjs +121 -44
- package/dist/host/channels.mjs.map +1 -1
- package/dist/host/execute.cjs +14 -2
- package/dist/host/execute.cjs.map +1 -1
- package/dist/host/execute.d.cts +4 -4
- package/dist/host/execute.d.cts.map +1 -1
- package/dist/host/execute.d.mts +4 -4
- package/dist/host/execute.d.mts.map +1 -1
- package/dist/host/execute.mjs +13 -1
- package/dist/host/execute.mjs.map +1 -1
- package/dist/host/fresh-read.cjs +30 -0
- package/dist/host/fresh-read.cjs.map +1 -0
- package/dist/host/fresh-read.d.cts +18 -0
- package/dist/host/fresh-read.d.cts.map +1 -0
- package/dist/host/fresh-read.d.mts +18 -0
- package/dist/host/fresh-read.d.mts.map +1 -0
- package/dist/host/fresh-read.mjs +29 -0
- package/dist/host/fresh-read.mjs.map +1 -0
- package/dist/host/http.cjs +321 -0
- package/dist/host/http.cjs.map +1 -0
- package/dist/host/http.d.cts +54 -0
- package/dist/host/http.d.cts.map +1 -0
- package/dist/host/http.d.mts +54 -0
- package/dist/host/http.d.mts.map +1 -0
- package/dist/host/http.mjs +317 -0
- package/dist/host/http.mjs.map +1 -0
- package/dist/host/{router.cjs → ingest.cjs} +147 -175
- package/dist/host/ingest.cjs.map +1 -0
- package/dist/host/{router.d.cts → ingest.d.cts} +48 -30
- package/dist/host/ingest.d.cts.map +1 -0
- package/dist/host/{router.d.mts → ingest.d.mts} +48 -30
- package/dist/host/ingest.d.mts.map +1 -0
- package/dist/host/{router.mjs → ingest.mjs} +144 -173
- package/dist/host/ingest.mjs.map +1 -0
- package/dist/host/journey-host.cjs +65 -93
- package/dist/host/journey-host.cjs.map +1 -1
- package/dist/host/journey-host.d.cts +19 -26
- package/dist/host/journey-host.d.cts.map +1 -1
- package/dist/host/journey-host.d.mts +19 -26
- package/dist/host/journey-host.d.mts.map +1 -1
- package/dist/host/journey-host.mjs +65 -93
- package/dist/host/journey-host.mjs.map +1 -1
- package/dist/host/platform.cjs +21 -0
- package/dist/host/platform.cjs.map +1 -0
- package/dist/host/platform.d.cts +126 -0
- package/dist/host/platform.d.cts.map +1 -0
- package/dist/host/platform.d.mts +126 -0
- package/dist/host/platform.d.mts.map +1 -0
- package/dist/host/platform.mjs +19 -0
- package/dist/host/platform.mjs.map +1 -0
- package/dist/host/senders.cjs +18 -9
- package/dist/host/senders.cjs.map +1 -1
- package/dist/host/senders.d.cts +9 -4
- package/dist/host/senders.d.cts.map +1 -1
- package/dist/host/senders.d.mts +9 -4
- package/dist/host/senders.d.mts.map +1 -1
- package/dist/host/senders.mjs +18 -9
- package/dist/host/senders.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/ir.cjs +5 -7
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +3 -12
- package/dist/ir.d.cts.map +1 -1
- package/dist/ir.d.mts +3 -12
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +5 -7
- package/dist/ir.mjs.map +1 -1
- package/dist/plan.cjs +27 -33
- package/dist/plan.cjs.map +1 -1
- package/dist/plan.d.cts +1 -1
- package/dist/plan.d.cts.map +1 -1
- package/dist/plan.d.mts +1 -1
- package/dist/plan.d.mts.map +1 -1
- package/dist/plan.mjs +27 -33
- package/dist/plan.mjs.map +1 -1
- package/dist/reporting/observer.cjs +1 -0
- package/dist/reporting/observer.cjs.map +1 -1
- package/dist/reporting/observer.mjs +1 -0
- package/dist/reporting/observer.mjs.map +1 -1
- package/dist/reporting/tracking.cjs +19 -4
- package/dist/reporting/tracking.cjs.map +1 -1
- package/dist/reporting/tracking.d.cts +13 -11
- package/dist/reporting/tracking.d.cts.map +1 -1
- package/dist/reporting/tracking.d.mts +13 -11
- package/dist/reporting/tracking.d.mts.map +1 -1
- package/dist/reporting/tracking.mjs +19 -4
- package/dist/reporting/tracking.mjs.map +1 -1
- package/dist/store/d1.cjs +88 -29
- package/dist/store/d1.cjs.map +1 -1
- package/dist/store/d1.d.cts +29 -11
- package/dist/store/d1.d.cts.map +1 -1
- package/dist/store/d1.d.mts +29 -11
- package/dist/store/d1.d.mts.map +1 -1
- package/dist/store/d1.mjs +88 -29
- package/dist/store/d1.mjs.map +1 -1
- package/dist/store/erasure.cjs +22 -0
- package/dist/store/erasure.cjs.map +1 -0
- package/dist/store/erasure.d.cts +18 -0
- package/dist/store/erasure.d.cts.map +1 -0
- package/dist/store/erasure.d.mts +18 -0
- package/dist/store/erasure.d.mts.map +1 -0
- package/dist/store/erasure.mjs +21 -0
- package/dist/store/erasure.mjs.map +1 -0
- package/dist/store/index.cjs +2 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +67 -13
- package/dist/store/index.d.cts.map +1 -1
- package/dist/store/index.d.mts +67 -13
- package/dist/store/index.d.mts.map +1 -1
- package/dist/store/index.mjs +2 -1
- package/dist/store/index.mjs.map +1 -1
- package/dist/store/postgres-reporting.cjs +125 -62
- package/dist/store/postgres-reporting.cjs.map +1 -1
- package/dist/store/postgres-reporting.d.cts +30 -2
- package/dist/store/postgres-reporting.d.cts.map +1 -1
- package/dist/store/postgres-reporting.d.mts +30 -2
- package/dist/store/postgres-reporting.d.mts.map +1 -1
- package/dist/store/postgres-reporting.mjs +125 -62
- package/dist/store/postgres-reporting.mjs.map +1 -1
- package/dist/store/postgres.cjs +179 -60
- package/dist/store/postgres.cjs.map +1 -1
- package/dist/store/postgres.d.cts +44 -7
- package/dist/store/postgres.d.cts.map +1 -1
- package/dist/store/postgres.d.mts +44 -7
- package/dist/store/postgres.d.mts.map +1 -1
- package/dist/store/postgres.mjs +179 -60
- package/dist/store/postgres.mjs.map +1 -1
- package/dist/store/tables.cjs +7 -5
- package/dist/store/tables.cjs.map +1 -1
- package/dist/store/tables.d.cts.map +1 -1
- package/dist/store/tables.d.mts.map +1 -1
- package/dist/store/tables.mjs +7 -5
- package/dist/store/tables.mjs.map +1 -1
- package/dist/store/user-lock.cjs +43 -0
- package/dist/store/user-lock.cjs.map +1 -0
- package/dist/store/user-lock.d.cts +32 -0
- package/dist/store/user-lock.d.cts.map +1 -0
- package/dist/store/user-lock.d.mts +32 -0
- package/dist/store/user-lock.d.mts.map +1 -0
- package/dist/store/user-lock.mjs +40 -0
- package/dist/store/user-lock.mjs.map +1 -0
- package/dist/transports/cloudflare-email.cjs +3 -2
- package/dist/transports/cloudflare-email.cjs.map +1 -1
- package/dist/transports/cloudflare-email.mjs +3 -2
- package/dist/transports/cloudflare-email.mjs.map +1 -1
- package/dist/transports/ses.cjs +52 -7
- package/dist/transports/ses.cjs.map +1 -1
- package/dist/transports/ses.d.cts +9 -1
- package/dist/transports/ses.d.cts.map +1 -1
- package/dist/transports/ses.d.mts +9 -1
- package/dist/transports/ses.d.mts.map +1 -1
- package/dist/transports/ses.mjs +52 -8
- package/dist/transports/ses.mjs.map +1 -1
- package/dist/webpush.cjs +133 -47
- package/dist/webpush.cjs.map +1 -1
- package/dist/webpush.d.cts +49 -8
- package/dist/webpush.d.cts.map +1 -1
- package/dist/webpush.d.mts +49 -8
- package/dist/webpush.d.mts.map +1 -1
- package/dist/webpush.mjs +130 -47
- package/dist/webpush.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cloudflare/schema.sql +27 -6
- package/dist/host/router.cjs.map +0 -1
- package/dist/host/router.d.cts.map +0 -1
- package/dist/host/router.d.mts.map +0 -1
- package/dist/host/router.mjs.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_runtime = require("../runtime.cjs");
|
|
3
2
|
const require_engine_retry = require("../engine/retry.cjs");
|
|
4
|
-
const
|
|
3
|
+
const require_runtime = require("../runtime.cjs");
|
|
5
4
|
const require_cloudflare_d1_env = require("./d1-env.cjs");
|
|
5
|
+
const require_host_platform = require("../host/platform.cjs");
|
|
6
6
|
const require_host_execute = require("../host/execute.cjs");
|
|
7
7
|
const require_engine_actions = require("../engine/actions.cjs");
|
|
8
8
|
const require_host_senders = require("../host/senders.cjs");
|
|
9
|
+
const require_cloudflare_bindings = require("./bindings.cjs");
|
|
9
10
|
let cloudflare_workers = require("cloudflare:workers");
|
|
10
11
|
let cloudflare_workflows = require("cloudflare:workflows");
|
|
11
12
|
//#region src/cloudflare/runner.ts
|
|
@@ -14,7 +15,8 @@ let cloudflare_workflows = require("cloudflare:workflows");
|
|
|
14
15
|
* subscribe/unsubscribe write the subscription table inside durable steps (the
|
|
15
16
|
* router wakes instances from that table); waitForWake parks on CF's
|
|
16
17
|
* waitForEvent, which signals a timeout by throwing — and so does every other
|
|
17
|
-
* failure in there, so only the clock
|
|
18
|
+
* failure in there, so only the clock, against the checkpointed deadline, can
|
|
19
|
+
* tell them apart (see wakeExpired).
|
|
18
20
|
*/
|
|
19
21
|
var CfEngineStep = class {
|
|
20
22
|
step;
|
|
@@ -30,14 +32,19 @@ var CfEngineStep = class {
|
|
|
30
32
|
/**
|
|
31
33
|
* A failure a retry would only repeat (`isRetryable`: a template refusing its props, the
|
|
32
34
|
* contract refusing an emitted event, a provider rejecting the request) ends the step at
|
|
33
|
-
* once instead of burning the retry budget.
|
|
35
|
+
* once instead of burning the retry budget. The engine recognizes a NonRetryableError by its
|
|
36
|
+
* `name`, so the original's name is not written over it — the original travels as `cause`.
|
|
34
37
|
*/
|
|
35
38
|
do(name, fn) {
|
|
36
39
|
return this.step.do(name, async () => {
|
|
37
40
|
try {
|
|
38
41
|
return await fn();
|
|
39
42
|
} catch (error) {
|
|
40
|
-
if (error instanceof Error && !require_engine_retry.isRetryable(error))
|
|
43
|
+
if (error instanceof Error && !require_engine_retry.isRetryable(error)) {
|
|
44
|
+
const permanent = new cloudflare_workflows.NonRetryableError(error.message);
|
|
45
|
+
permanent.cause = error;
|
|
46
|
+
throw permanent;
|
|
47
|
+
}
|
|
41
48
|
throw error;
|
|
42
49
|
}
|
|
43
50
|
});
|
|
@@ -45,25 +52,21 @@ var CfEngineStep = class {
|
|
|
45
52
|
sleep(name, ms) {
|
|
46
53
|
return this.step.sleep(name, ms);
|
|
47
54
|
}
|
|
48
|
-
sleepUntil(name, timestampMs) {
|
|
49
|
-
return this.step.sleepUntil(name, timestampMs);
|
|
50
|
-
}
|
|
51
55
|
subscribe(name, events) {
|
|
52
56
|
return this.step.do(name, () => this.store.subscribe(this.instanceId, this.userId, events, Date.now()));
|
|
53
57
|
}
|
|
54
58
|
unsubscribe(name) {
|
|
55
59
|
return this.step.do(name, () => this.store.unsubscribe(this.instanceId));
|
|
56
60
|
}
|
|
57
|
-
async waitForWake(name, timeoutMs) {
|
|
58
|
-
const startedAt = Date.now();
|
|
61
|
+
async waitForWake(name, { timeoutMs, deadlineMs }) {
|
|
59
62
|
try {
|
|
60
63
|
await this.step.waitForEvent(name, {
|
|
61
|
-
type:
|
|
64
|
+
type: require_host_platform.WAKE_EVENT_TYPE,
|
|
62
65
|
timeout: timeoutMs
|
|
63
66
|
});
|
|
64
67
|
return "event";
|
|
65
68
|
} catch (error) {
|
|
66
|
-
if (await this.step.do(`${name}:timed-out`, async () => require_cloudflare_bindings.wakeExpired(
|
|
69
|
+
if (await this.step.do(`${name}:timed-out`, async () => require_cloudflare_bindings.wakeExpired(deadlineMs, Date.now()))) return "timeout";
|
|
67
70
|
throw error;
|
|
68
71
|
}
|
|
69
72
|
}
|
|
@@ -76,7 +79,7 @@ var CfEngineStep = class {
|
|
|
76
79
|
*/
|
|
77
80
|
var JourneyRunner = class extends cloudflare_workers.WorkflowEntrypoint {
|
|
78
81
|
/**
|
|
79
|
-
* The data plane. D1
|
|
82
|
+
* The data plane. D1 from the env by default; an app on Postgres
|
|
80
83
|
* overrides this (and `closeStore` if its client needs closing):
|
|
81
84
|
*
|
|
82
85
|
* protected override createStore() { return new PostgresJourneyStore(client(this.env)); }
|
|
@@ -86,6 +89,18 @@ var JourneyRunner = class extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
86
89
|
}
|
|
87
90
|
/** Called once `run` is over, success or not — release whatever `createStore` opened. */
|
|
88
91
|
async closeStore(_store) {}
|
|
92
|
+
adapted;
|
|
93
|
+
/**
|
|
94
|
+
* The env a run ingests its emitted events with: the Workflow binding behind the host's port,
|
|
95
|
+
* so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the
|
|
96
|
+
* host drop its wakes). Built once per instance.
|
|
97
|
+
*/
|
|
98
|
+
hostEnv() {
|
|
99
|
+
return this.adapted ??= {
|
|
100
|
+
...this.env,
|
|
101
|
+
JOURNEY: require_cloudflare_bindings.cloudflareJourneyBinding(this.env.JOURNEY)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
89
104
|
/**
|
|
90
105
|
* Runtime options for this instance — typed configuration, resolved by the app (typically
|
|
91
106
|
* `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from
|
|
@@ -145,7 +160,7 @@ var JourneyRunner = class extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
145
160
|
createRunParts(store, params) {
|
|
146
161
|
return {
|
|
147
162
|
store,
|
|
148
|
-
env: this.
|
|
163
|
+
env: this.hostEnv(),
|
|
149
164
|
runtime: this.runtime(params),
|
|
150
165
|
messages: this.createMessageSender(),
|
|
151
166
|
actions: this.createActionInvoker(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.cjs","names":["isRetryable","NonRetryableError","WAKE_EVENT_TYPE","wakeExpired","WorkflowEntrypoint","d1Store","resolveRuntime","WebhookMessageSender","LogMessageSender","RegistryActionInvoker","executeJourney"],"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 { isRetryable } from '../engine/retry';\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 */\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 failure a retry would only repeat (`isRetryable`: a template refusing its props, the\n * contract refusing an emitted event, a provider rejecting the request) 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 && !isRetryable(error)) {\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":"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"}
|
|
1
|
+
{"version":3,"file":"runner.cjs","names":["isRetryable","NonRetryableError","WAKE_EVENT_TYPE","wakeExpired","WorkflowEntrypoint","d1Store","cloudflareJourneyBinding","resolveRuntime","WebhookMessageSender","LogMessageSender","RegistryActionInvoker","executeJourney"],"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 { isRetryable } from '../engine/retry';\nimport { type JourneyRunParts, executeJourney } from '../host/execute';\nimport { type JourneyEnv, type JourneyParams, WAKE_EVENT_TYPE } from '../host/platform';\nimport { LogMessageSender, WebhookMessageSender } from '../host/senders';\nimport { type JourneyRuntimeOptions, resolveRuntime } from '../runtime';\nimport type { JourneyStore } from '../store/index';\nimport { cloudflareJourneyBinding, 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, against the checkpointed deadline, can\n * tell them apart (see wakeExpired).\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 failure a retry would only repeat (`isRetryable`: a template refusing its props, the\n * contract refusing an emitted event, a provider rejecting the request) ends the step at\n * once instead of burning the retry budget. The engine recognizes a NonRetryableError by its\n * `name`, so the original's name is not written over it — the original travels as `cause`.\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 && !isRetryable(error)) {\n const permanent = new NonRetryableError(error.message);\n permanent.cause = error;\n throw permanent;\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 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(\n name: string,\n { timeoutMs, deadlineMs }: { timeoutMs: number; deadlineMs: number }\n ): Promise<'event' | 'timeout'> {\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 wakeExpired). The\n * deadline is the interpreter's checkpoint, so the verdict is the same on the attempt\n * that parked and on a replay after an eviction — which is where the wait usually\n * resurfaces: the instance is rebuilt, the wait is already over, and the first thing to\n * run is this step. The verdict is checkpointed too, so later replays reuse it.\n */\n const timedOut = await this.step.do(`${name}:timed-out`, async () =>\n wakeExpired(deadlineMs, 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 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 private adapted: JourneyEnv | undefined;\n\n /**\n * The env a run ingests its emitted events with: the Workflow binding behind the host's port,\n * so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the\n * host drop its wakes). Built once per instance.\n */\n protected hostEnv(): JourneyEnv {\n return (this.adapted ??= { ...this.env, JOURNEY: cloudflareJourneyBinding(this.env.JOURNEY) });\n }\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.hostEnv(),\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":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ActionInvoker, JourneyObserver, JourneyOutcome, MessageSender, MessageValidator } from "../engine/ports.cjs";
|
|
2
2
|
import { RegisteredAction } from "../engine/actions.cjs";
|
|
3
|
-
import { JourneyEnv, JourneyParams } from "
|
|
3
|
+
import { JourneyEnv, JourneyParams } from "../host/platform.cjs";
|
|
4
4
|
import { JourneyStore } from "../store/index.cjs";
|
|
5
5
|
import { Contract } from "../contract.cjs";
|
|
6
6
|
import { JourneyRuntimeOptions } from "../runtime.cjs";
|
|
@@ -15,7 +15,7 @@ import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:work
|
|
|
15
15
|
*/
|
|
16
16
|
export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {
|
|
17
17
|
/**
|
|
18
|
-
* The data plane. D1
|
|
18
|
+
* The data plane. D1 from the env by default; an app on Postgres
|
|
19
19
|
* overrides this (and `closeStore` if its client needs closing):
|
|
20
20
|
*
|
|
21
21
|
* protected override createStore() { return new PostgresJourneyStore(client(this.env)); }
|
|
@@ -23,6 +23,13 @@ export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, Journe
|
|
|
23
23
|
protected createStore(): JourneyStore;
|
|
24
24
|
/** Called once `run` is over, success or not — release whatever `createStore` opened. */
|
|
25
25
|
protected closeStore(_store: JourneyStore): Promise<void>;
|
|
26
|
+
private adapted;
|
|
27
|
+
/**
|
|
28
|
+
* The env a run ingests its emitted events with: the Workflow binding behind the host's port,
|
|
29
|
+
* so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the
|
|
30
|
+
* host drop its wakes). Built once per instance.
|
|
31
|
+
*/
|
|
32
|
+
protected hostEnv(): JourneyEnv;
|
|
26
33
|
/**
|
|
27
34
|
* Runtime options for this instance — typed configuration, resolved by the app (typically
|
|
28
35
|
* `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from
|
|
@@ -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":";;;;;;;;;;;;;;;qBAwGa,sBAAsB,mBAAmB,YAAY;;;;;;;YAOtD,eAAe;;YAKT,WAAW,QAAQ,eAAe;UAE1C;;;;;;YAOE,WAAW;;;;;;;;;;;;YAeX,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,6 +1,6 @@
|
|
|
1
1
|
import { ActionInvoker, JourneyObserver, JourneyOutcome, MessageSender, MessageValidator } from "../engine/ports.mjs";
|
|
2
2
|
import { RegisteredAction } from "../engine/actions.mjs";
|
|
3
|
-
import { JourneyEnv, JourneyParams } from "
|
|
3
|
+
import { JourneyEnv, JourneyParams } from "../host/platform.mjs";
|
|
4
4
|
import { JourneyStore } from "../store/index.mjs";
|
|
5
5
|
import { Contract } from "../contract.mjs";
|
|
6
6
|
import { JourneyRuntimeOptions } from "../runtime.mjs";
|
|
@@ -15,7 +15,7 @@ import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:work
|
|
|
15
15
|
*/
|
|
16
16
|
export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {
|
|
17
17
|
/**
|
|
18
|
-
* The data plane. D1
|
|
18
|
+
* The data plane. D1 from the env by default; an app on Postgres
|
|
19
19
|
* overrides this (and `closeStore` if its client needs closing):
|
|
20
20
|
*
|
|
21
21
|
* protected override createStore() { return new PostgresJourneyStore(client(this.env)); }
|
|
@@ -23,6 +23,13 @@ export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, Journe
|
|
|
23
23
|
protected createStore(): JourneyStore;
|
|
24
24
|
/** Called once `run` is over, success or not — release whatever `createStore` opened. */
|
|
25
25
|
protected closeStore(_store: JourneyStore): Promise<void>;
|
|
26
|
+
private adapted;
|
|
27
|
+
/**
|
|
28
|
+
* The env a run ingests its emitted events with: the Workflow binding behind the host's port,
|
|
29
|
+
* so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the
|
|
30
|
+
* host drop its wakes). Built once per instance.
|
|
31
|
+
*/
|
|
32
|
+
protected hostEnv(): JourneyEnv;
|
|
26
33
|
/**
|
|
27
34
|
* Runtime options for this instance — typed configuration, resolved by the app (typically
|
|
28
35
|
* `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from
|
|
@@ -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":";;;;;;;;;;;;;;;qBAwGa,sBAAsB,mBAAmB,YAAY;;;;;;;YAOtD,eAAe;;YAKT,WAAW,QAAQ,eAAe;UAE1C;;;;;;YAOE,WAAW;;;;;;;;;;;;YAeX,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,10 +1,11 @@
|
|
|
1
|
-
import { resolveRuntime } from "../runtime.mjs";
|
|
2
1
|
import { isRetryable } from "../engine/retry.mjs";
|
|
3
|
-
import {
|
|
2
|
+
import { resolveRuntime } from "../runtime.mjs";
|
|
4
3
|
import { d1Store } from "./d1-env.mjs";
|
|
4
|
+
import { WAKE_EVENT_TYPE } from "../host/platform.mjs";
|
|
5
5
|
import { executeJourney } from "../host/execute.mjs";
|
|
6
6
|
import { RegistryActionInvoker } from "../engine/actions.mjs";
|
|
7
7
|
import { LogMessageSender, WebhookMessageSender } from "../host/senders.mjs";
|
|
8
|
+
import { cloudflareJourneyBinding, wakeExpired } from "./bindings.mjs";
|
|
8
9
|
import { WorkflowEntrypoint } from "cloudflare:workers";
|
|
9
10
|
import { NonRetryableError } from "cloudflare:workflows";
|
|
10
11
|
//#region src/cloudflare/runner.ts
|
|
@@ -13,7 +14,8 @@ import { NonRetryableError } from "cloudflare:workflows";
|
|
|
13
14
|
* subscribe/unsubscribe write the subscription table inside durable steps (the
|
|
14
15
|
* router wakes instances from that table); waitForWake parks on CF's
|
|
15
16
|
* waitForEvent, which signals a timeout by throwing — and so does every other
|
|
16
|
-
* failure in there, so only the clock
|
|
17
|
+
* failure in there, so only the clock, against the checkpointed deadline, can
|
|
18
|
+
* tell them apart (see wakeExpired).
|
|
17
19
|
*/
|
|
18
20
|
var CfEngineStep = class {
|
|
19
21
|
step;
|
|
@@ -29,14 +31,19 @@ var CfEngineStep = class {
|
|
|
29
31
|
/**
|
|
30
32
|
* A failure a retry would only repeat (`isRetryable`: a template refusing its props, the
|
|
31
33
|
* contract refusing an emitted event, a provider rejecting the request) ends the step at
|
|
32
|
-
* once instead of burning the retry budget.
|
|
34
|
+
* once instead of burning the retry budget. The engine recognizes a NonRetryableError by its
|
|
35
|
+
* `name`, so the original's name is not written over it — the original travels as `cause`.
|
|
33
36
|
*/
|
|
34
37
|
do(name, fn) {
|
|
35
38
|
return this.step.do(name, async () => {
|
|
36
39
|
try {
|
|
37
40
|
return await fn();
|
|
38
41
|
} catch (error) {
|
|
39
|
-
if (error instanceof Error && !isRetryable(error))
|
|
42
|
+
if (error instanceof Error && !isRetryable(error)) {
|
|
43
|
+
const permanent = new NonRetryableError(error.message);
|
|
44
|
+
permanent.cause = error;
|
|
45
|
+
throw permanent;
|
|
46
|
+
}
|
|
40
47
|
throw error;
|
|
41
48
|
}
|
|
42
49
|
});
|
|
@@ -44,17 +51,13 @@ var CfEngineStep = class {
|
|
|
44
51
|
sleep(name, ms) {
|
|
45
52
|
return this.step.sleep(name, ms);
|
|
46
53
|
}
|
|
47
|
-
sleepUntil(name, timestampMs) {
|
|
48
|
-
return this.step.sleepUntil(name, timestampMs);
|
|
49
|
-
}
|
|
50
54
|
subscribe(name, events) {
|
|
51
55
|
return this.step.do(name, () => this.store.subscribe(this.instanceId, this.userId, events, Date.now()));
|
|
52
56
|
}
|
|
53
57
|
unsubscribe(name) {
|
|
54
58
|
return this.step.do(name, () => this.store.unsubscribe(this.instanceId));
|
|
55
59
|
}
|
|
56
|
-
async waitForWake(name, timeoutMs) {
|
|
57
|
-
const startedAt = Date.now();
|
|
60
|
+
async waitForWake(name, { timeoutMs, deadlineMs }) {
|
|
58
61
|
try {
|
|
59
62
|
await this.step.waitForEvent(name, {
|
|
60
63
|
type: WAKE_EVENT_TYPE,
|
|
@@ -62,7 +65,7 @@ var CfEngineStep = class {
|
|
|
62
65
|
});
|
|
63
66
|
return "event";
|
|
64
67
|
} catch (error) {
|
|
65
|
-
if (await this.step.do(`${name}:timed-out`, async () => wakeExpired(
|
|
68
|
+
if (await this.step.do(`${name}:timed-out`, async () => wakeExpired(deadlineMs, Date.now()))) return "timeout";
|
|
66
69
|
throw error;
|
|
67
70
|
}
|
|
68
71
|
}
|
|
@@ -75,7 +78,7 @@ var CfEngineStep = class {
|
|
|
75
78
|
*/
|
|
76
79
|
var JourneyRunner = class extends WorkflowEntrypoint {
|
|
77
80
|
/**
|
|
78
|
-
* The data plane. D1
|
|
81
|
+
* The data plane. D1 from the env by default; an app on Postgres
|
|
79
82
|
* overrides this (and `closeStore` if its client needs closing):
|
|
80
83
|
*
|
|
81
84
|
* protected override createStore() { return new PostgresJourneyStore(client(this.env)); }
|
|
@@ -85,6 +88,18 @@ var JourneyRunner = class extends WorkflowEntrypoint {
|
|
|
85
88
|
}
|
|
86
89
|
/** Called once `run` is over, success or not — release whatever `createStore` opened. */
|
|
87
90
|
async closeStore(_store) {}
|
|
91
|
+
adapted;
|
|
92
|
+
/**
|
|
93
|
+
* The env a run ingests its emitted events with: the Workflow binding behind the host's port,
|
|
94
|
+
* so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the
|
|
95
|
+
* host drop its wakes). Built once per instance.
|
|
96
|
+
*/
|
|
97
|
+
hostEnv() {
|
|
98
|
+
return this.adapted ??= {
|
|
99
|
+
...this.env,
|
|
100
|
+
JOURNEY: cloudflareJourneyBinding(this.env.JOURNEY)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
88
103
|
/**
|
|
89
104
|
* Runtime options for this instance — typed configuration, resolved by the app (typically
|
|
90
105
|
* `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from
|
|
@@ -144,7 +159,7 @@ var JourneyRunner = class extends WorkflowEntrypoint {
|
|
|
144
159
|
createRunParts(store, params) {
|
|
145
160
|
return {
|
|
146
161
|
store,
|
|
147
|
-
env: this.
|
|
162
|
+
env: this.hostEnv(),
|
|
148
163
|
runtime: this.runtime(params),
|
|
149
164
|
messages: this.createMessageSender(),
|
|
150
165
|
actions: this.createActionInvoker(),
|
|
@@ -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 } 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 { isRetryable } from '../engine/retry';\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 */\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 failure a retry would only repeat (`isRetryable`: a template refusing its props, the\n * contract refusing an emitted event, a provider rejecting the request) 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 && !isRetryable(error)) {\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,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,YAAY,KAAK,GAC9C,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
|
+
{"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 { isRetryable } from '../engine/retry';\nimport { type JourneyRunParts, executeJourney } from '../host/execute';\nimport { type JourneyEnv, type JourneyParams, WAKE_EVENT_TYPE } from '../host/platform';\nimport { LogMessageSender, WebhookMessageSender } from '../host/senders';\nimport { type JourneyRuntimeOptions, resolveRuntime } from '../runtime';\nimport type { JourneyStore } from '../store/index';\nimport { cloudflareJourneyBinding, 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, against the checkpointed deadline, can\n * tell them apart (see wakeExpired).\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 failure a retry would only repeat (`isRetryable`: a template refusing its props, the\n * contract refusing an emitted event, a provider rejecting the request) ends the step at\n * once instead of burning the retry budget. The engine recognizes a NonRetryableError by its\n * `name`, so the original's name is not written over it — the original travels as `cause`.\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 && !isRetryable(error)) {\n const permanent = new NonRetryableError(error.message);\n permanent.cause = error;\n throw permanent;\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 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(\n name: string,\n { timeoutMs, deadlineMs }: { timeoutMs: number; deadlineMs: number }\n ): Promise<'event' | 'timeout'> {\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 wakeExpired). The\n * deadline is the interpreter's checkpoint, so the verdict is the same on the attempt\n * that parked and on a replay after an eviction — which is where the wait usually\n * resurfaces: the instance is rebuilt, the wait is already over, and the first thing to\n * run is this step. The verdict is checkpointed too, so later replays reuse it.\n */\n const timedOut = await this.step.do(`${name}:timed-out`, async () =>\n wakeExpired(deadlineMs, 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 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 private adapted: JourneyEnv | undefined;\n\n /**\n * The env a run ingests its emitted events with: the Workflow binding behind the host's port,\n * so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the\n * host drop its wakes). Built once per instance.\n */\n protected hostEnv(): JourneyEnv {\n return (this.adapted ??= { ...this.env, JOURNEY: cloudflareJourneyBinding(this.env.JOURNEY) });\n }\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.hostEnv(),\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":";;;;;;;;;;;;;;;;;;;AA6BA,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;;;;;;;CAQH,GAAM,MAAc,IAAkC;EACpD,OAAO,KAAK,KAAK,GAAG,MAAM,YAAY;GACpC,IAAI;IACF,OAAO,MAAM,GAAG;GAClB,SAAS,OAAO;IACd,IAAI,iBAAiB,SAAS,CAAC,YAAY,KAAK,GAAG;KACjD,MAAM,YAAY,IAAI,kBAAkB,MAAM,OAAO;KACrD,UAAU,QAAQ;KAClB,MAAM;IACR;IACA,MAAM;GACR;EACF,CAAC;CACH;CAEA,MAAM,MAAc,IAA2B;EAC7C,OAAO,KAAK,KAAK,MAAM,MAAM,EAAE;CACjC;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,YACJ,MACA,EAAE,WAAW,cACiB;EAC9B,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,YAAY,KAAK,IAAI,CAAC,CACpC,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;CAEjE;;;;;;CAOA,UAAgC;EAC9B,OAAQ,KAAK,YAAY;GAAE,GAAG,KAAK;GAAK,SAAS,yBAAyB,KAAK,IAAI,OAAO;EAAE;CAC9F;;;;;;;;;;;;CAaA,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,QAAQ;GAClB,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"}
|
|
@@ -9,16 +9,21 @@
|
|
|
9
9
|
-- interpreter's send_event step being retried after it already committed its
|
|
10
10
|
-- row), so both pass an id they can reproduce.
|
|
11
11
|
--
|
|
12
|
+
-- The id is per user: the key is (user_id, id), so one sender's id can never
|
|
13
|
+
-- block another user's event, and a producer only has to keep its ids unique
|
|
14
|
+
-- within a user.
|
|
15
|
+
--
|
|
12
16
|
-- Only the sender knows whether two deliveries describe one occurrence or two,
|
|
13
17
|
-- so the engine never invents that answer: an ingest that arrives without an id
|
|
14
18
|
-- is given a fresh one, which is a truthful "this is its own occurrence" rather
|
|
15
19
|
-- than a guess.
|
|
16
20
|
CREATE TABLE IF NOT EXISTS events (
|
|
17
|
-
id TEXT
|
|
21
|
+
id TEXT NOT NULL,
|
|
18
22
|
user_id TEXT NOT NULL,
|
|
19
23
|
name TEXT NOT NULL,
|
|
20
24
|
ts INTEGER NOT NULL,
|
|
21
|
-
payload TEXT NOT NULL DEFAULT '{}'
|
|
25
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
26
|
+
PRIMARY KEY (user_id, id)
|
|
22
27
|
);
|
|
23
28
|
CREATE INDEX IF NOT EXISTS idx_events_user_name_ts ON events (user_id, name, ts);
|
|
24
29
|
|
|
@@ -27,6 +32,15 @@ CREATE TABLE IF NOT EXISTS profiles (
|
|
|
27
32
|
props TEXT NOT NULL DEFAULT '{}'
|
|
28
33
|
);
|
|
29
34
|
|
|
35
|
+
-- Workflow bodies (WorkflowIR JSON), content-addressed. They live beside the
|
|
36
|
+
-- routes that point at them so a deploy is read-your-writes: the trigger row
|
|
37
|
+
-- and the body it names are in one strongly consistent database, and a route
|
|
38
|
+
-- never names a body another store has yet to catch up on.
|
|
39
|
+
CREATE TABLE IF NOT EXISTS workflow_ir (
|
|
40
|
+
content_hash TEXT PRIMARY KEY,
|
|
41
|
+
ir TEXT NOT NULL
|
|
42
|
+
);
|
|
43
|
+
|
|
30
44
|
-- Deploy artefacts: segment definitions (resolved by id during evaluation)
|
|
31
45
|
-- and the trigger routing table.
|
|
32
46
|
-- Push devices: one row per provider token, owned by the user who registered it last.
|
|
@@ -87,11 +101,14 @@ CREATE TABLE IF NOT EXISTS segment_starts (
|
|
|
87
101
|
PRIMARY KEY (segment, workflow, user_id)
|
|
88
102
|
);
|
|
89
103
|
|
|
90
|
-
--
|
|
91
|
-
--
|
|
104
|
+
-- Erasure tombstones (forgetUser): every write about a user is conditional on
|
|
105
|
+
-- no row here. While erased_at is null the user's data stays until every
|
|
106
|
+
-- running instance is stopped, and the scheduled sweep retries what is left;
|
|
107
|
+
-- once set, the data is gone and the row stays so the id is never written again.
|
|
92
108
|
CREATE TABLE IF NOT EXISTS erasures (
|
|
93
|
-
user_id
|
|
94
|
-
ts
|
|
109
|
+
user_id TEXT PRIMARY KEY,
|
|
110
|
+
ts INTEGER NOT NULL,
|
|
111
|
+
erased_at INTEGER
|
|
95
112
|
);
|
|
96
113
|
|
|
97
114
|
-- Entry ledger: the once-per-user policy, instance addressing, and the audit
|
|
@@ -110,6 +127,10 @@ CREATE TABLE IF NOT EXISTS entries (
|
|
|
110
127
|
hash TEXT NOT NULL,
|
|
111
128
|
status TEXT NOT NULL,
|
|
112
129
|
ts INTEGER NOT NULL,
|
|
130
|
+
-- When the runner admitted itself (admitInstance), before its first
|
|
131
|
+
-- observable effect; null until then, so an erasure knows nothing of the
|
|
132
|
+
-- instance has run yet.
|
|
133
|
+
admitted_at INTEGER,
|
|
113
134
|
PRIMARY KEY (workflow, user_id)
|
|
114
135
|
);
|
|
115
136
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_host_journey_host = require("../host/journey-host.cjs");
|
|
3
|
+
const require_cloudflare_bindings = require("./bindings.cjs");
|
|
3
4
|
const require_cloudflare_runner = require("./runner.cjs");
|
|
4
5
|
//#region src/cloudflare/worker.ts
|
|
5
6
|
/**
|
|
@@ -27,9 +28,20 @@ function journeyWorker(options) {
|
|
|
27
28
|
await this.leases.get(store)?.();
|
|
28
29
|
}
|
|
29
30
|
createRunParts(store, params) {
|
|
30
|
-
return host.runParts(host.checkEnv(this.
|
|
31
|
+
return host.runParts(host.checkEnv(this.hostEnv()), store, params);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The validated environment, its Workflow binding behind the host's port (an unknown instance
|
|
36
|
+
* answers `InstanceNotFoundError`). Everything below reads this one, never the raw env.
|
|
37
|
+
*/
|
|
38
|
+
const hostEnv = (rawEnv) => {
|
|
39
|
+
const env = host.checkEnv(rawEnv);
|
|
40
|
+
return {
|
|
41
|
+
...env,
|
|
42
|
+
JOURNEY: require_cloudflare_bindings.cloudflareJourneyBinding(env.JOURNEY)
|
|
43
|
+
};
|
|
44
|
+
};
|
|
33
45
|
/** CF's version metadata: the bundle compiled into each new version goes live by itself. */
|
|
34
46
|
const ownVersion = (env) => {
|
|
35
47
|
const metadata = env.CF_VERSION_METADATA;
|
|
@@ -42,7 +54,7 @@ function journeyWorker(options) {
|
|
|
42
54
|
const fetch = async (request, rawEnv, ctx) => {
|
|
43
55
|
let env;
|
|
44
56
|
try {
|
|
45
|
-
env =
|
|
57
|
+
env = hostEnv(rawEnv);
|
|
46
58
|
} catch (error) {
|
|
47
59
|
if (!(error instanceof require_host_journey_host.EnvValidationError)) throw error;
|
|
48
60
|
console.error(`[workflow] ${error.message}`);
|
|
@@ -64,13 +76,11 @@ function journeyWorker(options) {
|
|
|
64
76
|
else ctx.waitUntil(close());
|
|
65
77
|
}
|
|
66
78
|
};
|
|
67
|
-
const sweep = (rawEnv) => host.sweep(
|
|
68
|
-
const scheduled = async (_controller,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await sweep(env);
|
|
73
|
-
})());
|
|
79
|
+
const sweep = (rawEnv) => host.sweep(hostEnv(rawEnv));
|
|
80
|
+
const scheduled = async (_controller, rawEnv, _ctx) => {
|
|
81
|
+
const env = hostEnv(rawEnv);
|
|
82
|
+
await host.applyOwnBundle(env, ownVersion(env));
|
|
83
|
+
await host.sweep(env);
|
|
74
84
|
};
|
|
75
85
|
return {
|
|
76
86
|
Runner,
|
|
@@ -1 +1 @@
|
|
|
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 {
|
|
1
|
+
{"version":3,"file":"worker.cjs","names":["createJourneyHost","JourneyRunner","cloudflareJourneyBinding","EnvValidationError"],"sources":["../../src/cloudflare/worker.ts"],"sourcesContent":["import type { JourneyRunParts } from '../host/execute';\nimport type { SweepResult } from '../host/ingest';\nimport {\n EnvValidationError,\n type JourneyHostOptions,\n createJourneyHost,\n} from '../host/journey-host';\nimport type { JourneyEnv, JourneyParams } from '../host/platform';\nimport type { BundleVersion, JourneyStore } from '../store/index';\nimport { cloudflareJourneyBinding } 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.hostEnv() as Env), store, params);\n }\n }\n\n /**\n * The validated environment, its Workflow binding behind the host's port (an unknown instance\n * answers `InstanceNotFoundError`). Everything below reads this one, never the raw env.\n */\n const hostEnv = (rawEnv: Env): Env => {\n const env = host.checkEnv(rawEnv);\n return { ...env, JOURNEY: cloudflareJourneyBinding(env.JOURNEY) };\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 let env: Env;\n try {\n env = hostEnv(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(hostEnv(rawEnv));\n\n const scheduled = async (_controller: unknown, rawEnv: Env, _ctx: ExecutionContextLike) => {\n // Awaited, not handed to waitUntil: the runtime keeps a scheduled handler alive for as long\n // as its promise runs, while a sweep only parked in waitUntil can be cut off by the\n // handler returning, and its failure would surface nowhere. A version with no traffic yet\n // goes live here; the sweep then reads its segments.\n const env = hostEnv(rawEnv);\n await host.applyOwnBundle(env, ownVersion(env));\n await host.sweep(env);\n };\n\n return { Runner, fetch, sweep, scheduled };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAsDA,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,QAAQ,CAAQ,GAAG,OAAO,MAAM;EAC1E;CACF;;;;;CAMA,MAAM,WAAW,WAAqB;EACpC,MAAM,MAAM,KAAK,SAAS,MAAM;EAChC,OAAO;GAAE,GAAG;GAAK,SAASC,4BAAAA,yBAAyB,IAAI,OAAO;EAAE;CAClE;;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;EACjF,IAAI;EACJ,IAAI;GACF,MAAM,QAAQ,MAAM;EACtB,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,QAAQ,MAAM,CAAC;CAEzD,MAAM,YAAY,OAAO,aAAsB,QAAa,SAA+B;EAKzF,MAAM,MAAM,QAAQ,MAAM;EAC1B,MAAM,KAAK,eAAe,KAAK,WAAW,GAAG,CAAC;EAC9C,MAAM,KAAK,MAAM,GAAG;CACtB;CAEA,OAAO;EAAE;EAAQ;EAAO;EAAO;CAAU;AAC3C"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { JourneyEnv } from "
|
|
2
|
-
import { SweepResult } from "../host/
|
|
1
|
+
import { JourneyEnv } from "../host/platform.cjs";
|
|
2
|
+
import { SweepResult } from "../host/ingest.cjs";
|
|
3
3
|
import { EnvValidationError, JourneyHostOptions } from "../host/journey-host.cjs";
|
|
4
4
|
import { JourneyRunner } from "./runner.cjs";
|
|
5
5
|
//#region src/cloudflare/worker.d.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.cts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"worker.d.cts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":";;;;;;iBAeiB;EACf,UAAU,SAAS;;;;;;;YAQT,qBAAqB,YAAY,cAAc,mBAAmB;iBAE7D,cAAc,YAAY;;EAEzC,gBAAgB,kBAAkB;;EAElC,QAAQ,SAAS,SAAS,KAAK,KAAK,MAAM,yBAAyB,QAAQ;;;;;;;EAO3E,QAAQ,KAAK,QAAQ,QAAQ;;EAE7B,YAAY,qBAAqB,KAAK,KAAK,KAAK,yBAAyB;;;;;;;;;;;;;;wBAe3D,cAAc,YAAY,YACxC,SAAS,qBAAqB,OAC7B,cAAc"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { JourneyEnv } from "
|
|
2
|
-
import { SweepResult } from "../host/
|
|
1
|
+
import { JourneyEnv } from "../host/platform.mjs";
|
|
2
|
+
import { SweepResult } from "../host/ingest.mjs";
|
|
3
3
|
import { EnvValidationError, JourneyHostOptions } from "../host/journey-host.mjs";
|
|
4
4
|
import { JourneyRunner } from "./runner.mjs";
|
|
5
5
|
//#region src/cloudflare/worker.d.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.mts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"worker.d.mts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":";;;;;;iBAeiB;EACf,UAAU,SAAS;;;;;;;YAQT,qBAAqB,YAAY,cAAc,mBAAmB;iBAE7D,cAAc,YAAY;;EAEzC,gBAAgB,kBAAkB;;EAElC,QAAQ,SAAS,SAAS,KAAK,KAAK,MAAM,yBAAyB,QAAQ;;;;;;;EAO3E,QAAQ,KAAK,QAAQ,QAAQ;;EAE7B,YAAY,qBAAqB,KAAK,KAAK,KAAK,yBAAyB;;;;;;;;;;;;;;wBAe3D,cAAc,YAAY,YACxC,SAAS,qBAAqB,OAC7B,cAAc"}
|