@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
package/dist/engine/actions.cjs
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
//#region src/engine/actions.ts
|
|
3
3
|
var RegistryActionInvoker = class {
|
|
4
|
-
policy;
|
|
5
4
|
registry;
|
|
6
|
-
constructor(actions
|
|
7
|
-
this.policy = policy;
|
|
5
|
+
constructor(actions) {
|
|
8
6
|
this.registry = new Map(actions.map((a) => [a.name, a]));
|
|
9
7
|
}
|
|
10
8
|
async invoke(invocation) {
|
|
11
9
|
const registered = this.registry.get(invocation.action);
|
|
12
10
|
if (registered === void 0) throw new Error(`action '${invocation.action}' is not registered in this runtime — was it removed from the deployed bundle while workflows still reference it?`);
|
|
13
|
-
if (this.policy !== "rolling" && invocation.codeHash !== void 0 && invocation.codeHash !== registered.codeHash) {
|
|
14
|
-
const skew = `action '${invocation.action}': deployed code (${registered.codeHash}) differs from the version pinned in this journey's IR (${invocation.codeHash})`;
|
|
15
|
-
if (this.policy === "strict") throw new Error(skew);
|
|
16
|
-
console.warn(`${skew} — running the deployed version`);
|
|
17
|
-
}
|
|
18
11
|
await registered.handler(invocation.args, {
|
|
19
12
|
userId: invocation.userId,
|
|
20
13
|
idempotencyKey: invocation.idempotencyKey
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.cjs","names":[],"sources":["../../src/engine/actions.ts"],"sourcesContent":["import type { ScalarIR } from '../ir';\nimport type { ActionInvocation, ActionInvoker } from './ports';\n\n/**\n * The default ActionInvoker: a name → handler registry (the runtime half of\n * the dual-plane model — IR carries identity, the deployed bundle carries\n * code). Adapter-agnostic: the Cloudflare runner uses it and an AWS adapter\n * would too.\n */\n\n/**\n * What the registry stores — structurally satisfied by the DSL's ActionRef,\n * so the same `action(...)` objects the workflows reference are registered\n * directly, exactly like createMessageSender overrides plug in senders.\n */\nexport interface RegisteredAction {\n readonly name: string;\n
|
|
1
|
+
{"version":3,"file":"actions.cjs","names":[],"sources":["../../src/engine/actions.ts"],"sourcesContent":["import type { ScalarIR } from '../ir';\nimport type { ActionInvocation, ActionInvoker } from './ports';\n\n/**\n * The default ActionInvoker: a name → handler registry (the runtime half of\n * the dual-plane model — IR carries identity, the deployed bundle carries\n * code). Adapter-agnostic: the Cloudflare runner uses it and an AWS adapter\n * would too.\n */\n\n/**\n * What the registry stores — structurally satisfied by the DSL's ActionRef,\n * so the same `action(...)` objects the workflows reference are registered\n * directly, exactly like createMessageSender overrides plug in senders.\n */\nexport interface RegisteredAction {\n readonly name: string;\n /** Method syntax on purpose: accepts ActionRef handlers with narrower arg types. */\n handler(\n args: Record<string, ScalarIR | undefined>,\n ctx: { userId: string; idempotencyKey: string }\n ): Promise<void> | void;\n}\n\nexport class RegistryActionInvoker implements ActionInvoker {\n private readonly registry: ReadonlyMap<string, RegisteredAction>;\n\n constructor(actions: readonly RegisteredAction[]) {\n this.registry = new Map(actions.map((a) => [a.name, a]));\n }\n\n async invoke(invocation: ActionInvocation): Promise<void> {\n const registered = this.registry.get(invocation.action);\n if (registered === undefined) {\n throw new Error(\n `action '${invocation.action}' is not registered in this runtime — was it removed from the deployed bundle while workflows still reference it?`\n );\n }\n // The key is forwarded, not consumed here: this invoker has no state to\n // de-duplicate against, and the step body it runs inside is replayed — the\n // handler is the only place that can collapse the duplicate side effect.\n await registered.handler(invocation.args, {\n userId: invocation.userId,\n idempotencyKey: invocation.idempotencyKey,\n });\n }\n}\n"],"mappings":";;AAwBA,IAAa,wBAAb,MAA4D;CAC1D;CAEA,YAAY,SAAsC;EAChD,KAAK,WAAW,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CACzD;CAEA,MAAM,OAAO,YAA6C;EACxD,MAAM,aAAa,KAAK,SAAS,IAAI,WAAW,MAAM;EACtD,IAAI,eAAe,KAAA,GACjB,MAAM,IAAI,MACR,WAAW,WAAW,OAAO,kHAC/B;EAKF,MAAM,WAAW,QAAQ,WAAW,MAAM;GACxC,QAAQ,WAAW;GACnB,gBAAgB,WAAW;EAC7B,CAAC;CACH;AACF"}
|
|
@@ -14,26 +14,15 @@ import { ActionInvocation, ActionInvoker } from "./ports.cjs";
|
|
|
14
14
|
*/
|
|
15
15
|
export interface RegisteredAction {
|
|
16
16
|
readonly name: string;
|
|
17
|
-
readonly codeHash: string;
|
|
18
17
|
/** Method syntax on purpose: accepts ActionRef handlers with narrower arg types. */
|
|
19
18
|
handler(args: Record<string, ScalarIR | undefined>, ctx: {
|
|
20
19
|
userId: string;
|
|
21
20
|
idempotencyKey: string;
|
|
22
21
|
}): Promise<void> | void;
|
|
23
22
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Version-consistency tiers for the codeHash pinned in IR vs the code actually
|
|
26
|
-
* registered. Deploys are rolling — Worker code and IR never flip in the same
|
|
27
|
-
* instant, and different build pipelines can print the same function
|
|
28
|
-
* differently — so 'warn' is the default: run the current code, log the skew.
|
|
29
|
-
* 'strict' is for actions where running the wrong version is worse than
|
|
30
|
-
* failing the step; 'rolling' silences the log entirely.
|
|
31
|
-
*/
|
|
32
|
-
export type ActionVersionPolicy = 'rolling' | 'warn' | 'strict';
|
|
33
23
|
export declare class RegistryActionInvoker implements ActionInvoker {
|
|
34
|
-
private readonly policy;
|
|
35
24
|
private readonly registry;
|
|
36
|
-
constructor(actions: readonly RegisteredAction[]
|
|
25
|
+
constructor(actions: readonly RegisteredAction[]);
|
|
37
26
|
invoke(invocation: ActionInvocation): Promise<void>;
|
|
38
27
|
}
|
|
39
28
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.cts","names":[],"sources":["../../src/engine/actions.ts"],"mappings":";;;;;;;;;;;;;;iBAeiB;WACN
|
|
1
|
+
{"version":3,"file":"actions.d.cts","names":[],"sources":["../../src/engine/actions.ts"],"mappings":";;;;;;;;;;;;;;iBAeiB;WACN;;EAET,QACE,MAAM,eAAe,uBACrB;IAAO;IAAgB;MACtB;;qBAGQ,iCAAiC;mBAC3B;EAEjB,YAAY,kBAAkB;EAIxB,OAAO,YAAY,mBAAmB"}
|
|
@@ -14,26 +14,15 @@ import { ActionInvocation, ActionInvoker } from "./ports.mjs";
|
|
|
14
14
|
*/
|
|
15
15
|
export interface RegisteredAction {
|
|
16
16
|
readonly name: string;
|
|
17
|
-
readonly codeHash: string;
|
|
18
17
|
/** Method syntax on purpose: accepts ActionRef handlers with narrower arg types. */
|
|
19
18
|
handler(args: Record<string, ScalarIR | undefined>, ctx: {
|
|
20
19
|
userId: string;
|
|
21
20
|
idempotencyKey: string;
|
|
22
21
|
}): Promise<void> | void;
|
|
23
22
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Version-consistency tiers for the codeHash pinned in IR vs the code actually
|
|
26
|
-
* registered. Deploys are rolling — Worker code and IR never flip in the same
|
|
27
|
-
* instant, and different build pipelines can print the same function
|
|
28
|
-
* differently — so 'warn' is the default: run the current code, log the skew.
|
|
29
|
-
* 'strict' is for actions where running the wrong version is worse than
|
|
30
|
-
* failing the step; 'rolling' silences the log entirely.
|
|
31
|
-
*/
|
|
32
|
-
export type ActionVersionPolicy = 'rolling' | 'warn' | 'strict';
|
|
33
23
|
export declare class RegistryActionInvoker implements ActionInvoker {
|
|
34
|
-
private readonly policy;
|
|
35
24
|
private readonly registry;
|
|
36
|
-
constructor(actions: readonly RegisteredAction[]
|
|
25
|
+
constructor(actions: readonly RegisteredAction[]);
|
|
37
26
|
invoke(invocation: ActionInvocation): Promise<void>;
|
|
38
27
|
}
|
|
39
28
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.mts","names":[],"sources":["../../src/engine/actions.ts"],"mappings":";;;;;;;;;;;;;;iBAeiB;WACN
|
|
1
|
+
{"version":3,"file":"actions.d.mts","names":[],"sources":["../../src/engine/actions.ts"],"mappings":";;;;;;;;;;;;;;iBAeiB;WACN;;EAET,QACE,MAAM,eAAe,uBACrB;IAAO;IAAgB;MACtB;;qBAGQ,iCAAiC;mBAC3B;EAEjB,YAAY,kBAAkB;EAIxB,OAAO,YAAY,mBAAmB"}
|
package/dist/engine/actions.mjs
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
//#region src/engine/actions.ts
|
|
2
2
|
var RegistryActionInvoker = class {
|
|
3
|
-
policy;
|
|
4
3
|
registry;
|
|
5
|
-
constructor(actions
|
|
6
|
-
this.policy = policy;
|
|
4
|
+
constructor(actions) {
|
|
7
5
|
this.registry = new Map(actions.map((a) => [a.name, a]));
|
|
8
6
|
}
|
|
9
7
|
async invoke(invocation) {
|
|
10
8
|
const registered = this.registry.get(invocation.action);
|
|
11
9
|
if (registered === void 0) throw new Error(`action '${invocation.action}' is not registered in this runtime — was it removed from the deployed bundle while workflows still reference it?`);
|
|
12
|
-
if (this.policy !== "rolling" && invocation.codeHash !== void 0 && invocation.codeHash !== registered.codeHash) {
|
|
13
|
-
const skew = `action '${invocation.action}': deployed code (${registered.codeHash}) differs from the version pinned in this journey's IR (${invocation.codeHash})`;
|
|
14
|
-
if (this.policy === "strict") throw new Error(skew);
|
|
15
|
-
console.warn(`${skew} — running the deployed version`);
|
|
16
|
-
}
|
|
17
10
|
await registered.handler(invocation.args, {
|
|
18
11
|
userId: invocation.userId,
|
|
19
12
|
idempotencyKey: invocation.idempotencyKey
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.mjs","names":[],"sources":["../../src/engine/actions.ts"],"sourcesContent":["import type { ScalarIR } from '../ir';\nimport type { ActionInvocation, ActionInvoker } from './ports';\n\n/**\n * The default ActionInvoker: a name → handler registry (the runtime half of\n * the dual-plane model — IR carries identity, the deployed bundle carries\n * code). Adapter-agnostic: the Cloudflare runner uses it and an AWS adapter\n * would too.\n */\n\n/**\n * What the registry stores — structurally satisfied by the DSL's ActionRef,\n * so the same `action(...)` objects the workflows reference are registered\n * directly, exactly like createMessageSender overrides plug in senders.\n */\nexport interface RegisteredAction {\n readonly name: string;\n
|
|
1
|
+
{"version":3,"file":"actions.mjs","names":[],"sources":["../../src/engine/actions.ts"],"sourcesContent":["import type { ScalarIR } from '../ir';\nimport type { ActionInvocation, ActionInvoker } from './ports';\n\n/**\n * The default ActionInvoker: a name → handler registry (the runtime half of\n * the dual-plane model — IR carries identity, the deployed bundle carries\n * code). Adapter-agnostic: the Cloudflare runner uses it and an AWS adapter\n * would too.\n */\n\n/**\n * What the registry stores — structurally satisfied by the DSL's ActionRef,\n * so the same `action(...)` objects the workflows reference are registered\n * directly, exactly like createMessageSender overrides plug in senders.\n */\nexport interface RegisteredAction {\n readonly name: string;\n /** Method syntax on purpose: accepts ActionRef handlers with narrower arg types. */\n handler(\n args: Record<string, ScalarIR | undefined>,\n ctx: { userId: string; idempotencyKey: string }\n ): Promise<void> | void;\n}\n\nexport class RegistryActionInvoker implements ActionInvoker {\n private readonly registry: ReadonlyMap<string, RegisteredAction>;\n\n constructor(actions: readonly RegisteredAction[]) {\n this.registry = new Map(actions.map((a) => [a.name, a]));\n }\n\n async invoke(invocation: ActionInvocation): Promise<void> {\n const registered = this.registry.get(invocation.action);\n if (registered === undefined) {\n throw new Error(\n `action '${invocation.action}' is not registered in this runtime — was it removed from the deployed bundle while workflows still reference it?`\n );\n }\n // The key is forwarded, not consumed here: this invoker has no state to\n // de-duplicate against, and the step body it runs inside is replayed — the\n // handler is the only place that can collapse the duplicate side effect.\n await registered.handler(invocation.args, {\n userId: invocation.userId,\n idempotencyKey: invocation.idempotencyKey,\n });\n }\n}\n"],"mappings":";AAwBA,IAAa,wBAAb,MAA4D;CAC1D;CAEA,YAAY,SAAsC;EAChD,KAAK,WAAW,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CACzD;CAEA,MAAM,OAAO,YAA6C;EACxD,MAAM,aAAa,KAAK,SAAS,IAAI,WAAW,MAAM;EACtD,IAAI,eAAe,KAAA,GACjB,MAAM,IAAI,MACR,WAAW,WAAW,OAAO,kHAC/B;EAKF,MAAM,WAAW,QAAQ,WAAW,MAAM;GACxC,QAAQ,WAAW;GACnB,gBAAgB,WAAW;EAC7B,CAAC;CACH;AACF"}
|
|
@@ -114,6 +114,14 @@ function isBetween(actual, values) {
|
|
|
114
114
|
*/
|
|
115
115
|
const PROFILE_UPDATED_EVENT = "$profile_updated";
|
|
116
116
|
/**
|
|
117
|
+
* The wake-up channel for one profile property: `$profile_updated:<path>`. A property predicate
|
|
118
|
+
* subscribes to its own property's channel, and `/identify` fires one per property it patched —
|
|
119
|
+
* so an identify that touches `plan` does not wake a wait on `email`.
|
|
120
|
+
*/
|
|
121
|
+
function profileUpdatedEvent(path) {
|
|
122
|
+
return `${PROFILE_UPDATED_EVENT}:${path}`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
117
125
|
* Wake-up channel for device changes: a device registered for the user, moved to another account
|
|
118
126
|
* or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.
|
|
119
127
|
*/
|
|
@@ -121,9 +129,12 @@ const DEVICES_UPDATED_EVENT = "$devices_updated";
|
|
|
121
129
|
/**
|
|
122
130
|
* The set of event names a condition cares about — what wait_until subscribes
|
|
123
131
|
* to for wake-ups. `performed` contributes its event; a property predicate
|
|
124
|
-
* contributes
|
|
125
|
-
*
|
|
126
|
-
*
|
|
132
|
+
* contributes its property's channel (`profileUpdatedEvent(path)`, so an
|
|
133
|
+
* /identify touching that property wakes the wait and one touching another
|
|
134
|
+
* does not); `reachable` contributes the recipient property's channel, or the
|
|
135
|
+
* device channel for push (in_app, slack and discord have no address to wait
|
|
136
|
+
* on). Segment references are expanded, which is why a FactSource is needed to
|
|
137
|
+
* resolve their definitions.
|
|
127
138
|
*/
|
|
128
139
|
async function relevantEvents(condition, facts) {
|
|
129
140
|
const found = /* @__PURE__ */ new Set();
|
|
@@ -148,9 +159,17 @@ async function relevantEvents(condition, facts) {
|
|
|
148
159
|
break;
|
|
149
160
|
}
|
|
150
161
|
case "property":
|
|
151
|
-
found.add(
|
|
162
|
+
found.add(profileUpdatedEvent(c.path));
|
|
152
163
|
break;
|
|
153
|
-
case "reachable":
|
|
164
|
+
case "reachable": {
|
|
165
|
+
if (c.channel === "push") {
|
|
166
|
+
found.add(DEVICES_UPDATED_EVENT);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
const path = require_engine_recipient.RECIPIENT_PROPERTY[c.channel];
|
|
170
|
+
if (path !== void 0) found.add(profileUpdatedEvent(path));
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
154
173
|
}
|
|
155
174
|
}
|
|
156
175
|
await walk(condition);
|
|
@@ -161,6 +180,7 @@ exports.DEVICES_UPDATED_EVENT = DEVICES_UPDATED_EVENT;
|
|
|
161
180
|
exports.PROFILE_UPDATED_EVENT = PROFILE_UPDATED_EVENT;
|
|
162
181
|
exports.evaluateCondition = evaluateCondition;
|
|
163
182
|
exports.matchesWhere = matchesWhere;
|
|
183
|
+
exports.profileUpdatedEvent = profileUpdatedEvent;
|
|
164
184
|
exports.relevantEvents = relevantEvents;
|
|
165
185
|
|
|
166
186
|
//# sourceMappingURL=condition.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condition.cjs","names":["RECIPIENT_PROPERTY"],"sources":["../../src/engine/condition.ts"],"sourcesContent":["import type { ConditionIR, PropertyOperatorIR, ScalarIR } from '../ir';\nimport type { FactSource } from './ports';\nimport { RECIPIENT_PROPERTY } from './recipient';\n\n/**\n * Evaluation options.\n * - anchorMs: the earliest instant `performed` counts events from — the goal\n * guard anchors at workflow entry (a conversion must happen *after* entry),\n * waitUntil at the moment the wait began (a wake must not be satisfied by\n * pre-existing history). Combined with `within` by taking the later bound.\n * - untilMs: the latest instant `performed` counts events up to — the goal's\n * attribution window ends there, so an event after it is no conversion even\n * when the check runs later (a resumed instance, a delayed wake).\n */\nexport interface EvaluateOptions {\n anchorMs?: number;\n untilMs?: number;\n}\n\n/**\n * ConditionIR evaluation: pure logic, with facts injected through FactSource,\n * so the D1 implementation and the in-memory test one share a single set of\n * semantics and cannot drift into two evaluators.\n */\nexport async function evaluateCondition(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts?: EvaluateOptions\n): Promise<boolean> {\n return evaluate(condition, facts, nowMs, opts, []);\n}\n\n/**\n * `enclosing` is the chain of segment names currently being expanded. A\n * segment that reaches itself has no truth value to converge on, and left\n * unchecked the recursion runs until memory gives out, one D1 round trip per\n * level — inside a step, so Workflows retries it and the instance never gets\n * out. Failing immediately keeps it bounded, and the message names the cycle\n * because only a deployed bundle can fix it. Only the *path* is tracked, so a\n * diamond (two branches referencing one segment) still evaluates twice.\n */\nasync function evaluate(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts: EvaluateOptions | undefined,\n enclosing: readonly string[]\n): Promise<boolean> {\n switch (condition.type) {\n case 'and': {\n for (const child of condition.conditions) {\n if (!(await evaluate(child, facts, nowMs, opts, enclosing))) return false;\n }\n return true;\n }\n case 'or': {\n for (const child of condition.conditions) {\n if (await evaluate(child, facts, nowMs, opts, enclosing)) return true;\n }\n return false;\n }\n case 'not':\n return !(await evaluate(condition.condition, facts, nowMs, opts, enclosing));\n case 'segment': {\n if (enclosing.includes(condition.segment)) {\n throw new Error(\n `segment '${condition.segment}' references itself: ${[...enclosing, condition.segment].join(' -> ')}`\n );\n }\n const def = await facts.getSegmentCondition(condition.segment);\n if (def === undefined) {\n throw new Error(`Unknown segment: '${condition.segment}' (bundle not deployed?)`);\n }\n return evaluate(def, facts, nowMs, opts, [...enclosing, condition.segment]);\n }\n case 'performed': {\n const bounds: number[] = [];\n if (condition.within !== undefined) bounds.push(nowMs - condition.within.ms);\n if (opts?.anchorMs !== undefined) bounds.push(opts.anchorMs);\n const sinceMs = bounds.length > 0 ? Math.max(...bounds) : undefined;\n const count = await facts.countEvents(condition.event, {\n ...(sinceMs !== undefined ? { sinceMs } : {}),\n ...(opts?.untilMs !== undefined ? { untilMs: opts.untilMs } : {}),\n ...(condition.where !== undefined ? { where: condition.where } : {}),\n });\n return count >= (condition.count ?? 1);\n }\n case 'property': {\n const actual = await facts.getProperty(condition.path);\n return compareProperty(condition, actual);\n }\n case 'reachable': {\n if (await facts.isSuppressed?.(condition.channel)) return false;\n if (condition.channel === 'push') return (await facts.listDevices()).length > 0;\n const path = RECIPIENT_PROPERTY[condition.channel];\n // in_app, slack and discord need no address: a signed-in user is reachable in the app, and\n // the webhook channels post to a team channel, not to the user\n return path === undefined || (await facts.getProperty(path)) !== undefined;\n }\n case 'payload':\n // Placement is validated at compile; reaching here means hand-built IR\n throw new Error(\n 'payload conditions are only valid inside performed({ where }) or a trigger where clause'\n );\n }\n}\n\n/**\n * Evaluate a where tree against one event's payload. Shared by performed\n * counting (FactSource implementations) and the ingest router's trigger gate.\n * Dotted paths descend nested objects; a missing or non-scalar leaf behaves\n * like a missing property (exists sees objects, comparisons do not).\n */\nexport function matchesWhere(payload: unknown, where: ConditionIR): boolean {\n switch (where.type) {\n case 'and':\n return where.conditions.every((child) => matchesWhere(payload, child));\n case 'or':\n return where.conditions.some((child) => matchesWhere(payload, child));\n case 'not':\n return !matchesWhere(payload, where.condition);\n case 'payload': {\n const raw = valueAtPath(payload, where.path);\n if (where.op === 'exists') return raw !== undefined && raw !== null;\n if (where.op === 'not_exists') return raw === undefined || raw === null;\n const actual =\n typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'\n ? raw\n : undefined;\n return compareProperty(where, actual);\n }\n default:\n // compileBundle rejects these; defend against hand-built IR\n throw new Error(`where clause may not contain a '${where.type}' condition`);\n }\n}\n\nfunction valueAtPath(payload: unknown, path: string): unknown {\n let current: unknown = payload;\n for (const key of path.split('.')) {\n if (current === null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\nfunction compareProperty(\n condition: {\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n },\n actual: ScalarIR | undefined\n): boolean {\n const { op, value, values } = condition;\n switch (op) {\n case 'exists':\n return actual !== undefined;\n case 'not_exists':\n return actual === undefined;\n case 'eq':\n return actual === value;\n case 'ne':\n return actual !== value;\n case 'gt':\n return actual !== undefined && value !== undefined && actual > value;\n case 'gte':\n return actual !== undefined && value !== undefined && actual >= value;\n case 'lt':\n return actual !== undefined && value !== undefined && actual < value;\n case 'lte':\n return actual !== undefined && value !== undefined && actual <= value;\n case 'between':\n return isBetween(actual, values);\n case 'not_between':\n // A missing property is outside any range, mirroring not_in_array.\n return !isBetween(actual, values);\n case 'in_array':\n return actual !== undefined && (values ?? []).includes(actual);\n case 'not_in_array':\n return actual === undefined || !(values ?? []).includes(actual);\n case 'contains':\n return typeof actual === 'string' && typeof value === 'string' && actual.includes(value);\n case 'not_contains':\n return !(typeof actual === 'string' && typeof value === 'string' && actual.includes(value));\n }\n}\n\nfunction isBetween(actual: ScalarIR | undefined, values: ScalarIR[] | undefined): boolean {\n if (actual === undefined || values === undefined || values.length < 2) return false;\n const [min, max] = values;\n return actual >= min && actual <= max;\n}\n\n/**\n * Wake-up channel for profile changes: a property predicate has no event name\n * of its own, so subscriptions use this reserved name and the ingest router\n * fires it on every /identify. The '$' prefix marks internal names — real\n * events may never use it (the router rejects them).\n */\nexport const PROFILE_UPDATED_EVENT = '$profile_updated';\n\n/**\n * Wake-up channel for device changes: a device registered for the user, moved to another account\n * or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.\n */\nexport const DEVICES_UPDATED_EVENT = '$devices_updated';\n\n/**\n * The set of event names a condition cares about — what wait_until subscribes\n * to for wake-ups. `performed` contributes its event; a property predicate\n * contributes PROFILE_UPDATED_EVENT (so /identify wakes the wait). Segment\n * references are expanded, which is why a FactSource is needed to resolve\n * their definitions.\n */\nexport async function relevantEvents(condition: ConditionIR, facts: FactSource): Promise<string[]> {\n const found = new Set<string>();\n // Names already expanded. The result is a union, so re-walking a segment can\n // only add what is already there — and a segment that reaches itself would\n // otherwise recurse until the stack blows, taking a D1 read per level.\n const expanded = new Set<string>();\n async function walk(c: ConditionIR): Promise<void> {\n switch (c.type) {\n case 'and':\n case 'or':\n for (const child of c.conditions) await walk(child);\n break;\n case 'not':\n await walk(c.condition);\n break;\n case 'performed':\n found.add(c.event);\n break;\n case 'segment': {\n if (expanded.has(c.segment)) break;\n expanded.add(c.segment);\n const def = await facts.getSegmentCondition(c.segment);\n if (def) await walk(def);\n break;\n }\n case 'property':\n found.add(PROFILE_UPDATED_EVENT);\n break;\n case 'reachable':\n found.add(c.channel === 'push' ? DEVICES_UPDATED_EVENT : PROFILE_UPDATED_EVENT);\n break;\n case 'payload':\n break; // only appears inside performed.where, which is not walked\n }\n }\n await walk(condition);\n return [...found];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"condition.cjs","names":["RECIPIENT_PROPERTY"],"sources":["../../src/engine/condition.ts"],"sourcesContent":["import type { ConditionIR, PropertyOperatorIR, ScalarIR } from '../ir';\nimport type { FactSource } from './ports';\nimport { RECIPIENT_PROPERTY } from './recipient';\n\n/**\n * Evaluation options.\n * - anchorMs: the earliest instant `performed` counts events from — the goal\n * guard anchors at workflow entry (a conversion must happen *after* entry),\n * waitUntil at the moment the wait began (a wake must not be satisfied by\n * pre-existing history). Combined with `within` by taking the later bound.\n * - untilMs: the latest instant `performed` counts events up to — the goal's\n * attribution window ends there, so an event after it is no conversion even\n * when the check runs later (a resumed instance, a delayed wake).\n */\nexport interface EvaluateOptions {\n anchorMs?: number;\n untilMs?: number;\n}\n\n/**\n * ConditionIR evaluation: pure logic, with facts injected through FactSource,\n * so the D1 implementation and the in-memory test one share a single set of\n * semantics and cannot drift into two evaluators.\n */\nexport async function evaluateCondition(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts?: EvaluateOptions\n): Promise<boolean> {\n return evaluate(condition, facts, nowMs, opts, []);\n}\n\n/**\n * `enclosing` is the chain of segment names currently being expanded. A\n * segment that reaches itself has no truth value to converge on, and left\n * unchecked the recursion runs until memory gives out, one D1 round trip per\n * level — inside a step, so Workflows retries it and the instance never gets\n * out. Failing immediately keeps it bounded, and the message names the cycle\n * because only a deployed bundle can fix it. Only the *path* is tracked, so a\n * diamond (two branches referencing one segment) still evaluates twice.\n */\nasync function evaluate(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts: EvaluateOptions | undefined,\n enclosing: readonly string[]\n): Promise<boolean> {\n switch (condition.type) {\n case 'and': {\n for (const child of condition.conditions) {\n if (!(await evaluate(child, facts, nowMs, opts, enclosing))) return false;\n }\n return true;\n }\n case 'or': {\n for (const child of condition.conditions) {\n if (await evaluate(child, facts, nowMs, opts, enclosing)) return true;\n }\n return false;\n }\n case 'not':\n return !(await evaluate(condition.condition, facts, nowMs, opts, enclosing));\n case 'segment': {\n if (enclosing.includes(condition.segment)) {\n throw new Error(\n `segment '${condition.segment}' references itself: ${[...enclosing, condition.segment].join(' -> ')}`\n );\n }\n const def = await facts.getSegmentCondition(condition.segment);\n if (def === undefined) {\n throw new Error(`Unknown segment: '${condition.segment}' (bundle not deployed?)`);\n }\n return evaluate(def, facts, nowMs, opts, [...enclosing, condition.segment]);\n }\n case 'performed': {\n const bounds: number[] = [];\n if (condition.within !== undefined) bounds.push(nowMs - condition.within.ms);\n if (opts?.anchorMs !== undefined) bounds.push(opts.anchorMs);\n const sinceMs = bounds.length > 0 ? Math.max(...bounds) : undefined;\n const count = await facts.countEvents(condition.event, {\n ...(sinceMs !== undefined ? { sinceMs } : {}),\n ...(opts?.untilMs !== undefined ? { untilMs: opts.untilMs } : {}),\n ...(condition.where !== undefined ? { where: condition.where } : {}),\n });\n return count >= (condition.count ?? 1);\n }\n case 'property': {\n const actual = await facts.getProperty(condition.path);\n return compareProperty(condition, actual);\n }\n case 'reachable': {\n if (await facts.isSuppressed?.(condition.channel)) return false;\n if (condition.channel === 'push') return (await facts.listDevices()).length > 0;\n const path = RECIPIENT_PROPERTY[condition.channel];\n // in_app, slack and discord need no address: a signed-in user is reachable in the app, and\n // the webhook channels post to a team channel, not to the user\n return path === undefined || (await facts.getProperty(path)) !== undefined;\n }\n case 'payload':\n // Placement is validated at compile; reaching here means hand-built IR\n throw new Error(\n 'payload conditions are only valid inside performed({ where }) or a trigger where clause'\n );\n }\n}\n\n/**\n * Evaluate a where tree against one event's payload. Shared by performed\n * counting (FactSource implementations) and the ingest router's trigger gate.\n * Dotted paths descend nested objects; a missing or non-scalar leaf behaves\n * like a missing property (exists sees objects, comparisons do not).\n */\nexport function matchesWhere(payload: unknown, where: ConditionIR): boolean {\n switch (where.type) {\n case 'and':\n return where.conditions.every((child) => matchesWhere(payload, child));\n case 'or':\n return where.conditions.some((child) => matchesWhere(payload, child));\n case 'not':\n return !matchesWhere(payload, where.condition);\n case 'payload': {\n const raw = valueAtPath(payload, where.path);\n if (where.op === 'exists') return raw !== undefined && raw !== null;\n if (where.op === 'not_exists') return raw === undefined || raw === null;\n const actual =\n typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'\n ? raw\n : undefined;\n return compareProperty(where, actual);\n }\n default:\n // compileBundle rejects these; defend against hand-built IR\n throw new Error(`where clause may not contain a '${where.type}' condition`);\n }\n}\n\nfunction valueAtPath(payload: unknown, path: string): unknown {\n let current: unknown = payload;\n for (const key of path.split('.')) {\n if (current === null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\nfunction compareProperty(\n condition: {\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n },\n actual: ScalarIR | undefined\n): boolean {\n const { op, value, values } = condition;\n switch (op) {\n case 'exists':\n return actual !== undefined;\n case 'not_exists':\n return actual === undefined;\n case 'eq':\n return actual === value;\n case 'ne':\n return actual !== value;\n case 'gt':\n return actual !== undefined && value !== undefined && actual > value;\n case 'gte':\n return actual !== undefined && value !== undefined && actual >= value;\n case 'lt':\n return actual !== undefined && value !== undefined && actual < value;\n case 'lte':\n return actual !== undefined && value !== undefined && actual <= value;\n case 'between':\n return isBetween(actual, values);\n case 'not_between':\n // A missing property is outside any range, mirroring not_in_array.\n return !isBetween(actual, values);\n case 'in_array':\n return actual !== undefined && (values ?? []).includes(actual);\n case 'not_in_array':\n return actual === undefined || !(values ?? []).includes(actual);\n case 'contains':\n return typeof actual === 'string' && typeof value === 'string' && actual.includes(value);\n case 'not_contains':\n return !(typeof actual === 'string' && typeof value === 'string' && actual.includes(value));\n }\n}\n\nfunction isBetween(actual: ScalarIR | undefined, values: ScalarIR[] | undefined): boolean {\n if (actual === undefined || values === undefined || values.length < 2) return false;\n const [min, max] = values;\n return actual >= min && actual <= max;\n}\n\n/**\n * Wake-up channel for profile changes: a property predicate has no event name\n * of its own, so subscriptions use this reserved name and the ingest router\n * fires it on every /identify. The '$' prefix marks internal names — real\n * events may never use it (the router rejects them).\n */\nexport const PROFILE_UPDATED_EVENT = '$profile_updated';\n\n/**\n * The wake-up channel for one profile property: `$profile_updated:<path>`. A property predicate\n * subscribes to its own property's channel, and `/identify` fires one per property it patched —\n * so an identify that touches `plan` does not wake a wait on `email`.\n */\nexport function profileUpdatedEvent(path: string): string {\n return `${PROFILE_UPDATED_EVENT}:${path}`;\n}\n\n/**\n * Wake-up channel for device changes: a device registered for the user, moved to another account\n * or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.\n */\nexport const DEVICES_UPDATED_EVENT = '$devices_updated';\n\n/**\n * The set of event names a condition cares about — what wait_until subscribes\n * to for wake-ups. `performed` contributes its event; a property predicate\n * contributes its property's channel (`profileUpdatedEvent(path)`, so an\n * /identify touching that property wakes the wait and one touching another\n * does not); `reachable` contributes the recipient property's channel, or the\n * device channel for push (in_app, slack and discord have no address to wait\n * on). Segment references are expanded, which is why a FactSource is needed to\n * resolve their definitions.\n */\nexport async function relevantEvents(condition: ConditionIR, facts: FactSource): Promise<string[]> {\n const found = new Set<string>();\n // Names already expanded. The result is a union, so re-walking a segment can\n // only add what is already there — and a segment that reaches itself would\n // otherwise recurse until the stack blows, taking a D1 read per level.\n const expanded = new Set<string>();\n async function walk(c: ConditionIR): Promise<void> {\n switch (c.type) {\n case 'and':\n case 'or':\n for (const child of c.conditions) await walk(child);\n break;\n case 'not':\n await walk(c.condition);\n break;\n case 'performed':\n found.add(c.event);\n break;\n case 'segment': {\n if (expanded.has(c.segment)) break;\n expanded.add(c.segment);\n const def = await facts.getSegmentCondition(c.segment);\n if (def) await walk(def);\n break;\n }\n case 'property':\n found.add(profileUpdatedEvent(c.path));\n break;\n case 'reachable': {\n if (c.channel === 'push') {\n found.add(DEVICES_UPDATED_EVENT);\n break;\n }\n const path = RECIPIENT_PROPERTY[c.channel];\n if (path !== undefined) found.add(profileUpdatedEvent(path));\n break;\n }\n case 'payload':\n break; // only appears inside performed.where, which is not walked\n }\n }\n await walk(condition);\n return [...found];\n}\n"],"mappings":"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"}
|
|
@@ -35,6 +35,12 @@ export declare function matchesWhere(payload: unknown, where: ConditionIR): bool
|
|
|
35
35
|
* events may never use it (the router rejects them).
|
|
36
36
|
*/
|
|
37
37
|
export declare const PROFILE_UPDATED_EVENT = "$profile_updated";
|
|
38
|
+
/**
|
|
39
|
+
* The wake-up channel for one profile property: `$profile_updated:<path>`. A property predicate
|
|
40
|
+
* subscribes to its own property's channel, and `/identify` fires one per property it patched —
|
|
41
|
+
* so an identify that touches `plan` does not wake a wait on `email`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function profileUpdatedEvent(path: string): string;
|
|
38
44
|
/**
|
|
39
45
|
* Wake-up channel for device changes: a device registered for the user, moved to another account
|
|
40
46
|
* or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.
|
|
@@ -43,9 +49,12 @@ export declare const DEVICES_UPDATED_EVENT = "$devices_updated";
|
|
|
43
49
|
/**
|
|
44
50
|
* The set of event names a condition cares about — what wait_until subscribes
|
|
45
51
|
* to for wake-ups. `performed` contributes its event; a property predicate
|
|
46
|
-
* contributes
|
|
47
|
-
*
|
|
48
|
-
*
|
|
52
|
+
* contributes its property's channel (`profileUpdatedEvent(path)`, so an
|
|
53
|
+
* /identify touching that property wakes the wait and one touching another
|
|
54
|
+
* does not); `reachable` contributes the recipient property's channel, or the
|
|
55
|
+
* device channel for push (in_app, slack and discord have no address to wait
|
|
56
|
+
* on). Segment references are expanded, which is why a FactSource is needed to
|
|
57
|
+
* resolve their definitions.
|
|
49
58
|
*/
|
|
50
59
|
export declare function relevantEvents(condition: ConditionIR, facts: FactSource): Promise<string[]>;
|
|
51
60
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condition.d.cts","names":[],"sources":["../../src/engine/condition.ts"],"mappings":";;;;;;;;;;;;;iBAciB;EACf;EACA;;;;;;;wBAQoB,kBACpB,WAAW,aACX,OAAO,YACP,eACA,OAAO,kBACN;;;;;;;wBAqFa,aAAa,kBAAkB,OAAO;;;;;;;qBAuFzC;;;;;
|
|
1
|
+
{"version":3,"file":"condition.d.cts","names":[],"sources":["../../src/engine/condition.ts"],"mappings":";;;;;;;;;;;;;iBAciB;EACf;EACA;;;;;;;wBAQoB,kBACpB,WAAW,aACX,OAAO,YACP,eACA,OAAO,kBACN;;;;;;;wBAqFa,aAAa,kBAAkB,OAAO;;;;;;;qBAuFzC;;;;;;wBAOG,oBAAoB;;;;;qBAQvB;;;;;;;;;;;wBAYS,eAAe,WAAW,aAAa,OAAO,aAAa"}
|
|
@@ -35,6 +35,12 @@ export declare function matchesWhere(payload: unknown, where: ConditionIR): bool
|
|
|
35
35
|
* events may never use it (the router rejects them).
|
|
36
36
|
*/
|
|
37
37
|
export declare const PROFILE_UPDATED_EVENT = "$profile_updated";
|
|
38
|
+
/**
|
|
39
|
+
* The wake-up channel for one profile property: `$profile_updated:<path>`. A property predicate
|
|
40
|
+
* subscribes to its own property's channel, and `/identify` fires one per property it patched —
|
|
41
|
+
* so an identify that touches `plan` does not wake a wait on `email`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function profileUpdatedEvent(path: string): string;
|
|
38
44
|
/**
|
|
39
45
|
* Wake-up channel for device changes: a device registered for the user, moved to another account
|
|
40
46
|
* or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.
|
|
@@ -43,9 +49,12 @@ export declare const DEVICES_UPDATED_EVENT = "$devices_updated";
|
|
|
43
49
|
/**
|
|
44
50
|
* The set of event names a condition cares about — what wait_until subscribes
|
|
45
51
|
* to for wake-ups. `performed` contributes its event; a property predicate
|
|
46
|
-
* contributes
|
|
47
|
-
*
|
|
48
|
-
*
|
|
52
|
+
* contributes its property's channel (`profileUpdatedEvent(path)`, so an
|
|
53
|
+
* /identify touching that property wakes the wait and one touching another
|
|
54
|
+
* does not); `reachable` contributes the recipient property's channel, or the
|
|
55
|
+
* device channel for push (in_app, slack and discord have no address to wait
|
|
56
|
+
* on). Segment references are expanded, which is why a FactSource is needed to
|
|
57
|
+
* resolve their definitions.
|
|
49
58
|
*/
|
|
50
59
|
export declare function relevantEvents(condition: ConditionIR, facts: FactSource): Promise<string[]>;
|
|
51
60
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condition.d.mts","names":[],"sources":["../../src/engine/condition.ts"],"mappings":";;;;;;;;;;;;;iBAciB;EACf;EACA;;;;;;;wBAQoB,kBACpB,WAAW,aACX,OAAO,YACP,eACA,OAAO,kBACN;;;;;;;wBAqFa,aAAa,kBAAkB,OAAO;;;;;;;qBAuFzC;;;;;
|
|
1
|
+
{"version":3,"file":"condition.d.mts","names":[],"sources":["../../src/engine/condition.ts"],"mappings":";;;;;;;;;;;;;iBAciB;EACf;EACA;;;;;;;wBAQoB,kBACpB,WAAW,aACX,OAAO,YACP,eACA,OAAO,kBACN;;;;;;;wBAqFa,aAAa,kBAAkB,OAAO;;;;;;;qBAuFzC;;;;;;wBAOG,oBAAoB;;;;;qBAQvB;;;;;;;;;;;wBAYS,eAAe,WAAW,aAAa,OAAO,aAAa"}
|
|
@@ -113,6 +113,14 @@ function isBetween(actual, values) {
|
|
|
113
113
|
*/
|
|
114
114
|
const PROFILE_UPDATED_EVENT = "$profile_updated";
|
|
115
115
|
/**
|
|
116
|
+
* The wake-up channel for one profile property: `$profile_updated:<path>`. A property predicate
|
|
117
|
+
* subscribes to its own property's channel, and `/identify` fires one per property it patched —
|
|
118
|
+
* so an identify that touches `plan` does not wake a wait on `email`.
|
|
119
|
+
*/
|
|
120
|
+
function profileUpdatedEvent(path) {
|
|
121
|
+
return `${PROFILE_UPDATED_EVENT}:${path}`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
116
124
|
* Wake-up channel for device changes: a device registered for the user, moved to another account
|
|
117
125
|
* or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.
|
|
118
126
|
*/
|
|
@@ -120,9 +128,12 @@ const DEVICES_UPDATED_EVENT = "$devices_updated";
|
|
|
120
128
|
/**
|
|
121
129
|
* The set of event names a condition cares about — what wait_until subscribes
|
|
122
130
|
* to for wake-ups. `performed` contributes its event; a property predicate
|
|
123
|
-
* contributes
|
|
124
|
-
*
|
|
125
|
-
*
|
|
131
|
+
* contributes its property's channel (`profileUpdatedEvent(path)`, so an
|
|
132
|
+
* /identify touching that property wakes the wait and one touching another
|
|
133
|
+
* does not); `reachable` contributes the recipient property's channel, or the
|
|
134
|
+
* device channel for push (in_app, slack and discord have no address to wait
|
|
135
|
+
* on). Segment references are expanded, which is why a FactSource is needed to
|
|
136
|
+
* resolve their definitions.
|
|
126
137
|
*/
|
|
127
138
|
async function relevantEvents(condition, facts) {
|
|
128
139
|
const found = /* @__PURE__ */ new Set();
|
|
@@ -147,15 +158,23 @@ async function relevantEvents(condition, facts) {
|
|
|
147
158
|
break;
|
|
148
159
|
}
|
|
149
160
|
case "property":
|
|
150
|
-
found.add(
|
|
161
|
+
found.add(profileUpdatedEvent(c.path));
|
|
151
162
|
break;
|
|
152
|
-
case "reachable":
|
|
163
|
+
case "reachable": {
|
|
164
|
+
if (c.channel === "push") {
|
|
165
|
+
found.add(DEVICES_UPDATED_EVENT);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
const path = RECIPIENT_PROPERTY[c.channel];
|
|
169
|
+
if (path !== void 0) found.add(profileUpdatedEvent(path));
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
153
172
|
}
|
|
154
173
|
}
|
|
155
174
|
await walk(condition);
|
|
156
175
|
return [...found];
|
|
157
176
|
}
|
|
158
177
|
//#endregion
|
|
159
|
-
export { DEVICES_UPDATED_EVENT, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, relevantEvents };
|
|
178
|
+
export { DEVICES_UPDATED_EVENT, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, profileUpdatedEvent, relevantEvents };
|
|
160
179
|
|
|
161
180
|
//# sourceMappingURL=condition.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condition.mjs","names":[],"sources":["../../src/engine/condition.ts"],"sourcesContent":["import type { ConditionIR, PropertyOperatorIR, ScalarIR } from '../ir';\nimport type { FactSource } from './ports';\nimport { RECIPIENT_PROPERTY } from './recipient';\n\n/**\n * Evaluation options.\n * - anchorMs: the earliest instant `performed` counts events from — the goal\n * guard anchors at workflow entry (a conversion must happen *after* entry),\n * waitUntil at the moment the wait began (a wake must not be satisfied by\n * pre-existing history). Combined with `within` by taking the later bound.\n * - untilMs: the latest instant `performed` counts events up to — the goal's\n * attribution window ends there, so an event after it is no conversion even\n * when the check runs later (a resumed instance, a delayed wake).\n */\nexport interface EvaluateOptions {\n anchorMs?: number;\n untilMs?: number;\n}\n\n/**\n * ConditionIR evaluation: pure logic, with facts injected through FactSource,\n * so the D1 implementation and the in-memory test one share a single set of\n * semantics and cannot drift into two evaluators.\n */\nexport async function evaluateCondition(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts?: EvaluateOptions\n): Promise<boolean> {\n return evaluate(condition, facts, nowMs, opts, []);\n}\n\n/**\n * `enclosing` is the chain of segment names currently being expanded. A\n * segment that reaches itself has no truth value to converge on, and left\n * unchecked the recursion runs until memory gives out, one D1 round trip per\n * level — inside a step, so Workflows retries it and the instance never gets\n * out. Failing immediately keeps it bounded, and the message names the cycle\n * because only a deployed bundle can fix it. Only the *path* is tracked, so a\n * diamond (two branches referencing one segment) still evaluates twice.\n */\nasync function evaluate(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts: EvaluateOptions | undefined,\n enclosing: readonly string[]\n): Promise<boolean> {\n switch (condition.type) {\n case 'and': {\n for (const child of condition.conditions) {\n if (!(await evaluate(child, facts, nowMs, opts, enclosing))) return false;\n }\n return true;\n }\n case 'or': {\n for (const child of condition.conditions) {\n if (await evaluate(child, facts, nowMs, opts, enclosing)) return true;\n }\n return false;\n }\n case 'not':\n return !(await evaluate(condition.condition, facts, nowMs, opts, enclosing));\n case 'segment': {\n if (enclosing.includes(condition.segment)) {\n throw new Error(\n `segment '${condition.segment}' references itself: ${[...enclosing, condition.segment].join(' -> ')}`\n );\n }\n const def = await facts.getSegmentCondition(condition.segment);\n if (def === undefined) {\n throw new Error(`Unknown segment: '${condition.segment}' (bundle not deployed?)`);\n }\n return evaluate(def, facts, nowMs, opts, [...enclosing, condition.segment]);\n }\n case 'performed': {\n const bounds: number[] = [];\n if (condition.within !== undefined) bounds.push(nowMs - condition.within.ms);\n if (opts?.anchorMs !== undefined) bounds.push(opts.anchorMs);\n const sinceMs = bounds.length > 0 ? Math.max(...bounds) : undefined;\n const count = await facts.countEvents(condition.event, {\n ...(sinceMs !== undefined ? { sinceMs } : {}),\n ...(opts?.untilMs !== undefined ? { untilMs: opts.untilMs } : {}),\n ...(condition.where !== undefined ? { where: condition.where } : {}),\n });\n return count >= (condition.count ?? 1);\n }\n case 'property': {\n const actual = await facts.getProperty(condition.path);\n return compareProperty(condition, actual);\n }\n case 'reachable': {\n if (await facts.isSuppressed?.(condition.channel)) return false;\n if (condition.channel === 'push') return (await facts.listDevices()).length > 0;\n const path = RECIPIENT_PROPERTY[condition.channel];\n // in_app, slack and discord need no address: a signed-in user is reachable in the app, and\n // the webhook channels post to a team channel, not to the user\n return path === undefined || (await facts.getProperty(path)) !== undefined;\n }\n case 'payload':\n // Placement is validated at compile; reaching here means hand-built IR\n throw new Error(\n 'payload conditions are only valid inside performed({ where }) or a trigger where clause'\n );\n }\n}\n\n/**\n * Evaluate a where tree against one event's payload. Shared by performed\n * counting (FactSource implementations) and the ingest router's trigger gate.\n * Dotted paths descend nested objects; a missing or non-scalar leaf behaves\n * like a missing property (exists sees objects, comparisons do not).\n */\nexport function matchesWhere(payload: unknown, where: ConditionIR): boolean {\n switch (where.type) {\n case 'and':\n return where.conditions.every((child) => matchesWhere(payload, child));\n case 'or':\n return where.conditions.some((child) => matchesWhere(payload, child));\n case 'not':\n return !matchesWhere(payload, where.condition);\n case 'payload': {\n const raw = valueAtPath(payload, where.path);\n if (where.op === 'exists') return raw !== undefined && raw !== null;\n if (where.op === 'not_exists') return raw === undefined || raw === null;\n const actual =\n typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'\n ? raw\n : undefined;\n return compareProperty(where, actual);\n }\n default:\n // compileBundle rejects these; defend against hand-built IR\n throw new Error(`where clause may not contain a '${where.type}' condition`);\n }\n}\n\nfunction valueAtPath(payload: unknown, path: string): unknown {\n let current: unknown = payload;\n for (const key of path.split('.')) {\n if (current === null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\nfunction compareProperty(\n condition: {\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n },\n actual: ScalarIR | undefined\n): boolean {\n const { op, value, values } = condition;\n switch (op) {\n case 'exists':\n return actual !== undefined;\n case 'not_exists':\n return actual === undefined;\n case 'eq':\n return actual === value;\n case 'ne':\n return actual !== value;\n case 'gt':\n return actual !== undefined && value !== undefined && actual > value;\n case 'gte':\n return actual !== undefined && value !== undefined && actual >= value;\n case 'lt':\n return actual !== undefined && value !== undefined && actual < value;\n case 'lte':\n return actual !== undefined && value !== undefined && actual <= value;\n case 'between':\n return isBetween(actual, values);\n case 'not_between':\n // A missing property is outside any range, mirroring not_in_array.\n return !isBetween(actual, values);\n case 'in_array':\n return actual !== undefined && (values ?? []).includes(actual);\n case 'not_in_array':\n return actual === undefined || !(values ?? []).includes(actual);\n case 'contains':\n return typeof actual === 'string' && typeof value === 'string' && actual.includes(value);\n case 'not_contains':\n return !(typeof actual === 'string' && typeof value === 'string' && actual.includes(value));\n }\n}\n\nfunction isBetween(actual: ScalarIR | undefined, values: ScalarIR[] | undefined): boolean {\n if (actual === undefined || values === undefined || values.length < 2) return false;\n const [min, max] = values;\n return actual >= min && actual <= max;\n}\n\n/**\n * Wake-up channel for profile changes: a property predicate has no event name\n * of its own, so subscriptions use this reserved name and the ingest router\n * fires it on every /identify. The '$' prefix marks internal names — real\n * events may never use it (the router rejects them).\n */\nexport const PROFILE_UPDATED_EVENT = '$profile_updated';\n\n/**\n * Wake-up channel for device changes: a device registered for the user, moved to another account\n * or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.\n */\nexport const DEVICES_UPDATED_EVENT = '$devices_updated';\n\n/**\n * The set of event names a condition cares about — what wait_until subscribes\n * to for wake-ups. `performed` contributes its event; a property predicate\n * contributes PROFILE_UPDATED_EVENT (so /identify wakes the wait). Segment\n * references are expanded, which is why a FactSource is needed to resolve\n * their definitions.\n */\nexport async function relevantEvents(condition: ConditionIR, facts: FactSource): Promise<string[]> {\n const found = new Set<string>();\n // Names already expanded. The result is a union, so re-walking a segment can\n // only add what is already there — and a segment that reaches itself would\n // otherwise recurse until the stack blows, taking a D1 read per level.\n const expanded = new Set<string>();\n async function walk(c: ConditionIR): Promise<void> {\n switch (c.type) {\n case 'and':\n case 'or':\n for (const child of c.conditions) await walk(child);\n break;\n case 'not':\n await walk(c.condition);\n break;\n case 'performed':\n found.add(c.event);\n break;\n case 'segment': {\n if (expanded.has(c.segment)) break;\n expanded.add(c.segment);\n const def = await facts.getSegmentCondition(c.segment);\n if (def) await walk(def);\n break;\n }\n case 'property':\n found.add(PROFILE_UPDATED_EVENT);\n break;\n case 'reachable':\n found.add(c.channel === 'push' ? DEVICES_UPDATED_EVENT : PROFILE_UPDATED_EVENT);\n break;\n case 'payload':\n break; // only appears inside performed.where, which is not walked\n }\n }\n await walk(condition);\n return [...found];\n}\n"],"mappings":";;;;;;;AAwBA,eAAsB,kBACpB,WACA,OACA,OACA,MACkB;CAClB,OAAO,SAAS,WAAW,OAAO,OAAO,MAAM,CAAC,CAAC;AACnD;;;;;;;;;;AAWA,eAAe,SACb,WACA,OACA,OACA,MACA,WACkB;CAClB,QAAQ,UAAU,MAAlB;EACE,KAAK;GACH,KAAK,MAAM,SAAS,UAAU,YAC5B,IAAI,CAAE,MAAM,SAAS,OAAO,OAAO,OAAO,MAAM,SAAS,GAAI,OAAO;GAEtE,OAAO;EAET,KAAK;GACH,KAAK,MAAM,SAAS,UAAU,YAC5B,IAAI,MAAM,SAAS,OAAO,OAAO,OAAO,MAAM,SAAS,GAAG,OAAO;GAEnE,OAAO;EAET,KAAK,OACH,OAAO,CAAE,MAAM,SAAS,UAAU,WAAW,OAAO,OAAO,MAAM,SAAS;EAC5E,KAAK,WAAW;GACd,IAAI,UAAU,SAAS,UAAU,OAAO,GACtC,MAAM,IAAI,MACR,YAAY,UAAU,QAAQ,uBAAuB,CAAC,GAAG,WAAW,UAAU,OAAO,CAAC,CAAC,KAAK,MAAM,GACpG;GAEF,MAAM,MAAM,MAAM,MAAM,oBAAoB,UAAU,OAAO;GAC7D,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,MAAM,qBAAqB,UAAU,QAAQ,yBAAyB;GAElF,OAAO,SAAS,KAAK,OAAO,OAAO,MAAM,CAAC,GAAG,WAAW,UAAU,OAAO,CAAC;EAC5E;EACA,KAAK,aAAa;GAChB,MAAM,SAAmB,CAAC;GAC1B,IAAI,UAAU,WAAW,KAAA,GAAW,OAAO,KAAK,QAAQ,UAAU,OAAO,EAAE;GAC3E,IAAI,MAAM,aAAa,KAAA,GAAW,OAAO,KAAK,KAAK,QAAQ;GAC3D,MAAM,UAAU,OAAO,SAAS,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI,KAAA;GAM1D,OAAO,MALa,MAAM,YAAY,UAAU,OAAO;IACrD,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;IAC3C,GAAI,MAAM,YAAY,KAAA,IAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;IAC/D,GAAI,UAAU,UAAU,KAAA,IAAY,EAAE,OAAO,UAAU,MAAM,IAAI,CAAC;GACpE,CAAC,MACgB,UAAU,SAAS;EACtC;EACA,KAAK,YAEH,OAAO,gBAAgB,WAAW,MADb,MAAM,YAAY,UAAU,IAAI,CACb;EAE1C,KAAK,aAAa;GAChB,IAAI,MAAM,MAAM,eAAe,UAAU,OAAO,GAAG,OAAO;GAC1D,IAAI,UAAU,YAAY,QAAQ,QAAQ,MAAM,MAAM,YAAY,EAAA,CAAG,SAAS;GAC9E,MAAM,OAAO,mBAAmB,UAAU;GAG1C,OAAO,SAAS,KAAA,KAAc,MAAM,MAAM,YAAY,IAAI,MAAO,KAAA;EACnE;EACA,KAAK,WAEH,MAAM,IAAI,MACR,yFACF;CACJ;AACF;;;;;;;AAQA,SAAgB,aAAa,SAAkB,OAA6B;CAC1E,QAAQ,MAAM,MAAd;EACE,KAAK,OACH,OAAO,MAAM,WAAW,OAAO,UAAU,aAAa,SAAS,KAAK,CAAC;EACvE,KAAK,MACH,OAAO,MAAM,WAAW,MAAM,UAAU,aAAa,SAAS,KAAK,CAAC;EACtE,KAAK,OACH,OAAO,CAAC,aAAa,SAAS,MAAM,SAAS;EAC/C,KAAK,WAAW;GACd,MAAM,MAAM,YAAY,SAAS,MAAM,IAAI;GAC3C,IAAI,MAAM,OAAO,UAAU,OAAO,QAAQ,KAAA,KAAa,QAAQ;GAC/D,IAAI,MAAM,OAAO,cAAc,OAAO,QAAQ,KAAA,KAAa,QAAQ;GAKnE,OAAO,gBAAgB,OAHrB,OAAO,QAAQ,YAAY,OAAO,QAAQ,YAAY,OAAO,QAAQ,YACjE,MACA,KAAA,CAC8B;EACtC;EACA,SAEE,MAAM,IAAI,MAAM,mCAAmC,MAAM,KAAK,YAAY;CAC9E;AACF;AAEA,SAAS,YAAY,SAAkB,MAAuB;CAC5D,IAAI,UAAmB;CACvB,KAAK,MAAM,OAAO,KAAK,MAAM,GAAG,GAAG;EACjC,IAAI,YAAY,QAAQ,OAAO,YAAY,UAAU,OAAO,KAAA;EAC5D,UAAW,QAAoC;CACjD;CACA,OAAO;AACT;AAEA,SAAS,gBACP,WAKA,QACS;CACT,MAAM,EAAE,IAAI,OAAO,WAAW;CAC9B,QAAQ,IAAR;EACE,KAAK,UACH,OAAO,WAAW,KAAA;EACpB,KAAK,cACH,OAAO,WAAW,KAAA;EACpB,KAAK,MACH,OAAO,WAAW;EACpB,KAAK,MACH,OAAO,WAAW;EACpB,KAAK,MACH,OAAO,WAAW,KAAA,KAAa,UAAU,KAAA,KAAa,SAAS;EACjE,KAAK,OACH,OAAO,WAAW,KAAA,KAAa,UAAU,KAAA,KAAa,UAAU;EAClE,KAAK,MACH,OAAO,WAAW,KAAA,KAAa,UAAU,KAAA,KAAa,SAAS;EACjE,KAAK,OACH,OAAO,WAAW,KAAA,KAAa,UAAU,KAAA,KAAa,UAAU;EAClE,KAAK,WACH,OAAO,UAAU,QAAQ,MAAM;EACjC,KAAK,eAEH,OAAO,CAAC,UAAU,QAAQ,MAAM;EAClC,KAAK,YACH,OAAO,WAAW,KAAA,MAAc,UAAU,CAAC,EAAA,CAAG,SAAS,MAAM;EAC/D,KAAK,gBACH,OAAO,WAAW,KAAA,KAAa,EAAE,UAAU,CAAC,EAAA,CAAG,SAAS,MAAM;EAChE,KAAK,YACH,OAAO,OAAO,WAAW,YAAY,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK;EACzF,KAAK,gBACH,OAAO,EAAE,OAAO,WAAW,YAAY,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK;CAC7F;AACF;AAEA,SAAS,UAAU,QAA8B,QAAyC;CACxF,IAAI,WAAW,KAAA,KAAa,WAAW,KAAA,KAAa,OAAO,SAAS,GAAG,OAAO;CAC9E,MAAM,CAAC,KAAK,OAAO;CACnB,OAAO,UAAU,OAAO,UAAU;AACpC;;;;;;;AAQA,MAAa,wBAAwB;;;;;AAMrC,MAAa,wBAAwB;;;;;;;;AASrC,eAAsB,eAAe,WAAwB,OAAsC;CACjG,MAAM,wBAAQ,IAAI,IAAY;CAI9B,MAAM,2BAAW,IAAI,IAAY;CACjC,eAAe,KAAK,GAA+B;EACjD,QAAQ,EAAE,MAAV;GACE,KAAK;GACL,KAAK;IACH,KAAK,MAAM,SAAS,EAAE,YAAY,MAAM,KAAK,KAAK;IAClD;GACF,KAAK;IACH,MAAM,KAAK,EAAE,SAAS;IACtB;GACF,KAAK;IACH,MAAM,IAAI,EAAE,KAAK;IACjB;GACF,KAAK,WAAW;IACd,IAAI,SAAS,IAAI,EAAE,OAAO,GAAG;IAC7B,SAAS,IAAI,EAAE,OAAO;IACtB,MAAM,MAAM,MAAM,MAAM,oBAAoB,EAAE,OAAO;IACrD,IAAI,KAAK,MAAM,KAAK,GAAG;IACvB;GACF;GACA,KAAK;IACH,MAAM,IAAI,qBAAqB;IAC/B;GACF,KAAK,aACH,MAAM,IAAI,EAAE,YAAY,SAAS,wBAAwB,qBAAqB;EAIlF;CACF;CACA,MAAM,KAAK,SAAS;CACpB,OAAO,CAAC,GAAG,KAAK;AAClB"}
|
|
1
|
+
{"version":3,"file":"condition.mjs","names":[],"sources":["../../src/engine/condition.ts"],"sourcesContent":["import type { ConditionIR, PropertyOperatorIR, ScalarIR } from '../ir';\nimport type { FactSource } from './ports';\nimport { RECIPIENT_PROPERTY } from './recipient';\n\n/**\n * Evaluation options.\n * - anchorMs: the earliest instant `performed` counts events from — the goal\n * guard anchors at workflow entry (a conversion must happen *after* entry),\n * waitUntil at the moment the wait began (a wake must not be satisfied by\n * pre-existing history). Combined with `within` by taking the later bound.\n * - untilMs: the latest instant `performed` counts events up to — the goal's\n * attribution window ends there, so an event after it is no conversion even\n * when the check runs later (a resumed instance, a delayed wake).\n */\nexport interface EvaluateOptions {\n anchorMs?: number;\n untilMs?: number;\n}\n\n/**\n * ConditionIR evaluation: pure logic, with facts injected through FactSource,\n * so the D1 implementation and the in-memory test one share a single set of\n * semantics and cannot drift into two evaluators.\n */\nexport async function evaluateCondition(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts?: EvaluateOptions\n): Promise<boolean> {\n return evaluate(condition, facts, nowMs, opts, []);\n}\n\n/**\n * `enclosing` is the chain of segment names currently being expanded. A\n * segment that reaches itself has no truth value to converge on, and left\n * unchecked the recursion runs until memory gives out, one D1 round trip per\n * level — inside a step, so Workflows retries it and the instance never gets\n * out. Failing immediately keeps it bounded, and the message names the cycle\n * because only a deployed bundle can fix it. Only the *path* is tracked, so a\n * diamond (two branches referencing one segment) still evaluates twice.\n */\nasync function evaluate(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts: EvaluateOptions | undefined,\n enclosing: readonly string[]\n): Promise<boolean> {\n switch (condition.type) {\n case 'and': {\n for (const child of condition.conditions) {\n if (!(await evaluate(child, facts, nowMs, opts, enclosing))) return false;\n }\n return true;\n }\n case 'or': {\n for (const child of condition.conditions) {\n if (await evaluate(child, facts, nowMs, opts, enclosing)) return true;\n }\n return false;\n }\n case 'not':\n return !(await evaluate(condition.condition, facts, nowMs, opts, enclosing));\n case 'segment': {\n if (enclosing.includes(condition.segment)) {\n throw new Error(\n `segment '${condition.segment}' references itself: ${[...enclosing, condition.segment].join(' -> ')}`\n );\n }\n const def = await facts.getSegmentCondition(condition.segment);\n if (def === undefined) {\n throw new Error(`Unknown segment: '${condition.segment}' (bundle not deployed?)`);\n }\n return evaluate(def, facts, nowMs, opts, [...enclosing, condition.segment]);\n }\n case 'performed': {\n const bounds: number[] = [];\n if (condition.within !== undefined) bounds.push(nowMs - condition.within.ms);\n if (opts?.anchorMs !== undefined) bounds.push(opts.anchorMs);\n const sinceMs = bounds.length > 0 ? Math.max(...bounds) : undefined;\n const count = await facts.countEvents(condition.event, {\n ...(sinceMs !== undefined ? { sinceMs } : {}),\n ...(opts?.untilMs !== undefined ? { untilMs: opts.untilMs } : {}),\n ...(condition.where !== undefined ? { where: condition.where } : {}),\n });\n return count >= (condition.count ?? 1);\n }\n case 'property': {\n const actual = await facts.getProperty(condition.path);\n return compareProperty(condition, actual);\n }\n case 'reachable': {\n if (await facts.isSuppressed?.(condition.channel)) return false;\n if (condition.channel === 'push') return (await facts.listDevices()).length > 0;\n const path = RECIPIENT_PROPERTY[condition.channel];\n // in_app, slack and discord need no address: a signed-in user is reachable in the app, and\n // the webhook channels post to a team channel, not to the user\n return path === undefined || (await facts.getProperty(path)) !== undefined;\n }\n case 'payload':\n // Placement is validated at compile; reaching here means hand-built IR\n throw new Error(\n 'payload conditions are only valid inside performed({ where }) or a trigger where clause'\n );\n }\n}\n\n/**\n * Evaluate a where tree against one event's payload. Shared by performed\n * counting (FactSource implementations) and the ingest router's trigger gate.\n * Dotted paths descend nested objects; a missing or non-scalar leaf behaves\n * like a missing property (exists sees objects, comparisons do not).\n */\nexport function matchesWhere(payload: unknown, where: ConditionIR): boolean {\n switch (where.type) {\n case 'and':\n return where.conditions.every((child) => matchesWhere(payload, child));\n case 'or':\n return where.conditions.some((child) => matchesWhere(payload, child));\n case 'not':\n return !matchesWhere(payload, where.condition);\n case 'payload': {\n const raw = valueAtPath(payload, where.path);\n if (where.op === 'exists') return raw !== undefined && raw !== null;\n if (where.op === 'not_exists') return raw === undefined || raw === null;\n const actual =\n typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'\n ? raw\n : undefined;\n return compareProperty(where, actual);\n }\n default:\n // compileBundle rejects these; defend against hand-built IR\n throw new Error(`where clause may not contain a '${where.type}' condition`);\n }\n}\n\nfunction valueAtPath(payload: unknown, path: string): unknown {\n let current: unknown = payload;\n for (const key of path.split('.')) {\n if (current === null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\nfunction compareProperty(\n condition: {\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n },\n actual: ScalarIR | undefined\n): boolean {\n const { op, value, values } = condition;\n switch (op) {\n case 'exists':\n return actual !== undefined;\n case 'not_exists':\n return actual === undefined;\n case 'eq':\n return actual === value;\n case 'ne':\n return actual !== value;\n case 'gt':\n return actual !== undefined && value !== undefined && actual > value;\n case 'gte':\n return actual !== undefined && value !== undefined && actual >= value;\n case 'lt':\n return actual !== undefined && value !== undefined && actual < value;\n case 'lte':\n return actual !== undefined && value !== undefined && actual <= value;\n case 'between':\n return isBetween(actual, values);\n case 'not_between':\n // A missing property is outside any range, mirroring not_in_array.\n return !isBetween(actual, values);\n case 'in_array':\n return actual !== undefined && (values ?? []).includes(actual);\n case 'not_in_array':\n return actual === undefined || !(values ?? []).includes(actual);\n case 'contains':\n return typeof actual === 'string' && typeof value === 'string' && actual.includes(value);\n case 'not_contains':\n return !(typeof actual === 'string' && typeof value === 'string' && actual.includes(value));\n }\n}\n\nfunction isBetween(actual: ScalarIR | undefined, values: ScalarIR[] | undefined): boolean {\n if (actual === undefined || values === undefined || values.length < 2) return false;\n const [min, max] = values;\n return actual >= min && actual <= max;\n}\n\n/**\n * Wake-up channel for profile changes: a property predicate has no event name\n * of its own, so subscriptions use this reserved name and the ingest router\n * fires it on every /identify. The '$' prefix marks internal names — real\n * events may never use it (the router rejects them).\n */\nexport const PROFILE_UPDATED_EVENT = '$profile_updated';\n\n/**\n * The wake-up channel for one profile property: `$profile_updated:<path>`. A property predicate\n * subscribes to its own property's channel, and `/identify` fires one per property it patched —\n * so an identify that touches `plan` does not wake a wait on `email`.\n */\nexport function profileUpdatedEvent(path: string): string {\n return `${PROFILE_UPDATED_EVENT}:${path}`;\n}\n\n/**\n * Wake-up channel for device changes: a device registered for the user, moved to another account\n * or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.\n */\nexport const DEVICES_UPDATED_EVENT = '$devices_updated';\n\n/**\n * The set of event names a condition cares about — what wait_until subscribes\n * to for wake-ups. `performed` contributes its event; a property predicate\n * contributes its property's channel (`profileUpdatedEvent(path)`, so an\n * /identify touching that property wakes the wait and one touching another\n * does not); `reachable` contributes the recipient property's channel, or the\n * device channel for push (in_app, slack and discord have no address to wait\n * on). Segment references are expanded, which is why a FactSource is needed to\n * resolve their definitions.\n */\nexport async function relevantEvents(condition: ConditionIR, facts: FactSource): Promise<string[]> {\n const found = new Set<string>();\n // Names already expanded. The result is a union, so re-walking a segment can\n // only add what is already there — and a segment that reaches itself would\n // otherwise recurse until the stack blows, taking a D1 read per level.\n const expanded = new Set<string>();\n async function walk(c: ConditionIR): Promise<void> {\n switch (c.type) {\n case 'and':\n case 'or':\n for (const child of c.conditions) await walk(child);\n break;\n case 'not':\n await walk(c.condition);\n break;\n case 'performed':\n found.add(c.event);\n break;\n case 'segment': {\n if (expanded.has(c.segment)) break;\n expanded.add(c.segment);\n const def = await facts.getSegmentCondition(c.segment);\n if (def) await walk(def);\n break;\n }\n case 'property':\n found.add(profileUpdatedEvent(c.path));\n break;\n case 'reachable': {\n if (c.channel === 'push') {\n found.add(DEVICES_UPDATED_EVENT);\n break;\n }\n const path = RECIPIENT_PROPERTY[c.channel];\n if (path !== undefined) found.add(profileUpdatedEvent(path));\n break;\n }\n case 'payload':\n break; // only appears inside performed.where, which is not walked\n }\n }\n await walk(condition);\n return [...found];\n}\n"],"mappings":"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"}
|
package/dist/engine/index.cjs
CHANGED
|
@@ -9,6 +9,7 @@ const require_engine_time_scale = require("./time-scale.cjs");
|
|
|
9
9
|
const require_engine_actions = require("./actions.cjs");
|
|
10
10
|
exports.DEVICES_UPDATED_EVENT = require_engine_condition.DEVICES_UPDATED_EVENT;
|
|
11
11
|
exports.PROFILE_UPDATED_EVENT = require_engine_condition.PROFILE_UPDATED_EVENT;
|
|
12
|
+
exports.PermanentError = require_engine_retry.PermanentError;
|
|
12
13
|
exports.RegistryActionInvoker = require_engine_actions.RegistryActionInvoker;
|
|
13
14
|
exports.evaluateCondition = require_engine_condition.evaluateCondition;
|
|
14
15
|
exports.hashToBucket = require_engine_bucket.hashToBucket;
|
|
@@ -18,8 +19,10 @@ exports.longestJourneyMs = require_engine_duration.longestJourneyMs;
|
|
|
18
19
|
exports.matchesWhere = require_engine_condition.matchesWhere;
|
|
19
20
|
exports.murmur3 = require_engine_bucket.murmur3;
|
|
20
21
|
exports.nextWindowStart = require_engine_time_window.nextWindowStart;
|
|
22
|
+
exports.profileUpdatedEvent = require_engine_condition.profileUpdatedEvent;
|
|
21
23
|
exports.relevantEvents = require_engine_condition.relevantEvents;
|
|
22
24
|
exports.resolveTimeZone = require_engine_time_window.resolveTimeZone;
|
|
23
25
|
exports.runJourney = require_engine_interpreter.runJourney;
|
|
26
|
+
exports.scaleCondition = require_engine_time_scale.scaleCondition;
|
|
24
27
|
exports.scaleDurations = require_engine_time_scale.scaleDurations;
|
|
25
28
|
exports.scaleSegmentWindows = require_engine_time_scale.scaleSegmentWindows;
|
package/dist/engine/index.d.cts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ActionInvocation, ActionInvoker, EngineStep, EventSink, FactSource, JourneyContext, JourneyObserver, JourneyOutcome, LinkPurpose, MessageSender, MessageTracking, OutboundMessage, PushDevice, SendResult } from "./ports.cjs";
|
|
2
|
-
import {
|
|
2
|
+
import { RegisteredAction, RegistryActionInvoker } from "./actions.cjs";
|
|
3
3
|
import { hashToBucket, murmur3 } from "./bucket.cjs";
|
|
4
|
-
import { DEVICES_UPDATED_EVENT, EvaluateOptions, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, relevantEvents } from "./condition.cjs";
|
|
4
|
+
import { DEVICES_UPDATED_EVENT, EvaluateOptions, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, profileUpdatedEvent, relevantEvents } from "./condition.cjs";
|
|
5
5
|
import { runJourney } from "./interpreter.cjs";
|
|
6
|
-
import { scaleDurations, scaleSegmentWindows } from "./time-scale.cjs";
|
|
6
|
+
import { scaleCondition, scaleDurations, scaleSegmentWindows } from "./time-scale.cjs";
|
|
7
7
|
import { nextWindowStart, resolveTimeZone } from "./time-window.cjs";
|
|
8
8
|
import { longestJourneyMs } from "./duration.cjs";
|
|
9
|
-
import { isRetryable, isRetryableStatus } from "./retry.cjs";
|
|
10
|
-
export { type ActionInvocation, type ActionInvoker,
|
|
9
|
+
import { PermanentError, isRetryable, isRetryableStatus } from "./retry.cjs";
|
|
10
|
+
export { type ActionInvocation, type ActionInvoker, DEVICES_UPDATED_EVENT, type EngineStep, type EvaluateOptions, type EventSink, type FactSource, type JourneyContext, type JourneyObserver, type JourneyOutcome, type LinkPurpose, type MessageSender, type MessageTracking, type OutboundMessage, PROFILE_UPDATED_EVENT, PermanentError, type PushDevice, type RegisteredAction, RegistryActionInvoker, type SendResult, evaluateCondition, hashToBucket, isRetryable, isRetryableStatus, longestJourneyMs, matchesWhere, murmur3, nextWindowStart, profileUpdatedEvent, relevantEvents, resolveTimeZone, runJourney, scaleCondition, scaleDurations, scaleSegmentWindows };
|
package/dist/engine/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ActionInvocation, ActionInvoker, EngineStep, EventSink, FactSource, JourneyContext, JourneyObserver, JourneyOutcome, LinkPurpose, MessageSender, MessageTracking, OutboundMessage, PushDevice, SendResult } from "./ports.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { RegisteredAction, RegistryActionInvoker } from "./actions.mjs";
|
|
3
3
|
import { hashToBucket, murmur3 } from "./bucket.mjs";
|
|
4
|
-
import { DEVICES_UPDATED_EVENT, EvaluateOptions, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, relevantEvents } from "./condition.mjs";
|
|
4
|
+
import { DEVICES_UPDATED_EVENT, EvaluateOptions, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, profileUpdatedEvent, relevantEvents } from "./condition.mjs";
|
|
5
5
|
import { runJourney } from "./interpreter.mjs";
|
|
6
|
-
import { scaleDurations, scaleSegmentWindows } from "./time-scale.mjs";
|
|
6
|
+
import { scaleCondition, scaleDurations, scaleSegmentWindows } from "./time-scale.mjs";
|
|
7
7
|
import { nextWindowStart, resolveTimeZone } from "./time-window.mjs";
|
|
8
8
|
import { longestJourneyMs } from "./duration.mjs";
|
|
9
|
-
import { isRetryable, isRetryableStatus } from "./retry.mjs";
|
|
10
|
-
export { type ActionInvocation, type ActionInvoker,
|
|
9
|
+
import { PermanentError, isRetryable, isRetryableStatus } from "./retry.mjs";
|
|
10
|
+
export { type ActionInvocation, type ActionInvoker, DEVICES_UPDATED_EVENT, type EngineStep, type EvaluateOptions, type EventSink, type FactSource, type JourneyContext, type JourneyObserver, type JourneyOutcome, type LinkPurpose, type MessageSender, type MessageTracking, type OutboundMessage, PROFILE_UPDATED_EVENT, PermanentError, type PushDevice, type RegisteredAction, RegistryActionInvoker, type SendResult, evaluateCondition, hashToBucket, isRetryable, isRetryableStatus, longestJourneyMs, matchesWhere, murmur3, nextWindowStart, profileUpdatedEvent, relevantEvents, resolveTimeZone, runJourney, scaleCondition, scaleDurations, scaleSegmentWindows };
|
package/dist/engine/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { isRetryable, isRetryableStatus } from "./retry.mjs";
|
|
1
|
+
import { PermanentError, isRetryable, isRetryableStatus } from "./retry.mjs";
|
|
2
2
|
import { longestJourneyMs } from "./duration.mjs";
|
|
3
3
|
import { hashToBucket, murmur3 } from "./bucket.mjs";
|
|
4
|
-
import { DEVICES_UPDATED_EVENT, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, relevantEvents } from "./condition.mjs";
|
|
4
|
+
import { DEVICES_UPDATED_EVENT, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, profileUpdatedEvent, relevantEvents } from "./condition.mjs";
|
|
5
5
|
import { nextWindowStart, resolveTimeZone } from "./time-window.mjs";
|
|
6
6
|
import { runJourney } from "./interpreter.mjs";
|
|
7
|
-
import { scaleDurations, scaleSegmentWindows } from "./time-scale.mjs";
|
|
7
|
+
import { scaleCondition, scaleDurations, scaleSegmentWindows } from "./time-scale.mjs";
|
|
8
8
|
import { RegistryActionInvoker } from "./actions.mjs";
|
|
9
|
-
export { DEVICES_UPDATED_EVENT, PROFILE_UPDATED_EVENT, RegistryActionInvoker, evaluateCondition, hashToBucket, isRetryable, isRetryableStatus, longestJourneyMs, matchesWhere, murmur3, nextWindowStart, relevantEvents, resolveTimeZone, runJourney, scaleDurations, scaleSegmentWindows };
|
|
9
|
+
export { DEVICES_UPDATED_EVENT, PROFILE_UPDATED_EVENT, PermanentError, RegistryActionInvoker, evaluateCondition, hashToBucket, isRetryable, isRetryableStatus, longestJourneyMs, matchesWhere, murmur3, nextWindowStart, profileUpdatedEvent, relevantEvents, resolveTimeZone, runJourney, scaleCondition, scaleDurations, scaleSegmentWindows };
|