@shware/workflow 5.0.0 → 6.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 +165 -42
- package/dist/_virtual/_rolldown/runtime.cjs +1 -2
- package/dist/apns-relay.cjs +1 -2
- package/dist/apns-relay.d.cts +0 -2
- package/dist/apns-relay.d.cts.map +1 -1
- package/dist/apns-relay.d.mts +0 -2
- package/dist/apns-relay.d.mts.map +1 -1
- package/dist/apns-relay.mjs +1 -2
- package/dist/apns.cjs +1 -2
- package/dist/apns.d.cts +0 -2
- package/dist/apns.d.cts.map +1 -1
- package/dist/apns.d.mts +0 -2
- package/dist/apns.d.mts.map +1 -1
- package/dist/apns.mjs +1 -2
- package/dist/aws/engine-step.cjs +1 -2
- package/dist/aws/engine-step.d.cts +0 -2
- package/dist/aws/engine-step.d.cts.map +1 -1
- package/dist/aws/engine-step.d.mts +0 -2
- package/dist/aws/engine-step.d.mts.map +1 -1
- package/dist/aws/engine-step.mjs +1 -2
- package/dist/aws/function-url.cjs +1 -2
- package/dist/aws/function-url.d.cts +0 -2
- package/dist/aws/function-url.d.cts.map +1 -1
- package/dist/aws/function-url.d.mts +0 -2
- package/dist/aws/function-url.d.mts.map +1 -1
- package/dist/aws/function-url.mjs +1 -2
- package/dist/aws/index.cjs +2 -3
- package/dist/aws/index.d.cts +0 -2
- package/dist/aws/index.d.cts.map +1 -1
- package/dist/aws/index.d.mts +0 -2
- package/dist/aws/index.d.mts.map +1 -1
- package/dist/aws/index.mjs +2 -3
- package/dist/aws/instances.cjs +1 -2
- package/dist/aws/instances.d.cts +0 -2
- package/dist/aws/instances.d.cts.map +1 -1
- package/dist/aws/instances.d.mts +0 -2
- package/dist/aws/instances.d.mts.map +1 -1
- package/dist/aws/instances.mjs +1 -2
- package/dist/aws/lambda-api.cjs +1 -2
- package/dist/aws/lambda-api.d.cts +0 -2
- package/dist/aws/lambda-api.d.cts.map +1 -1
- package/dist/aws/lambda-api.d.mts +0 -2
- package/dist/aws/lambda-api.d.mts.map +1 -1
- package/dist/aws/lambda-api.mjs +1 -2
- package/dist/client.cjs +46 -6
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +25 -5
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.mts +25 -5
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +46 -6
- package/dist/client.mjs.map +1 -1
- package/dist/cloudflare/bindings.cjs +1 -2
- package/dist/cloudflare/bindings.d.cts +0 -2
- package/dist/cloudflare/bindings.d.cts.map +1 -1
- package/dist/cloudflare/bindings.d.mts +0 -2
- package/dist/cloudflare/bindings.d.mts.map +1 -1
- package/dist/cloudflare/bindings.mjs +1 -2
- package/dist/cloudflare/index.cjs +2 -2
- package/dist/cloudflare/index.mjs +2 -2
- package/dist/cloudflare/runner.cjs +2 -3
- package/dist/cloudflare/runner.d.cts +1 -3
- package/dist/cloudflare/runner.d.cts.map +1 -1
- package/dist/cloudflare/runner.d.mts +1 -3
- package/dist/cloudflare/runner.d.mts.map +1 -1
- package/dist/cloudflare/runner.mjs +2 -3
- package/dist/cloudflare/worker.cjs +1 -2
- package/dist/cloudflare/worker.d.cts +0 -2
- package/dist/cloudflare/worker.d.cts.map +1 -1
- package/dist/cloudflare/worker.d.mts +0 -2
- package/dist/cloudflare/worker.d.mts.map +1 -1
- package/dist/cloudflare/worker.mjs +1 -2
- package/dist/cloudflare/workers.d.cts +0 -2
- package/dist/cloudflare/workers.d.cts.map +1 -1
- package/dist/cloudflare/workers.d.mts +0 -2
- package/dist/cloudflare/workers.d.mts.map +1 -1
- package/dist/config.cjs +25 -5
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +15 -11
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +15 -11
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +26 -6
- package/dist/config.mjs.map +1 -1
- package/dist/contract.cjs +12 -2
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +28 -9
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +28 -9
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.mjs +12 -2
- package/dist/contract.mjs.map +1 -1
- package/dist/dsl/action.cjs +1 -2
- package/dist/dsl/action.d.cts +0 -2
- package/dist/dsl/action.d.cts.map +1 -1
- package/dist/dsl/action.d.mts +0 -2
- package/dist/dsl/action.d.mts.map +1 -1
- package/dist/dsl/action.mjs +1 -2
- package/dist/dsl/base.cjs +1 -2
- package/dist/dsl/base.d.cts +0 -2
- package/dist/dsl/base.d.cts.map +1 -1
- package/dist/dsl/base.d.mts +0 -2
- package/dist/dsl/base.d.mts.map +1 -1
- package/dist/dsl/base.mjs +1 -2
- package/dist/dsl/bundle.cjs +5 -5
- package/dist/dsl/bundle.cjs.map +1 -1
- package/dist/dsl/bundle.d.cts +5 -3
- package/dist/dsl/bundle.d.cts.map +1 -1
- package/dist/dsl/bundle.d.mts +5 -3
- package/dist/dsl/bundle.d.mts.map +1 -1
- package/dist/dsl/bundle.mjs +5 -5
- package/dist/dsl/bundle.mjs.map +1 -1
- package/dist/dsl/condition.cjs +1 -2
- package/dist/dsl/condition.d.cts +0 -2
- package/dist/dsl/condition.d.cts.map +1 -1
- package/dist/dsl/condition.d.mts +0 -2
- package/dist/dsl/condition.d.mts.map +1 -1
- package/dist/dsl/condition.mjs +1 -2
- package/dist/dsl/flow.cjs +3 -3
- package/dist/dsl/flow.cjs.map +1 -1
- package/dist/dsl/flow.d.cts +1 -3
- package/dist/dsl/flow.d.cts.map +1 -1
- package/dist/dsl/flow.d.mts +1 -3
- package/dist/dsl/flow.d.mts.map +1 -1
- package/dist/dsl/flow.mjs +3 -3
- package/dist/dsl/flow.mjs.map +1 -1
- package/dist/dsl/refs.cjs +1 -2
- package/dist/dsl/refs.d.cts +0 -2
- package/dist/dsl/refs.d.cts.map +1 -1
- package/dist/dsl/refs.d.mts +0 -2
- package/dist/dsl/refs.d.mts.map +1 -1
- package/dist/dsl/refs.mjs +1 -2
- package/dist/dsl/template.cjs +1 -2
- package/dist/dsl/template.d.cts +0 -2
- package/dist/dsl/template.d.cts.map +1 -1
- package/dist/dsl/template.d.mts +0 -2
- package/dist/dsl/template.d.mts.map +1 -1
- package/dist/dsl/template.mjs +1 -2
- package/dist/dsl/trigger.cjs +1 -2
- package/dist/dsl/trigger.d.cts +0 -2
- package/dist/dsl/trigger.d.cts.map +1 -1
- package/dist/dsl/trigger.d.mts +0 -2
- package/dist/dsl/trigger.d.mts.map +1 -1
- package/dist/dsl/trigger.mjs +1 -2
- package/dist/dsl/workflow.cjs +3 -4
- package/dist/dsl/workflow.d.cts +0 -2
- package/dist/dsl/workflow.d.cts.map +1 -1
- package/dist/dsl/workflow.d.mts +0 -2
- package/dist/dsl/workflow.d.mts.map +1 -1
- package/dist/dsl/workflow.mjs +3 -4
- package/dist/email-context.cjs +1 -2
- package/dist/email-context.d.cts +0 -2
- package/dist/email-context.d.cts.map +1 -1
- package/dist/email-context.d.mts +0 -2
- package/dist/email-context.d.mts.map +1 -1
- package/dist/email-context.mjs +1 -2
- package/dist/email-sender.cjs +2 -3
- package/dist/email-sender.cjs.map +1 -1
- package/dist/email-sender.d.cts +1 -3
- package/dist/email-sender.d.cts.map +1 -1
- package/dist/email-sender.d.mts +1 -3
- package/dist/email-sender.d.mts.map +1 -1
- package/dist/email-sender.mjs +2 -3
- package/dist/email-sender.mjs.map +1 -1
- package/dist/email.cjs +33 -7
- package/dist/email.cjs.map +1 -1
- package/dist/email.d.cts +38 -11
- package/dist/email.d.cts.map +1 -1
- package/dist/email.d.mts +38 -11
- package/dist/email.d.mts.map +1 -1
- package/dist/email.mjs +33 -8
- package/dist/email.mjs.map +1 -1
- package/dist/engine/actions.cjs +1 -2
- package/dist/engine/actions.d.cts +0 -2
- package/dist/engine/actions.d.cts.map +1 -1
- package/dist/engine/actions.d.mts +0 -2
- package/dist/engine/actions.d.mts.map +1 -1
- package/dist/engine/actions.mjs +1 -2
- package/dist/engine/bucket.cjs +1 -2
- package/dist/engine/bucket.d.cts +0 -2
- package/dist/engine/bucket.d.cts.map +1 -1
- package/dist/engine/bucket.d.mts +0 -2
- package/dist/engine/bucket.d.mts.map +1 -1
- package/dist/engine/bucket.mjs +1 -2
- package/dist/engine/condition.cjs +1 -2
- package/dist/engine/condition.cjs.map +1 -1
- package/dist/engine/condition.d.cts +0 -2
- package/dist/engine/condition.d.cts.map +1 -1
- package/dist/engine/condition.d.mts +0 -2
- package/dist/engine/condition.d.mts.map +1 -1
- package/dist/engine/condition.mjs +1 -2
- package/dist/engine/condition.mjs.map +1 -1
- package/dist/engine/duration.cjs +1 -2
- package/dist/engine/duration.d.cts +0 -2
- package/dist/engine/duration.d.cts.map +1 -1
- package/dist/engine/duration.d.mts +0 -2
- package/dist/engine/duration.d.mts.map +1 -1
- package/dist/engine/duration.mjs +1 -2
- package/dist/engine/index.cjs +1 -1
- package/dist/engine/index.mjs +1 -1
- package/dist/engine/interpreter.cjs +22 -6
- package/dist/engine/interpreter.cjs.map +1 -1
- package/dist/engine/interpreter.d.cts +0 -2
- package/dist/engine/interpreter.d.cts.map +1 -1
- package/dist/engine/interpreter.d.mts +0 -2
- package/dist/engine/interpreter.d.mts.map +1 -1
- package/dist/engine/interpreter.mjs +22 -6
- package/dist/engine/interpreter.mjs.map +1 -1
- package/dist/engine/ports.d.cts +8 -4
- package/dist/engine/ports.d.cts.map +1 -1
- package/dist/engine/ports.d.mts +8 -4
- package/dist/engine/ports.d.mts.map +1 -1
- package/dist/engine/recipient.cjs +1 -2
- package/dist/engine/recipient.d.cts +0 -2
- package/dist/engine/recipient.d.cts.map +1 -1
- package/dist/engine/recipient.d.mts +0 -2
- package/dist/engine/recipient.d.mts.map +1 -1
- package/dist/engine/recipient.mjs +1 -2
- package/dist/engine/retry.cjs +1 -2
- package/dist/engine/retry.d.cts +0 -2
- package/dist/engine/retry.d.cts.map +1 -1
- package/dist/engine/retry.d.mts +0 -2
- package/dist/engine/retry.d.mts.map +1 -1
- package/dist/engine/retry.mjs +1 -2
- package/dist/engine/time-scale.cjs +1 -2
- package/dist/engine/time-scale.d.cts +0 -2
- package/dist/engine/time-scale.d.cts.map +1 -1
- package/dist/engine/time-scale.d.mts +0 -2
- package/dist/engine/time-scale.d.mts.map +1 -1
- package/dist/engine/time-scale.mjs +1 -2
- package/dist/engine/time-window.cjs +1 -2
- package/dist/engine/time-window.d.cts +0 -2
- package/dist/engine/time-window.d.cts.map +1 -1
- package/dist/engine/time-window.d.mts +0 -2
- package/dist/engine/time-window.d.mts.map +1 -1
- package/dist/engine/time-window.mjs +1 -2
- package/dist/fcm.cjs +1 -2
- package/dist/fcm.d.cts +0 -2
- package/dist/fcm.d.cts.map +1 -1
- package/dist/fcm.d.mts +0 -2
- package/dist/fcm.d.mts.map +1 -1
- package/dist/fcm.mjs +1 -2
- package/dist/hash.cjs +1 -2
- package/dist/hash.d.cts +0 -2
- package/dist/hash.d.cts.map +1 -1
- package/dist/hash.d.mts +0 -2
- package/dist/hash.d.mts.map +1 -1
- package/dist/hash.mjs +1 -2
- package/dist/host/auth.cjs +1 -2
- package/dist/host/auth.d.cts +0 -2
- package/dist/host/auth.d.cts.map +1 -1
- package/dist/host/auth.d.mts +0 -2
- package/dist/host/auth.d.mts.map +1 -1
- package/dist/host/auth.mjs +1 -2
- package/dist/host/channels.cjs +2 -3
- package/dist/host/channels.d.cts +0 -2
- package/dist/host/channels.d.cts.map +1 -1
- package/dist/host/channels.d.mts +0 -2
- package/dist/host/channels.d.mts.map +1 -1
- package/dist/host/channels.mjs +2 -3
- package/dist/host/execute.cjs +3 -4
- package/dist/host/execute.d.cts +1 -3
- package/dist/host/execute.d.cts.map +1 -1
- package/dist/host/execute.d.mts +1 -3
- package/dist/host/execute.d.mts.map +1 -1
- package/dist/host/execute.mjs +3 -4
- package/dist/host/fresh-read.cjs +1 -2
- package/dist/host/fresh-read.d.cts +0 -2
- package/dist/host/fresh-read.d.cts.map +1 -1
- package/dist/host/fresh-read.d.mts +0 -2
- package/dist/host/fresh-read.d.mts.map +1 -1
- package/dist/host/fresh-read.mjs +1 -2
- package/dist/host/http.cjs +39 -5
- package/dist/host/http.cjs.map +1 -1
- package/dist/host/http.d.cts +2 -4
- package/dist/host/http.d.cts.map +1 -1
- package/dist/host/http.d.mts +2 -4
- package/dist/host/http.d.mts.map +1 -1
- package/dist/host/http.mjs +40 -6
- package/dist/host/http.mjs.map +1 -1
- package/dist/host/ingest.cjs +129 -6
- package/dist/host/ingest.cjs.map +1 -1
- package/dist/host/ingest.d.cts +71 -2
- package/dist/host/ingest.d.cts.map +1 -1
- package/dist/host/ingest.d.mts +71 -2
- package/dist/host/ingest.d.mts.map +1 -1
- package/dist/host/ingest.mjs +126 -7
- package/dist/host/ingest.mjs.map +1 -1
- package/dist/host/journey-host.cjs +11 -10
- package/dist/host/journey-host.cjs.map +1 -1
- package/dist/host/journey-host.d.cts +0 -2
- package/dist/host/journey-host.d.cts.map +1 -1
- package/dist/host/journey-host.d.mts +0 -2
- package/dist/host/journey-host.d.mts.map +1 -1
- package/dist/host/journey-host.mjs +11 -10
- package/dist/host/journey-host.mjs.map +1 -1
- package/dist/host/platform.cjs +1 -2
- package/dist/host/platform.d.cts +0 -2
- package/dist/host/platform.d.cts.map +1 -1
- package/dist/host/platform.d.mts +0 -2
- package/dist/host/platform.d.mts.map +1 -1
- package/dist/host/platform.mjs +1 -2
- package/dist/host/senders.cjs +1 -2
- package/dist/host/senders.d.cts +0 -2
- package/dist/host/senders.d.cts.map +1 -1
- package/dist/host/senders.d.mts +0 -2
- package/dist/host/senders.d.mts.map +1 -1
- package/dist/host/senders.mjs +1 -2
- package/dist/http2-client.cjs +1 -2
- package/dist/http2-client.d.cts +0 -2
- package/dist/http2-client.d.cts.map +1 -1
- package/dist/http2-client.d.mts +0 -2
- package/dist/http2-client.d.mts.map +1 -1
- package/dist/http2-client.mjs +1 -2
- package/dist/index.cjs +5 -2
- package/dist/index.d.cts +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +5 -5
- package/dist/ir.cjs +12 -4
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +17 -2
- package/dist/ir.d.cts.map +1 -1
- package/dist/ir.d.mts +17 -2
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +12 -5
- package/dist/ir.mjs.map +1 -1
- package/dist/jwt.cjs +1 -2
- package/dist/jwt.d.cts +0 -2
- package/dist/jwt.d.cts.map +1 -1
- package/dist/jwt.d.mts +0 -2
- package/dist/jwt.d.mts.map +1 -1
- package/dist/jwt.mjs +1 -2
- package/dist/plan.cjs +1 -2
- package/dist/plan.d.cts +0 -2
- package/dist/plan.d.cts.map +1 -1
- package/dist/plan.d.mts +0 -2
- package/dist/plan.d.mts.map +1 -1
- package/dist/plan.mjs +1 -2
- package/dist/postgres-js.cjs +1 -2
- package/dist/postgres-js.d.cts +0 -2
- package/dist/postgres-js.d.cts.map +1 -1
- package/dist/postgres-js.d.mts +0 -2
- package/dist/postgres-js.d.mts.map +1 -1
- package/dist/postgres-js.mjs +1 -2
- package/dist/provenance.cjs +1 -2
- package/dist/provenance.d.cts +0 -2
- package/dist/provenance.d.cts.map +1 -1
- package/dist/provenance.d.mts +0 -2
- package/dist/provenance.d.mts.map +1 -1
- package/dist/provenance.mjs +1 -2
- package/dist/push.cjs +1 -2
- package/dist/push.d.cts +0 -2
- package/dist/push.d.cts.map +1 -1
- package/dist/push.d.mts +0 -2
- package/dist/push.d.mts.map +1 -1
- package/dist/push.mjs +1 -2
- package/dist/react-email.cjs +1 -2
- package/dist/react-email.d.cts +0 -2
- package/dist/react-email.d.cts.map +1 -1
- package/dist/react-email.d.mts +0 -2
- package/dist/react-email.d.mts.map +1 -1
- package/dist/react-email.mjs +1 -2
- package/dist/reporting/classify.cjs +1 -2
- package/dist/reporting/classify.d.cts +0 -2
- package/dist/reporting/classify.d.cts.map +1 -1
- package/dist/reporting/classify.d.mts +0 -2
- package/dist/reporting/classify.d.mts.map +1 -1
- package/dist/reporting/classify.mjs +1 -2
- package/dist/reporting/observer.cjs +2 -2
- package/dist/reporting/observer.cjs.map +1 -1
- package/dist/reporting/observer.d.cts +0 -2
- package/dist/reporting/observer.d.cts.map +1 -1
- package/dist/reporting/observer.d.mts +0 -2
- package/dist/reporting/observer.d.mts.map +1 -1
- package/dist/reporting/observer.mjs +2 -2
- package/dist/reporting/observer.mjs.map +1 -1
- package/dist/reporting/tracking.cjs +29 -12
- package/dist/reporting/tracking.cjs.map +1 -1
- package/dist/reporting/tracking.d.cts +13 -8
- package/dist/reporting/tracking.d.cts.map +1 -1
- package/dist/reporting/tracking.d.mts +13 -8
- package/dist/reporting/tracking.d.mts.map +1 -1
- package/dist/reporting/tracking.mjs +29 -12
- package/dist/reporting/tracking.mjs.map +1 -1
- package/dist/runtime.cjs +1 -2
- package/dist/runtime.d.cts +0 -2
- package/dist/runtime.d.cts.map +1 -1
- package/dist/runtime.d.mts +0 -2
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -2
- package/dist/store/erasure.cjs +1 -2
- package/dist/store/erasure.d.cts +0 -2
- package/dist/store/erasure.d.cts.map +1 -1
- package/dist/store/erasure.d.mts +0 -2
- package/dist/store/erasure.d.mts.map +1 -1
- package/dist/store/erasure.mjs +1 -2
- package/dist/store/facts.cjs +4 -5
- package/dist/store/facts.cjs.map +1 -1
- package/dist/store/facts.d.cts +2 -4
- package/dist/store/facts.d.cts.map +1 -1
- package/dist/store/facts.d.mts +2 -4
- package/dist/store/facts.d.mts.map +1 -1
- package/dist/store/facts.mjs +4 -5
- package/dist/store/facts.mjs.map +1 -1
- package/dist/store/index.cjs +2 -3
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +44 -15
- package/dist/store/index.d.cts.map +1 -1
- package/dist/store/index.d.mts +44 -15
- package/dist/store/index.d.mts.map +1 -1
- package/dist/store/index.mjs +2 -3
- package/dist/store/index.mjs.map +1 -1
- package/dist/store/postgres-reporting.cjs +31 -12
- package/dist/store/postgres-reporting.cjs.map +1 -1
- package/dist/store/postgres-reporting.d.cts +4 -4
- package/dist/store/postgres-reporting.d.cts.map +1 -1
- package/dist/store/postgres-reporting.d.mts +4 -4
- package/dist/store/postgres-reporting.d.mts.map +1 -1
- package/dist/store/postgres-reporting.mjs +31 -12
- package/dist/store/postgres-reporting.mjs.map +1 -1
- package/dist/store/postgres.cjs +40 -5
- package/dist/store/postgres.cjs.map +1 -1
- package/dist/store/postgres.d.cts +4 -4
- package/dist/store/postgres.d.cts.map +1 -1
- package/dist/store/postgres.d.mts +4 -4
- package/dist/store/postgres.d.mts.map +1 -1
- package/dist/store/postgres.mjs +41 -6
- package/dist/store/postgres.mjs.map +1 -1
- package/dist/store/sql.cjs +1 -2
- package/dist/store/sql.d.cts +0 -2
- package/dist/store/sql.d.cts.map +1 -1
- package/dist/store/sql.d.mts +0 -2
- package/dist/store/sql.d.mts.map +1 -1
- package/dist/store/sql.mjs +1 -2
- package/dist/store/tables.cjs +76 -13
- package/dist/store/tables.cjs.map +1 -1
- package/dist/store/tables.d.cts +17 -3
- package/dist/store/tables.d.cts.map +1 -1
- package/dist/store/tables.d.mts +17 -3
- package/dist/store/tables.d.mts.map +1 -1
- package/dist/store/tables.mjs +75 -14
- package/dist/store/tables.mjs.map +1 -1
- package/dist/store/user-lock.cjs +1 -2
- package/dist/store/user-lock.d.cts +0 -2
- package/dist/store/user-lock.d.cts.map +1 -1
- package/dist/store/user-lock.d.mts +0 -2
- package/dist/store/user-lock.d.mts.map +1 -1
- package/dist/store/user-lock.mjs +1 -2
- package/dist/templates.cjs +61 -9
- package/dist/templates.cjs.map +1 -1
- package/dist/templates.d.cts +101 -15
- package/dist/templates.d.cts.map +1 -1
- package/dist/templates.d.mts +101 -15
- package/dist/templates.d.mts.map +1 -1
- package/dist/templates.mjs +62 -11
- package/dist/templates.mjs.map +1 -1
- package/dist/transports/cloudflare-email.cjs +1 -2
- package/dist/transports/cloudflare-email.d.cts +0 -2
- package/dist/transports/cloudflare-email.d.cts.map +1 -1
- package/dist/transports/cloudflare-email.d.mts +0 -2
- package/dist/transports/cloudflare-email.d.mts.map +1 -1
- package/dist/transports/cloudflare-email.mjs +1 -2
- package/dist/transports/ses.cjs +1 -2
- package/dist/transports/ses.d.cts +0 -2
- package/dist/transports/ses.d.cts.map +1 -1
- package/dist/transports/ses.d.mts +0 -2
- package/dist/transports/ses.d.mts.map +1 -1
- package/dist/transports/ses.mjs +1 -2
- package/dist/transports/sigv4.cjs +1 -2
- package/dist/transports/sigv4.d.cts +0 -2
- package/dist/transports/sigv4.d.cts.map +1 -1
- package/dist/transports/sigv4.d.mts +0 -2
- package/dist/transports/sigv4.d.mts.map +1 -1
- package/dist/transports/sigv4.mjs +1 -2
- package/dist/webhooks.cjs +1 -2
- package/dist/webhooks.d.cts +0 -2
- package/dist/webhooks.d.cts.map +1 -1
- package/dist/webhooks.d.mts +0 -2
- package/dist/webhooks.d.mts.map +1 -1
- package/dist/webhooks.mjs +1 -2
- package/dist/webpush.cjs +1 -2
- package/dist/webpush.d.cts +0 -2
- package/dist/webpush.d.cts.map +1 -1
- package/dist/webpush.d.mts +0 -2
- package/dist/webpush.d.mts.map +1 -1
- package/dist/webpush.mjs +1 -2
- package/package.json +5 -5
package/dist/engine/actions.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
// src/engine/actions.ts
|
|
3
3
|
var RegistryActionInvoker = class {
|
|
4
4
|
registry;
|
|
5
5
|
constructor(actions) {
|
|
@@ -14,7 +14,6 @@ var RegistryActionInvoker = class {
|
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
-
//#endregion
|
|
18
17
|
exports.RegistryActionInvoker = RegistryActionInvoker;
|
|
19
18
|
|
|
20
19
|
//# sourceMappingURL=actions.cjs.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ScalarIR } from "../ir.cjs";
|
|
2
2
|
import { ActionInvocation, ActionInvoker } from "./ports.cjs";
|
|
3
|
-
//#region src/engine/actions.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* The default ActionInvoker: a name → handler registry (the runtime half of
|
|
6
5
|
* the dual-plane model — IR carries identity, the deployed bundle carries
|
|
@@ -25,5 +24,4 @@ export declare class RegistryActionInvoker implements ActionInvoker {
|
|
|
25
24
|
constructor(actions: readonly RegisteredAction[]);
|
|
26
25
|
invoke(invocation: ActionInvocation): Promise<void>;
|
|
27
26
|
}
|
|
28
|
-
//#endregion
|
|
29
27
|
//# sourceMappingURL=actions.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.cts","names":[],"sources":["../../src/engine/actions.ts"],"mappings":"
|
|
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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ScalarIR } from "../ir.mjs";
|
|
2
2
|
import { ActionInvocation, ActionInvoker } from "./ports.mjs";
|
|
3
|
-
//#region src/engine/actions.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* The default ActionInvoker: a name → handler registry (the runtime half of
|
|
6
5
|
* the dual-plane model — IR carries identity, the deployed bundle carries
|
|
@@ -25,5 +24,4 @@ export declare class RegistryActionInvoker implements ActionInvoker {
|
|
|
25
24
|
constructor(actions: readonly RegisteredAction[]);
|
|
26
25
|
invoke(invocation: ActionInvocation): Promise<void>;
|
|
27
26
|
}
|
|
28
|
-
//#endregion
|
|
29
27
|
//# sourceMappingURL=actions.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.mts","names":[],"sources":["../../src/engine/actions.ts"],"mappings":"
|
|
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,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/engine/actions.ts
|
|
2
2
|
var RegistryActionInvoker = class {
|
|
3
3
|
registry;
|
|
4
4
|
constructor(actions) {
|
|
@@ -13,7 +13,6 @@ var RegistryActionInvoker = class {
|
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
-
//#endregion
|
|
17
16
|
export { RegistryActionInvoker };
|
|
18
17
|
|
|
19
18
|
//# sourceMappingURL=actions.mjs.map
|
package/dist/engine/bucket.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
// src/engine/bucket.ts
|
|
3
3
|
/**
|
|
4
4
|
* Cohort bucketing — deterministic assignment of a user to an A/B arm.
|
|
5
5
|
*
|
|
@@ -64,7 +64,6 @@ const BUCKET_RESOLUTION = 1e4;
|
|
|
64
64
|
function hashToBucket(input) {
|
|
65
65
|
return murmur3(utf8.encode(input)) % BUCKET_RESOLUTION;
|
|
66
66
|
}
|
|
67
|
-
//#endregion
|
|
68
67
|
exports.BUCKET_RESOLUTION = BUCKET_RESOLUTION;
|
|
69
68
|
exports.hashToBucket = hashToBucket;
|
|
70
69
|
exports.murmur3 = murmur3;
|
package/dist/engine/bucket.d.cts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/engine/bucket.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Cohort bucketing — deterministic assignment of a user to an A/B arm.
|
|
4
3
|
*
|
|
@@ -28,5 +27,4 @@ export declare const BUCKET_RESOLUTION = 10000;
|
|
|
28
27
|
* regardless of the user id's alphabet.
|
|
29
28
|
*/
|
|
30
29
|
export declare function hashToBucket(input: string): number;
|
|
31
|
-
//#endregion
|
|
32
30
|
//# sourceMappingURL=bucket.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bucket.d.cts","names":[],"sources":["../../src/engine/bucket.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"bucket.d.cts","names":[],"sources":["../../src/engine/bucket.ts"],"mappings":";;;;;;;;;;;;;;;wBAgBgB,QAAQ,OAAO,YAAY;;;;;;;qBA6C9B;;;;;;wBAOG,aAAa"}
|
package/dist/engine/bucket.d.mts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/engine/bucket.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Cohort bucketing — deterministic assignment of a user to an A/B arm.
|
|
4
3
|
*
|
|
@@ -28,5 +27,4 @@ export declare const BUCKET_RESOLUTION = 10000;
|
|
|
28
27
|
* regardless of the user id's alphabet.
|
|
29
28
|
*/
|
|
30
29
|
export declare function hashToBucket(input: string): number;
|
|
31
|
-
//#endregion
|
|
32
30
|
//# sourceMappingURL=bucket.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bucket.d.mts","names":[],"sources":["../../src/engine/bucket.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"bucket.d.mts","names":[],"sources":["../../src/engine/bucket.ts"],"mappings":";;;;;;;;;;;;;;;wBAgBgB,QAAQ,OAAO,YAAY;;;;;;;qBA6C9B;;;;;;wBAOG,aAAa"}
|
package/dist/engine/bucket.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/engine/bucket.ts
|
|
2
2
|
/**
|
|
3
3
|
* Cohort bucketing — deterministic assignment of a user to an A/B arm.
|
|
4
4
|
*
|
|
@@ -63,7 +63,6 @@ const BUCKET_RESOLUTION = 1e4;
|
|
|
63
63
|
function hashToBucket(input) {
|
|
64
64
|
return murmur3(utf8.encode(input)) % BUCKET_RESOLUTION;
|
|
65
65
|
}
|
|
66
|
-
//#endregion
|
|
67
66
|
export { BUCKET_RESOLUTION, hashToBucket, murmur3 };
|
|
68
67
|
|
|
69
68
|
//# sourceMappingURL=bucket.mjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_engine_recipient = require("./recipient.cjs");
|
|
3
|
-
|
|
3
|
+
// src/engine/condition.ts
|
|
4
4
|
/**
|
|
5
5
|
* ConditionIR evaluation: pure logic, with facts injected through FactSource,
|
|
6
6
|
* so the store-backed implementation and the in-memory test one share a single set of
|
|
@@ -175,7 +175,6 @@ async function relevantEvents(condition, facts) {
|
|
|
175
175
|
await walk(condition);
|
|
176
176
|
return [...found];
|
|
177
177
|
}
|
|
178
|
-
//#endregion
|
|
179
178
|
exports.DEVICES_UPDATED_EVENT = DEVICES_UPDATED_EVENT;
|
|
180
179
|
exports.PROFILE_UPDATED_EVENT = PROFILE_UPDATED_EVENT;
|
|
181
180
|
exports.evaluateCondition = evaluateCondition;
|
|
@@ -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 store-backed 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 store 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 store 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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"}
|
|
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 store-backed 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 store 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 // The channel-wide opt-out only: a user opted out of one topic (an email topic, a push\n // topic) is still reachable on the channel, so `exitWhen: not(reachable('email'))` does\n // not fire on it\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 store 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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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ConditionIR } from "../ir.cjs";
|
|
2
2
|
import { FactSource } from "./ports.cjs";
|
|
3
|
-
//#region src/engine/condition.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Evaluation options.
|
|
6
5
|
* - anchorMs: the earliest instant `performed` counts events from — the goal
|
|
@@ -57,5 +56,4 @@ export declare const DEVICES_UPDATED_EVENT = "$devices_updated";
|
|
|
57
56
|
* resolve their definitions.
|
|
58
57
|
*/
|
|
59
58
|
export declare function relevantEvents(condition: ConditionIR, facts: FactSource): Promise<string[]>;
|
|
60
|
-
//#endregion
|
|
61
59
|
//# sourceMappingURL=condition.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condition.d.cts","names":[],"sources":["../../src/engine/condition.ts"],"mappings":"
|
|
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;;;;;;;wBAwFa,aAAa,kBAAkB,OAAO;;;;;;;qBAuFzC;;;;;;wBAOG,oBAAoB;;;;;qBAQvB;;;;;;;;;;;wBAYS,eAAe,WAAW,aAAa,OAAO,aAAa"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ConditionIR } from "../ir.mjs";
|
|
2
2
|
import { FactSource } from "./ports.mjs";
|
|
3
|
-
//#region src/engine/condition.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Evaluation options.
|
|
6
5
|
* - anchorMs: the earliest instant `performed` counts events from — the goal
|
|
@@ -57,5 +56,4 @@ export declare const DEVICES_UPDATED_EVENT = "$devices_updated";
|
|
|
57
56
|
* resolve their definitions.
|
|
58
57
|
*/
|
|
59
58
|
export declare function relevantEvents(condition: ConditionIR, facts: FactSource): Promise<string[]>;
|
|
60
|
-
//#endregion
|
|
61
59
|
//# sourceMappingURL=condition.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condition.d.mts","names":[],"sources":["../../src/engine/condition.ts"],"mappings":"
|
|
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;;;;;;;wBAwFa,aAAa,kBAAkB,OAAO;;;;;;;qBAuFzC;;;;;;wBAOG,oBAAoB;;;;;qBAQvB;;;;;;;;;;;wBAYS,eAAe,WAAW,aAAa,OAAO,aAAa"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RECIPIENT_PROPERTY } from "./recipient.mjs";
|
|
2
|
-
|
|
2
|
+
// src/engine/condition.ts
|
|
3
3
|
/**
|
|
4
4
|
* ConditionIR evaluation: pure logic, with facts injected through FactSource,
|
|
5
5
|
* so the store-backed implementation and the in-memory test one share a single set of
|
|
@@ -174,7 +174,6 @@ async function relevantEvents(condition, facts) {
|
|
|
174
174
|
await walk(condition);
|
|
175
175
|
return [...found];
|
|
176
176
|
}
|
|
177
|
-
//#endregion
|
|
178
177
|
export { DEVICES_UPDATED_EVENT, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, profileUpdatedEvent, relevantEvents };
|
|
179
178
|
|
|
180
179
|
//# 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 store-backed 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 store 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 store 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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"}
|
|
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 store-backed 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 store 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 // The channel-wide opt-out only: a user opted out of one topic (an email topic, a push\n // topic) is still reachable on the channel, so `exitWhen: not(reachable('email'))` does\n // not fire on it\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 store 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/duration.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
// src/engine/duration.ts
|
|
3
3
|
/**
|
|
4
4
|
* The longest an instance of a workflow can live, from its IR: every delay at its longest, every
|
|
5
5
|
* wait to its timeout, a time window as a full week (the furthest the next allowed slot can be),
|
|
@@ -28,7 +28,6 @@ function nodeMs(node) {
|
|
|
28
28
|
function longestJourneyMs(ir) {
|
|
29
29
|
return Math.max(flowMs(ir.flow), ir.goal?.within?.ms ?? 0);
|
|
30
30
|
}
|
|
31
|
-
//#endregion
|
|
32
31
|
exports.longestJourneyMs = longestJourneyMs;
|
|
33
32
|
|
|
34
33
|
//# sourceMappingURL=duration.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duration.d.cts","names":[],"sources":["../../src/engine/duration.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"duration.d.cts","names":[],"sources":["../../src/engine/duration.ts"],"mappings":";wBAyCgB,iBAAiB,IAAI,KAAK"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duration.d.mts","names":[],"sources":["../../src/engine/duration.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"duration.d.mts","names":[],"sources":["../../src/engine/duration.ts"],"mappings":";wBAyCgB,iBAAiB,IAAI,KAAK"}
|
package/dist/engine/duration.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/engine/duration.ts
|
|
2
2
|
/**
|
|
3
3
|
* The longest an instance of a workflow can live, from its IR: every delay at its longest, every
|
|
4
4
|
* wait to its timeout, a time window as a full week (the furthest the next allowed slot can be),
|
|
@@ -27,7 +27,6 @@ function nodeMs(node) {
|
|
|
27
27
|
function longestJourneyMs(ir) {
|
|
28
28
|
return Math.max(flowMs(ir.flow), ir.goal?.within?.ms ?? 0);
|
|
29
29
|
}
|
|
30
|
-
//#endregion
|
|
31
30
|
export { longestJourneyMs };
|
|
32
31
|
|
|
33
32
|
//# sourceMappingURL=duration.mjs.map
|
package/dist/engine/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_engine_retry = require("./retry.cjs");
|
|
3
|
-
const require_engine_duration = require("./duration.cjs");
|
|
4
3
|
const require_engine_bucket = require("./bucket.cjs");
|
|
5
4
|
const require_engine_condition = require("./condition.cjs");
|
|
5
|
+
const require_engine_duration = require("./duration.cjs");
|
|
6
6
|
const require_engine_time_window = require("./time-window.cjs");
|
|
7
7
|
const require_engine_interpreter = require("./interpreter.cjs");
|
|
8
8
|
const require_engine_time_scale = require("./time-scale.cjs");
|
package/dist/engine/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PermanentError, isRetryable, isRetryableStatus } from "./retry.mjs";
|
|
2
|
-
import { longestJourneyMs } from "./duration.mjs";
|
|
3
2
|
import { hashToBucket, murmur3 } from "./bucket.mjs";
|
|
4
3
|
import { DEVICES_UPDATED_EVENT, PROFILE_UPDATED_EVENT, evaluateCondition, matchesWhere, profileUpdatedEvent, relevantEvents } from "./condition.mjs";
|
|
4
|
+
import { longestJourneyMs } from "./duration.mjs";
|
|
5
5
|
import { nextWindowStart, resolveTimeZone } from "./time-window.mjs";
|
|
6
6
|
import { runJourney } from "./interpreter.mjs";
|
|
7
7
|
import { scaleCondition, scaleDurations, scaleSegmentWindows } from "./time-scale.mjs";
|
|
@@ -3,7 +3,7 @@ const require_engine_bucket = require("./bucket.cjs");
|
|
|
3
3
|
const require_engine_recipient = require("./recipient.cjs");
|
|
4
4
|
const require_engine_condition = require("./condition.cjs");
|
|
5
5
|
const require_engine_time_window = require("./time-window.cjs");
|
|
6
|
-
|
|
6
|
+
// src/engine/interpreter.ts
|
|
7
7
|
/**
|
|
8
8
|
* IR interpreter: the execution core for one user's journey instance.
|
|
9
9
|
*
|
|
@@ -198,6 +198,21 @@ function validate(node, props, ctx) {
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
201
|
+
* Whether the user opted out of what this step sends. An email or push step checks its topic —
|
|
202
|
+
* an opt-out of the topic on that channel or of the whole channel skips it — and a transactional
|
|
203
|
+
* message (`topic: null`) is never skipped: a receipt or a code goes out whatever the user opted
|
|
204
|
+
* out of. A node compiled before topics existed (no `topic`) checks the channel-wide opt-out
|
|
205
|
+
* only, as it always did. Other channels check the channel.
|
|
206
|
+
*/
|
|
207
|
+
async function suppressed(node, ctx) {
|
|
208
|
+
if (node.topic === null) return false;
|
|
209
|
+
return await ctx.facts.isSuppressed?.(node.channel, node.topic) === true;
|
|
210
|
+
}
|
|
211
|
+
/** The copy's topic, for the channels that have one: what the observer records and the senders may read. */
|
|
212
|
+
function topicOf(node) {
|
|
213
|
+
return node.channel === "email" || node.channel === "push" ? { topic: node.topic ?? null } : {};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
201
216
|
* A push goes to devices, not to a profile address: one copy per device the
|
|
202
217
|
* host's directory lists (or only the most recent, `devices: 'latest'`). Each
|
|
203
218
|
* copy's idempotency key carries its token, so a retried step that already
|
|
@@ -214,7 +229,7 @@ function validate(node, props, ctx) {
|
|
|
214
229
|
async function sendPush(node, ir, ctx) {
|
|
215
230
|
const plan = await ctx.step.do(`${node.id}:send`, async () => {
|
|
216
231
|
const props = validate(node, await resolveValues(node.props, ctx.facts), ctx);
|
|
217
|
-
if (await
|
|
232
|
+
if (await suppressed(node, ctx)) return null;
|
|
218
233
|
const listed = await ctx.facts.listDevices();
|
|
219
234
|
return {
|
|
220
235
|
props,
|
|
@@ -237,7 +252,8 @@ async function sendPush(node, ir, ctx) {
|
|
|
237
252
|
userId: ctx.userId,
|
|
238
253
|
recipient: device.token,
|
|
239
254
|
device,
|
|
240
|
-
idempotencyKey: `${ctx.instanceId}:${node.id}:${device.token}
|
|
255
|
+
idempotencyKey: `${ctx.instanceId}:${node.id}:${device.token}`,
|
|
256
|
+
...topicOf(node)
|
|
241
257
|
}, ctx);
|
|
242
258
|
});
|
|
243
259
|
}
|
|
@@ -250,7 +266,7 @@ async function runNode(node, ir, ctx) {
|
|
|
250
266
|
}
|
|
251
267
|
await ctx.step.do(`${node.id}:send`, async () => {
|
|
252
268
|
const props = validate(node, await resolveValues(node.props, ctx.facts), ctx);
|
|
253
|
-
if (await
|
|
269
|
+
if (await suppressed(node, ctx)) return;
|
|
254
270
|
const recipientPath = require_engine_recipient.RECIPIENT_PROPERTY[node.channel];
|
|
255
271
|
const recipient = recipientPath === void 0 ? void 0 : await ctx.facts.getProperty(recipientPath);
|
|
256
272
|
await deliver({
|
|
@@ -262,7 +278,8 @@ async function runNode(node, ir, ctx) {
|
|
|
262
278
|
props,
|
|
263
279
|
userId: ctx.userId,
|
|
264
280
|
recipient: recipient === void 0 ? void 0 : String(recipient),
|
|
265
|
-
idempotencyKey: `${ctx.instanceId}:${node.id}
|
|
281
|
+
idempotencyKey: `${ctx.instanceId}:${node.id}`,
|
|
282
|
+
...topicOf(node)
|
|
266
283
|
}, ctx);
|
|
267
284
|
});
|
|
268
285
|
return FALL_THROUGH;
|
|
@@ -429,7 +446,6 @@ async function resolveValues(values, facts) {
|
|
|
429
446
|
for (const [key, value] of Object.entries(values)) resolved[key] = typeof value === "object" ? await facts.getProperty(value.path) : value;
|
|
430
447
|
return resolved;
|
|
431
448
|
}
|
|
432
|
-
//#endregion
|
|
433
449
|
exports.runJourney = runJourney;
|
|
434
450
|
|
|
435
451
|
//# sourceMappingURL=interpreter.cjs.map
|