@shware/workflow 4.1.1 → 5.0.1
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 +16 -38
- 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 +3 -5
- package/dist/aws/index.cjs.map +1 -1
- package/dist/aws/index.d.cts +1 -3
- package/dist/aws/index.d.cts.map +1 -1
- package/dist/aws/index.d.mts +1 -3
- package/dist/aws/index.d.mts.map +1 -1
- package/dist/aws/index.mjs +3 -5
- package/dist/aws/index.mjs.map +1 -1
- package/dist/aws/instances.cjs +2 -3
- package/dist/aws/instances.cjs.map +1 -1
- package/dist/aws/instances.d.cts +1 -3
- package/dist/aws/instances.d.cts.map +1 -1
- package/dist/aws/instances.d.mts +1 -3
- package/dist/aws/instances.d.mts.map +1 -1
- package/dist/aws/instances.mjs +2 -3
- package/dist/aws/instances.mjs.map +1 -1
- 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 +1 -2
- package/dist/client.d.cts +0 -2
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.mts +0 -2
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +1 -2
- package/dist/cloudflare/bindings.cjs +1 -2
- package/dist/cloudflare/bindings.cjs.map +1 -1
- package/dist/cloudflare/bindings.d.cts +0 -21
- package/dist/cloudflare/bindings.d.cts.map +1 -1
- package/dist/cloudflare/bindings.d.mts +0 -21
- package/dist/cloudflare/bindings.d.mts.map +1 -1
- package/dist/cloudflare/bindings.mjs +1 -2
- package/dist/cloudflare/bindings.mjs.map +1 -1
- package/dist/cloudflare/index.cjs +0 -5
- package/dist/cloudflare/index.d.cts +4 -6
- package/dist/cloudflare/index.d.mts +4 -6
- package/dist/cloudflare/index.mjs +2 -4
- package/dist/cloudflare/runner.cjs +4 -6
- package/dist/cloudflare/runner.cjs.map +1 -1
- package/dist/cloudflare/runner.d.cts +3 -5
- package/dist/cloudflare/runner.d.cts.map +1 -1
- package/dist/cloudflare/runner.d.mts +3 -5
- package/dist/cloudflare/runner.d.mts.map +1 -1
- package/dist/cloudflare/runner.mjs +4 -6
- package/dist/cloudflare/runner.mjs.map +1 -1
- 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 +1 -2
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +1 -3
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +1 -3
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +1 -2
- package/dist/config.mjs.map +1 -1
- package/dist/contract.cjs +1 -2
- package/dist/contract.d.cts +0 -2
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +0 -2
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.mjs +1 -2
- 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 +1 -2
- package/dist/dsl/bundle.d.cts +0 -2
- package/dist/dsl/bundle.d.cts.map +1 -1
- package/dist/dsl/bundle.d.mts +0 -2
- package/dist/dsl/bundle.d.mts.map +1 -1
- package/dist/dsl/bundle.mjs +1 -2
- 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 +1 -2
- package/dist/dsl/flow.d.cts +0 -2
- package/dist/dsl/flow.d.cts.map +1 -1
- package/dist/dsl/flow.d.mts +0 -2
- package/dist/dsl/flow.d.mts.map +1 -1
- package/dist/dsl/flow.mjs +1 -2
- 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 +1 -2
- 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 +1 -2
- 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 +1 -2
- package/dist/email-sender.d.cts +0 -2
- package/dist/email-sender.d.cts.map +1 -1
- package/dist/email-sender.d.mts +0 -2
- package/dist/email-sender.d.mts.map +1 -1
- package/dist/email-sender.mjs +1 -2
- package/dist/email.cjs +1 -2
- package/dist/email.d.cts +0 -2
- package/dist/email.d.cts.map +1 -1
- package/dist/email.d.mts +0 -2
- package/dist/email.d.mts.map +1 -1
- package/dist/email.mjs +1 -2
- 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 +3 -4
- package/dist/engine/condition.cjs.map +1 -1
- package/dist/engine/condition.d.cts +1 -3
- package/dist/engine/condition.d.cts.map +1 -1
- package/dist/engine/condition.d.mts +1 -3
- package/dist/engine/condition.d.mts.map +1 -1
- package/dist/engine/condition.mjs +3 -4
- 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/interpreter.cjs +1 -2
- 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 +1 -2
- package/dist/engine/ports.d.cts +1 -3
- package/dist/engine/ports.d.cts.map +1 -1
- package/dist/engine/ports.d.mts +1 -3
- 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 +1 -2
- package/dist/host/channels.d.cts +1 -3
- package/dist/host/channels.d.cts.map +1 -1
- package/dist/host/channels.d.mts +1 -3
- package/dist/host/channels.d.mts.map +1 -1
- package/dist/host/channels.mjs +1 -2
- package/dist/host/execute.cjs +1 -2
- 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 +1 -2
- 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 +4 -6
- 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 +4 -6
- package/dist/host/http.mjs.map +1 -1
- package/dist/host/ingest.cjs +12 -24
- package/dist/host/ingest.cjs.map +1 -1
- package/dist/host/ingest.d.cts +8 -15
- package/dist/host/ingest.d.cts.map +1 -1
- package/dist/host/ingest.d.mts +8 -15
- package/dist/host/ingest.d.mts.map +1 -1
- package/dist/host/ingest.mjs +13 -24
- package/dist/host/ingest.mjs.map +1 -1
- package/dist/host/journey-host.cjs +3 -6
- package/dist/host/journey-host.cjs.map +1 -1
- package/dist/host/journey-host.d.cts +6 -8
- package/dist/host/journey-host.d.cts.map +1 -1
- package/dist/host/journey-host.d.mts +6 -8
- package/dist/host/journey-host.d.mts.map +1 -1
- package/dist/host/journey-host.mjs +3 -6
- package/dist/host/journey-host.mjs.map +1 -1
- package/dist/host/platform.cjs +1 -2
- package/dist/host/platform.cjs.map +1 -1
- package/dist/host/platform.d.cts +1 -9
- package/dist/host/platform.d.cts.map +1 -1
- package/dist/host/platform.d.mts +1 -9
- package/dist/host/platform.d.mts.map +1 -1
- package/dist/host/platform.mjs +1 -2
- package/dist/host/platform.mjs.map +1 -1
- 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/ir.cjs +1 -2
- package/dist/ir.d.cts +0 -2
- package/dist/ir.d.cts.map +1 -1
- package/dist/ir.d.mts +0 -2
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -2
- 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 +1 -2
- 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 +1 -2
- package/dist/reporting/tracking.cjs +1 -2
- package/dist/reporting/tracking.d.cts +0 -2
- package/dist/reporting/tracking.d.cts.map +1 -1
- package/dist/reporting/tracking.d.mts +0 -2
- package/dist/reporting/tracking.d.mts.map +1 -1
- package/dist/reporting/tracking.mjs +1 -2
- 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 +2 -3
- package/dist/store/facts.cjs.map +1 -1
- package/dist/store/facts.d.cts +0 -2
- package/dist/store/facts.d.cts.map +1 -1
- package/dist/store/facts.d.mts +0 -2
- package/dist/store/facts.d.mts.map +1 -1
- package/dist/store/facts.mjs +2 -3
- package/dist/store/facts.mjs.map +1 -1
- package/dist/store/index.cjs +1 -4
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +21 -28
- package/dist/store/index.d.cts.map +1 -1
- package/dist/store/index.d.mts +21 -28
- package/dist/store/index.d.mts.map +1 -1
- package/dist/store/index.mjs +2 -4
- package/dist/store/index.mjs.map +1 -1
- package/dist/store/postgres-reporting.cjs +1 -2
- package/dist/store/postgres-reporting.d.cts +0 -2
- package/dist/store/postgres-reporting.d.cts.map +1 -1
- package/dist/store/postgres-reporting.d.mts +0 -2
- package/dist/store/postgres-reporting.d.mts.map +1 -1
- package/dist/store/postgres-reporting.mjs +1 -2
- package/dist/store/postgres.cjs +3 -5
- package/dist/store/postgres.cjs.map +1 -1
- package/dist/store/postgres.d.cts +2 -5
- package/dist/store/postgres.d.cts.map +1 -1
- package/dist/store/postgres.d.mts +2 -5
- package/dist/store/postgres.d.mts.map +1 -1
- package/dist/store/postgres.mjs +3 -5
- 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 +1 -2
- package/dist/store/tables.d.cts +0 -2
- package/dist/store/tables.d.cts.map +1 -1
- package/dist/store/tables.d.mts +0 -2
- package/dist/store/tables.d.mts.map +1 -1
- package/dist/store/tables.mjs +1 -2
- 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 +1 -2
- package/dist/templates.d.cts +0 -2
- package/dist/templates.d.cts.map +1 -1
- package/dist/templates.d.mts +0 -2
- package/dist/templates.d.mts.map +1 -1
- package/dist/templates.mjs +1 -2
- package/dist/transports/cloudflare-email.cjs +1 -2
- package/dist/transports/cloudflare-email.d.cts +1 -3
- package/dist/transports/cloudflare-email.d.cts.map +1 -1
- package/dist/transports/cloudflare-email.d.mts +1 -3
- 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 -7
- package/dist/cloudflare/d1-env.cjs +0 -17
- package/dist/cloudflare/d1-env.cjs.map +0 -1
- package/dist/cloudflare/d1-env.d.cts +0 -12
- package/dist/cloudflare/d1-env.d.cts.map +0 -1
- package/dist/cloudflare/d1-env.d.mts +0 -12
- package/dist/cloudflare/d1-env.d.mts.map +0 -1
- package/dist/cloudflare/d1-env.mjs +0 -16
- package/dist/cloudflare/d1-env.mjs.map +0 -1
- package/dist/cloudflare/schema.sql +0 -148
- package/dist/store/d1.cjs +0 -245
- package/dist/store/d1.cjs.map +0 -1
- package/dist/store/d1.d.cts +0 -73
- package/dist/store/d1.d.cts.map +0 -1
- package/dist/store/d1.d.mts +0 -73
- package/dist/store/d1.d.mts.map +0 -1
- package/dist/store/d1.mjs +0 -244
- package/dist/store/d1.mjs.map +0 -1
- package/src/cloudflare/schema.sql +0 -148
|
@@ -2,7 +2,6 @@ import { JourneyEnv } from "../host/platform.mjs";
|
|
|
2
2
|
import { SweepResult } from "../host/ingest.mjs";
|
|
3
3
|
import { EnvValidationError, JourneyHostOptions } from "../host/journey-host.mjs";
|
|
4
4
|
import { JourneyRunner } from "./runner.mjs";
|
|
5
|
-
//#region src/cloudflare/worker.d.ts
|
|
6
5
|
/** The fetch handler's execution context (the subset used). */
|
|
7
6
|
export interface ExecutionContextLike {
|
|
8
7
|
waitUntil(promise: Promise<unknown>): void;
|
|
@@ -41,6 +40,5 @@ export interface JourneyWorker<Env extends JourneyEnv> {
|
|
|
41
40
|
* export default { fetch: journeys.fetch };
|
|
42
41
|
*/
|
|
43
42
|
export declare function journeyWorker<Env extends JourneyEnv>(options: JourneyWorkerOptions<Env>): JourneyWorker<Env>;
|
|
44
|
-
//#endregion
|
|
45
43
|
export { EnvValidationError };
|
|
46
44
|
//# sourceMappingURL=worker.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.mts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"worker.d.mts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":";;;;;iBAeiB;EACf,UAAU,SAAS;;;;;;;YAQT,qBAAqB,YAAY,cAAc,mBAAmB;iBAE7D,cAAc,YAAY;;EAEzC,gBAAgB,kBAAkB;;EAElC,QAAQ,SAAS,SAAS,KAAK,KAAK,MAAM,yBAAyB,QAAQ;;;;;;;EAO3E,QAAQ,KAAK,QAAQ,QAAQ;;EAE7B,YAAY,qBAAqB,KAAK,KAAK,KAAK,yBAAyB;;;;;;;;;;;;;;wBAe3D,cAAc,YAAY,YACxC,SAAS,qBAAqB,OAC7B,cAAc"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EnvValidationError, createJourneyHost } from "../host/journey-host.mjs";
|
|
2
2
|
import { cloudflareJourneyBinding } from "./bindings.mjs";
|
|
3
3
|
import { JourneyRunner } from "./runner.mjs";
|
|
4
|
-
|
|
4
|
+
// src/cloudflare/worker.ts
|
|
5
5
|
/**
|
|
6
6
|
* A complete journey host in one call: the runner class with the app's store,
|
|
7
7
|
* outlets and runtime plugged in, and the fetch handler that serves the ingest
|
|
@@ -88,7 +88,6 @@ function journeyWorker(options) {
|
|
|
88
88
|
scheduled
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
-
//#endregion
|
|
92
91
|
export { EnvValidationError, journeyWorker };
|
|
93
92
|
|
|
94
93
|
//# sourceMappingURL=worker.mjs.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/cloudflare/workers.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Minimal ambient declaration for cloudflare:workers — only the surface
|
|
4
3
|
* JourneyRunner actually uses. @cloudflare/workers-types is avoided because its
|
|
@@ -39,6 +38,5 @@ declare module 'cloudflare:workflows' {
|
|
|
39
38
|
constructor(message: string, name?: string);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
|
-
//#endregion
|
|
43
41
|
export {};
|
|
44
42
|
//# sourceMappingURL=workers.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workers.d.cts","names":[],"sources":["../../src/cloudflare/workers.d.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"workers.d.cts","names":[],"sources":["../../src/cloudflare/workers.d.ts"],"mappings":";;;;;;;mBAOmB,cAAc;IAC7B,SAAS,SAAS;IAClB,WAAW;IACX;;mBAGe,kBAAkB;IACjC,SAAS;IACT,WAAW;IACX;;mBAGe;IACf,GAAG,GAAG,cAAc,gBAAgB,QAAQ,KAAK,QAAQ;IACzD,MAAM,cAAc,8BAA8B;IAClD,WAAW,cAAc,WAAW,gBAAgB;IACpD,aAAa,mBACX,cACA;MAAW;MAAc;QACxB,QAAQ,kBAAkB;;wBAGT,mBAAmB,eAAe;;IAEtD,YAAY,cAAc,KAAK;uBACZ;uBACA,KAAK;aACf,IAAI,OAAO,cAAc,SAAS,MAAM,eAAe;;;;;eAMrD,0BAA0B;IACrC,YAAY,iBAAiB"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/cloudflare/workers.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Minimal ambient declaration for cloudflare:workers — only the surface
|
|
4
3
|
* JourneyRunner actually uses. @cloudflare/workers-types is avoided because its
|
|
@@ -39,6 +38,5 @@ declare module 'cloudflare:workflows' {
|
|
|
39
38
|
constructor(message: string, name?: string);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
|
-
//#endregion
|
|
43
41
|
export {};
|
|
44
42
|
//# sourceMappingURL=workers.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workers.d.mts","names":[],"sources":["../../src/cloudflare/workers.d.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"workers.d.mts","names":[],"sources":["../../src/cloudflare/workers.d.ts"],"mappings":";;;;;;;mBAOmB,cAAc;IAC7B,SAAS,SAAS;IAClB,WAAW;IACX;;mBAGe,kBAAkB;IACjC,SAAS;IACT,WAAW;IACX;;mBAGe;IACf,GAAG,GAAG,cAAc,gBAAgB,QAAQ,KAAK,QAAQ;IACzD,MAAM,cAAc,8BAA8B;IAClD,WAAW,cAAc,WAAW,gBAAgB;IACpD,aAAa,mBACX,cACA;MAAW;MAAc;QACxB,QAAQ,kBAAkB;;wBAGT,mBAAmB,eAAe;;IAEtD,YAAY,cAAc,KAAK;uBACZ;uBACA,KAAK;aACf,IAAI,OAAO,cAAc,SAAS,MAAM,eAAe;;;;;eAMrD,0BAA0B;IACrC,YAAY,iBAAiB"}
|
package/dist/config.cjs
CHANGED
|
@@ -7,7 +7,7 @@ const require_dsl_bundle = require("./dsl/bundle.cjs");
|
|
|
7
7
|
require("./dsl/index.cjs");
|
|
8
8
|
let zod_mini = require("zod/mini");
|
|
9
9
|
zod_mini = require_runtime.__toESM(zod_mini, 1);
|
|
10
|
-
|
|
10
|
+
// src/config.ts
|
|
11
11
|
function defineConfig(definition) {
|
|
12
12
|
return {
|
|
13
13
|
kind: "journey-config",
|
|
@@ -181,7 +181,6 @@ function checkEmit(workflow, node, config) {
|
|
|
181
181
|
for (const [field, fieldSchema] of Object.entries(shape)) if (!canBeOmitted(fieldSchema) && !Object.hasOwn(node.payload, field)) problems.push(`required field '${field}' is missing`);
|
|
182
182
|
if (problems.length > 0) throw new ConfigCompileError(`${where}: event '${node.event}': ${problems.join("; ")}`);
|
|
183
183
|
}
|
|
184
|
-
//#endregion
|
|
185
184
|
exports.ConfigCompileError = ConfigCompileError;
|
|
186
185
|
exports.apnsEnvironment = apnsEnvironment;
|
|
187
186
|
exports.compileConfig = compileConfig;
|
package/dist/config.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.cjs","names":["indexTemplates","compileBundle","templateUses","CHANNEL_OF","z"],"sources":["../src/config.ts"],"sourcesContent":["import * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport type { Contract } from './contract';\nimport { type ActionRef, type SegmentRef, type WorkflowBuilder, compileBundle } from './dsl';\nimport { templateUses } from './dsl/flow';\nimport { type EmailConfig, assertEmailConfig } from './email';\nimport type { BundleIR, ChannelIR, NodeIR, WorkflowIR } from './ir';\nimport type { JourneyMode, JourneyRuntimeConfig } from './runtime';\nimport {\n type AnyTemplate,\n CHANNEL_OF,\n type TemplateChannel,\n type TemplateRegistry,\n indexTemplates,\n} from './templates';\n\n/**\n * `workflow.config.ts` — the project's journey configuration, typed, flat and\n * committed with the code: what it deploys (workflows, segments, actions),\n * the template registries behind them, the contract every inbound event is\n * checked against, what the email templates share, and how the engine runs per\n * environment (time scale, APNs, FCM). The compiler (`compileConfig`), the\n * Worker (`journeyWorker({ config })`) and the Workflow Studio all read this\n * one object, so everything is registered exactly once:\n *\n * export default defineConfig({\n * title: 'Workflow Studio · Acme',\n * contract,\n * workflows, // [onboarding, notificationTest]\n * templates, // { email, push }: the definitions the workflows send\n * email: { unsubscribeUrl: 'https://acme.test/unsubscribe' },\n * runtime: { development: { timeScale: 600, logMessages: true } },\n * apns: { topic: 'io.acme.app', keyId: 'ABC123DEFG', teamId: 'TEAM123456' },\n * });\n *\n * Secrets are the one thing that stay out: a `.p8`, a service account, the\n * ingest bearer. The host reads them from its environment under fixed names\n * (see `JourneyEnv`), so the config says *which* key without carrying it. The\n * config is a source object (schemas, components, functions); only its\n * compiled bundle and preview-safe template data may cross to a browser.\n */\n\n/** Token-based APNs, the `.p8` auth key; the key itself is `APNS_PRIVATE_KEY_BASE64` in the environment. */\nexport interface ApnsConfig {\n /** The app's bundle id (`apns-topic`). */\n topic: string;\n /** The key's 10-character id. */\n keyId: string;\n /** The Apple Developer team id. */\n teamId: string;\n /**\n * Which APNs host each mode's device tokens belong to: `sandbox` for builds\n * signed with `aps-environment: development` (Xcode, the simulator),\n * `production` for App Store and TestFlight builds. Unlisted modes default\n * to `production` for the production mode and `sandbox` for every other.\n */\n environment?: Partial<Record<JourneyMode, 'sandbox' | 'production'>>;\n}\n\n/**\n * Standard Web Push for browsers (VAPID); the key pair is `VAPID_PUBLIC_KEY` /\n * `VAPID_PRIVATE_KEY` in the environment, and the site subscribes with the same public key.\n */\nexport interface WebPushConfig {\n /** Who push services may contact about this sender: `mailto:` or an `https:` URL. */\n subject: string;\n /**\n * The contract event a notification tap the browser reports (`opened_url`) is also recorded\n * as, with `{ message_id }`: the app reports its own taps as an event, and a service worker has\n * no session to do so — without this, browser opens reach the reports but not the conditions\n * (a `performed(e.push_opened)` segment). One event per copy, however often it is tapped.\n */\n openedEvent?: string;\n}\n\n/** FCM HTTP v1 for Android; the service account is `GOOGLE_APPLICATION_CREDENTIALS_BASE64` in the environment. */\nexport interface FcmConfig {\n /** Android notification channel the pushes land in (`android.notification.channel_id`). */\n androidChannelId?: string;\n}\n\n/**\n * The engine's tables live in a Postgres schema of their own, so their short names (`event`,\n * `profile`, `message`, …) never meet the app's. `workflow-ui generate` writes them as one drizzle\n * file on this schema; the host migrates it with its own tooling.\n */\nexport interface DatabaseConfig {\n /** The Postgres schema; `workflow` by default. */\n schema?: string;\n}\n\nexport interface JourneyConfigDefinition {\n /** Studio title (the browser tab names the project). */\n title?: string;\n /** The shared `defineContract(...)` the Worker validates ingest against. */\n contract?: Contract;\n workflows: readonly WorkflowBuilder[];\n segments?: readonly SegmentRef[];\n actions?: readonly ActionRef[];\n /** The template definitions, per channel; each is identified by its own `id`. */\n templates?: TemplateRegistry;\n /** What every email template shares: the default unsubscribe link, how opens and clicks are judged. */\n email?: EmailConfig;\n /** How the engine behaves per mode (`development`, `production`, …): time scale, message logging. */\n runtime?: JourneyRuntimeConfig;\n apns?: ApnsConfig;\n fcm?: FcmConfig;\n webPush?: WebPushConfig;\n /** Where a Postgres store keeps the engine's tables (the D1 store ignores it). */\n database?: DatabaseConfig;\n}\n\nexport interface JourneyConfig extends JourneyConfigDefinition {\n readonly kind: 'journey-config';\n}\n\nexport function defineConfig(definition: JourneyConfigDefinition): JourneyConfig {\n return { kind: 'journey-config', ...definition };\n}\n\nexport function isConfig(value: unknown): value is JourneyConfig {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === 'journey-config'\n );\n}\n\n/** The APNs environment for `mode`: the configured one, else production only for production. */\nexport function apnsEnvironment(config: ApnsConfig, mode: JourneyMode): 'sandbox' | 'production' {\n return config.environment?.[mode] ?? (mode === 'production' ? 'production' : 'sandbox');\n}\n\n/** A message node the config's registries cannot serve, or whose literal props its template refuses. */\nexport class ConfigCompileError extends Error {\n override readonly name = 'ConfigCompileError';\n}\n\n/**\n * Compile the config: the bundle `compileBundle` would produce, with the\n * template manifest generated from the registry (every registered template,\n * by id, not a hand-kept list). The registry is checked first, used or not:\n * every entry is a template of its channel with an id, an id is registered\n * once per channel, and every email template's metadata is valid, as is the\n * config's `email` (which replaced `emails`: a config still carrying it fails).\n * Then every message node is checked against it — an\n * unregistered or cross-channel template fails with the workflow, node and\n * channel named, and so does a literal prop the template's schema refuses (a\n * missing required prop, an undeclared one, a wrong literal) — and every\n * definition a workflow sends must be the very object registered under its id,\n * not merely one with the same id. User-property references cannot be parsed\n * here (their values exist only at send time) — they are checked by name only,\n * and parsed before sending.\n */\nexport function compileConfig(config: JourneyConfig): BundleIR {\n let index: RegistryIndex;\n try {\n const email = indexTemplates('email', config.templates?.email);\n assertEmailConfig(config, email.values());\n index = {\n email,\n push: indexTemplates('push', config.templates?.push),\n discord: indexTemplates('discord', config.templates?.discord),\n slack: indexTemplates('slack', config.templates?.slack),\n };\n } catch (error) {\n throw new ConfigCompileError((error as Error).message);\n }\n const bundle = compileBundle({\n workflows: config.workflows,\n ...(config.segments === undefined ? {} : { segments: config.segments }),\n ...(config.actions === undefined ? {} : { actions: config.actions }),\n templates: TEMPLATE_CHANNELS.flatMap((channel) => [...index[channel].values()]),\n });\n bundle.workflows.forEach((workflow, i) => {\n checkMessages(workflow, workflow.flow, index, config);\n checkUses(workflow, config.workflows[i], index, config);\n });\n return bundle;\n}\n\nconst TEMPLATE_CHANNELS = ['email', 'push', 'discord', 'slack'] as const;\n\n/** The registry by id, per channel. */\ntype RegistryIndex = Record<TemplateChannel, ReadonlyMap<string, AnyTemplate>>;\n\n/** The channel's templates by id; undefined for a channel templates cannot be declared for. */\nfunction indexOf(\n index: RegistryIndex,\n channel: ChannelIR\n): ReadonlyMap<string, AnyTemplate> | undefined {\n return (TEMPLATE_CHANNELS as readonly ChannelIR[]).includes(channel)\n ? index[channel as TemplateChannel]\n : undefined;\n}\n\n/**\n * The definitions a workflow sends against the registered ones: same id is not enough — a\n * workflow typed by one definition and a registry holding another under that id would render\n * with a schema the call was never checked against.\n */\nfunction checkUses(\n workflow: WorkflowIR,\n builder: WorkflowBuilder,\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n for (const { template, loc } of templateUses(builder)) {\n const channel = CHANNEL_OF[template.kind];\n const registered = index[channel].get(template.id);\n if (registered === template) continue;\n const at = loc === undefined ? '' : ` (${loc.file}:${loc.line})`;\n const where = `workflow '${workflow.id}'${at}: ${channel} template '${template.id}'`;\n if (registered === undefined) throw new ConfigCompileError(`${where} is not registered`);\n const name = Object.entries(config.templates?.[channel] ?? {}).find(\n ([, entry]) => entry === registered\n )?.[0];\n throw new ConfigCompileError(\n `${where} is not the definition registered under that id (templates.${channel}.${name}) — two definitions share the id`\n );\n }\n}\n\n/** A field that may be left out: optional, or with a default the schema fills in. */\nfunction canBeOmitted(schema: core.$ZodType): boolean {\n const type = schema._zod.def.type;\n return type === 'optional' || type === 'default' || type === 'prefault';\n}\n\nfunction checkMessages(\n workflow: WorkflowIR,\n nodes: readonly NodeIR[],\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'message':\n checkMessage(workflow, node, index, config);\n break;\n case 'send_event':\n checkEmit(workflow, node, config);\n break;\n case 'branch':\n for (const c of node.cases) checkMessages(workflow, c.flow, index, config);\n if (node.otherwise) checkMessages(workflow, node.otherwise, index, config);\n break;\n case 'cohort':\n for (const arm of node.arms) checkMessages(workflow, arm.flow, index, config);\n break;\n case 'wait_until':\n if (Array.isArray(node.onTimeout)) checkMessages(workflow, node.onTimeout, index, config);\n break;\n default:\n break;\n }\n }\n}\n\nfunction checkMessage(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'message' }>,\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n const where = `workflow '${workflow.id}' node ${node.id}`;\n const registry = indexOf(index, node.channel);\n // Channels without template definitions (sms, …) are rendered by app-supplied senders\n if (registry === undefined) return;\n const entry = registry.get(node.template);\n if (entry === undefined) {\n const other = TEMPLATE_CHANNELS.find(\n (channel) => channel !== node.channel && index[channel].has(node.template)\n );\n const hint = other === undefined ? '' : ` (it is registered as a ${other} template)`;\n throw new ConfigCompileError(\n `${where}: ${node.channel} template '${node.template}' is not registered${hint}`\n );\n }\n const shape = (entry.props._zod.def as core.$ZodObjectDef).shape;\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.props)) {\n if (!Object.hasOwn(shape, field)) {\n problems.push(`'${field}' is not a prop of the template`);\n continue;\n }\n if (typeof value === 'object' && 'type' in value) {\n // A user ref's value exists only at send time (parsed then); here its name can be checked\n const profile = config.contract?.schemas.profile._zod.def;\n if (\n profile !== undefined &&\n value.path !== 'userId' &&\n !Object.hasOwn(profile.shape, value.path)\n ) {\n problems.push(\n `'${field}' reads u.${value.path}, which the contract's profile does not declare`\n );\n }\n continue;\n }\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the schema`);\n }\n for (const [field, schema] of Object.entries(shape)) {\n const optional = canBeOmitted(schema);\n if (!optional && !Object.hasOwn(node.props, field))\n problems.push(`required prop '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(\n `${where}: ${node.channel} '${node.template}': ${problems.join('; ')}`\n );\n }\n}\n\n/**\n * Whether an object schema refuses keys it does not declare: `z.strictObject`\n * sets its catchall to `never`; `z.object` strips unknown keys and\n * `z.looseObject` passes them through, and neither refuses.\n */\nfunction refusesUnknownKeys(schema: core.$ZodType): boolean {\n return (schema._zod.def as core.$ZodObjectDef).catchall?._zod.def.type === 'never';\n}\n\n/**\n * An emitted event against the contract, as far as compile time can see: the\n * event is declared, literal payload values parse against their field, no\n * required field is missing, and — when the event's schema is strict — no\n * literal field is undeclared (the Worker would refuse the whole event at\n * emit time, failing the journey where the compile could have failed the\n * build). Values read from the profile are checked when the event is emitted,\n * by the same contract.\n */\nfunction checkEmit(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'send_event' }>,\n config: JourneyConfig\n): void {\n const contract = config.contract;\n if (contract === undefined) return;\n const where = `workflow '${workflow.id}' node ${node.id}`;\n if (!contract.isEvent(node.event)) {\n throw new ConfigCompileError(\n `${where}: emits '${String(node.event)}', which is not in the contract`\n );\n }\n const schema = contract.schemas.events[node.event];\n const shape = (schema._zod.def as core.$ZodObjectDef).shape;\n const strict = refusesUnknownKeys(schema);\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.payload)) {\n if (typeof value === 'object' && 'type' in value) continue; // read at emit time\n if (!Object.hasOwn(shape, field)) {\n // A loose or stripping payload may carry extra fields; a strict one refuses them\n if (strict) problems.push(`'${field}' is not a field the contract declares for the event`);\n continue;\n }\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the contract`);\n }\n for (const [field, fieldSchema] of Object.entries(shape)) {\n const optional = canBeOmitted(fieldSchema);\n if (!optional && !Object.hasOwn(node.payload, field))\n problems.push(`required field '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(`${where}: event '${node.event}': ${problems.join('; ')}`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.cjs","names":["indexTemplates","compileBundle","templateUses","CHANNEL_OF","z"],"sources":["../src/config.ts"],"sourcesContent":["import * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport type { Contract } from './contract';\nimport { type ActionRef, type SegmentRef, type WorkflowBuilder, compileBundle } from './dsl';\nimport { templateUses } from './dsl/flow';\nimport { type EmailConfig, assertEmailConfig } from './email';\nimport type { BundleIR, ChannelIR, NodeIR, WorkflowIR } from './ir';\nimport type { JourneyMode, JourneyRuntimeConfig } from './runtime';\nimport {\n type AnyTemplate,\n CHANNEL_OF,\n type TemplateChannel,\n type TemplateRegistry,\n indexTemplates,\n} from './templates';\n\n/**\n * `workflow.config.ts` — the project's journey configuration, typed, flat and\n * committed with the code: what it deploys (workflows, segments, actions),\n * the template registries behind them, the contract every inbound event is\n * checked against, what the email templates share, and how the engine runs per\n * environment (time scale, APNs, FCM). The compiler (`compileConfig`), the\n * Worker (`journeyWorker({ config })`) and the Workflow Studio all read this\n * one object, so everything is registered exactly once:\n *\n * export default defineConfig({\n * title: 'Workflow Studio · Acme',\n * contract,\n * workflows, // [onboarding, notificationTest]\n * templates, // { email, push }: the definitions the workflows send\n * email: { unsubscribeUrl: 'https://acme.test/unsubscribe' },\n * runtime: { development: { timeScale: 600, logMessages: true } },\n * apns: { topic: 'io.acme.app', keyId: 'ABC123DEFG', teamId: 'TEAM123456' },\n * });\n *\n * Secrets are the one thing that stay out: a `.p8`, a service account, the\n * ingest bearer. The host reads them from its environment under fixed names\n * (see `JourneyEnv`), so the config says *which* key without carrying it. The\n * config is a source object (schemas, components, functions); only its\n * compiled bundle and preview-safe template data may cross to a browser.\n */\n\n/** Token-based APNs, the `.p8` auth key; the key itself is `APNS_PRIVATE_KEY_BASE64` in the environment. */\nexport interface ApnsConfig {\n /** The app's bundle id (`apns-topic`). */\n topic: string;\n /** The key's 10-character id. */\n keyId: string;\n /** The Apple Developer team id. */\n teamId: string;\n /**\n * Which APNs host each mode's device tokens belong to: `sandbox` for builds\n * signed with `aps-environment: development` (Xcode, the simulator),\n * `production` for App Store and TestFlight builds. Unlisted modes default\n * to `production` for the production mode and `sandbox` for every other.\n */\n environment?: Partial<Record<JourneyMode, 'sandbox' | 'production'>>;\n}\n\n/**\n * Standard Web Push for browsers (VAPID); the key pair is `VAPID_PUBLIC_KEY` /\n * `VAPID_PRIVATE_KEY` in the environment, and the site subscribes with the same public key.\n */\nexport interface WebPushConfig {\n /** Who push services may contact about this sender: `mailto:` or an `https:` URL. */\n subject: string;\n /**\n * The contract event a notification tap the browser reports (`opened_url`) is also recorded\n * as, with `{ message_id }`: the app reports its own taps as an event, and a service worker has\n * no session to do so — without this, browser opens reach the reports but not the conditions\n * (a `performed(e.push_opened)` segment). One event per copy, however often it is tapped.\n */\n openedEvent?: string;\n}\n\n/** FCM HTTP v1 for Android; the service account is `GOOGLE_APPLICATION_CREDENTIALS_BASE64` in the environment. */\nexport interface FcmConfig {\n /** Android notification channel the pushes land in (`android.notification.channel_id`). */\n androidChannelId?: string;\n}\n\n/**\n * The engine's tables live in a Postgres schema of their own, so their short names (`event`,\n * `profile`, `message`, …) never meet the app's. `workflow-ui generate` writes them as one drizzle\n * file on this schema; the host migrates it with its own tooling.\n */\nexport interface DatabaseConfig {\n /** The Postgres schema; `workflow` by default. */\n schema?: string;\n}\n\nexport interface JourneyConfigDefinition {\n /** Studio title (the browser tab names the project). */\n title?: string;\n /** The shared `defineContract(...)` the Worker validates ingest against. */\n contract?: Contract;\n workflows: readonly WorkflowBuilder[];\n segments?: readonly SegmentRef[];\n actions?: readonly ActionRef[];\n /** The template definitions, per channel; each is identified by its own `id`. */\n templates?: TemplateRegistry;\n /** What every email template shares: the default unsubscribe link, how opens and clicks are judged. */\n email?: EmailConfig;\n /** How the engine behaves per mode (`development`, `production`, …): time scale, message logging. */\n runtime?: JourneyRuntimeConfig;\n apns?: ApnsConfig;\n fcm?: FcmConfig;\n webPush?: WebPushConfig;\n /** The schema the store keeps the engine's tables in. */\n database?: DatabaseConfig;\n}\n\nexport interface JourneyConfig extends JourneyConfigDefinition {\n readonly kind: 'journey-config';\n}\n\nexport function defineConfig(definition: JourneyConfigDefinition): JourneyConfig {\n return { kind: 'journey-config', ...definition };\n}\n\nexport function isConfig(value: unknown): value is JourneyConfig {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === 'journey-config'\n );\n}\n\n/** The APNs environment for `mode`: the configured one, else production only for production. */\nexport function apnsEnvironment(config: ApnsConfig, mode: JourneyMode): 'sandbox' | 'production' {\n return config.environment?.[mode] ?? (mode === 'production' ? 'production' : 'sandbox');\n}\n\n/** A message node the config's registries cannot serve, or whose literal props its template refuses. */\nexport class ConfigCompileError extends Error {\n override readonly name = 'ConfigCompileError';\n}\n\n/**\n * Compile the config: the bundle `compileBundle` would produce, with the\n * template manifest generated from the registry (every registered template,\n * by id, not a hand-kept list). The registry is checked first, used or not:\n * every entry is a template of its channel with an id, an id is registered\n * once per channel, and every email template's metadata is valid, as is the\n * config's `email` (which replaced `emails`: a config still carrying it fails).\n * Then every message node is checked against it — an\n * unregistered or cross-channel template fails with the workflow, node and\n * channel named, and so does a literal prop the template's schema refuses (a\n * missing required prop, an undeclared one, a wrong literal) — and every\n * definition a workflow sends must be the very object registered under its id,\n * not merely one with the same id. User-property references cannot be parsed\n * here (their values exist only at send time) — they are checked by name only,\n * and parsed before sending.\n */\nexport function compileConfig(config: JourneyConfig): BundleIR {\n let index: RegistryIndex;\n try {\n const email = indexTemplates('email', config.templates?.email);\n assertEmailConfig(config, email.values());\n index = {\n email,\n push: indexTemplates('push', config.templates?.push),\n discord: indexTemplates('discord', config.templates?.discord),\n slack: indexTemplates('slack', config.templates?.slack),\n };\n } catch (error) {\n throw new ConfigCompileError((error as Error).message);\n }\n const bundle = compileBundle({\n workflows: config.workflows,\n ...(config.segments === undefined ? {} : { segments: config.segments }),\n ...(config.actions === undefined ? {} : { actions: config.actions }),\n templates: TEMPLATE_CHANNELS.flatMap((channel) => [...index[channel].values()]),\n });\n bundle.workflows.forEach((workflow, i) => {\n checkMessages(workflow, workflow.flow, index, config);\n checkUses(workflow, config.workflows[i], index, config);\n });\n return bundle;\n}\n\nconst TEMPLATE_CHANNELS = ['email', 'push', 'discord', 'slack'] as const;\n\n/** The registry by id, per channel. */\ntype RegistryIndex = Record<TemplateChannel, ReadonlyMap<string, AnyTemplate>>;\n\n/** The channel's templates by id; undefined for a channel templates cannot be declared for. */\nfunction indexOf(\n index: RegistryIndex,\n channel: ChannelIR\n): ReadonlyMap<string, AnyTemplate> | undefined {\n return (TEMPLATE_CHANNELS as readonly ChannelIR[]).includes(channel)\n ? index[channel as TemplateChannel]\n : undefined;\n}\n\n/**\n * The definitions a workflow sends against the registered ones: same id is not enough — a\n * workflow typed by one definition and a registry holding another under that id would render\n * with a schema the call was never checked against.\n */\nfunction checkUses(\n workflow: WorkflowIR,\n builder: WorkflowBuilder,\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n for (const { template, loc } of templateUses(builder)) {\n const channel = CHANNEL_OF[template.kind];\n const registered = index[channel].get(template.id);\n if (registered === template) continue;\n const at = loc === undefined ? '' : ` (${loc.file}:${loc.line})`;\n const where = `workflow '${workflow.id}'${at}: ${channel} template '${template.id}'`;\n if (registered === undefined) throw new ConfigCompileError(`${where} is not registered`);\n const name = Object.entries(config.templates?.[channel] ?? {}).find(\n ([, entry]) => entry === registered\n )?.[0];\n throw new ConfigCompileError(\n `${where} is not the definition registered under that id (templates.${channel}.${name}) — two definitions share the id`\n );\n }\n}\n\n/** A field that may be left out: optional, or with a default the schema fills in. */\nfunction canBeOmitted(schema: core.$ZodType): boolean {\n const type = schema._zod.def.type;\n return type === 'optional' || type === 'default' || type === 'prefault';\n}\n\nfunction checkMessages(\n workflow: WorkflowIR,\n nodes: readonly NodeIR[],\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'message':\n checkMessage(workflow, node, index, config);\n break;\n case 'send_event':\n checkEmit(workflow, node, config);\n break;\n case 'branch':\n for (const c of node.cases) checkMessages(workflow, c.flow, index, config);\n if (node.otherwise) checkMessages(workflow, node.otherwise, index, config);\n break;\n case 'cohort':\n for (const arm of node.arms) checkMessages(workflow, arm.flow, index, config);\n break;\n case 'wait_until':\n if (Array.isArray(node.onTimeout)) checkMessages(workflow, node.onTimeout, index, config);\n break;\n default:\n break;\n }\n }\n}\n\nfunction checkMessage(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'message' }>,\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n const where = `workflow '${workflow.id}' node ${node.id}`;\n const registry = indexOf(index, node.channel);\n // Channels without template definitions (sms, …) are rendered by app-supplied senders\n if (registry === undefined) return;\n const entry = registry.get(node.template);\n if (entry === undefined) {\n const other = TEMPLATE_CHANNELS.find(\n (channel) => channel !== node.channel && index[channel].has(node.template)\n );\n const hint = other === undefined ? '' : ` (it is registered as a ${other} template)`;\n throw new ConfigCompileError(\n `${where}: ${node.channel} template '${node.template}' is not registered${hint}`\n );\n }\n const shape = (entry.props._zod.def as core.$ZodObjectDef).shape;\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.props)) {\n if (!Object.hasOwn(shape, field)) {\n problems.push(`'${field}' is not a prop of the template`);\n continue;\n }\n if (typeof value === 'object' && 'type' in value) {\n // A user ref's value exists only at send time (parsed then); here its name can be checked\n const profile = config.contract?.schemas.profile._zod.def;\n if (\n profile !== undefined &&\n value.path !== 'userId' &&\n !Object.hasOwn(profile.shape, value.path)\n ) {\n problems.push(\n `'${field}' reads u.${value.path}, which the contract's profile does not declare`\n );\n }\n continue;\n }\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the schema`);\n }\n for (const [field, schema] of Object.entries(shape)) {\n const optional = canBeOmitted(schema);\n if (!optional && !Object.hasOwn(node.props, field))\n problems.push(`required prop '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(\n `${where}: ${node.channel} '${node.template}': ${problems.join('; ')}`\n );\n }\n}\n\n/**\n * Whether an object schema refuses keys it does not declare: `z.strictObject`\n * sets its catchall to `never`; `z.object` strips unknown keys and\n * `z.looseObject` passes them through, and neither refuses.\n */\nfunction refusesUnknownKeys(schema: core.$ZodType): boolean {\n return (schema._zod.def as core.$ZodObjectDef).catchall?._zod.def.type === 'never';\n}\n\n/**\n * An emitted event against the contract, as far as compile time can see: the\n * event is declared, literal payload values parse against their field, no\n * required field is missing, and — when the event's schema is strict — no\n * literal field is undeclared (the Worker would refuse the whole event at\n * emit time, failing the journey where the compile could have failed the\n * build). Values read from the profile are checked when the event is emitted,\n * by the same contract.\n */\nfunction checkEmit(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'send_event' }>,\n config: JourneyConfig\n): void {\n const contract = config.contract;\n if (contract === undefined) return;\n const where = `workflow '${workflow.id}' node ${node.id}`;\n if (!contract.isEvent(node.event)) {\n throw new ConfigCompileError(\n `${where}: emits '${String(node.event)}', which is not in the contract`\n );\n }\n const schema = contract.schemas.events[node.event];\n const shape = (schema._zod.def as core.$ZodObjectDef).shape;\n const strict = refusesUnknownKeys(schema);\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.payload)) {\n if (typeof value === 'object' && 'type' in value) continue; // read at emit time\n if (!Object.hasOwn(shape, field)) {\n // A loose or stripping payload may carry extra fields; a strict one refuses them\n if (strict) problems.push(`'${field}' is not a field the contract declares for the event`);\n continue;\n }\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the contract`);\n }\n for (const [field, fieldSchema] of Object.entries(shape)) {\n const optional = canBeOmitted(fieldSchema);\n if (!optional && !Object.hasOwn(node.payload, field))\n problems.push(`required field '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(`${where}: event '${node.event}': ${problems.join('; ')}`);\n }\n}\n"],"mappings":"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"}
|
package/dist/config.d.cts
CHANGED
|
@@ -7,7 +7,6 @@ import { WorkflowBuilder } from "./dsl/workflow.cjs";
|
|
|
7
7
|
import "./dsl/index.cjs";
|
|
8
8
|
import { Contract } from "./contract.cjs";
|
|
9
9
|
import { JourneyMode, JourneyRuntimeConfig } from "./runtime.cjs";
|
|
10
|
-
//#region src/config.d.ts
|
|
11
10
|
/**
|
|
12
11
|
* `workflow.config.ts` — the project's journey configuration, typed, flat and
|
|
13
12
|
* committed with the code: what it deploys (workflows, segments, actions),
|
|
@@ -95,7 +94,7 @@ export interface JourneyConfigDefinition {
|
|
|
95
94
|
apns?: ApnsConfig;
|
|
96
95
|
fcm?: FcmConfig;
|
|
97
96
|
webPush?: WebPushConfig;
|
|
98
|
-
/**
|
|
97
|
+
/** The schema the store keeps the engine's tables in. */
|
|
99
98
|
database?: DatabaseConfig;
|
|
100
99
|
}
|
|
101
100
|
export interface JourneyConfig extends JourneyConfigDefinition {
|
|
@@ -126,5 +125,4 @@ export declare class ConfigCompileError extends Error {
|
|
|
126
125
|
* and parsed before sending.
|
|
127
126
|
*/
|
|
128
127
|
export declare function compileConfig(config: JourneyConfig): BundleIR;
|
|
129
|
-
//#endregion
|
|
130
128
|
//# sourceMappingURL=config.d.cts.map
|
package/dist/config.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2CiB;;EAEf;;EAEA;;EAEA;;;;;;;EAOA,cAAc,QAAQ,OAAO;;;;;;iBAOd;;EAEf;;;;;;;EAOA;;;iBAIe;;EAEf;;;;;;;iBAQe;;EAEf;;iBAGe;;EAEf;;EAEA,WAAW;EACX,oBAAoB;EACpB,oBAAoB;EACpB,mBAAmB;;EAEnB,YAAY;;EAEZ,QAAQ;;EAER,UAAU;EACV,OAAO;EACP,MAAM;EACN,UAAU;;EAEV,WAAW;;iBAGI,sBAAsB;WAC5B;;wBAGK,aAAa,YAAY,0BAA0B;wBAInD,SAAS,iBAAiB,SAAS;;wBASnC,gBAAgB,QAAQ,YAAY,MAAM;;qBAK7C,2BAA2B;WACpB;;;;;;;;;;;;;;;;;;wBAmBJ,cAAc,QAAQ,gBAAgB"}
|
package/dist/config.d.mts
CHANGED
|
@@ -7,7 +7,6 @@ import { WorkflowBuilder } from "./dsl/workflow.mjs";
|
|
|
7
7
|
import "./dsl/index.mjs";
|
|
8
8
|
import { Contract } from "./contract.mjs";
|
|
9
9
|
import { JourneyMode, JourneyRuntimeConfig } from "./runtime.mjs";
|
|
10
|
-
//#region src/config.d.ts
|
|
11
10
|
/**
|
|
12
11
|
* `workflow.config.ts` — the project's journey configuration, typed, flat and
|
|
13
12
|
* committed with the code: what it deploys (workflows, segments, actions),
|
|
@@ -95,7 +94,7 @@ export interface JourneyConfigDefinition {
|
|
|
95
94
|
apns?: ApnsConfig;
|
|
96
95
|
fcm?: FcmConfig;
|
|
97
96
|
webPush?: WebPushConfig;
|
|
98
|
-
/**
|
|
97
|
+
/** The schema the store keeps the engine's tables in. */
|
|
99
98
|
database?: DatabaseConfig;
|
|
100
99
|
}
|
|
101
100
|
export interface JourneyConfig extends JourneyConfigDefinition {
|
|
@@ -126,5 +125,4 @@ export declare class ConfigCompileError extends Error {
|
|
|
126
125
|
* and parsed before sending.
|
|
127
126
|
*/
|
|
128
127
|
export declare function compileConfig(config: JourneyConfig): BundleIR;
|
|
129
|
-
//#endregion
|
|
130
128
|
//# sourceMappingURL=config.d.mts.map
|
package/dist/config.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2CiB;;EAEf;;EAEA;;EAEA;;;;;;;EAOA,cAAc,QAAQ,OAAO;;;;;;iBAOd;;EAEf;;;;;;;EAOA;;;iBAIe;;EAEf;;;;;;;iBAQe;;EAEf;;iBAGe;;EAEf;;EAEA,WAAW;EACX,oBAAoB;EACpB,oBAAoB;EACpB,mBAAmB;;EAEnB,YAAY;;EAEZ,QAAQ;;EAER,UAAU;EACV,OAAO;EACP,MAAM;EACN,UAAU;;EAEV,WAAW;;iBAGI,sBAAsB;WAC5B;;wBAGK,aAAa,YAAY,0BAA0B;wBAInD,SAAS,iBAAiB,SAAS;;wBASnC,gBAAgB,QAAQ,YAAY,MAAM;;qBAK7C,2BAA2B;WACpB;;;;;;;;;;;;;;;;;;wBAmBJ,cAAc,QAAQ,gBAAgB"}
|
package/dist/config.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { templateUses } from "./dsl/flow.mjs";
|
|
|
4
4
|
import { compileBundle } from "./dsl/bundle.mjs";
|
|
5
5
|
import "./dsl/index.mjs";
|
|
6
6
|
import * as z from "zod/mini";
|
|
7
|
-
|
|
7
|
+
// src/config.ts
|
|
8
8
|
function defineConfig(definition) {
|
|
9
9
|
return {
|
|
10
10
|
kind: "journey-config",
|
|
@@ -178,7 +178,6 @@ function checkEmit(workflow, node, config) {
|
|
|
178
178
|
for (const [field, fieldSchema] of Object.entries(shape)) if (!canBeOmitted(fieldSchema) && !Object.hasOwn(node.payload, field)) problems.push(`required field '${field}' is missing`);
|
|
179
179
|
if (problems.length > 0) throw new ConfigCompileError(`${where}: event '${node.event}': ${problems.join("; ")}`);
|
|
180
180
|
}
|
|
181
|
-
//#endregion
|
|
182
181
|
export { ConfigCompileError, apnsEnvironment, compileConfig, defineConfig, isConfig };
|
|
183
182
|
|
|
184
183
|
//# sourceMappingURL=config.mjs.map
|
package/dist/config.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport type { Contract } from './contract';\nimport { type ActionRef, type SegmentRef, type WorkflowBuilder, compileBundle } from './dsl';\nimport { templateUses } from './dsl/flow';\nimport { type EmailConfig, assertEmailConfig } from './email';\nimport type { BundleIR, ChannelIR, NodeIR, WorkflowIR } from './ir';\nimport type { JourneyMode, JourneyRuntimeConfig } from './runtime';\nimport {\n type AnyTemplate,\n CHANNEL_OF,\n type TemplateChannel,\n type TemplateRegistry,\n indexTemplates,\n} from './templates';\n\n/**\n * `workflow.config.ts` — the project's journey configuration, typed, flat and\n * committed with the code: what it deploys (workflows, segments, actions),\n * the template registries behind them, the contract every inbound event is\n * checked against, what the email templates share, and how the engine runs per\n * environment (time scale, APNs, FCM). The compiler (`compileConfig`), the\n * Worker (`journeyWorker({ config })`) and the Workflow Studio all read this\n * one object, so everything is registered exactly once:\n *\n * export default defineConfig({\n * title: 'Workflow Studio · Acme',\n * contract,\n * workflows, // [onboarding, notificationTest]\n * templates, // { email, push }: the definitions the workflows send\n * email: { unsubscribeUrl: 'https://acme.test/unsubscribe' },\n * runtime: { development: { timeScale: 600, logMessages: true } },\n * apns: { topic: 'io.acme.app', keyId: 'ABC123DEFG', teamId: 'TEAM123456' },\n * });\n *\n * Secrets are the one thing that stay out: a `.p8`, a service account, the\n * ingest bearer. The host reads them from its environment under fixed names\n * (see `JourneyEnv`), so the config says *which* key without carrying it. The\n * config is a source object (schemas, components, functions); only its\n * compiled bundle and preview-safe template data may cross to a browser.\n */\n\n/** Token-based APNs, the `.p8` auth key; the key itself is `APNS_PRIVATE_KEY_BASE64` in the environment. */\nexport interface ApnsConfig {\n /** The app's bundle id (`apns-topic`). */\n topic: string;\n /** The key's 10-character id. */\n keyId: string;\n /** The Apple Developer team id. */\n teamId: string;\n /**\n * Which APNs host each mode's device tokens belong to: `sandbox` for builds\n * signed with `aps-environment: development` (Xcode, the simulator),\n * `production` for App Store and TestFlight builds. Unlisted modes default\n * to `production` for the production mode and `sandbox` for every other.\n */\n environment?: Partial<Record<JourneyMode, 'sandbox' | 'production'>>;\n}\n\n/**\n * Standard Web Push for browsers (VAPID); the key pair is `VAPID_PUBLIC_KEY` /\n * `VAPID_PRIVATE_KEY` in the environment, and the site subscribes with the same public key.\n */\nexport interface WebPushConfig {\n /** Who push services may contact about this sender: `mailto:` or an `https:` URL. */\n subject: string;\n /**\n * The contract event a notification tap the browser reports (`opened_url`) is also recorded\n * as, with `{ message_id }`: the app reports its own taps as an event, and a service worker has\n * no session to do so — without this, browser opens reach the reports but not the conditions\n * (a `performed(e.push_opened)` segment). One event per copy, however often it is tapped.\n */\n openedEvent?: string;\n}\n\n/** FCM HTTP v1 for Android; the service account is `GOOGLE_APPLICATION_CREDENTIALS_BASE64` in the environment. */\nexport interface FcmConfig {\n /** Android notification channel the pushes land in (`android.notification.channel_id`). */\n androidChannelId?: string;\n}\n\n/**\n * The engine's tables live in a Postgres schema of their own, so their short names (`event`,\n * `profile`, `message`, …) never meet the app's. `workflow-ui generate` writes them as one drizzle\n * file on this schema; the host migrates it with its own tooling.\n */\nexport interface DatabaseConfig {\n /** The Postgres schema; `workflow` by default. */\n schema?: string;\n}\n\nexport interface JourneyConfigDefinition {\n /** Studio title (the browser tab names the project). */\n title?: string;\n /** The shared `defineContract(...)` the Worker validates ingest against. */\n contract?: Contract;\n workflows: readonly WorkflowBuilder[];\n segments?: readonly SegmentRef[];\n actions?: readonly ActionRef[];\n /** The template definitions, per channel; each is identified by its own `id`. */\n templates?: TemplateRegistry;\n /** What every email template shares: the default unsubscribe link, how opens and clicks are judged. */\n email?: EmailConfig;\n /** How the engine behaves per mode (`development`, `production`, …): time scale, message logging. */\n runtime?: JourneyRuntimeConfig;\n apns?: ApnsConfig;\n fcm?: FcmConfig;\n webPush?: WebPushConfig;\n /** Where a Postgres store keeps the engine's tables (the D1 store ignores it). */\n database?: DatabaseConfig;\n}\n\nexport interface JourneyConfig extends JourneyConfigDefinition {\n readonly kind: 'journey-config';\n}\n\nexport function defineConfig(definition: JourneyConfigDefinition): JourneyConfig {\n return { kind: 'journey-config', ...definition };\n}\n\nexport function isConfig(value: unknown): value is JourneyConfig {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === 'journey-config'\n );\n}\n\n/** The APNs environment for `mode`: the configured one, else production only for production. */\nexport function apnsEnvironment(config: ApnsConfig, mode: JourneyMode): 'sandbox' | 'production' {\n return config.environment?.[mode] ?? (mode === 'production' ? 'production' : 'sandbox');\n}\n\n/** A message node the config's registries cannot serve, or whose literal props its template refuses. */\nexport class ConfigCompileError extends Error {\n override readonly name = 'ConfigCompileError';\n}\n\n/**\n * Compile the config: the bundle `compileBundle` would produce, with the\n * template manifest generated from the registry (every registered template,\n * by id, not a hand-kept list). The registry is checked first, used or not:\n * every entry is a template of its channel with an id, an id is registered\n * once per channel, and every email template's metadata is valid, as is the\n * config's `email` (which replaced `emails`: a config still carrying it fails).\n * Then every message node is checked against it — an\n * unregistered or cross-channel template fails with the workflow, node and\n * channel named, and so does a literal prop the template's schema refuses (a\n * missing required prop, an undeclared one, a wrong literal) — and every\n * definition a workflow sends must be the very object registered under its id,\n * not merely one with the same id. User-property references cannot be parsed\n * here (their values exist only at send time) — they are checked by name only,\n * and parsed before sending.\n */\nexport function compileConfig(config: JourneyConfig): BundleIR {\n let index: RegistryIndex;\n try {\n const email = indexTemplates('email', config.templates?.email);\n assertEmailConfig(config, email.values());\n index = {\n email,\n push: indexTemplates('push', config.templates?.push),\n discord: indexTemplates('discord', config.templates?.discord),\n slack: indexTemplates('slack', config.templates?.slack),\n };\n } catch (error) {\n throw new ConfigCompileError((error as Error).message);\n }\n const bundle = compileBundle({\n workflows: config.workflows,\n ...(config.segments === undefined ? {} : { segments: config.segments }),\n ...(config.actions === undefined ? {} : { actions: config.actions }),\n templates: TEMPLATE_CHANNELS.flatMap((channel) => [...index[channel].values()]),\n });\n bundle.workflows.forEach((workflow, i) => {\n checkMessages(workflow, workflow.flow, index, config);\n checkUses(workflow, config.workflows[i], index, config);\n });\n return bundle;\n}\n\nconst TEMPLATE_CHANNELS = ['email', 'push', 'discord', 'slack'] as const;\n\n/** The registry by id, per channel. */\ntype RegistryIndex = Record<TemplateChannel, ReadonlyMap<string, AnyTemplate>>;\n\n/** The channel's templates by id; undefined for a channel templates cannot be declared for. */\nfunction indexOf(\n index: RegistryIndex,\n channel: ChannelIR\n): ReadonlyMap<string, AnyTemplate> | undefined {\n return (TEMPLATE_CHANNELS as readonly ChannelIR[]).includes(channel)\n ? index[channel as TemplateChannel]\n : undefined;\n}\n\n/**\n * The definitions a workflow sends against the registered ones: same id is not enough — a\n * workflow typed by one definition and a registry holding another under that id would render\n * with a schema the call was never checked against.\n */\nfunction checkUses(\n workflow: WorkflowIR,\n builder: WorkflowBuilder,\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n for (const { template, loc } of templateUses(builder)) {\n const channel = CHANNEL_OF[template.kind];\n const registered = index[channel].get(template.id);\n if (registered === template) continue;\n const at = loc === undefined ? '' : ` (${loc.file}:${loc.line})`;\n const where = `workflow '${workflow.id}'${at}: ${channel} template '${template.id}'`;\n if (registered === undefined) throw new ConfigCompileError(`${where} is not registered`);\n const name = Object.entries(config.templates?.[channel] ?? {}).find(\n ([, entry]) => entry === registered\n )?.[0];\n throw new ConfigCompileError(\n `${where} is not the definition registered under that id (templates.${channel}.${name}) — two definitions share the id`\n );\n }\n}\n\n/** A field that may be left out: optional, or with a default the schema fills in. */\nfunction canBeOmitted(schema: core.$ZodType): boolean {\n const type = schema._zod.def.type;\n return type === 'optional' || type === 'default' || type === 'prefault';\n}\n\nfunction checkMessages(\n workflow: WorkflowIR,\n nodes: readonly NodeIR[],\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'message':\n checkMessage(workflow, node, index, config);\n break;\n case 'send_event':\n checkEmit(workflow, node, config);\n break;\n case 'branch':\n for (const c of node.cases) checkMessages(workflow, c.flow, index, config);\n if (node.otherwise) checkMessages(workflow, node.otherwise, index, config);\n break;\n case 'cohort':\n for (const arm of node.arms) checkMessages(workflow, arm.flow, index, config);\n break;\n case 'wait_until':\n if (Array.isArray(node.onTimeout)) checkMessages(workflow, node.onTimeout, index, config);\n break;\n default:\n break;\n }\n }\n}\n\nfunction checkMessage(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'message' }>,\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n const where = `workflow '${workflow.id}' node ${node.id}`;\n const registry = indexOf(index, node.channel);\n // Channels without template definitions (sms, …) are rendered by app-supplied senders\n if (registry === undefined) return;\n const entry = registry.get(node.template);\n if (entry === undefined) {\n const other = TEMPLATE_CHANNELS.find(\n (channel) => channel !== node.channel && index[channel].has(node.template)\n );\n const hint = other === undefined ? '' : ` (it is registered as a ${other} template)`;\n throw new ConfigCompileError(\n `${where}: ${node.channel} template '${node.template}' is not registered${hint}`\n );\n }\n const shape = (entry.props._zod.def as core.$ZodObjectDef).shape;\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.props)) {\n if (!Object.hasOwn(shape, field)) {\n problems.push(`'${field}' is not a prop of the template`);\n continue;\n }\n if (typeof value === 'object' && 'type' in value) {\n // A user ref's value exists only at send time (parsed then); here its name can be checked\n const profile = config.contract?.schemas.profile._zod.def;\n if (\n profile !== undefined &&\n value.path !== 'userId' &&\n !Object.hasOwn(profile.shape, value.path)\n ) {\n problems.push(\n `'${field}' reads u.${value.path}, which the contract's profile does not declare`\n );\n }\n continue;\n }\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the schema`);\n }\n for (const [field, schema] of Object.entries(shape)) {\n const optional = canBeOmitted(schema);\n if (!optional && !Object.hasOwn(node.props, field))\n problems.push(`required prop '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(\n `${where}: ${node.channel} '${node.template}': ${problems.join('; ')}`\n );\n }\n}\n\n/**\n * Whether an object schema refuses keys it does not declare: `z.strictObject`\n * sets its catchall to `never`; `z.object` strips unknown keys and\n * `z.looseObject` passes them through, and neither refuses.\n */\nfunction refusesUnknownKeys(schema: core.$ZodType): boolean {\n return (schema._zod.def as core.$ZodObjectDef).catchall?._zod.def.type === 'never';\n}\n\n/**\n * An emitted event against the contract, as far as compile time can see: the\n * event is declared, literal payload values parse against their field, no\n * required field is missing, and — when the event's schema is strict — no\n * literal field is undeclared (the Worker would refuse the whole event at\n * emit time, failing the journey where the compile could have failed the\n * build). Values read from the profile are checked when the event is emitted,\n * by the same contract.\n */\nfunction checkEmit(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'send_event' }>,\n config: JourneyConfig\n): void {\n const contract = config.contract;\n if (contract === undefined) return;\n const where = `workflow '${workflow.id}' node ${node.id}`;\n if (!contract.isEvent(node.event)) {\n throw new ConfigCompileError(\n `${where}: emits '${String(node.event)}', which is not in the contract`\n );\n }\n const schema = contract.schemas.events[node.event];\n const shape = (schema._zod.def as core.$ZodObjectDef).shape;\n const strict = refusesUnknownKeys(schema);\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.payload)) {\n if (typeof value === 'object' && 'type' in value) continue; // read at emit time\n if (!Object.hasOwn(shape, field)) {\n // A loose or stripping payload may carry extra fields; a strict one refuses them\n if (strict) problems.push(`'${field}' is not a field the contract declares for the event`);\n continue;\n }\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the contract`);\n }\n for (const [field, fieldSchema] of Object.entries(shape)) {\n const optional = canBeOmitted(fieldSchema);\n if (!optional && !Object.hasOwn(node.payload, field))\n problems.push(`required field '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(`${where}: event '${node.event}': ${problems.join('; ')}`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport type { Contract } from './contract';\nimport { type ActionRef, type SegmentRef, type WorkflowBuilder, compileBundle } from './dsl';\nimport { templateUses } from './dsl/flow';\nimport { type EmailConfig, assertEmailConfig } from './email';\nimport type { BundleIR, ChannelIR, NodeIR, WorkflowIR } from './ir';\nimport type { JourneyMode, JourneyRuntimeConfig } from './runtime';\nimport {\n type AnyTemplate,\n CHANNEL_OF,\n type TemplateChannel,\n type TemplateRegistry,\n indexTemplates,\n} from './templates';\n\n/**\n * `workflow.config.ts` — the project's journey configuration, typed, flat and\n * committed with the code: what it deploys (workflows, segments, actions),\n * the template registries behind them, the contract every inbound event is\n * checked against, what the email templates share, and how the engine runs per\n * environment (time scale, APNs, FCM). The compiler (`compileConfig`), the\n * Worker (`journeyWorker({ config })`) and the Workflow Studio all read this\n * one object, so everything is registered exactly once:\n *\n * export default defineConfig({\n * title: 'Workflow Studio · Acme',\n * contract,\n * workflows, // [onboarding, notificationTest]\n * templates, // { email, push }: the definitions the workflows send\n * email: { unsubscribeUrl: 'https://acme.test/unsubscribe' },\n * runtime: { development: { timeScale: 600, logMessages: true } },\n * apns: { topic: 'io.acme.app', keyId: 'ABC123DEFG', teamId: 'TEAM123456' },\n * });\n *\n * Secrets are the one thing that stay out: a `.p8`, a service account, the\n * ingest bearer. The host reads them from its environment under fixed names\n * (see `JourneyEnv`), so the config says *which* key without carrying it. The\n * config is a source object (schemas, components, functions); only its\n * compiled bundle and preview-safe template data may cross to a browser.\n */\n\n/** Token-based APNs, the `.p8` auth key; the key itself is `APNS_PRIVATE_KEY_BASE64` in the environment. */\nexport interface ApnsConfig {\n /** The app's bundle id (`apns-topic`). */\n topic: string;\n /** The key's 10-character id. */\n keyId: string;\n /** The Apple Developer team id. */\n teamId: string;\n /**\n * Which APNs host each mode's device tokens belong to: `sandbox` for builds\n * signed with `aps-environment: development` (Xcode, the simulator),\n * `production` for App Store and TestFlight builds. Unlisted modes default\n * to `production` for the production mode and `sandbox` for every other.\n */\n environment?: Partial<Record<JourneyMode, 'sandbox' | 'production'>>;\n}\n\n/**\n * Standard Web Push for browsers (VAPID); the key pair is `VAPID_PUBLIC_KEY` /\n * `VAPID_PRIVATE_KEY` in the environment, and the site subscribes with the same public key.\n */\nexport interface WebPushConfig {\n /** Who push services may contact about this sender: `mailto:` or an `https:` URL. */\n subject: string;\n /**\n * The contract event a notification tap the browser reports (`opened_url`) is also recorded\n * as, with `{ message_id }`: the app reports its own taps as an event, and a service worker has\n * no session to do so — without this, browser opens reach the reports but not the conditions\n * (a `performed(e.push_opened)` segment). One event per copy, however often it is tapped.\n */\n openedEvent?: string;\n}\n\n/** FCM HTTP v1 for Android; the service account is `GOOGLE_APPLICATION_CREDENTIALS_BASE64` in the environment. */\nexport interface FcmConfig {\n /** Android notification channel the pushes land in (`android.notification.channel_id`). */\n androidChannelId?: string;\n}\n\n/**\n * The engine's tables live in a Postgres schema of their own, so their short names (`event`,\n * `profile`, `message`, …) never meet the app's. `workflow-ui generate` writes them as one drizzle\n * file on this schema; the host migrates it with its own tooling.\n */\nexport interface DatabaseConfig {\n /** The Postgres schema; `workflow` by default. */\n schema?: string;\n}\n\nexport interface JourneyConfigDefinition {\n /** Studio title (the browser tab names the project). */\n title?: string;\n /** The shared `defineContract(...)` the Worker validates ingest against. */\n contract?: Contract;\n workflows: readonly WorkflowBuilder[];\n segments?: readonly SegmentRef[];\n actions?: readonly ActionRef[];\n /** The template definitions, per channel; each is identified by its own `id`. */\n templates?: TemplateRegistry;\n /** What every email template shares: the default unsubscribe link, how opens and clicks are judged. */\n email?: EmailConfig;\n /** How the engine behaves per mode (`development`, `production`, …): time scale, message logging. */\n runtime?: JourneyRuntimeConfig;\n apns?: ApnsConfig;\n fcm?: FcmConfig;\n webPush?: WebPushConfig;\n /** The schema the store keeps the engine's tables in. */\n database?: DatabaseConfig;\n}\n\nexport interface JourneyConfig extends JourneyConfigDefinition {\n readonly kind: 'journey-config';\n}\n\nexport function defineConfig(definition: JourneyConfigDefinition): JourneyConfig {\n return { kind: 'journey-config', ...definition };\n}\n\nexport function isConfig(value: unknown): value is JourneyConfig {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === 'journey-config'\n );\n}\n\n/** The APNs environment for `mode`: the configured one, else production only for production. */\nexport function apnsEnvironment(config: ApnsConfig, mode: JourneyMode): 'sandbox' | 'production' {\n return config.environment?.[mode] ?? (mode === 'production' ? 'production' : 'sandbox');\n}\n\n/** A message node the config's registries cannot serve, or whose literal props its template refuses. */\nexport class ConfigCompileError extends Error {\n override readonly name = 'ConfigCompileError';\n}\n\n/**\n * Compile the config: the bundle `compileBundle` would produce, with the\n * template manifest generated from the registry (every registered template,\n * by id, not a hand-kept list). The registry is checked first, used or not:\n * every entry is a template of its channel with an id, an id is registered\n * once per channel, and every email template's metadata is valid, as is the\n * config's `email` (which replaced `emails`: a config still carrying it fails).\n * Then every message node is checked against it — an\n * unregistered or cross-channel template fails with the workflow, node and\n * channel named, and so does a literal prop the template's schema refuses (a\n * missing required prop, an undeclared one, a wrong literal) — and every\n * definition a workflow sends must be the very object registered under its id,\n * not merely one with the same id. User-property references cannot be parsed\n * here (their values exist only at send time) — they are checked by name only,\n * and parsed before sending.\n */\nexport function compileConfig(config: JourneyConfig): BundleIR {\n let index: RegistryIndex;\n try {\n const email = indexTemplates('email', config.templates?.email);\n assertEmailConfig(config, email.values());\n index = {\n email,\n push: indexTemplates('push', config.templates?.push),\n discord: indexTemplates('discord', config.templates?.discord),\n slack: indexTemplates('slack', config.templates?.slack),\n };\n } catch (error) {\n throw new ConfigCompileError((error as Error).message);\n }\n const bundle = compileBundle({\n workflows: config.workflows,\n ...(config.segments === undefined ? {} : { segments: config.segments }),\n ...(config.actions === undefined ? {} : { actions: config.actions }),\n templates: TEMPLATE_CHANNELS.flatMap((channel) => [...index[channel].values()]),\n });\n bundle.workflows.forEach((workflow, i) => {\n checkMessages(workflow, workflow.flow, index, config);\n checkUses(workflow, config.workflows[i], index, config);\n });\n return bundle;\n}\n\nconst TEMPLATE_CHANNELS = ['email', 'push', 'discord', 'slack'] as const;\n\n/** The registry by id, per channel. */\ntype RegistryIndex = Record<TemplateChannel, ReadonlyMap<string, AnyTemplate>>;\n\n/** The channel's templates by id; undefined for a channel templates cannot be declared for. */\nfunction indexOf(\n index: RegistryIndex,\n channel: ChannelIR\n): ReadonlyMap<string, AnyTemplate> | undefined {\n return (TEMPLATE_CHANNELS as readonly ChannelIR[]).includes(channel)\n ? index[channel as TemplateChannel]\n : undefined;\n}\n\n/**\n * The definitions a workflow sends against the registered ones: same id is not enough — a\n * workflow typed by one definition and a registry holding another under that id would render\n * with a schema the call was never checked against.\n */\nfunction checkUses(\n workflow: WorkflowIR,\n builder: WorkflowBuilder,\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n for (const { template, loc } of templateUses(builder)) {\n const channel = CHANNEL_OF[template.kind];\n const registered = index[channel].get(template.id);\n if (registered === template) continue;\n const at = loc === undefined ? '' : ` (${loc.file}:${loc.line})`;\n const where = `workflow '${workflow.id}'${at}: ${channel} template '${template.id}'`;\n if (registered === undefined) throw new ConfigCompileError(`${where} is not registered`);\n const name = Object.entries(config.templates?.[channel] ?? {}).find(\n ([, entry]) => entry === registered\n )?.[0];\n throw new ConfigCompileError(\n `${where} is not the definition registered under that id (templates.${channel}.${name}) — two definitions share the id`\n );\n }\n}\n\n/** A field that may be left out: optional, or with a default the schema fills in. */\nfunction canBeOmitted(schema: core.$ZodType): boolean {\n const type = schema._zod.def.type;\n return type === 'optional' || type === 'default' || type === 'prefault';\n}\n\nfunction checkMessages(\n workflow: WorkflowIR,\n nodes: readonly NodeIR[],\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'message':\n checkMessage(workflow, node, index, config);\n break;\n case 'send_event':\n checkEmit(workflow, node, config);\n break;\n case 'branch':\n for (const c of node.cases) checkMessages(workflow, c.flow, index, config);\n if (node.otherwise) checkMessages(workflow, node.otherwise, index, config);\n break;\n case 'cohort':\n for (const arm of node.arms) checkMessages(workflow, arm.flow, index, config);\n break;\n case 'wait_until':\n if (Array.isArray(node.onTimeout)) checkMessages(workflow, node.onTimeout, index, config);\n break;\n default:\n break;\n }\n }\n}\n\nfunction checkMessage(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'message' }>,\n index: RegistryIndex,\n config: JourneyConfig\n): void {\n const where = `workflow '${workflow.id}' node ${node.id}`;\n const registry = indexOf(index, node.channel);\n // Channels without template definitions (sms, …) are rendered by app-supplied senders\n if (registry === undefined) return;\n const entry = registry.get(node.template);\n if (entry === undefined) {\n const other = TEMPLATE_CHANNELS.find(\n (channel) => channel !== node.channel && index[channel].has(node.template)\n );\n const hint = other === undefined ? '' : ` (it is registered as a ${other} template)`;\n throw new ConfigCompileError(\n `${where}: ${node.channel} template '${node.template}' is not registered${hint}`\n );\n }\n const shape = (entry.props._zod.def as core.$ZodObjectDef).shape;\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.props)) {\n if (!Object.hasOwn(shape, field)) {\n problems.push(`'${field}' is not a prop of the template`);\n continue;\n }\n if (typeof value === 'object' && 'type' in value) {\n // A user ref's value exists only at send time (parsed then); here its name can be checked\n const profile = config.contract?.schemas.profile._zod.def;\n if (\n profile !== undefined &&\n value.path !== 'userId' &&\n !Object.hasOwn(profile.shape, value.path)\n ) {\n problems.push(\n `'${field}' reads u.${value.path}, which the contract's profile does not declare`\n );\n }\n continue;\n }\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the schema`);\n }\n for (const [field, schema] of Object.entries(shape)) {\n const optional = canBeOmitted(schema);\n if (!optional && !Object.hasOwn(node.props, field))\n problems.push(`required prop '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(\n `${where}: ${node.channel} '${node.template}': ${problems.join('; ')}`\n );\n }\n}\n\n/**\n * Whether an object schema refuses keys it does not declare: `z.strictObject`\n * sets its catchall to `never`; `z.object` strips unknown keys and\n * `z.looseObject` passes them through, and neither refuses.\n */\nfunction refusesUnknownKeys(schema: core.$ZodType): boolean {\n return (schema._zod.def as core.$ZodObjectDef).catchall?._zod.def.type === 'never';\n}\n\n/**\n * An emitted event against the contract, as far as compile time can see: the\n * event is declared, literal payload values parse against their field, no\n * required field is missing, and — when the event's schema is strict — no\n * literal field is undeclared (the Worker would refuse the whole event at\n * emit time, failing the journey where the compile could have failed the\n * build). Values read from the profile are checked when the event is emitted,\n * by the same contract.\n */\nfunction checkEmit(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'send_event' }>,\n config: JourneyConfig\n): void {\n const contract = config.contract;\n if (contract === undefined) return;\n const where = `workflow '${workflow.id}' node ${node.id}`;\n if (!contract.isEvent(node.event)) {\n throw new ConfigCompileError(\n `${where}: emits '${String(node.event)}', which is not in the contract`\n );\n }\n const schema = contract.schemas.events[node.event];\n const shape = (schema._zod.def as core.$ZodObjectDef).shape;\n const strict = refusesUnknownKeys(schema);\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.payload)) {\n if (typeof value === 'object' && 'type' in value) continue; // read at emit time\n if (!Object.hasOwn(shape, field)) {\n // A loose or stripping payload may carry extra fields; a strict one refuses them\n if (strict) problems.push(`'${field}' is not a field the contract declares for the event`);\n continue;\n }\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the contract`);\n }\n for (const [field, fieldSchema] of Object.entries(shape)) {\n const optional = canBeOmitted(fieldSchema);\n if (!optional && !Object.hasOwn(node.payload, field))\n problems.push(`required field '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(`${where}: event '${node.event}': ${problems.join('; ')}`);\n }\n}\n"],"mappings":"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"}
|
package/dist/contract.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const require_dsl_refs = require("./dsl/refs.cjs");
|
|
|
4
4
|
require("./dsl/index.cjs");
|
|
5
5
|
let zod_mini = require("zod/mini");
|
|
6
6
|
zod_mini = require_runtime.__toESM(zod_mini, 1);
|
|
7
|
-
|
|
7
|
+
// src/contract.ts
|
|
8
8
|
function defineContract(definition) {
|
|
9
9
|
for (const [name, schema] of Object.entries(definition.events)) {
|
|
10
10
|
if (name.startsWith("$")) throw new TypeError(`contract event '${name}': names starting with $ are reserved`);
|
|
@@ -203,7 +203,6 @@ function assertFlatScalar(schema, where) {
|
|
|
203
203
|
if (def.type === "optional") def = def.innerType._zod.def;
|
|
204
204
|
if (!SCALARS.has(def.type)) throw new TypeError(`contract ${where}: profile fields must be flat scalars (string, number, boolean, enum, literal), got ${def.type}`);
|
|
205
205
|
}
|
|
206
|
-
//#endregion
|
|
207
206
|
exports.ContractValidationError = ContractValidationError;
|
|
208
207
|
exports.checkEvent = checkEvent;
|
|
209
208
|
exports.defineContract = defineContract;
|
package/dist/contract.d.cts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { EventRefs, UserRefs } from "./dsl/refs.cjs";
|
|
2
2
|
import "./dsl/index.cjs";
|
|
3
3
|
import * as core from "zod/v4/core";
|
|
4
|
-
//#region src/contract.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* `defineContract({ events, profile })` — the producer/engine contract as Zod
|
|
7
6
|
* schemas, written once in a package both sides import. From it:
|
|
@@ -86,6 +85,5 @@ export declare class ContractValidationError extends Error {
|
|
|
86
85
|
/** The one check every event passes, from HTTP or from a journey: declared, and its payload parses. */
|
|
87
86
|
export declare function checkEvent(contract: Contract, name: string, payload: unknown): ContractResult<Record<string, unknown>>;
|
|
88
87
|
export declare function isContract(value: unknown): value is Contract;
|
|
89
|
-
//#endregion
|
|
90
88
|
export {};
|
|
91
89
|
//# sourceMappingURL=contract.d.cts.map
|
package/dist/contract.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.cts","names":[],"sources":["../src/contract.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"contract.d.cts","names":[],"sources":["../src/contract.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;KAyBK,eAAe,KAAK;;YAGb,eAAe,KACzB,UAAU,KAAK,iBAAiB,SAC5B,KAAK,mBAAmB,OAAO,KAAK;iBAGzB,mBACf,UAAU,eAAe,eACzB,UAAU;EAEV,QAAQ;EACR,SAAS;;;iBAIM;EACf;EACA;;YAGU,eAAe;EAAO;EAAU,MAAM;;EAAQ;EAAW,QAAQ;;iBAE5D,SACf,UAAU,eAAe,gBAAgB,eAAe,eACxD,UAAU,eAAe;WAEhB;;WAEA,QAAQ,aAAa,WAAW,IAAI,eAAe,EAAE;;WAErD,MAAM,SAAS,eAAe;IAAO;;;WAErC;aAAoB,QAAQ;aAAY,SAAS;;;WAEjD,4BAA4B;EACrC,QAAQ,eAAe,cAAc;;EAErC,WAAW,gBAAgB,YACzB,MAAM,GACN,mBACC,eAAe,KAAK,OAAO,EAAE;;;;;;;EAOhC,kBAAkB,iBAAiB,eAAe,aAAa;;;YAIrD,aAAa,QAAQ,WAAW,eAAe,MAAM,eAAe,GAAG;;YAGvE,aAAa,GAAG,oBAC1B,UAAU,eAAe,MAAM,gBAAgB,IAAI,KAAK,MAAM,EAAE;wBAElD,eAAe,UAAU,eAAe,eAAe,UAAU,cAC/E,YAAY,mBAAmB,GAAG,KACjC,SAAS,GAAG;;;;;;qBA8DF,gCAAgC;WAGhC;WACA,QAAQ;WAHD;EAClB,YACW,eACA,QAAQ;;;wBAWL,WACd,UAAU,UACV,cACA,mBACC,eAAe;wBAUF,WAAW,iBAAiB,SAAS"}
|
package/dist/contract.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { EventRefs, UserRefs } from "./dsl/refs.mjs";
|
|
2
2
|
import "./dsl/index.mjs";
|
|
3
3
|
import * as core from "zod/v4/core";
|
|
4
|
-
//#region src/contract.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* `defineContract({ events, profile })` — the producer/engine contract as Zod
|
|
7
6
|
* schemas, written once in a package both sides import. From it:
|
|
@@ -86,6 +85,5 @@ export declare class ContractValidationError extends Error {
|
|
|
86
85
|
/** The one check every event passes, from HTTP or from a journey: declared, and its payload parses. */
|
|
87
86
|
export declare function checkEvent(contract: Contract, name: string, payload: unknown): ContractResult<Record<string, unknown>>;
|
|
88
87
|
export declare function isContract(value: unknown): value is Contract;
|
|
89
|
-
//#endregion
|
|
90
88
|
export {};
|
|
91
89
|
//# sourceMappingURL=contract.d.mts.map
|
package/dist/contract.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.mts","names":[],"sources":["../src/contract.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"contract.d.mts","names":[],"sources":["../src/contract.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;KAyBK,eAAe,KAAK;;YAGb,eAAe,KACzB,UAAU,KAAK,iBAAiB,SAC5B,KAAK,mBAAmB,OAAO,KAAK;iBAGzB,mBACf,UAAU,eAAe,eACzB,UAAU;EAEV,QAAQ;EACR,SAAS;;;iBAIM;EACf;EACA;;YAGU,eAAe;EAAO;EAAU,MAAM;;EAAQ;EAAW,QAAQ;;iBAE5D,SACf,UAAU,eAAe,gBAAgB,eAAe,eACxD,UAAU,eAAe;WAEhB;;WAEA,QAAQ,aAAa,WAAW,IAAI,eAAe,EAAE;;WAErD,MAAM,SAAS,eAAe;IAAO;;;WAErC;aAAoB,QAAQ;aAAY,SAAS;;;WAEjD,4BAA4B;EACrC,QAAQ,eAAe,cAAc;;EAErC,WAAW,gBAAgB,YACzB,MAAM,GACN,mBACC,eAAe,KAAK,OAAO,EAAE;;;;;;;EAOhC,kBAAkB,iBAAiB,eAAe,aAAa;;;YAIrD,aAAa,QAAQ,WAAW,eAAe,MAAM,eAAe,GAAG;;YAGvE,aAAa,GAAG,oBAC1B,UAAU,eAAe,MAAM,gBAAgB,IAAI,KAAK,MAAM,EAAE;wBAElD,eAAe,UAAU,eAAe,eAAe,UAAU,cAC/E,YAAY,mBAAmB,GAAG,KACjC,SAAS,GAAG;;;;;;qBA8DF,gCAAgC;WAGhC;WACA,QAAQ;WAHD;EAClB,YACW,eACA,QAAQ;;;wBAWL,WACd,UAAU,UACV,cACA,mBACC,eAAe;wBAUF,WAAW,iBAAiB,SAAS"}
|
package/dist/contract.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { event, user } from "./dsl/refs.mjs";
|
|
2
2
|
import "./dsl/index.mjs";
|
|
3
3
|
import * as z from "zod/mini";
|
|
4
|
-
|
|
4
|
+
// src/contract.ts
|
|
5
5
|
function defineContract(definition) {
|
|
6
6
|
for (const [name, schema] of Object.entries(definition.events)) {
|
|
7
7
|
if (name.startsWith("$")) throw new TypeError(`contract event '${name}': names starting with $ are reserved`);
|
|
@@ -200,7 +200,6 @@ function assertFlatScalar(schema, where) {
|
|
|
200
200
|
if (def.type === "optional") def = def.innerType._zod.def;
|
|
201
201
|
if (!SCALARS.has(def.type)) throw new TypeError(`contract ${where}: profile fields must be flat scalars (string, number, boolean, enum, literal), got ${def.type}`);
|
|
202
202
|
}
|
|
203
|
-
//#endregion
|
|
204
203
|
export { ContractValidationError, checkEvent, defineContract, isContract };
|
|
205
204
|
|
|
206
205
|
//# sourceMappingURL=contract.mjs.map
|
package/dist/dsl/action.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
// src/dsl/action.ts
|
|
3
3
|
/**
|
|
4
4
|
* Define a custom action. The generic `A` types the args `.run()` accepts —
|
|
5
5
|
* the only type injection point, mirroring `template.sms<P>()`:
|
|
@@ -20,7 +20,6 @@ function action(name, handler) {
|
|
|
20
20
|
handler
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
//#endregion
|
|
24
23
|
exports.action = action;
|
|
25
24
|
|
|
26
25
|
//# sourceMappingURL=action.cjs.map
|
package/dist/dsl/action.d.cts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/dsl/action.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Custom actions — the escape hatch for side effects the built-in nodes do not
|
|
4
3
|
* cover (CRM sync, coupon issuing, outbound webhooks with custom auth, …).
|
|
@@ -61,5 +60,4 @@ export interface ActionRef<A extends object = object> {
|
|
|
61
60
|
* two definitions cannot share a name by accident.
|
|
62
61
|
*/
|
|
63
62
|
export declare function action<A extends object = Record<string, never>>(name: string, handler: ActionHandler<A>): ActionRef<A>;
|
|
64
|
-
//#endregion
|
|
65
63
|
//# sourceMappingURL=action.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action.d.cts","names":[],"sources":["../../src/dsl/action.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"action.d.cts","names":[],"sources":["../../src/dsl/action.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;iBAsBiB;EACf;;;;;;;EAOA;;;;;;;;YASU,cAAc,qBAAqB,MAAM,GAAG,KAAK,kBAAkB;;;;;iBAM9D,UAAU;WAChB;;EAET,QAAQ,MAAM,GAAG,KAAK,gBAAgB;;;;;;;;;;;;;;;;wBAiBxB,OAAO,mBAAmB,uBACxC,cACA,SAAS,cAAc,KACtB,UAAU"}
|
package/dist/dsl/action.d.mts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/dsl/action.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Custom actions — the escape hatch for side effects the built-in nodes do not
|
|
4
3
|
* cover (CRM sync, coupon issuing, outbound webhooks with custom auth, …).
|
|
@@ -61,5 +60,4 @@ export interface ActionRef<A extends object = object> {
|
|
|
61
60
|
* two definitions cannot share a name by accident.
|
|
62
61
|
*/
|
|
63
62
|
export declare function action<A extends object = Record<string, never>>(name: string, handler: ActionHandler<A>): ActionRef<A>;
|
|
64
|
-
//#endregion
|
|
65
63
|
//# sourceMappingURL=action.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action.d.mts","names":[],"sources":["../../src/dsl/action.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"action.d.mts","names":[],"sources":["../../src/dsl/action.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;iBAsBiB;EACf;;;;;;;EAOA;;;;;;;;YASU,cAAc,qBAAqB,MAAM,GAAG,KAAK,kBAAkB;;;;;iBAM9D,UAAU;WAChB;;EAET,QAAQ,MAAM,GAAG,KAAK,gBAAgB;;;;;;;;;;;;;;;;wBAiBxB,OAAO,mBAAmB,uBACxC,cACA,SAAS,cAAc,KACtB,UAAU"}
|
package/dist/dsl/action.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/dsl/action.ts
|
|
2
2
|
/**
|
|
3
3
|
* Define a custom action. The generic `A` types the args `.run()` accepts —
|
|
4
4
|
* the only type injection point, mirroring `template.sms<P>()`:
|
|
@@ -19,7 +19,6 @@ function action(name, handler) {
|
|
|
19
19
|
handler
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
-
//#endregion
|
|
23
22
|
export { action };
|
|
24
23
|
|
|
25
24
|
//# sourceMappingURL=action.mjs.map
|
package/dist/dsl/base.cjs
CHANGED
|
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
3
3
|
let ms = require("ms");
|
|
4
4
|
ms = require_runtime.__toESM(ms, 1);
|
|
5
|
-
|
|
5
|
+
// src/dsl/base.ts
|
|
6
6
|
const TIME_OF_DAY = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
7
7
|
/**
|
|
8
8
|
* Parse an 'HH:mm' literal to minutes since midnight, throwing on anything the
|
|
@@ -27,7 +27,6 @@ function durationIR(value) {
|
|
|
27
27
|
ms: millis
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
//#endregion
|
|
31
30
|
exports.durationIR = durationIR;
|
|
32
31
|
exports.timeOfDayMinutes = timeOfDayMinutes;
|
|
33
32
|
|
package/dist/dsl/base.d.cts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DurationIR } from "../ir.cjs";
|
|
2
|
-
//#region src/dsl/base.d.ts
|
|
3
2
|
/**
|
|
4
3
|
* Base vocabulary shared by the whole DSL: the schema constraints an app's
|
|
5
4
|
* types must satisfy, and the literal types (durations, weekdays, clock times)
|
|
@@ -41,5 +40,4 @@ export declare function timeOfDayMinutes(value: TimeOfDay): number;
|
|
|
41
40
|
* @internal shared by condition/flow/workflow, not part of the public API
|
|
42
41
|
*/
|
|
43
42
|
export declare function durationIR(value: Duration): DurationIR;
|
|
44
|
-
//#endregion
|
|
45
43
|
//# sourceMappingURL=base.d.cts.map
|
package/dist/dsl/base.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.cts","names":[],"sources":["../../src/dsl/base.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"base.d.cts","names":[],"sources":["../../src/dsl/base.ts"],"mappings":";;;;;;;;;;;;;;;YAkBY;;;;;;iBAOK;EACf;EACA;;;YAIU;YAYA;;YAGA;;;;;;wBASI,iBAAiB,OAAO;;;;;;wBAWxB,WAAW,OAAO,WAAW"}
|
package/dist/dsl/base.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DurationIR } from "../ir.mjs";
|
|
2
|
-
//#region src/dsl/base.d.ts
|
|
3
2
|
/**
|
|
4
3
|
* Base vocabulary shared by the whole DSL: the schema constraints an app's
|
|
5
4
|
* types must satisfy, and the literal types (durations, weekdays, clock times)
|
|
@@ -41,5 +40,4 @@ export declare function timeOfDayMinutes(value: TimeOfDay): number;
|
|
|
41
40
|
* @internal shared by condition/flow/workflow, not part of the public API
|
|
42
41
|
*/
|
|
43
42
|
export declare function durationIR(value: Duration): DurationIR;
|
|
44
|
-
//#endregion
|
|
45
43
|
//# sourceMappingURL=base.d.mts.map
|
package/dist/dsl/base.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.mts","names":[],"sources":["../../src/dsl/base.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"base.d.mts","names":[],"sources":["../../src/dsl/base.ts"],"mappings":";;;;;;;;;;;;;;;YAkBY;;;;;;iBAOK;EACf;EACA;;;YAIU;YAYA;;YAGA;;;;;;wBASI,iBAAiB,OAAO;;;;;;wBAWxB,WAAW,OAAO,WAAW"}
|
package/dist/dsl/base.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ms from "ms";
|
|
2
|
-
|
|
2
|
+
// src/dsl/base.ts
|
|
3
3
|
const TIME_OF_DAY = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
4
4
|
/**
|
|
5
5
|
* Parse an 'HH:mm' literal to minutes since midnight, throwing on anything the
|
|
@@ -24,7 +24,6 @@ function durationIR(value) {
|
|
|
24
24
|
ms: millis
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
//#endregion
|
|
28
27
|
export { durationIR, timeOfDayMinutes };
|
|
29
28
|
|
|
30
29
|
//# sourceMappingURL=base.mjs.map
|
package/dist/dsl/bundle.cjs
CHANGED
|
@@ -3,7 +3,7 @@ const require_templates = require("../templates.cjs");
|
|
|
3
3
|
const require_dsl_flow = require("./flow.cjs");
|
|
4
4
|
const require_hash = require("../hash.cjs");
|
|
5
5
|
const require_ir = require("../ir.cjs");
|
|
6
|
-
|
|
6
|
+
// src/dsl/bundle.ts
|
|
7
7
|
/**
|
|
8
8
|
* The deployment unit — compileBundle gathers workflows, segments and the
|
|
9
9
|
* template manifest into one BundleIR snapshot (terraform-apply mental model);
|
|
@@ -206,7 +206,6 @@ function assertNodeTrees(nodes, owner) {
|
|
|
206
206
|
case "cohort": for (const arm of node.arms) assertNodeTrees(arm.flow, owner);
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
|
-
//#endregion
|
|
210
209
|
exports.compileBundle = compileBundle;
|
|
211
210
|
|
|
212
211
|
//# sourceMappingURL=bundle.cjs.map
|