@shware/workflow 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +154 -2
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/apns-relay.cjs +105 -0
- package/dist/apns-relay.cjs.map +1 -0
- package/dist/apns-relay.d.cts +28 -0
- package/dist/apns-relay.d.cts.map +1 -0
- package/dist/apns-relay.d.mts +28 -0
- package/dist/apns-relay.d.mts.map +1 -0
- package/dist/apns-relay.mjs +104 -0
- package/dist/apns-relay.mjs.map +1 -0
- package/dist/apns.cjs +121 -0
- package/dist/apns.cjs.map +1 -0
- package/dist/apns.d.cts +63 -0
- package/dist/apns.d.cts.map +1 -0
- package/dist/apns.d.mts +63 -0
- package/dist/apns.d.mts.map +1 -0
- package/dist/apns.mjs +116 -0
- package/dist/apns.mjs.map +1 -0
- package/dist/client.cjs +125 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +108 -0
- package/dist/client.d.cts.map +1 -0
- package/dist/client.d.mts +108 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +122 -0
- package/dist/client.mjs.map +1 -0
- package/dist/cloudflare/bindings.cjs +28 -0
- package/dist/cloudflare/bindings.cjs.map +1 -0
- package/dist/cloudflare/bindings.d.cts +144 -0
- package/dist/cloudflare/bindings.d.cts.map +1 -0
- package/dist/cloudflare/bindings.d.mts +144 -0
- package/dist/cloudflare/bindings.d.mts.map +1 -0
- package/dist/cloudflare/bindings.mjs +25 -0
- package/dist/cloudflare/bindings.mjs.map +1 -0
- package/dist/cloudflare/channels.cjs +164 -0
- package/dist/cloudflare/channels.cjs.map +1 -0
- package/dist/cloudflare/channels.d.cts +29 -0
- package/dist/cloudflare/channels.d.cts.map +1 -0
- package/dist/cloudflare/channels.d.mts +29 -0
- package/dist/cloudflare/channels.d.mts.map +1 -0
- package/dist/cloudflare/channels.mjs +161 -0
- package/dist/cloudflare/channels.mjs.map +1 -0
- package/dist/cloudflare/d1-env.cjs +17 -0
- package/dist/cloudflare/d1-env.cjs.map +1 -0
- package/dist/cloudflare/d1-env.d.cts +12 -0
- package/dist/cloudflare/d1-env.d.cts.map +1 -0
- package/dist/cloudflare/d1-env.d.mts +12 -0
- package/dist/cloudflare/d1-env.d.mts.map +1 -0
- package/dist/cloudflare/d1-env.mjs +16 -0
- package/dist/cloudflare/d1-env.mjs.map +1 -0
- package/dist/cloudflare/index.cjs +54 -0
- package/dist/cloudflare/index.d.cts +19 -0
- package/dist/cloudflare/index.d.mts +19 -0
- package/dist/cloudflare/index.mjs +16 -0
- package/dist/cloudflare/router.cjs +516 -0
- package/dist/cloudflare/router.cjs.map +1 -0
- package/dist/cloudflare/router.d.cts +157 -0
- package/dist/cloudflare/router.d.cts.map +1 -0
- package/dist/cloudflare/router.d.mts +157 -0
- package/dist/cloudflare/router.d.mts.map +1 -0
- package/dist/cloudflare/router.mjs +503 -0
- package/dist/cloudflare/router.mjs.map +1 -0
- package/dist/cloudflare/runner.cjs +225 -0
- package/dist/cloudflare/runner.cjs.map +1 -0
- package/dist/cloudflare/runner.d.cts +73 -0
- package/dist/cloudflare/runner.d.cts.map +1 -0
- package/dist/cloudflare/runner.d.mts +73 -0
- package/dist/cloudflare/runner.d.mts.map +1 -0
- package/dist/cloudflare/runner.mjs +224 -0
- package/dist/cloudflare/runner.mjs.map +1 -0
- package/dist/cloudflare/schema.sql +127 -0
- package/dist/cloudflare/senders.cjs +114 -0
- package/dist/cloudflare/senders.cjs.map +1 -0
- package/dist/cloudflare/senders.d.cts +68 -0
- package/dist/cloudflare/senders.d.cts.map +1 -0
- package/dist/cloudflare/senders.d.mts +68 -0
- package/dist/cloudflare/senders.d.mts.map +1 -0
- package/dist/cloudflare/senders.mjs +108 -0
- package/dist/cloudflare/senders.mjs.map +1 -0
- package/dist/cloudflare/worker.cjs +228 -0
- package/dist/cloudflare/worker.cjs.map +1 -0
- package/dist/cloudflare/worker.d.cts +92 -0
- package/dist/cloudflare/worker.d.cts.map +1 -0
- package/dist/cloudflare/worker.d.mts +92 -0
- package/dist/cloudflare/worker.d.mts.map +1 -0
- package/dist/cloudflare/worker.mjs +224 -0
- package/dist/cloudflare/worker.mjs.map +1 -0
- package/dist/cloudflare/workers.d.cjs +0 -0
- package/dist/cloudflare/workers.d.cts +44 -0
- package/dist/cloudflare/workers.d.cts.map +1 -0
- package/dist/cloudflare/workers.d.mts +44 -0
- package/dist/cloudflare/workers.d.mts.map +1 -0
- package/dist/config.cjs +151 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +149 -0
- package/dist/config.d.cts.map +1 -0
- package/dist/config.d.mts +149 -0
- package/dist/config.d.mts.map +1 -0
- package/dist/config.mjs +143 -0
- package/dist/config.mjs.map +1 -0
- package/dist/contract.cjs +212 -0
- package/dist/contract.cjs.map +1 -0
- package/dist/contract.d.cts +90 -0
- package/dist/contract.d.cts.map +1 -0
- package/dist/contract.d.mts +90 -0
- package/dist/contract.d.mts.map +1 -0
- package/dist/contract.mjs +206 -0
- package/dist/contract.mjs.map +1 -0
- package/dist/dsl/action.cjs +29 -0
- package/dist/dsl/action.cjs.map +1 -0
- package/dist/dsl/action.d.cts +64 -0
- package/dist/dsl/action.d.cts.map +1 -0
- package/dist/dsl/action.d.mts +64 -0
- package/dist/dsl/action.d.mts.map +1 -0
- package/dist/dsl/action.mjs +28 -0
- package/dist/dsl/action.mjs.map +1 -0
- package/dist/dsl/base.cjs +34 -0
- package/dist/dsl/base.cjs.map +1 -0
- package/dist/dsl/base.d.cts +45 -0
- package/dist/dsl/base.d.cts.map +1 -0
- package/dist/dsl/base.d.mts +45 -0
- package/dist/dsl/base.d.mts.map +1 -0
- package/dist/dsl/base.mjs +30 -0
- package/dist/dsl/base.mjs.map +1 -0
- package/dist/dsl/bundle.cjs +207 -0
- package/dist/dsl/bundle.cjs.map +1 -0
- package/dist/dsl/bundle.d.cts +19 -0
- package/dist/dsl/bundle.d.cts.map +1 -0
- package/dist/dsl/bundle.d.mts +19 -0
- package/dist/dsl/bundle.d.mts.map +1 -0
- package/dist/dsl/bundle.mjs +206 -0
- package/dist/dsl/bundle.mjs.map +1 -0
- package/dist/dsl/condition.cjs +177 -0
- package/dist/dsl/condition.cjs.map +1 -0
- package/dist/dsl/condition.d.cts +140 -0
- package/dist/dsl/condition.d.cts.map +1 -0
- package/dist/dsl/condition.d.mts +140 -0
- package/dist/dsl/condition.d.mts.map +1 -0
- package/dist/dsl/condition.mjs +156 -0
- package/dist/dsl/condition.mjs.map +1 -0
- package/dist/dsl/flow.cjs +228 -0
- package/dist/dsl/flow.cjs.map +1 -0
- package/dist/dsl/flow.d.cts +223 -0
- package/dist/dsl/flow.d.cts.map +1 -0
- package/dist/dsl/flow.d.mts +223 -0
- package/dist/dsl/flow.d.mts.map +1 -0
- package/dist/dsl/flow.mjs +226 -0
- package/dist/dsl/flow.mjs.map +1 -0
- package/dist/dsl/index.cjs +38 -0
- package/dist/dsl/index.d.cts +10 -0
- package/dist/dsl/index.d.mts +10 -0
- package/dist/dsl/index.mjs +9 -0
- package/dist/dsl/refs.cjs +59 -0
- package/dist/dsl/refs.cjs.map +1 -0
- package/dist/dsl/refs.d.cts +88 -0
- package/dist/dsl/refs.d.cts.map +1 -0
- package/dist/dsl/refs.d.mts +88 -0
- package/dist/dsl/refs.d.mts.map +1 -0
- package/dist/dsl/refs.mjs +55 -0
- package/dist/dsl/refs.mjs.map +1 -0
- package/dist/dsl/template.cjs +65 -0
- package/dist/dsl/template.cjs.map +1 -0
- package/dist/dsl/template.d.cts +75 -0
- package/dist/dsl/template.d.cts.map +1 -0
- package/dist/dsl/template.d.mts +75 -0
- package/dist/dsl/template.d.mts.map +1 -0
- package/dist/dsl/template.mjs +63 -0
- package/dist/dsl/template.mjs.map +1 -0
- package/dist/dsl/trigger.cjs +40 -0
- package/dist/dsl/trigger.cjs.map +1 -0
- package/dist/dsl/trigger.d.cts +65 -0
- package/dist/dsl/trigger.d.cts.map +1 -0
- package/dist/dsl/trigger.d.mts +65 -0
- package/dist/dsl/trigger.d.mts.map +1 -0
- package/dist/dsl/trigger.mjs +39 -0
- package/dist/dsl/trigger.mjs.map +1 -0
- package/dist/dsl/workflow.cjs +121 -0
- package/dist/dsl/workflow.cjs.map +1 -0
- package/dist/dsl/workflow.d.cts +59 -0
- package/dist/dsl/workflow.d.cts.map +1 -0
- package/dist/dsl/workflow.d.mts +59 -0
- package/dist/dsl/workflow.d.mts.map +1 -0
- package/dist/dsl/workflow.mjs +120 -0
- package/dist/dsl/workflow.mjs.map +1 -0
- package/dist/engine/actions.cjs +27 -0
- package/dist/engine/actions.cjs.map +1 -0
- package/dist/engine/actions.d.cts +40 -0
- package/dist/engine/actions.d.cts.map +1 -0
- package/dist/engine/actions.d.mts +40 -0
- package/dist/engine/actions.d.mts.map +1 -0
- package/dist/engine/actions.mjs +26 -0
- package/dist/engine/actions.mjs.map +1 -0
- package/dist/engine/bucket.cjs +72 -0
- package/dist/engine/bucket.cjs.map +1 -0
- package/dist/engine/bucket.d.cts +32 -0
- package/dist/engine/bucket.d.cts.map +1 -0
- package/dist/engine/bucket.d.mts +32 -0
- package/dist/engine/bucket.d.mts.map +1 -0
- package/dist/engine/bucket.mjs +69 -0
- package/dist/engine/bucket.mjs.map +1 -0
- package/dist/engine/condition.cjs +166 -0
- package/dist/engine/condition.cjs.map +1 -0
- package/dist/engine/condition.d.cts +52 -0
- package/dist/engine/condition.d.cts.map +1 -0
- package/dist/engine/condition.d.mts +52 -0
- package/dist/engine/condition.d.mts.map +1 -0
- package/dist/engine/condition.mjs +161 -0
- package/dist/engine/condition.mjs.map +1 -0
- package/dist/engine/index.cjs +20 -0
- package/dist/engine/index.d.cts +8 -0
- package/dist/engine/index.d.mts +8 -0
- package/dist/engine/index.mjs +7 -0
- package/dist/engine/interpreter.cjs +428 -0
- package/dist/engine/interpreter.cjs.map +1 -0
- package/dist/engine/interpreter.d.cts +6 -0
- package/dist/engine/interpreter.d.cts.map +1 -0
- package/dist/engine/interpreter.d.mts +6 -0
- package/dist/engine/interpreter.d.mts.map +1 -0
- package/dist/engine/interpreter.mjs +427 -0
- package/dist/engine/interpreter.mjs.map +1 -0
- package/dist/engine/ports.cjs +0 -0
- package/dist/engine/ports.d.cts +252 -0
- package/dist/engine/ports.d.cts.map +1 -0
- package/dist/engine/ports.d.mts +252 -0
- package/dist/engine/ports.d.mts.map +1 -0
- package/dist/engine/ports.mjs +1 -0
- package/dist/engine/recipient.cjs +23 -0
- package/dist/engine/recipient.cjs.map +1 -0
- package/dist/engine/recipient.d.cts +13 -0
- package/dist/engine/recipient.d.cts.map +1 -0
- package/dist/engine/recipient.d.mts +13 -0
- package/dist/engine/recipient.d.mts.map +1 -0
- package/dist/engine/recipient.mjs +22 -0
- package/dist/engine/recipient.mjs.map +1 -0
- package/dist/engine/time-scale.cjs +54 -0
- package/dist/engine/time-scale.cjs.map +1 -0
- package/dist/engine/time-scale.d.cts +31 -0
- package/dist/engine/time-scale.d.cts.map +1 -0
- package/dist/engine/time-scale.d.mts +31 -0
- package/dist/engine/time-scale.d.mts.map +1 -0
- package/dist/engine/time-scale.mjs +52 -0
- package/dist/engine/time-scale.mjs.map +1 -0
- package/dist/engine/time-window.cjs +149 -0
- package/dist/engine/time-window.cjs.map +1 -0
- package/dist/engine/time-window.d.cts +22 -0
- package/dist/engine/time-window.d.cts.map +1 -0
- package/dist/engine/time-window.d.mts +22 -0
- package/dist/engine/time-window.d.mts.map +1 -0
- package/dist/engine/time-window.mjs +147 -0
- package/dist/engine/time-window.mjs.map +1 -0
- package/dist/fcm.cjs +125 -0
- package/dist/fcm.cjs.map +1 -0
- package/dist/fcm.d.cts +48 -0
- package/dist/fcm.d.cts.map +1 -0
- package/dist/fcm.d.mts +48 -0
- package/dist/fcm.d.mts.map +1 -0
- package/dist/fcm.mjs +121 -0
- package/dist/fcm.mjs.map +1 -0
- package/dist/hash.cjs +245 -0
- package/dist/hash.cjs.map +1 -0
- package/dist/hash.d.cts +30 -0
- package/dist/hash.d.cts.map +1 -0
- package/dist/hash.d.mts +30 -0
- package/dist/hash.d.mts.map +1 -0
- package/dist/hash.mjs +239 -0
- package/dist/hash.mjs.map +1 -0
- package/dist/index.cjs +100 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.mjs +17 -0
- package/dist/ir.cjs +366 -0
- package/dist/ir.cjs.map +1 -0
- package/dist/ir.d.cts +486 -0
- package/dist/ir.d.cts.map +1 -0
- package/dist/ir.d.mts +486 -0
- package/dist/ir.d.mts.map +1 -0
- package/dist/ir.mjs +344 -0
- package/dist/ir.mjs.map +1 -0
- package/dist/jwt.cjs +22 -0
- package/dist/jwt.cjs.map +1 -0
- package/dist/jwt.d.cts +9 -0
- package/dist/jwt.d.cts.map +1 -0
- package/dist/jwt.d.mts +9 -0
- package/dist/jwt.d.mts.map +1 -0
- package/dist/jwt.mjs +20 -0
- package/dist/jwt.mjs.map +1 -0
- package/dist/plan.cjs +223 -0
- package/dist/plan.cjs.map +1 -0
- package/dist/plan.d.cts +68 -0
- package/dist/plan.d.cts.map +1 -0
- package/dist/plan.d.mts +68 -0
- package/dist/plan.d.mts.map +1 -0
- package/dist/plan.mjs +221 -0
- package/dist/plan.mjs.map +1 -0
- package/dist/postgres-js.cjs +42 -0
- package/dist/postgres-js.cjs.map +1 -0
- package/dist/postgres-js.d.cts +28 -0
- package/dist/postgres-js.d.cts.map +1 -0
- package/dist/postgres-js.d.mts +28 -0
- package/dist/postgres-js.d.mts.map +1 -0
- package/dist/postgres-js.mjs +38 -0
- package/dist/postgres-js.mjs.map +1 -0
- package/dist/provenance.cjs +67 -0
- package/dist/provenance.cjs.map +1 -0
- package/dist/provenance.d.cts +41 -0
- package/dist/provenance.d.cts.map +1 -0
- package/dist/provenance.d.mts +41 -0
- package/dist/provenance.d.mts.map +1 -0
- package/dist/provenance.mjs +65 -0
- package/dist/provenance.mjs.map +1 -0
- package/dist/push.cjs +14 -0
- package/dist/push.cjs.map +1 -0
- package/dist/push.d.cts +16 -0
- package/dist/push.d.cts.map +1 -0
- package/dist/push.d.mts +16 -0
- package/dist/push.d.mts.map +1 -0
- package/dist/push.mjs +13 -0
- package/dist/push.mjs.map +1 -0
- package/dist/react-email.cjs +48 -0
- package/dist/react-email.cjs.map +1 -0
- package/dist/react-email.d.cts +7 -0
- package/dist/react-email.d.cts.map +1 -0
- package/dist/react-email.d.mts +7 -0
- package/dist/react-email.d.mts.map +1 -0
- package/dist/react-email.mjs +47 -0
- package/dist/react-email.mjs.map +1 -0
- package/dist/reporting/classify.cjs +42 -0
- package/dist/reporting/classify.cjs.map +1 -0
- package/dist/reporting/classify.d.cts +60 -0
- package/dist/reporting/classify.d.cts.map +1 -0
- package/dist/reporting/classify.d.mts +60 -0
- package/dist/reporting/classify.d.mts.map +1 -0
- package/dist/reporting/classify.mjs +38 -0
- package/dist/reporting/classify.mjs.map +1 -0
- package/dist/reporting/observer.cjs +63 -0
- package/dist/reporting/observer.cjs.map +1 -0
- package/dist/reporting/observer.d.cts +18 -0
- package/dist/reporting/observer.d.cts.map +1 -0
- package/dist/reporting/observer.d.mts +18 -0
- package/dist/reporting/observer.d.mts.map +1 -0
- package/dist/reporting/observer.mjs +62 -0
- package/dist/reporting/observer.mjs.map +1 -0
- package/dist/reporting/tracking.cjs +153 -0
- package/dist/reporting/tracking.cjs.map +1 -0
- package/dist/reporting/tracking.d.cts +43 -0
- package/dist/reporting/tracking.d.cts.map +1 -0
- package/dist/reporting/tracking.d.mts +43 -0
- package/dist/reporting/tracking.d.mts.map +1 -0
- package/dist/reporting/tracking.mjs +151 -0
- package/dist/reporting/tracking.mjs.map +1 -0
- package/dist/runtime.cjs +19 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.d.cts +30 -0
- package/dist/runtime.d.cts.map +1 -0
- package/dist/runtime.d.mts +30 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +18 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/store/d1.cjs +185 -0
- package/dist/store/d1.cjs.map +1 -0
- package/dist/store/d1.d.cts +55 -0
- package/dist/store/d1.d.cts.map +1 -0
- package/dist/store/d1.d.mts +55 -0
- package/dist/store/d1.d.mts.map +1 -0
- package/dist/store/d1.mjs +184 -0
- package/dist/store/d1.mjs.map +1 -0
- package/dist/store/facts.cjs +60 -0
- package/dist/store/facts.cjs.map +1 -0
- package/dist/store/facts.d.cts +38 -0
- package/dist/store/facts.d.cts.map +1 -0
- package/dist/store/facts.d.mts +38 -0
- package/dist/store/facts.d.mts.map +1 -0
- package/dist/store/facts.mjs +58 -0
- package/dist/store/facts.mjs.map +1 -0
- package/dist/store/index.cjs +31 -0
- package/dist/store/index.cjs.map +1 -0
- package/dist/store/index.d.cts +291 -0
- package/dist/store/index.d.cts.map +1 -0
- package/dist/store/index.d.mts +291 -0
- package/dist/store/index.d.mts.map +1 -0
- package/dist/store/index.mjs +22 -0
- package/dist/store/index.mjs.map +1 -0
- package/dist/store/postgres-reporting.cjs +213 -0
- package/dist/store/postgres-reporting.cjs.map +1 -0
- package/dist/store/postgres-reporting.d.cts +63 -0
- package/dist/store/postgres-reporting.d.cts.map +1 -0
- package/dist/store/postgres-reporting.d.mts +63 -0
- package/dist/store/postgres-reporting.d.mts.map +1 -0
- package/dist/store/postgres-reporting.mjs +212 -0
- package/dist/store/postgres-reporting.mjs.map +1 -0
- package/dist/store/postgres.cjs +309 -0
- package/dist/store/postgres.cjs.map +1 -0
- package/dist/store/postgres.d.cts +77 -0
- package/dist/store/postgres.d.cts.map +1 -0
- package/dist/store/postgres.d.mts +77 -0
- package/dist/store/postgres.d.mts.map +1 -0
- package/dist/store/postgres.mjs +306 -0
- package/dist/store/postgres.mjs.map +1 -0
- package/dist/store/sql.cjs +24 -0
- package/dist/store/sql.cjs.map +1 -0
- package/dist/store/sql.d.cts +25 -0
- package/dist/store/sql.d.cts.map +1 -0
- package/dist/store/sql.d.mts +25 -0
- package/dist/store/sql.d.mts.map +1 -0
- package/dist/store/sql.mjs +23 -0
- package/dist/store/sql.mjs.map +1 -0
- package/dist/store/tables.cjs +404 -0
- package/dist/store/tables.cjs.map +1 -0
- package/dist/store/tables.d.cts +81 -0
- package/dist/store/tables.d.cts.map +1 -0
- package/dist/store/tables.d.mts +81 -0
- package/dist/store/tables.d.mts.map +1 -0
- package/dist/store/tables.mjs +399 -0
- package/dist/store/tables.mjs.map +1 -0
- package/dist/templates.cjs +247 -0
- package/dist/templates.cjs.map +1 -0
- package/dist/templates.d.cts +187 -0
- package/dist/templates.d.cts.map +1 -0
- package/dist/templates.d.mts +187 -0
- package/dist/templates.d.mts.map +1 -0
- package/dist/templates.mjs +230 -0
- package/dist/templates.mjs.map +1 -0
- package/dist/webhooks.cjs +96 -0
- package/dist/webhooks.cjs.map +1 -0
- package/dist/webhooks.d.cts +25 -0
- package/dist/webhooks.d.cts.map +1 -0
- package/dist/webhooks.d.mts +25 -0
- package/dist/webhooks.d.mts.map +1 -0
- package/dist/webhooks.mjs +94 -0
- package/dist/webhooks.mjs.map +1 -0
- package/dist/webpush.cjs +180 -0
- package/dist/webpush.cjs.map +1 -0
- package/dist/webpush.d.cts +53 -0
- package/dist/webpush.d.cts.map +1 -0
- package/dist/webpush.d.mts +53 -0
- package/dist/webpush.d.mts.map +1 -0
- package/dist/webpush.mjs +175 -0
- package/dist/webpush.mjs.map +1 -0
- package/package.json +189 -3
- package/src/cloudflare/schema.sql +127 -0
- package/src/cloudflare/workers.d.ts +44 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.mjs","names":["WorkflowIRSchema"],"sources":["../../src/dsl/workflow.ts"],"sourcesContent":["import { semanticHash } from '../hash';\nimport {\n type GoalIR,\n IR_VERSION,\n type MetaIR,\n type NodeIR,\n type SourceLocIR,\n type WorkflowIR,\n WorkflowIR as WorkflowIRSchema,\n} from '../ir';\nimport { captureLoc } from '../provenance';\nimport { type Duration, durationIR } from './base';\nimport { type Condition, condIR } from './condition';\nimport { type FlowBuilder, FlowBuilderImpl } from './flow';\nimport type { TriggerInternal, TriggerRef } from './trigger';\n\n/**\n * The workflow — a named flow chain plus its configuration (options =\n * configuration, the chain = steps), and the compiler back half: `toIR()`\n * deep-clones the collected nodes, assigns structural-path ids, computes the\n * contentHash (metadata stripped, see hash.ts) and self-checks the result\n * against the authoritative IR schema.\n */\n\n/** Full conversion-goal configuration (customer.io's Goal & Exit semantics). */\nexport interface GoalOptions {\n condition: Condition;\n /** Attribution window: how long after entry a match still counts as a conversion. Unbounded by default. */\n within?: Duration;\n /** Exit on conversion. Defaults to true — this is what absorbs all those True→Exit arms in a UI canvas. */\n exitOnMatch?: boolean;\n}\n\nexport interface WorkflowDefinition {\n /**\n * Wire identity — engine instances, the entry ledger and stats keys are all\n * built on it (`WorkflowIR.id`), so it is not something to rename for\n * readability; `name` is.\n */\n id: string;\n\n /** Trigger: a trigger.xxx() asset. TODO: multiple triggers, entry frequency / re-entry policy. */\n trigger: TriggerRef;\n\n /**\n * Conversion goal: feeds reporting (conversion rate / attribution) and by\n * default exits on match. Checked before each node runs. Pass a condition\n * for the shorthand, or GoalOptions for the full form.\n */\n goal?: Condition | GoalOptions;\n\n /** Plain exit condition: leaving without counting as a conversion (unsubscribed, no longer eligible). Coexists with goal. */\n exitWhen?: Condition;\n\n /*\n * Everything below is human-facing metadata: it lands in IR's `meta` field\n * and is **excluded from contentHash**. Editing it neither invalidates the\n * version in-flight users are pinned to nor shows up as a change in plan.\n */\n\n /** Human label: what the studio shows instead of the id. */\n name?: string;\n\n /** One line on what this flow does (UI lists, plan output). */\n description?: string;\n}\n\nexport interface WorkflowBuilder extends FlowBuilder {\n toIR(): WorkflowIR;\n}\n\n/**\n * Assign node ids from the structural path (rules in ir.ts's header). Written\n * in place into the tree toIR deep-cloned, never into the builder's own nodes.\n */\nfunction assignIds(nodes: NodeIR[], prefix: string): void {\n nodes.forEach((node, i) => {\n const id = prefix === '' ? String(i) : `${prefix}.${i}`;\n node.id = id;\n switch (node.type) {\n case 'branch':\n node.cases.forEach((c, ci) => assignIds(c.flow, `${id}.c${ci}`));\n if (node.otherwise) assignIds(node.otherwise, `${id}.o`);\n break;\n case 'cohort':\n node.arms.forEach((arm) => assignIds(arm.flow, `${id}.${arm.name}`));\n break;\n case 'wait_until':\n if (Array.isArray(node.onTimeout)) assignIds(node.onTimeout, `${id}.t`);\n break;\n default:\n break;\n }\n });\n}\n\n/**\n * Ids are derived, never authored, so a duplicate is always a compiler bug —\n * and a silent one: assignIds writes in place, so the second write simply wins\n * and two nodes ship as one. What that costs is concrete, because an id is the\n * durable step name (`step.do('{id}:send')`), the message idempotency key\n * (`{instanceId}:{id}`) and the cohort bucket seed: the twins share a\n * checkpoint, and the second send is suppressed as a duplicate of the first.\n * Cheap to check once at the end of the compile, so it is checked.\n */\nfunction assertUniqueIds(nodes: readonly NodeIR[], seen = new Set<string>()): void {\n for (const node of nodes) {\n if (seen.has(node.id)) {\n throw new Error(\n `toIR(): node id '${node.id}' is used twice — ids are durable step names and message idempotency keys, so they must be unique`\n );\n }\n seen.add(node.id);\n switch (node.type) {\n case 'branch':\n for (const branchCase of node.cases) assertUniqueIds(branchCase.flow, seen);\n if (node.otherwise) assertUniqueIds(node.otherwise, seen);\n break;\n case 'cohort':\n for (const arm of node.arms) assertUniqueIds(arm.flow, seen);\n break;\n case 'wait_until':\n if (Array.isArray(node.onTimeout)) assertUniqueIds(node.onTimeout, seen);\n break;\n default:\n break;\n }\n }\n}\n\nclass WorkflowBuilderImpl extends FlowBuilderImpl implements WorkflowBuilder {\n constructor(\n private readonly options: WorkflowDefinition,\n /** Callsite of the workflow() call (provenance; lands in meta.loc). */\n private readonly loc: SourceLocIR | undefined\n ) {\n super();\n }\n\n private goalIR(): GoalIR | undefined {\n const goal = this.options.goal;\n if (goal === undefined) return undefined;\n if ('__condition' in goal) {\n return { condition: condIR(goal), exitOnMatch: true };\n }\n return {\n condition: condIR(goal.condition),\n ...(goal.within !== undefined ? { within: durationIR(goal.within) } : {}),\n exitOnMatch: goal.exitOnMatch ?? true,\n };\n }\n\n private metaIR(): MetaIR | undefined {\n const { name, description } = this.options;\n if (name === undefined && description === undefined && this.loc === undefined) {\n return undefined;\n }\n return {\n ...(name !== undefined ? { name } : {}),\n ...(description !== undefined ? { description } : {}),\n ...(this.loc !== undefined ? { loc: this.loc } : {}),\n };\n }\n\n toIR(): WorkflowIR {\n const flowNodes = structuredClone(this.nodes);\n assignIds(flowNodes, '');\n assertUniqueIds(flowNodes);\n const goal = this.goalIR();\n const meta = this.metaIR();\n const body = {\n irVersion: IR_VERSION,\n id: this.options.id,\n ...(meta !== undefined ? { meta } : {}),\n trigger: (this.options.trigger as TriggerInternal).ir,\n ...(goal !== undefined ? { goal } : {}),\n ...(this.options.exitWhen !== undefined ? { exitWhen: condIR(this.options.exitWhen) } : {}),\n flow: flowNodes,\n };\n // semanticHash strips meta / label / reason, so metadata edits leave contentHash alone\n // Self-check: the compiler's output must pass IR's authoritative schema\n return WorkflowIRSchema.parse({ ...body, contentHash: semanticHash(body) });\n }\n}\n\n/**\n * Workflow definition: an id, configuration (trigger / goal / exitWhen),\n * optional labels, and chained steps.\n *\n * export const onboarding = workflow({\n * id: 'onboarding',\n * name: 'Onboarding',\n * trigger: trigger.event(e.sign_up),\n * }).email(t.email.welcome);\n */\nexport function workflow(definition: WorkflowDefinition): WorkflowBuilder {\n return new WorkflowBuilderImpl(definition, captureLoc(workflow));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/engine/actions.ts
|
|
3
|
+
var RegistryActionInvoker = class {
|
|
4
|
+
policy;
|
|
5
|
+
registry;
|
|
6
|
+
constructor(actions, policy = "warn") {
|
|
7
|
+
this.policy = policy;
|
|
8
|
+
this.registry = new Map(actions.map((a) => [a.name, a]));
|
|
9
|
+
}
|
|
10
|
+
async invoke(invocation) {
|
|
11
|
+
const registered = this.registry.get(invocation.action);
|
|
12
|
+
if (registered === void 0) throw new Error(`action '${invocation.action}' is not registered in this runtime — was it removed from the deployed bundle while workflows still reference it?`);
|
|
13
|
+
if (this.policy !== "rolling" && invocation.codeHash !== void 0 && invocation.codeHash !== registered.codeHash) {
|
|
14
|
+
const skew = `action '${invocation.action}': deployed code (${registered.codeHash}) differs from the version pinned in this journey's IR (${invocation.codeHash})`;
|
|
15
|
+
if (this.policy === "strict") throw new Error(skew);
|
|
16
|
+
console.warn(`${skew} — running the deployed version`);
|
|
17
|
+
}
|
|
18
|
+
await registered.handler(invocation.args, {
|
|
19
|
+
userId: invocation.userId,
|
|
20
|
+
idempotencyKey: invocation.idempotencyKey
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
exports.RegistryActionInvoker = RegistryActionInvoker;
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=actions.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.cjs","names":[],"sources":["../../src/engine/actions.ts"],"sourcesContent":["import type { ScalarIR } from '../ir';\nimport type { ActionInvocation, ActionInvoker } from './ports';\n\n/**\n * The default ActionInvoker: a name → handler registry (the runtime half of\n * the dual-plane model — IR carries identity, the deployed bundle carries\n * code). Adapter-agnostic: the Cloudflare runner uses it and an AWS adapter\n * would too.\n */\n\n/**\n * What the registry stores — structurally satisfied by the DSL's ActionRef,\n * so the same `action(...)` objects the workflows reference are registered\n * directly, exactly like createMessageSender overrides plug in senders.\n */\nexport interface RegisteredAction {\n readonly name: string;\n readonly codeHash: string;\n /** Method syntax on purpose: accepts ActionRef handlers with narrower arg types. */\n handler(\n args: Record<string, ScalarIR | undefined>,\n ctx: { userId: string; idempotencyKey: string }\n ): Promise<void> | void;\n}\n\n/**\n * Version-consistency tiers for the codeHash pinned in IR vs the code actually\n * registered. Deploys are rolling — Worker code and IR never flip in the same\n * instant, and different build pipelines can print the same function\n * differently — so 'warn' is the default: run the current code, log the skew.\n * 'strict' is for actions where running the wrong version is worse than\n * failing the step; 'rolling' silences the log entirely.\n */\nexport type ActionVersionPolicy = 'rolling' | 'warn' | 'strict';\n\nexport class RegistryActionInvoker implements ActionInvoker {\n private readonly registry: ReadonlyMap<string, RegisteredAction>;\n\n constructor(\n actions: readonly RegisteredAction[],\n private readonly policy: ActionVersionPolicy = 'warn'\n ) {\n this.registry = new Map(actions.map((a) => [a.name, a]));\n }\n\n async invoke(invocation: ActionInvocation): Promise<void> {\n const registered = this.registry.get(invocation.action);\n if (registered === undefined) {\n throw new Error(\n `action '${invocation.action}' is not registered in this runtime — was it removed from the deployed bundle while workflows still reference it?`\n );\n }\n if (\n this.policy !== 'rolling' &&\n invocation.codeHash !== undefined &&\n invocation.codeHash !== registered.codeHash\n ) {\n const skew = `action '${invocation.action}': deployed code (${registered.codeHash}) differs from the version pinned in this journey's IR (${invocation.codeHash})`;\n if (this.policy === 'strict') throw new Error(skew);\n console.warn(`${skew} — running the deployed version`);\n }\n // The key is forwarded, not consumed here: this invoker has no state to\n // de-duplicate against, and the step body it runs inside is replayed — the\n // handler is the only place that can collapse the duplicate side effect.\n await registered.handler(invocation.args, {\n userId: invocation.userId,\n idempotencyKey: invocation.idempotencyKey,\n });\n }\n}\n"],"mappings":";;AAmCA,IAAa,wBAAb,MAA4D;CAKvC;CAJnB;CAEA,YACE,SACA,SAA+C,QAC/C;EADiB,KAAA,SAAA;EAEjB,KAAK,WAAW,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CACzD;CAEA,MAAM,OAAO,YAA6C;EACxD,MAAM,aAAa,KAAK,SAAS,IAAI,WAAW,MAAM;EACtD,IAAI,eAAe,KAAA,GACjB,MAAM,IAAI,MACR,WAAW,WAAW,OAAO,kHAC/B;EAEF,IACE,KAAK,WAAW,aAChB,WAAW,aAAa,KAAA,KACxB,WAAW,aAAa,WAAW,UACnC;GACA,MAAM,OAAO,WAAW,WAAW,OAAO,oBAAoB,WAAW,SAAS,0DAA0D,WAAW,SAAS;GAChK,IAAI,KAAK,WAAW,UAAU,MAAM,IAAI,MAAM,IAAI;GAClD,QAAQ,KAAK,GAAG,KAAK,gCAAgC;EACvD;EAIA,MAAM,WAAW,QAAQ,WAAW,MAAM;GACxC,QAAQ,WAAW;GACnB,gBAAgB,WAAW;EAC7B,CAAC;CACH;AACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ScalarIR } from "../ir.cjs";
|
|
2
|
+
import { ActionInvocation, ActionInvoker } from "./ports.cjs";
|
|
3
|
+
//#region src/engine/actions.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The default ActionInvoker: a name → handler registry (the runtime half of
|
|
6
|
+
* the dual-plane model — IR carries identity, the deployed bundle carries
|
|
7
|
+
* code). Adapter-agnostic: the Cloudflare runner uses it and an AWS adapter
|
|
8
|
+
* would too.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* What the registry stores — structurally satisfied by the DSL's ActionRef,
|
|
12
|
+
* so the same `action(...)` objects the workflows reference are registered
|
|
13
|
+
* directly, exactly like createMessageSender overrides plug in senders.
|
|
14
|
+
*/
|
|
15
|
+
export interface RegisteredAction {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly codeHash: string;
|
|
18
|
+
/** Method syntax on purpose: accepts ActionRef handlers with narrower arg types. */
|
|
19
|
+
handler(args: Record<string, ScalarIR | undefined>, ctx: {
|
|
20
|
+
userId: string;
|
|
21
|
+
idempotencyKey: string;
|
|
22
|
+
}): Promise<void> | void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Version-consistency tiers for the codeHash pinned in IR vs the code actually
|
|
26
|
+
* registered. Deploys are rolling — Worker code and IR never flip in the same
|
|
27
|
+
* instant, and different build pipelines can print the same function
|
|
28
|
+
* differently — so 'warn' is the default: run the current code, log the skew.
|
|
29
|
+
* 'strict' is for actions where running the wrong version is worse than
|
|
30
|
+
* failing the step; 'rolling' silences the log entirely.
|
|
31
|
+
*/
|
|
32
|
+
export type ActionVersionPolicy = 'rolling' | 'warn' | 'strict';
|
|
33
|
+
export declare class RegistryActionInvoker implements ActionInvoker {
|
|
34
|
+
private readonly policy;
|
|
35
|
+
private readonly registry;
|
|
36
|
+
constructor(actions: readonly RegisteredAction[], policy?: ActionVersionPolicy);
|
|
37
|
+
invoke(invocation: ActionInvocation): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//# sourceMappingURL=actions.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.cts","names":[],"sources":["../../src/engine/actions.ts"],"mappings":";;;;;;;;;;;;;;iBAeiB;WACN;WACA;;EAET,QACE,MAAM,eAAe,uBACrB;IAAO;IAAgB;MACtB;;;;;;;;;;YAWO;qBAEC,iCAAiC;mBAKzB;mBAJF;EAEjB,YACE,kBAAkB,oBACD,SAAQ;EAKrB,OAAO,YAAY,mBAAmB"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ScalarIR } from "../ir.mjs";
|
|
2
|
+
import { ActionInvocation, ActionInvoker } from "./ports.mjs";
|
|
3
|
+
//#region src/engine/actions.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The default ActionInvoker: a name → handler registry (the runtime half of
|
|
6
|
+
* the dual-plane model — IR carries identity, the deployed bundle carries
|
|
7
|
+
* code). Adapter-agnostic: the Cloudflare runner uses it and an AWS adapter
|
|
8
|
+
* would too.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* What the registry stores — structurally satisfied by the DSL's ActionRef,
|
|
12
|
+
* so the same `action(...)` objects the workflows reference are registered
|
|
13
|
+
* directly, exactly like createMessageSender overrides plug in senders.
|
|
14
|
+
*/
|
|
15
|
+
export interface RegisteredAction {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly codeHash: string;
|
|
18
|
+
/** Method syntax on purpose: accepts ActionRef handlers with narrower arg types. */
|
|
19
|
+
handler(args: Record<string, ScalarIR | undefined>, ctx: {
|
|
20
|
+
userId: string;
|
|
21
|
+
idempotencyKey: string;
|
|
22
|
+
}): Promise<void> | void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Version-consistency tiers for the codeHash pinned in IR vs the code actually
|
|
26
|
+
* registered. Deploys are rolling — Worker code and IR never flip in the same
|
|
27
|
+
* instant, and different build pipelines can print the same function
|
|
28
|
+
* differently — so 'warn' is the default: run the current code, log the skew.
|
|
29
|
+
* 'strict' is for actions where running the wrong version is worse than
|
|
30
|
+
* failing the step; 'rolling' silences the log entirely.
|
|
31
|
+
*/
|
|
32
|
+
export type ActionVersionPolicy = 'rolling' | 'warn' | 'strict';
|
|
33
|
+
export declare class RegistryActionInvoker implements ActionInvoker {
|
|
34
|
+
private readonly policy;
|
|
35
|
+
private readonly registry;
|
|
36
|
+
constructor(actions: readonly RegisteredAction[], policy?: ActionVersionPolicy);
|
|
37
|
+
invoke(invocation: ActionInvocation): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//# sourceMappingURL=actions.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.mts","names":[],"sources":["../../src/engine/actions.ts"],"mappings":";;;;;;;;;;;;;;iBAeiB;WACN;WACA;;EAET,QACE,MAAM,eAAe,uBACrB;IAAO;IAAgB;MACtB;;;;;;;;;;YAWO;qBAEC,iCAAiC;mBAKzB;mBAJF;EAEjB,YACE,kBAAkB,oBACD,SAAQ;EAKrB,OAAO,YAAY,mBAAmB"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/engine/actions.ts
|
|
2
|
+
var RegistryActionInvoker = class {
|
|
3
|
+
policy;
|
|
4
|
+
registry;
|
|
5
|
+
constructor(actions, policy = "warn") {
|
|
6
|
+
this.policy = policy;
|
|
7
|
+
this.registry = new Map(actions.map((a) => [a.name, a]));
|
|
8
|
+
}
|
|
9
|
+
async invoke(invocation) {
|
|
10
|
+
const registered = this.registry.get(invocation.action);
|
|
11
|
+
if (registered === void 0) throw new Error(`action '${invocation.action}' is not registered in this runtime — was it removed from the deployed bundle while workflows still reference it?`);
|
|
12
|
+
if (this.policy !== "rolling" && invocation.codeHash !== void 0 && invocation.codeHash !== registered.codeHash) {
|
|
13
|
+
const skew = `action '${invocation.action}': deployed code (${registered.codeHash}) differs from the version pinned in this journey's IR (${invocation.codeHash})`;
|
|
14
|
+
if (this.policy === "strict") throw new Error(skew);
|
|
15
|
+
console.warn(`${skew} — running the deployed version`);
|
|
16
|
+
}
|
|
17
|
+
await registered.handler(invocation.args, {
|
|
18
|
+
userId: invocation.userId,
|
|
19
|
+
idempotencyKey: invocation.idempotencyKey
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { RegistryActionInvoker };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=actions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.mjs","names":[],"sources":["../../src/engine/actions.ts"],"sourcesContent":["import type { ScalarIR } from '../ir';\nimport type { ActionInvocation, ActionInvoker } from './ports';\n\n/**\n * The default ActionInvoker: a name → handler registry (the runtime half of\n * the dual-plane model — IR carries identity, the deployed bundle carries\n * code). Adapter-agnostic: the Cloudflare runner uses it and an AWS adapter\n * would too.\n */\n\n/**\n * What the registry stores — structurally satisfied by the DSL's ActionRef,\n * so the same `action(...)` objects the workflows reference are registered\n * directly, exactly like createMessageSender overrides plug in senders.\n */\nexport interface RegisteredAction {\n readonly name: string;\n readonly codeHash: string;\n /** Method syntax on purpose: accepts ActionRef handlers with narrower arg types. */\n handler(\n args: Record<string, ScalarIR | undefined>,\n ctx: { userId: string; idempotencyKey: string }\n ): Promise<void> | void;\n}\n\n/**\n * Version-consistency tiers for the codeHash pinned in IR vs the code actually\n * registered. Deploys are rolling — Worker code and IR never flip in the same\n * instant, and different build pipelines can print the same function\n * differently — so 'warn' is the default: run the current code, log the skew.\n * 'strict' is for actions where running the wrong version is worse than\n * failing the step; 'rolling' silences the log entirely.\n */\nexport type ActionVersionPolicy = 'rolling' | 'warn' | 'strict';\n\nexport class RegistryActionInvoker implements ActionInvoker {\n private readonly registry: ReadonlyMap<string, RegisteredAction>;\n\n constructor(\n actions: readonly RegisteredAction[],\n private readonly policy: ActionVersionPolicy = 'warn'\n ) {\n this.registry = new Map(actions.map((a) => [a.name, a]));\n }\n\n async invoke(invocation: ActionInvocation): Promise<void> {\n const registered = this.registry.get(invocation.action);\n if (registered === undefined) {\n throw new Error(\n `action '${invocation.action}' is not registered in this runtime — was it removed from the deployed bundle while workflows still reference it?`\n );\n }\n if (\n this.policy !== 'rolling' &&\n invocation.codeHash !== undefined &&\n invocation.codeHash !== registered.codeHash\n ) {\n const skew = `action '${invocation.action}': deployed code (${registered.codeHash}) differs from the version pinned in this journey's IR (${invocation.codeHash})`;\n if (this.policy === 'strict') throw new Error(skew);\n console.warn(`${skew} — running the deployed version`);\n }\n // The key is forwarded, not consumed here: this invoker has no state to\n // de-duplicate against, and the step body it runs inside is replayed — the\n // handler is the only place that can collapse the duplicate side effect.\n await registered.handler(invocation.args, {\n userId: invocation.userId,\n idempotencyKey: invocation.idempotencyKey,\n });\n }\n}\n"],"mappings":";AAmCA,IAAa,wBAAb,MAA4D;CAKvC;CAJnB;CAEA,YACE,SACA,SAA+C,QAC/C;EADiB,KAAA,SAAA;EAEjB,KAAK,WAAW,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CACzD;CAEA,MAAM,OAAO,YAA6C;EACxD,MAAM,aAAa,KAAK,SAAS,IAAI,WAAW,MAAM;EACtD,IAAI,eAAe,KAAA,GACjB,MAAM,IAAI,MACR,WAAW,WAAW,OAAO,kHAC/B;EAEF,IACE,KAAK,WAAW,aAChB,WAAW,aAAa,KAAA,KACxB,WAAW,aAAa,WAAW,UACnC;GACA,MAAM,OAAO,WAAW,WAAW,OAAO,oBAAoB,WAAW,SAAS,0DAA0D,WAAW,SAAS;GAChK,IAAI,KAAK,WAAW,UAAU,MAAM,IAAI,MAAM,IAAI;GAClD,QAAQ,KAAK,GAAG,KAAK,gCAAgC;EACvD;EAIA,MAAM,WAAW,QAAQ,WAAW,MAAM;GACxC,QAAQ,WAAW;GACnB,gBAAgB,WAAW;EAC7B,CAAC;CACH;AACF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/engine/bucket.ts
|
|
3
|
+
/**
|
|
4
|
+
* Cohort bucketing — deterministic assignment of a user to an A/B arm.
|
|
5
|
+
*
|
|
6
|
+
* MurmurHash3 (x86, 32-bit, seed 0) over the UTF-8 bytes of the key, the same
|
|
7
|
+
* construction A/B systems like Unleash use: excellent avalanche behaviour
|
|
8
|
+
* (sequential user ids spread evenly, where FNV-1a showed low-bit bias) and a
|
|
9
|
+
* frozen, widely cross-checked algorithm. Hand-written on purpose: the engine
|
|
10
|
+
* core stays dependency-free, and the known-answer tests pin this
|
|
11
|
+
* implementation against the reference vectors — a stronger guarantee than
|
|
12
|
+
* trusting an npm package's variant.
|
|
13
|
+
*
|
|
14
|
+
* The bucket function is a permanent contract: changing it reshuffles every
|
|
15
|
+
* in-flight cohort, so it must never change once production traffic exists.
|
|
16
|
+
*/
|
|
17
|
+
/** MurmurHash3 x86 32-bit over raw bytes; seed 0. Returns an unsigned 32-bit integer. */
|
|
18
|
+
function murmur3(bytes, seed = 0) {
|
|
19
|
+
const c1 = 3432918353;
|
|
20
|
+
const c2 = 461845907;
|
|
21
|
+
let h = seed >>> 0;
|
|
22
|
+
const tail = bytes.length & -4;
|
|
23
|
+
for (let i = 0; i < tail; i += 4) {
|
|
24
|
+
let k = bytes[i] | bytes[i + 1] << 8 | bytes[i + 2] << 16 | bytes[i + 3] << 24;
|
|
25
|
+
k = Math.imul(k, c1);
|
|
26
|
+
k = k << 15 | k >>> 17;
|
|
27
|
+
k = Math.imul(k, c2);
|
|
28
|
+
h ^= k;
|
|
29
|
+
h = h << 13 | h >>> 19;
|
|
30
|
+
h = Math.imul(h, 5) + 3864292196 >>> 0;
|
|
31
|
+
}
|
|
32
|
+
let k = 0;
|
|
33
|
+
const remainder = bytes.length & 3;
|
|
34
|
+
if (remainder === 3) k ^= bytes[tail + 2] << 16;
|
|
35
|
+
if (remainder >= 2) k ^= bytes[tail + 1] << 8;
|
|
36
|
+
if (remainder >= 1) {
|
|
37
|
+
k ^= bytes[tail];
|
|
38
|
+
k = Math.imul(k, c1);
|
|
39
|
+
k = k << 15 | k >>> 17;
|
|
40
|
+
k = Math.imul(k, c2);
|
|
41
|
+
h ^= k;
|
|
42
|
+
}
|
|
43
|
+
h ^= bytes.length;
|
|
44
|
+
h ^= h >>> 16;
|
|
45
|
+
h = Math.imul(h, 2246822507);
|
|
46
|
+
h ^= h >>> 13;
|
|
47
|
+
h = Math.imul(h, 3266489909);
|
|
48
|
+
h ^= h >>> 16;
|
|
49
|
+
return h >>> 0;
|
|
50
|
+
}
|
|
51
|
+
const utf8 = new TextEncoder();
|
|
52
|
+
/**
|
|
53
|
+
* Bucket resolution: basis points, i.e. hundredths of a percent. Weights are
|
|
54
|
+
* compared on the same integer grid (weight% × 100), so fractional splits like
|
|
55
|
+
* 33.3/33.3/33.4 are honoured exactly and the whole path stays float-free —
|
|
56
|
+
* the Optimizely/Statsig construction.
|
|
57
|
+
*/
|
|
58
|
+
const BUCKET_RESOLUTION = 1e4;
|
|
59
|
+
/**
|
|
60
|
+
* Map a bucketing key (`${userId}:${cohortKey}`) to [0, 10000). Keys hash
|
|
61
|
+
* their UTF-8 bytes, so results agree with any standard Murmur3 implementation
|
|
62
|
+
* regardless of the user id's alphabet.
|
|
63
|
+
*/
|
|
64
|
+
function hashToBucket(input) {
|
|
65
|
+
return murmur3(utf8.encode(input)) % BUCKET_RESOLUTION;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
exports.BUCKET_RESOLUTION = BUCKET_RESOLUTION;
|
|
69
|
+
exports.hashToBucket = hashToBucket;
|
|
70
|
+
exports.murmur3 = murmur3;
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=bucket.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket.cjs","names":[],"sources":["../../src/engine/bucket.ts"],"sourcesContent":["/**\n * Cohort bucketing — deterministic assignment of a user to an A/B arm.\n *\n * MurmurHash3 (x86, 32-bit, seed 0) over the UTF-8 bytes of the key, the same\n * construction A/B systems like Unleash use: excellent avalanche behaviour\n * (sequential user ids spread evenly, where FNV-1a showed low-bit bias) and a\n * frozen, widely cross-checked algorithm. Hand-written on purpose: the engine\n * core stays dependency-free, and the known-answer tests pin this\n * implementation against the reference vectors — a stronger guarantee than\n * trusting an npm package's variant.\n *\n * The bucket function is a permanent contract: changing it reshuffles every\n * in-flight cohort, so it must never change once production traffic exists.\n */\n\n/** MurmurHash3 x86 32-bit over raw bytes; seed 0. Returns an unsigned 32-bit integer. */\nexport function murmur3(bytes: Uint8Array, seed = 0): number {\n const c1 = 0xcc9e2d51;\n const c2 = 0x1b873593;\n let h = seed >>> 0;\n\n const tail = bytes.length & ~3;\n for (let i = 0; i < tail; i += 4) {\n let k = bytes[i] | (bytes[i + 1] << 8) | (bytes[i + 2] << 16) | (bytes[i + 3] << 24);\n k = Math.imul(k, c1);\n k = (k << 15) | (k >>> 17);\n k = Math.imul(k, c2);\n h ^= k;\n h = (h << 13) | (h >>> 19);\n h = (Math.imul(h, 5) + 0xe6546b64) >>> 0;\n }\n\n let k = 0;\n const remainder = bytes.length & 3;\n if (remainder === 3) k ^= bytes[tail + 2] << 16;\n if (remainder >= 2) k ^= bytes[tail + 1] << 8;\n if (remainder >= 1) {\n k ^= bytes[tail];\n k = Math.imul(k, c1);\n k = (k << 15) | (k >>> 17);\n k = Math.imul(k, c2);\n h ^= k;\n }\n\n h ^= bytes.length;\n h ^= h >>> 16;\n h = Math.imul(h, 0x85ebca6b);\n h ^= h >>> 13;\n h = Math.imul(h, 0xc2b2ae35);\n h ^= h >>> 16;\n return h >>> 0;\n}\n\nconst utf8 = new TextEncoder();\n\n/**\n * Bucket resolution: basis points, i.e. hundredths of a percent. Weights are\n * compared on the same integer grid (weight% × 100), so fractional splits like\n * 33.3/33.3/33.4 are honoured exactly and the whole path stays float-free —\n * the Optimizely/Statsig construction.\n */\nexport const BUCKET_RESOLUTION = 10_000;\n\n/**\n * Map a bucketing key (`${userId}:${cohortKey}`) to [0, 10000). Keys hash\n * their UTF-8 bytes, so results agree with any standard Murmur3 implementation\n * regardless of the user id's alphabet.\n */\nexport function hashToBucket(input: string): number {\n return murmur3(utf8.encode(input)) % BUCKET_RESOLUTION;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,SAAgB,QAAQ,OAAmB,OAAO,GAAW;CAC3D,MAAM,KAAK;CACX,MAAM,KAAK;CACX,IAAI,IAAI,SAAS;CAEjB,MAAM,OAAO,MAAM,SAAS;CAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,KAAK,GAAG;EAChC,IAAI,IAAI,MAAM,KAAM,MAAM,IAAI,MAAM,IAAM,MAAM,IAAI,MAAM,KAAO,MAAM,IAAI,MAAM;EACjF,IAAI,KAAK,KAAK,GAAG,EAAE;EACnB,IAAK,KAAK,KAAO,MAAM;EACvB,IAAI,KAAK,KAAK,GAAG,EAAE;EACnB,KAAK;EACL,IAAK,KAAK,KAAO,MAAM;EACvB,IAAK,KAAK,KAAK,GAAG,CAAC,IAAI,eAAgB;CACzC;CAEA,IAAI,IAAI;CACR,MAAM,YAAY,MAAM,SAAS;CACjC,IAAI,cAAc,GAAG,KAAK,MAAM,OAAO,MAAM;CAC7C,IAAI,aAAa,GAAG,KAAK,MAAM,OAAO,MAAM;CAC5C,IAAI,aAAa,GAAG;EAClB,KAAK,MAAM;EACX,IAAI,KAAK,KAAK,GAAG,EAAE;EACnB,IAAK,KAAK,KAAO,MAAM;EACvB,IAAI,KAAK,KAAK,GAAG,EAAE;EACnB,KAAK;CACP;CAEA,KAAK,MAAM;CACX,KAAK,MAAM;CACX,IAAI,KAAK,KAAK,GAAG,UAAU;CAC3B,KAAK,MAAM;CACX,IAAI,KAAK,KAAK,GAAG,UAAU;CAC3B,KAAK,MAAM;CACX,OAAO,MAAM;AACf;AAEA,MAAM,OAAO,IAAI,YAAY;;;;;;;AAQ7B,MAAa,oBAAoB;;;;;;AAOjC,SAAgB,aAAa,OAAuB;CAClD,OAAO,QAAQ,KAAK,OAAO,KAAK,CAAC,IAAI;AACvC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/engine/bucket.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Cohort bucketing — deterministic assignment of a user to an A/B arm.
|
|
4
|
+
*
|
|
5
|
+
* MurmurHash3 (x86, 32-bit, seed 0) over the UTF-8 bytes of the key, the same
|
|
6
|
+
* construction A/B systems like Unleash use: excellent avalanche behaviour
|
|
7
|
+
* (sequential user ids spread evenly, where FNV-1a showed low-bit bias) and a
|
|
8
|
+
* frozen, widely cross-checked algorithm. Hand-written on purpose: the engine
|
|
9
|
+
* core stays dependency-free, and the known-answer tests pin this
|
|
10
|
+
* implementation against the reference vectors — a stronger guarantee than
|
|
11
|
+
* trusting an npm package's variant.
|
|
12
|
+
*
|
|
13
|
+
* The bucket function is a permanent contract: changing it reshuffles every
|
|
14
|
+
* in-flight cohort, so it must never change once production traffic exists.
|
|
15
|
+
*/
|
|
16
|
+
/** MurmurHash3 x86 32-bit over raw bytes; seed 0. Returns an unsigned 32-bit integer. */
|
|
17
|
+
export declare function murmur3(bytes: Uint8Array, seed?: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* Bucket resolution: basis points, i.e. hundredths of a percent. Weights are
|
|
20
|
+
* compared on the same integer grid (weight% × 100), so fractional splits like
|
|
21
|
+
* 33.3/33.3/33.4 are honoured exactly and the whole path stays float-free —
|
|
22
|
+
* the Optimizely/Statsig construction.
|
|
23
|
+
*/
|
|
24
|
+
export declare const BUCKET_RESOLUTION = 10000;
|
|
25
|
+
/**
|
|
26
|
+
* Map a bucketing key (`${userId}:${cohortKey}`) to [0, 10000). Keys hash
|
|
27
|
+
* their UTF-8 bytes, so results agree with any standard Murmur3 implementation
|
|
28
|
+
* regardless of the user id's alphabet.
|
|
29
|
+
*/
|
|
30
|
+
export declare function hashToBucket(input: string): number;
|
|
31
|
+
//#endregion
|
|
32
|
+
//# sourceMappingURL=bucket.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket.d.cts","names":[],"sources":["../../src/engine/bucket.ts"],"mappings":";;;;;;;;;;;;;;;;wBAgBgB,QAAQ,OAAO,YAAY;;;;;;;qBA6C9B;;;;;;wBAOG,aAAa"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/engine/bucket.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Cohort bucketing — deterministic assignment of a user to an A/B arm.
|
|
4
|
+
*
|
|
5
|
+
* MurmurHash3 (x86, 32-bit, seed 0) over the UTF-8 bytes of the key, the same
|
|
6
|
+
* construction A/B systems like Unleash use: excellent avalanche behaviour
|
|
7
|
+
* (sequential user ids spread evenly, where FNV-1a showed low-bit bias) and a
|
|
8
|
+
* frozen, widely cross-checked algorithm. Hand-written on purpose: the engine
|
|
9
|
+
* core stays dependency-free, and the known-answer tests pin this
|
|
10
|
+
* implementation against the reference vectors — a stronger guarantee than
|
|
11
|
+
* trusting an npm package's variant.
|
|
12
|
+
*
|
|
13
|
+
* The bucket function is a permanent contract: changing it reshuffles every
|
|
14
|
+
* in-flight cohort, so it must never change once production traffic exists.
|
|
15
|
+
*/
|
|
16
|
+
/** MurmurHash3 x86 32-bit over raw bytes; seed 0. Returns an unsigned 32-bit integer. */
|
|
17
|
+
export declare function murmur3(bytes: Uint8Array, seed?: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* Bucket resolution: basis points, i.e. hundredths of a percent. Weights are
|
|
20
|
+
* compared on the same integer grid (weight% × 100), so fractional splits like
|
|
21
|
+
* 33.3/33.3/33.4 are honoured exactly and the whole path stays float-free —
|
|
22
|
+
* the Optimizely/Statsig construction.
|
|
23
|
+
*/
|
|
24
|
+
export declare const BUCKET_RESOLUTION = 10000;
|
|
25
|
+
/**
|
|
26
|
+
* Map a bucketing key (`${userId}:${cohortKey}`) to [0, 10000). Keys hash
|
|
27
|
+
* their UTF-8 bytes, so results agree with any standard Murmur3 implementation
|
|
28
|
+
* regardless of the user id's alphabet.
|
|
29
|
+
*/
|
|
30
|
+
export declare function hashToBucket(input: string): number;
|
|
31
|
+
//#endregion
|
|
32
|
+
//# sourceMappingURL=bucket.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket.d.mts","names":[],"sources":["../../src/engine/bucket.ts"],"mappings":";;;;;;;;;;;;;;;;wBAgBgB,QAAQ,OAAO,YAAY;;;;;;;qBA6C9B;;;;;;wBAOG,aAAa"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/engine/bucket.ts
|
|
2
|
+
/**
|
|
3
|
+
* Cohort bucketing — deterministic assignment of a user to an A/B arm.
|
|
4
|
+
*
|
|
5
|
+
* MurmurHash3 (x86, 32-bit, seed 0) over the UTF-8 bytes of the key, the same
|
|
6
|
+
* construction A/B systems like Unleash use: excellent avalanche behaviour
|
|
7
|
+
* (sequential user ids spread evenly, where FNV-1a showed low-bit bias) and a
|
|
8
|
+
* frozen, widely cross-checked algorithm. Hand-written on purpose: the engine
|
|
9
|
+
* core stays dependency-free, and the known-answer tests pin this
|
|
10
|
+
* implementation against the reference vectors — a stronger guarantee than
|
|
11
|
+
* trusting an npm package's variant.
|
|
12
|
+
*
|
|
13
|
+
* The bucket function is a permanent contract: changing it reshuffles every
|
|
14
|
+
* in-flight cohort, so it must never change once production traffic exists.
|
|
15
|
+
*/
|
|
16
|
+
/** MurmurHash3 x86 32-bit over raw bytes; seed 0. Returns an unsigned 32-bit integer. */
|
|
17
|
+
function murmur3(bytes, seed = 0) {
|
|
18
|
+
const c1 = 3432918353;
|
|
19
|
+
const c2 = 461845907;
|
|
20
|
+
let h = seed >>> 0;
|
|
21
|
+
const tail = bytes.length & -4;
|
|
22
|
+
for (let i = 0; i < tail; i += 4) {
|
|
23
|
+
let k = bytes[i] | bytes[i + 1] << 8 | bytes[i + 2] << 16 | bytes[i + 3] << 24;
|
|
24
|
+
k = Math.imul(k, c1);
|
|
25
|
+
k = k << 15 | k >>> 17;
|
|
26
|
+
k = Math.imul(k, c2);
|
|
27
|
+
h ^= k;
|
|
28
|
+
h = h << 13 | h >>> 19;
|
|
29
|
+
h = Math.imul(h, 5) + 3864292196 >>> 0;
|
|
30
|
+
}
|
|
31
|
+
let k = 0;
|
|
32
|
+
const remainder = bytes.length & 3;
|
|
33
|
+
if (remainder === 3) k ^= bytes[tail + 2] << 16;
|
|
34
|
+
if (remainder >= 2) k ^= bytes[tail + 1] << 8;
|
|
35
|
+
if (remainder >= 1) {
|
|
36
|
+
k ^= bytes[tail];
|
|
37
|
+
k = Math.imul(k, c1);
|
|
38
|
+
k = k << 15 | k >>> 17;
|
|
39
|
+
k = Math.imul(k, c2);
|
|
40
|
+
h ^= k;
|
|
41
|
+
}
|
|
42
|
+
h ^= bytes.length;
|
|
43
|
+
h ^= h >>> 16;
|
|
44
|
+
h = Math.imul(h, 2246822507);
|
|
45
|
+
h ^= h >>> 13;
|
|
46
|
+
h = Math.imul(h, 3266489909);
|
|
47
|
+
h ^= h >>> 16;
|
|
48
|
+
return h >>> 0;
|
|
49
|
+
}
|
|
50
|
+
const utf8 = new TextEncoder();
|
|
51
|
+
/**
|
|
52
|
+
* Bucket resolution: basis points, i.e. hundredths of a percent. Weights are
|
|
53
|
+
* compared on the same integer grid (weight% × 100), so fractional splits like
|
|
54
|
+
* 33.3/33.3/33.4 are honoured exactly and the whole path stays float-free —
|
|
55
|
+
* the Optimizely/Statsig construction.
|
|
56
|
+
*/
|
|
57
|
+
const BUCKET_RESOLUTION = 1e4;
|
|
58
|
+
/**
|
|
59
|
+
* Map a bucketing key (`${userId}:${cohortKey}`) to [0, 10000). Keys hash
|
|
60
|
+
* their UTF-8 bytes, so results agree with any standard Murmur3 implementation
|
|
61
|
+
* regardless of the user id's alphabet.
|
|
62
|
+
*/
|
|
63
|
+
function hashToBucket(input) {
|
|
64
|
+
return murmur3(utf8.encode(input)) % BUCKET_RESOLUTION;
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { BUCKET_RESOLUTION, hashToBucket, murmur3 };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=bucket.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket.mjs","names":[],"sources":["../../src/engine/bucket.ts"],"sourcesContent":["/**\n * Cohort bucketing — deterministic assignment of a user to an A/B arm.\n *\n * MurmurHash3 (x86, 32-bit, seed 0) over the UTF-8 bytes of the key, the same\n * construction A/B systems like Unleash use: excellent avalanche behaviour\n * (sequential user ids spread evenly, where FNV-1a showed low-bit bias) and a\n * frozen, widely cross-checked algorithm. Hand-written on purpose: the engine\n * core stays dependency-free, and the known-answer tests pin this\n * implementation against the reference vectors — a stronger guarantee than\n * trusting an npm package's variant.\n *\n * The bucket function is a permanent contract: changing it reshuffles every\n * in-flight cohort, so it must never change once production traffic exists.\n */\n\n/** MurmurHash3 x86 32-bit over raw bytes; seed 0. Returns an unsigned 32-bit integer. */\nexport function murmur3(bytes: Uint8Array, seed = 0): number {\n const c1 = 0xcc9e2d51;\n const c2 = 0x1b873593;\n let h = seed >>> 0;\n\n const tail = bytes.length & ~3;\n for (let i = 0; i < tail; i += 4) {\n let k = bytes[i] | (bytes[i + 1] << 8) | (bytes[i + 2] << 16) | (bytes[i + 3] << 24);\n k = Math.imul(k, c1);\n k = (k << 15) | (k >>> 17);\n k = Math.imul(k, c2);\n h ^= k;\n h = (h << 13) | (h >>> 19);\n h = (Math.imul(h, 5) + 0xe6546b64) >>> 0;\n }\n\n let k = 0;\n const remainder = bytes.length & 3;\n if (remainder === 3) k ^= bytes[tail + 2] << 16;\n if (remainder >= 2) k ^= bytes[tail + 1] << 8;\n if (remainder >= 1) {\n k ^= bytes[tail];\n k = Math.imul(k, c1);\n k = (k << 15) | (k >>> 17);\n k = Math.imul(k, c2);\n h ^= k;\n }\n\n h ^= bytes.length;\n h ^= h >>> 16;\n h = Math.imul(h, 0x85ebca6b);\n h ^= h >>> 13;\n h = Math.imul(h, 0xc2b2ae35);\n h ^= h >>> 16;\n return h >>> 0;\n}\n\nconst utf8 = new TextEncoder();\n\n/**\n * Bucket resolution: basis points, i.e. hundredths of a percent. Weights are\n * compared on the same integer grid (weight% × 100), so fractional splits like\n * 33.3/33.3/33.4 are honoured exactly and the whole path stays float-free —\n * the Optimizely/Statsig construction.\n */\nexport const BUCKET_RESOLUTION = 10_000;\n\n/**\n * Map a bucketing key (`${userId}:${cohortKey}`) to [0, 10000). Keys hash\n * their UTF-8 bytes, so results agree with any standard Murmur3 implementation\n * regardless of the user id's alphabet.\n */\nexport function hashToBucket(input: string): number {\n return murmur3(utf8.encode(input)) % BUCKET_RESOLUTION;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,SAAgB,QAAQ,OAAmB,OAAO,GAAW;CAC3D,MAAM,KAAK;CACX,MAAM,KAAK;CACX,IAAI,IAAI,SAAS;CAEjB,MAAM,OAAO,MAAM,SAAS;CAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,KAAK,GAAG;EAChC,IAAI,IAAI,MAAM,KAAM,MAAM,IAAI,MAAM,IAAM,MAAM,IAAI,MAAM,KAAO,MAAM,IAAI,MAAM;EACjF,IAAI,KAAK,KAAK,GAAG,EAAE;EACnB,IAAK,KAAK,KAAO,MAAM;EACvB,IAAI,KAAK,KAAK,GAAG,EAAE;EACnB,KAAK;EACL,IAAK,KAAK,KAAO,MAAM;EACvB,IAAK,KAAK,KAAK,GAAG,CAAC,IAAI,eAAgB;CACzC;CAEA,IAAI,IAAI;CACR,MAAM,YAAY,MAAM,SAAS;CACjC,IAAI,cAAc,GAAG,KAAK,MAAM,OAAO,MAAM;CAC7C,IAAI,aAAa,GAAG,KAAK,MAAM,OAAO,MAAM;CAC5C,IAAI,aAAa,GAAG;EAClB,KAAK,MAAM;EACX,IAAI,KAAK,KAAK,GAAG,EAAE;EACnB,IAAK,KAAK,KAAO,MAAM;EACvB,IAAI,KAAK,KAAK,GAAG,EAAE;EACnB,KAAK;CACP;CAEA,KAAK,MAAM;CACX,KAAK,MAAM;CACX,IAAI,KAAK,KAAK,GAAG,UAAU;CAC3B,KAAK,MAAM;CACX,IAAI,KAAK,KAAK,GAAG,UAAU;CAC3B,KAAK,MAAM;CACX,OAAO,MAAM;AACf;AAEA,MAAM,OAAO,IAAI,YAAY;;;;;;;AAQ7B,MAAa,oBAAoB;;;;;;AAOjC,SAAgB,aAAa,OAAuB;CAClD,OAAO,QAAQ,KAAK,OAAO,KAAK,CAAC,IAAI;AACvC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_engine_recipient = require("./recipient.cjs");
|
|
3
|
+
//#region src/engine/condition.ts
|
|
4
|
+
/**
|
|
5
|
+
* ConditionIR evaluation: pure logic, with facts injected through FactSource,
|
|
6
|
+
* so the D1 implementation and the in-memory test one share a single set of
|
|
7
|
+
* semantics and cannot drift into two evaluators.
|
|
8
|
+
*/
|
|
9
|
+
async function evaluateCondition(condition, facts, nowMs, opts) {
|
|
10
|
+
return evaluate(condition, facts, nowMs, opts, []);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `enclosing` is the chain of segment names currently being expanded. A
|
|
14
|
+
* segment that reaches itself has no truth value to converge on, and left
|
|
15
|
+
* unchecked the recursion runs until memory gives out, one D1 round trip per
|
|
16
|
+
* level — inside a step, so Workflows retries it and the instance never gets
|
|
17
|
+
* out. Failing immediately keeps it bounded, and the message names the cycle
|
|
18
|
+
* because only a deployed bundle can fix it. Only the *path* is tracked, so a
|
|
19
|
+
* diamond (two branches referencing one segment) still evaluates twice.
|
|
20
|
+
*/
|
|
21
|
+
async function evaluate(condition, facts, nowMs, opts, enclosing) {
|
|
22
|
+
switch (condition.type) {
|
|
23
|
+
case "and":
|
|
24
|
+
for (const child of condition.conditions) if (!await evaluate(child, facts, nowMs, opts, enclosing)) return false;
|
|
25
|
+
return true;
|
|
26
|
+
case "or":
|
|
27
|
+
for (const child of condition.conditions) if (await evaluate(child, facts, nowMs, opts, enclosing)) return true;
|
|
28
|
+
return false;
|
|
29
|
+
case "not": return !await evaluate(condition.condition, facts, nowMs, opts, enclosing);
|
|
30
|
+
case "segment": {
|
|
31
|
+
if (enclosing.includes(condition.segment)) throw new Error(`segment '${condition.segment}' references itself: ${[...enclosing, condition.segment].join(" -> ")}`);
|
|
32
|
+
const def = await facts.getSegmentCondition(condition.segment);
|
|
33
|
+
if (def === void 0) throw new Error(`Unknown segment: '${condition.segment}' (bundle not deployed?)`);
|
|
34
|
+
return evaluate(def, facts, nowMs, opts, [...enclosing, condition.segment]);
|
|
35
|
+
}
|
|
36
|
+
case "performed": {
|
|
37
|
+
const bounds = [];
|
|
38
|
+
if (condition.within !== void 0) bounds.push(nowMs - condition.within.ms);
|
|
39
|
+
if (opts?.anchorMs !== void 0) bounds.push(opts.anchorMs);
|
|
40
|
+
const sinceMs = bounds.length > 0 ? Math.max(...bounds) : void 0;
|
|
41
|
+
return await facts.countEvents(condition.event, {
|
|
42
|
+
...sinceMs !== void 0 ? { sinceMs } : {},
|
|
43
|
+
...opts?.untilMs !== void 0 ? { untilMs: opts.untilMs } : {},
|
|
44
|
+
...condition.where !== void 0 ? { where: condition.where } : {}
|
|
45
|
+
}) >= (condition.count ?? 1);
|
|
46
|
+
}
|
|
47
|
+
case "property": return compareProperty(condition, await facts.getProperty(condition.path));
|
|
48
|
+
case "reachable": {
|
|
49
|
+
if (await facts.isSuppressed?.(condition.channel)) return false;
|
|
50
|
+
if (condition.channel === "push") return (await facts.listDevices()).length > 0;
|
|
51
|
+
const path = require_engine_recipient.RECIPIENT_PROPERTY[condition.channel];
|
|
52
|
+
return path === void 0 || await facts.getProperty(path) !== void 0;
|
|
53
|
+
}
|
|
54
|
+
case "payload": throw new Error("payload conditions are only valid inside performed({ where }) or a trigger where clause");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Evaluate a where tree against one event's payload. Shared by performed
|
|
59
|
+
* counting (FactSource implementations) and the ingest router's trigger gate.
|
|
60
|
+
* Dotted paths descend nested objects; a missing or non-scalar leaf behaves
|
|
61
|
+
* like a missing property (exists sees objects, comparisons do not).
|
|
62
|
+
*/
|
|
63
|
+
function matchesWhere(payload, where) {
|
|
64
|
+
switch (where.type) {
|
|
65
|
+
case "and": return where.conditions.every((child) => matchesWhere(payload, child));
|
|
66
|
+
case "or": return where.conditions.some((child) => matchesWhere(payload, child));
|
|
67
|
+
case "not": return !matchesWhere(payload, where.condition);
|
|
68
|
+
case "payload": {
|
|
69
|
+
const raw = valueAtPath(payload, where.path);
|
|
70
|
+
if (where.op === "exists") return raw !== void 0 && raw !== null;
|
|
71
|
+
if (where.op === "not_exists") return raw === void 0 || raw === null;
|
|
72
|
+
return compareProperty(where, typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean" ? raw : void 0);
|
|
73
|
+
}
|
|
74
|
+
default: throw new Error(`where clause may not contain a '${where.type}' condition`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function valueAtPath(payload, path) {
|
|
78
|
+
let current = payload;
|
|
79
|
+
for (const key of path.split(".")) {
|
|
80
|
+
if (current === null || typeof current !== "object") return void 0;
|
|
81
|
+
current = current[key];
|
|
82
|
+
}
|
|
83
|
+
return current;
|
|
84
|
+
}
|
|
85
|
+
function compareProperty(condition, actual) {
|
|
86
|
+
const { op, value, values } = condition;
|
|
87
|
+
switch (op) {
|
|
88
|
+
case "exists": return actual !== void 0;
|
|
89
|
+
case "not_exists": return actual === void 0;
|
|
90
|
+
case "eq": return actual === value;
|
|
91
|
+
case "ne": return actual !== value;
|
|
92
|
+
case "gt": return actual !== void 0 && value !== void 0 && actual > value;
|
|
93
|
+
case "gte": return actual !== void 0 && value !== void 0 && actual >= value;
|
|
94
|
+
case "lt": return actual !== void 0 && value !== void 0 && actual < value;
|
|
95
|
+
case "lte": return actual !== void 0 && value !== void 0 && actual <= value;
|
|
96
|
+
case "between": return isBetween(actual, values);
|
|
97
|
+
case "not_between": return !isBetween(actual, values);
|
|
98
|
+
case "in_array": return actual !== void 0 && (values ?? []).includes(actual);
|
|
99
|
+
case "not_in_array": return actual === void 0 || !(values ?? []).includes(actual);
|
|
100
|
+
case "contains": return typeof actual === "string" && typeof value === "string" && actual.includes(value);
|
|
101
|
+
case "not_contains": return !(typeof actual === "string" && typeof value === "string" && actual.includes(value));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function isBetween(actual, values) {
|
|
105
|
+
if (actual === void 0 || values === void 0 || values.length < 2) return false;
|
|
106
|
+
const [min, max] = values;
|
|
107
|
+
return actual >= min && actual <= max;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Wake-up channel for profile changes: a property predicate has no event name
|
|
111
|
+
* of its own, so subscriptions use this reserved name and the ingest router
|
|
112
|
+
* fires it on every /identify. The '$' prefix marks internal names — real
|
|
113
|
+
* events may never use it (the router rejects them).
|
|
114
|
+
*/
|
|
115
|
+
const PROFILE_UPDATED_EVENT = "$profile_updated";
|
|
116
|
+
/**
|
|
117
|
+
* Wake-up channel for device changes: a device registered for the user, moved to another account
|
|
118
|
+
* or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.
|
|
119
|
+
*/
|
|
120
|
+
const DEVICES_UPDATED_EVENT = "$devices_updated";
|
|
121
|
+
/**
|
|
122
|
+
* The set of event names a condition cares about — what wait_until subscribes
|
|
123
|
+
* to for wake-ups. `performed` contributes its event; a property predicate
|
|
124
|
+
* contributes PROFILE_UPDATED_EVENT (so /identify wakes the wait). Segment
|
|
125
|
+
* references are expanded, which is why a FactSource is needed to resolve
|
|
126
|
+
* their definitions.
|
|
127
|
+
*/
|
|
128
|
+
async function relevantEvents(condition, facts) {
|
|
129
|
+
const found = /* @__PURE__ */ new Set();
|
|
130
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
131
|
+
async function walk(c) {
|
|
132
|
+
switch (c.type) {
|
|
133
|
+
case "and":
|
|
134
|
+
case "or":
|
|
135
|
+
for (const child of c.conditions) await walk(child);
|
|
136
|
+
break;
|
|
137
|
+
case "not":
|
|
138
|
+
await walk(c.condition);
|
|
139
|
+
break;
|
|
140
|
+
case "performed":
|
|
141
|
+
found.add(c.event);
|
|
142
|
+
break;
|
|
143
|
+
case "segment": {
|
|
144
|
+
if (expanded.has(c.segment)) break;
|
|
145
|
+
expanded.add(c.segment);
|
|
146
|
+
const def = await facts.getSegmentCondition(c.segment);
|
|
147
|
+
if (def) await walk(def);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "property":
|
|
151
|
+
found.add(PROFILE_UPDATED_EVENT);
|
|
152
|
+
break;
|
|
153
|
+
case "reachable": found.add(c.channel === "push" ? DEVICES_UPDATED_EVENT : PROFILE_UPDATED_EVENT);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
await walk(condition);
|
|
157
|
+
return [...found];
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
exports.DEVICES_UPDATED_EVENT = DEVICES_UPDATED_EVENT;
|
|
161
|
+
exports.PROFILE_UPDATED_EVENT = PROFILE_UPDATED_EVENT;
|
|
162
|
+
exports.evaluateCondition = evaluateCondition;
|
|
163
|
+
exports.matchesWhere = matchesWhere;
|
|
164
|
+
exports.relevantEvents = relevantEvents;
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=condition.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"condition.cjs","names":["RECIPIENT_PROPERTY"],"sources":["../../src/engine/condition.ts"],"sourcesContent":["import type { ConditionIR, PropertyOperatorIR, ScalarIR } from '../ir';\nimport type { FactSource } from './ports';\nimport { RECIPIENT_PROPERTY } from './recipient';\n\n/**\n * Evaluation options.\n * - anchorMs: the earliest instant `performed` counts events from — the goal\n * guard anchors at workflow entry (a conversion must happen *after* entry),\n * waitUntil at the moment the wait began (a wake must not be satisfied by\n * pre-existing history). Combined with `within` by taking the later bound.\n * - untilMs: the latest instant `performed` counts events up to — the goal's\n * attribution window ends there, so an event after it is no conversion even\n * when the check runs later (a resumed instance, a delayed wake).\n */\nexport interface EvaluateOptions {\n anchorMs?: number;\n untilMs?: number;\n}\n\n/**\n * ConditionIR evaluation: pure logic, with facts injected through FactSource,\n * so the D1 implementation and the in-memory test one share a single set of\n * semantics and cannot drift into two evaluators.\n */\nexport async function evaluateCondition(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts?: EvaluateOptions\n): Promise<boolean> {\n return evaluate(condition, facts, nowMs, opts, []);\n}\n\n/**\n * `enclosing` is the chain of segment names currently being expanded. A\n * segment that reaches itself has no truth value to converge on, and left\n * unchecked the recursion runs until memory gives out, one D1 round trip per\n * level — inside a step, so Workflows retries it and the instance never gets\n * out. Failing immediately keeps it bounded, and the message names the cycle\n * because only a deployed bundle can fix it. Only the *path* is tracked, so a\n * diamond (two branches referencing one segment) still evaluates twice.\n */\nasync function evaluate(\n condition: ConditionIR,\n facts: FactSource,\n nowMs: number,\n opts: EvaluateOptions | undefined,\n enclosing: readonly string[]\n): Promise<boolean> {\n switch (condition.type) {\n case 'and': {\n for (const child of condition.conditions) {\n if (!(await evaluate(child, facts, nowMs, opts, enclosing))) return false;\n }\n return true;\n }\n case 'or': {\n for (const child of condition.conditions) {\n if (await evaluate(child, facts, nowMs, opts, enclosing)) return true;\n }\n return false;\n }\n case 'not':\n return !(await evaluate(condition.condition, facts, nowMs, opts, enclosing));\n case 'segment': {\n if (enclosing.includes(condition.segment)) {\n throw new Error(\n `segment '${condition.segment}' references itself: ${[...enclosing, condition.segment].join(' -> ')}`\n );\n }\n const def = await facts.getSegmentCondition(condition.segment);\n if (def === undefined) {\n throw new Error(`Unknown segment: '${condition.segment}' (bundle not deployed?)`);\n }\n return evaluate(def, facts, nowMs, opts, [...enclosing, condition.segment]);\n }\n case 'performed': {\n const bounds: number[] = [];\n if (condition.within !== undefined) bounds.push(nowMs - condition.within.ms);\n if (opts?.anchorMs !== undefined) bounds.push(opts.anchorMs);\n const sinceMs = bounds.length > 0 ? Math.max(...bounds) : undefined;\n const count = await facts.countEvents(condition.event, {\n ...(sinceMs !== undefined ? { sinceMs } : {}),\n ...(opts?.untilMs !== undefined ? { untilMs: opts.untilMs } : {}),\n ...(condition.where !== undefined ? { where: condition.where } : {}),\n });\n return count >= (condition.count ?? 1);\n }\n case 'property': {\n const actual = await facts.getProperty(condition.path);\n return compareProperty(condition, actual);\n }\n case 'reachable': {\n if (await facts.isSuppressed?.(condition.channel)) return false;\n if (condition.channel === 'push') return (await facts.listDevices()).length > 0;\n const path = RECIPIENT_PROPERTY[condition.channel];\n // in_app, slack and discord need no address: a signed-in user is reachable in the app, and\n // the webhook channels post to a team channel, not to the user\n return path === undefined || (await facts.getProperty(path)) !== undefined;\n }\n case 'payload':\n // Placement is validated at compile; reaching here means hand-built IR\n throw new Error(\n 'payload conditions are only valid inside performed({ where }) or a trigger where clause'\n );\n }\n}\n\n/**\n * Evaluate a where tree against one event's payload. Shared by performed\n * counting (FactSource implementations) and the ingest router's trigger gate.\n * Dotted paths descend nested objects; a missing or non-scalar leaf behaves\n * like a missing property (exists sees objects, comparisons do not).\n */\nexport function matchesWhere(payload: unknown, where: ConditionIR): boolean {\n switch (where.type) {\n case 'and':\n return where.conditions.every((child) => matchesWhere(payload, child));\n case 'or':\n return where.conditions.some((child) => matchesWhere(payload, child));\n case 'not':\n return !matchesWhere(payload, where.condition);\n case 'payload': {\n const raw = valueAtPath(payload, where.path);\n if (where.op === 'exists') return raw !== undefined && raw !== null;\n if (where.op === 'not_exists') return raw === undefined || raw === null;\n const actual =\n typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean'\n ? raw\n : undefined;\n return compareProperty(where, actual);\n }\n default:\n // compileBundle rejects these; defend against hand-built IR\n throw new Error(`where clause may not contain a '${where.type}' condition`);\n }\n}\n\nfunction valueAtPath(payload: unknown, path: string): unknown {\n let current: unknown = payload;\n for (const key of path.split('.')) {\n if (current === null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n return current;\n}\n\nfunction compareProperty(\n condition: {\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n },\n actual: ScalarIR | undefined\n): boolean {\n const { op, value, values } = condition;\n switch (op) {\n case 'exists':\n return actual !== undefined;\n case 'not_exists':\n return actual === undefined;\n case 'eq':\n return actual === value;\n case 'ne':\n return actual !== value;\n case 'gt':\n return actual !== undefined && value !== undefined && actual > value;\n case 'gte':\n return actual !== undefined && value !== undefined && actual >= value;\n case 'lt':\n return actual !== undefined && value !== undefined && actual < value;\n case 'lte':\n return actual !== undefined && value !== undefined && actual <= value;\n case 'between':\n return isBetween(actual, values);\n case 'not_between':\n // A missing property is outside any range, mirroring not_in_array.\n return !isBetween(actual, values);\n case 'in_array':\n return actual !== undefined && (values ?? []).includes(actual);\n case 'not_in_array':\n return actual === undefined || !(values ?? []).includes(actual);\n case 'contains':\n return typeof actual === 'string' && typeof value === 'string' && actual.includes(value);\n case 'not_contains':\n return !(typeof actual === 'string' && typeof value === 'string' && actual.includes(value));\n }\n}\n\nfunction isBetween(actual: ScalarIR | undefined, values: ScalarIR[] | undefined): boolean {\n if (actual === undefined || values === undefined || values.length < 2) return false;\n const [min, max] = values;\n return actual >= min && actual <= max;\n}\n\n/**\n * Wake-up channel for profile changes: a property predicate has no event name\n * of its own, so subscriptions use this reserved name and the ingest router\n * fires it on every /identify. The '$' prefix marks internal names — real\n * events may never use it (the router rejects them).\n */\nexport const PROFILE_UPDATED_EVENT = '$profile_updated';\n\n/**\n * Wake-up channel for device changes: a device registered for the user, moved to another account\n * or was unbound or removed (`POST /devices`, `/devices/unbind`, `/devices/remove`). `reachable('push')` subscribes to it.\n */\nexport const DEVICES_UPDATED_EVENT = '$devices_updated';\n\n/**\n * The set of event names a condition cares about — what wait_until subscribes\n * to for wake-ups. `performed` contributes its event; a property predicate\n * contributes PROFILE_UPDATED_EVENT (so /identify wakes the wait). Segment\n * references are expanded, which is why a FactSource is needed to resolve\n * their definitions.\n */\nexport async function relevantEvents(condition: ConditionIR, facts: FactSource): Promise<string[]> {\n const found = new Set<string>();\n // Names already expanded. The result is a union, so re-walking a segment can\n // only add what is already there — and a segment that reaches itself would\n // otherwise recurse until the stack blows, taking a D1 read per level.\n const expanded = new Set<string>();\n async function walk(c: ConditionIR): Promise<void> {\n switch (c.type) {\n case 'and':\n case 'or':\n for (const child of c.conditions) await walk(child);\n break;\n case 'not':\n await walk(c.condition);\n break;\n case 'performed':\n found.add(c.event);\n break;\n case 'segment': {\n if (expanded.has(c.segment)) break;\n expanded.add(c.segment);\n const def = await facts.getSegmentCondition(c.segment);\n if (def) await walk(def);\n break;\n }\n case 'property':\n found.add(PROFILE_UPDATED_EVENT);\n break;\n case 'reachable':\n found.add(c.channel === 'push' ? DEVICES_UPDATED_EVENT : PROFILE_UPDATED_EVENT);\n break;\n case 'payload':\n break; // only appears inside performed.where, which is not walked\n }\n }\n await walk(condition);\n return [...found];\n}\n"],"mappings":"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"}
|