@shware/workflow 7.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -34
- package/dist/aws/engine-step.cjs +4 -1
- package/dist/aws/engine-step.cjs.map +1 -1
- package/dist/aws/engine-step.d.cts.map +1 -1
- package/dist/aws/engine-step.d.mts.map +1 -1
- package/dist/aws/engine-step.mjs +4 -1
- package/dist/aws/engine-step.mjs.map +1 -1
- package/dist/client.cjs +66 -41
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +72 -22
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.mts +72 -22
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +66 -42
- package/dist/client.mjs.map +1 -1
- package/dist/cloudflare/index.d.cts +4 -3
- package/dist/cloudflare/index.d.mts +4 -3
- package/dist/cloudflare/runner.cjs +3 -1
- package/dist/cloudflare/runner.cjs.map +1 -1
- package/dist/cloudflare/runner.d.cts.map +1 -1
- package/dist/cloudflare/runner.d.mts.map +1 -1
- package/dist/cloudflare/runner.mjs +3 -1
- package/dist/cloudflare/runner.mjs.map +1 -1
- package/dist/cloudflare/worker.cjs +12 -1
- package/dist/cloudflare/worker.cjs.map +1 -1
- package/dist/cloudflare/worker.d.cts +9 -1
- package/dist/cloudflare/worker.d.cts.map +1 -1
- package/dist/cloudflare/worker.d.mts +9 -1
- package/dist/cloudflare/worker.d.mts.map +1 -1
- package/dist/cloudflare/worker.mjs +12 -1
- package/dist/cloudflare/worker.mjs.map +1 -1
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +4 -4
- package/dist/contract.d.mts +4 -4
- package/dist/contract.mjs.map +1 -1
- package/dist/dsl/trigger.cjs +2 -1
- package/dist/dsl/trigger.cjs.map +1 -1
- package/dist/dsl/trigger.d.cts +6 -0
- package/dist/dsl/trigger.d.cts.map +1 -1
- package/dist/dsl/trigger.d.mts +6 -0
- package/dist/dsl/trigger.d.mts.map +1 -1
- package/dist/dsl/trigger.mjs +2 -1
- package/dist/dsl/trigger.mjs.map +1 -1
- package/dist/engine/condition.cjs +3 -3
- package/dist/engine/condition.cjs.map +1 -1
- package/dist/engine/condition.d.cts +3 -3
- package/dist/engine/condition.d.mts +3 -3
- package/dist/engine/condition.mjs +3 -3
- package/dist/engine/condition.mjs.map +1 -1
- package/dist/engine/interpreter.cjs +9 -5
- package/dist/engine/interpreter.cjs.map +1 -1
- package/dist/engine/interpreter.d.cts.map +1 -1
- package/dist/engine/interpreter.d.mts.map +1 -1
- package/dist/engine/interpreter.mjs +9 -5
- package/dist/engine/interpreter.mjs.map +1 -1
- package/dist/engine/ports.d.cts +2 -0
- package/dist/engine/ports.d.cts.map +1 -1
- package/dist/engine/ports.d.mts +2 -0
- package/dist/engine/ports.d.mts.map +1 -1
- package/dist/host/batch.cjs +172 -0
- package/dist/host/batch.cjs.map +1 -0
- package/dist/host/batch.d.cts +88 -0
- package/dist/host/batch.d.cts.map +1 -0
- package/dist/host/batch.d.mts +88 -0
- package/dist/host/batch.d.mts.map +1 -0
- package/dist/host/batch.mjs +163 -0
- package/dist/host/batch.mjs.map +1 -0
- package/dist/host/execute.cjs +10 -6
- package/dist/host/execute.cjs.map +1 -1
- package/dist/host/execute.mjs +10 -6
- package/dist/host/execute.mjs.map +1 -1
- package/dist/host/http.cjs +30 -89
- package/dist/host/http.cjs.map +1 -1
- package/dist/host/http.d.cts +1 -1
- package/dist/host/http.d.cts.map +1 -1
- package/dist/host/http.d.mts +1 -1
- package/dist/host/http.d.mts.map +1 -1
- package/dist/host/http.mjs +24 -83
- package/dist/host/http.mjs.map +1 -1
- package/dist/host/ingest.cjs +56 -13
- package/dist/host/ingest.cjs.map +1 -1
- package/dist/host/ingest.d.cts +17 -6
- package/dist/host/ingest.d.cts.map +1 -1
- package/dist/host/ingest.d.mts +17 -6
- package/dist/host/ingest.d.mts.map +1 -1
- package/dist/host/ingest.mjs +55 -14
- package/dist/host/ingest.mjs.map +1 -1
- package/dist/host/journey-host.cjs +6 -0
- package/dist/host/journey-host.cjs.map +1 -1
- package/dist/host/journey-host.d.cts +5 -2
- package/dist/host/journey-host.d.cts.map +1 -1
- package/dist/host/journey-host.d.mts +5 -2
- package/dist/host/journey-host.d.mts.map +1 -1
- package/dist/host/journey-host.mjs +6 -0
- package/dist/host/journey-host.mjs.map +1 -1
- package/dist/host/platform.cjs.map +1 -1
- package/dist/host/platform.d.cts +2 -0
- package/dist/host/platform.d.cts.map +1 -1
- package/dist/host/platform.d.mts +2 -0
- package/dist/host/platform.d.mts.map +1 -1
- package/dist/host/platform.mjs.map +1 -1
- package/dist/ir.cjs +2 -1
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +3 -0
- package/dist/ir.d.cts.map +1 -1
- package/dist/ir.d.mts +3 -0
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +2 -1
- package/dist/ir.mjs.map +1 -1
- package/dist/store/facts.cjs +13 -9
- package/dist/store/facts.cjs.map +1 -1
- package/dist/store/facts.d.cts +6 -2
- package/dist/store/facts.d.cts.map +1 -1
- package/dist/store/facts.d.mts +6 -2
- package/dist/store/facts.d.mts.map +1 -1
- package/dist/store/facts.mjs +13 -9
- package/dist/store/facts.mjs.map +1 -1
- package/dist/store/identity.cjs +34 -0
- package/dist/store/identity.cjs.map +1 -0
- package/dist/store/identity.d.cts +13 -0
- package/dist/store/identity.d.cts.map +1 -0
- package/dist/store/identity.d.mts +13 -0
- package/dist/store/identity.d.mts.map +1 -0
- package/dist/store/identity.mjs +30 -0
- package/dist/store/identity.mjs.map +1 -0
- package/dist/store/index.cjs +5 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +49 -15
- package/dist/store/index.d.cts.map +1 -1
- package/dist/store/index.d.mts +49 -15
- package/dist/store/index.d.mts.map +1 -1
- package/dist/store/index.mjs +2 -1
- package/dist/store/index.mjs.map +1 -1
- package/dist/store/postgres.cjs +206 -44
- package/dist/store/postgres.cjs.map +1 -1
- package/dist/store/postgres.d.cts +17 -4
- package/dist/store/postgres.d.cts.map +1 -1
- package/dist/store/postgres.d.mts +17 -4
- package/dist/store/postgres.d.mts.map +1 -1
- package/dist/store/postgres.mjs +207 -45
- package/dist/store/postgres.mjs.map +1 -1
- package/dist/store/tables.cjs +24 -6
- package/dist/store/tables.cjs.map +1 -1
- package/dist/store/tables.d.cts +1 -0
- package/dist/store/tables.d.cts.map +1 -1
- package/dist/store/tables.d.mts +1 -0
- package/dist/store/tables.d.mts.map +1 -1
- package/dist/store/tables.mjs +24 -6
- package/dist/store/tables.mjs.map +1 -1
- package/dist/webpush.cjs.map +1 -1
- package/dist/webpush.d.cts +1 -1
- package/dist/webpush.d.mts +1 -1
- package/dist/webpush.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journey-host.mjs","names":[],"sources":["../../src/host/journey-host.ts"],"sourcesContent":["import * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport { type JourneyConfig, compileConfig } from '../config';\nimport { type Contract, ContractValidationError, checkEvent } from '../contract';\nimport { assertEmailConfig } from '../email';\nimport type { EmailTransport } from '../email-sender';\nimport { RegistryActionInvoker } from '../engine/actions';\nimport { profileUpdatedEvent } from '../engine/condition';\nimport type { JourneyObserver, MessageSender } from '../engine/ports';\nimport { RECIPIENT_PROPERTY } from '../engine/recipient';\nimport { scaleSegmentWindows } from '../engine/time-scale';\nimport type { BundleIR, NodeIR } from '../ir';\nimport { registryRenderer } from '../react-email';\nimport { reportingObserver } from '../reporting/observer';\nimport type { TrackingOptions } from '../reporting/tracking';\nimport type { JourneyRuntimeOptions } from '../runtime';\nimport type { BundleVersion, JourneyStore, StoreLease } from '../store/index';\nimport { DEFAULT_SCHEMA } from '../store/tables';\nimport { indexTemplates, registryValidator } from '../templates';\nimport type { Authorize } from './auth';\nimport { channelsFromConfig, createProviderCache, runtimeFromConfig } from './channels';\nimport type { JourneyRunParts } from './execute';\nimport { serve } from './http';\nimport {\n type SweepResult,\n deployBundle,\n ingestEvent,\n sweepSegments,\n wakeSubscribers,\n} from './ingest';\nimport type { JourneyEnv, JourneyParams } from './platform';\n\n/**\n * What a journey host is, apart from the runtime it runs on: the validated environment, the\n * store lease, the outlets a run is wired to, the HTTP API (`http.ts`, with the host's parts\n * plugged in) and the bundle that goes live with the code. `journeyWorker` (Cloudflare) and\n * `journeyLambda` (AWS) are this plus how their platform starts, wakes and stops instances.\n */\n\nexport interface JourneyHostOptions<Env extends JourneyEnv> {\n /**\n * The project's `workflow.config.ts` (`defineConfig`): what the host deploys (`GET /bundle`,\n * `POST /bundle/deploy` serve `compileConfig`), the template registries the senders, the\n * validator and `/preview` render from, the contract every `/events` and `/identify` is\n * checked against, the email defaults, actions, and the runtime / APNs / FCM settings.\n * Secrets come from the env under the names `JourneyEnv` documents.\n */\n config: JourneyConfig;\n /**\n * The data plane, opened per request and per workflow run. Return a `StoreLease` when the\n * client must be released afterwards (a Postgres connection). `database` is the config's\n * (`schema` defaulting to `workflow`):\n *\n * store: (env, database) => postgresJsStore(env.DATABASE_URL, database)\n */\n store: (env: Env, database: { schema: string }) => JourneyStore | StoreLease;\n /**\n * Where journey emails go: a transport built from the validated env — `sesEmailTransport(...)`,\n * `cloudflareEmailTransport(...)`, or your own. Which one is independent of where the workflow\n * runs. Without it the email channel fails its sends, saying so.\n */\n email?: (env: Env) => EmailTransport;\n /**\n * Reporting hooks: node arrivals, every copy sent (with the tracking it should carry — click\n * links and an open pixel for email, payload data for a push), and how each instance ended.\n * By default the store records them itself, and the host serves the email tracking routes\n * (`/t/...`, on `WORKFLOW_ORIGIN`); override to report elsewhere.\n */\n observer?: (env: Env, store: JourneyStore) => JourneyObserver;\n /** Message outlet override (tests, a custom gateway): replaces every channel, `email` included. */\n messages?: (env: Env, store: JourneyStore) => MessageSender;\n /** Per-instance runtime options override (a tester in fast-forward); by default `config.runtime` for `ENVIRONMENT`. */\n runtime?: (env: Env, params: JourneyParams) => JourneyRuntimeOptions;\n /**\n * The host's environment, as a zod schema over its vars and secrets (bindings pass through\n * untouched): checked the first time the host runs, so a missing or malformed variable fails\n * there — every problem listed — not at the send that needs it. Webhook templates read their\n * URL from the parsed values: `webhook: (env: Env) => env.WEBHOOK_DISCORD_ALERTS`.\n */\n env?: core.$ZodType<object>;\n /** 'enforce' (default) answers an event or patch the contract refuses with a 400; 'report' only logs it. */\n contractMode?: 'enforce' | 'report';\n /**\n * Who may call the API routes, in whatever form the host chooses: an HMAC over the body, a\n * JWT, a header its proxy adds. Receives a copy of the request and the env; true lets it\n * through, false answers 401, a throw answers 500. The tracking routes and `/health` stay\n * public. Without it, the env's `API_TOKEN` as a bearer (`bearerMatches`), which production\n * then requires.\n *\n * authorize: async (request, env) =>\n * timingSafeEqual(request.headers.get('x-signature') ?? '', await hmac(env.SECRET, await request.text()))\n */\n authorize?: Authorize<Env>;\n}\n\n/** The host's environment failed its schema (`env` option). */\nexport class EnvValidationError extends Error {\n override readonly name = 'EnvValidationError';\n constructor(readonly issues: readonly string[]) {\n super(`the host's environment is invalid:\\n ${issues.join('\\n ')}`);\n }\n}\n\nexport interface JourneyHost<Env extends JourneyEnv> {\n config: JourneyConfig;\n /** The env, parsed by the schema once per env object (throws EnvValidationError). */\n checkEnv(env: Env): Env;\n /** A store for one request or run, its segment windows scaled with the runtime. */\n lease(env: Env): Required<StoreLease>;\n /** The bundle compiled into this code. */\n bundle(): BundleIR;\n /** The outlets one run is wired to (the store is the run's). */\n runParts(env: Env, store: JourneyStore, params: JourneyParams): JourneyRunParts;\n /**\n * The HTTP API over one store: the ingest API plus tracking, the sweep, the bundle and previews.\n * `version` is the code version serving the request, recorded by `POST /bundle/deploy`.\n */\n handle(\n request: Request,\n env: Env,\n store: JourneyStore,\n version?: BundleVersion\n ): Promise<Response>;\n /** The scheduled sweep (`sweepSegments`) on a store of its own. */\n sweep(env: Env): Promise<SweepResult>;\n /**\n * Make the bundle compiled into `version` live, once per process, only over an older live\n * version (the store records it). Undefined when there is nothing to do; a failure is logged\n * and retried a minute later.\n */\n applyOwnBundle(env: Env, version: BundleVersion | undefined): Promise<void> | undefined;\n}\n\n/** What the platform adapter itself contributes. */\nexport interface JourneyPlatform {\n /** An HTTP/2 fetch for APNs, where the runtime's own fetch is HTTP/1.1 (Node). */\n apnsFetch?: typeof fetch;\n}\n\nfunction sendsEmail(nodes: readonly NodeIR[]): boolean {\n return nodes.some((node) => {\n switch (node.type) {\n case 'message':\n return node.channel === 'email';\n case 'wait_until':\n return Array.isArray(node.onTimeout) && sendsEmail(node.onTimeout);\n case 'branch':\n return (\n node.cases.some((branch) => sendsEmail(branch.flow)) || sendsEmail(node.otherwise ?? [])\n );\n case 'cohort':\n return node.arms.some((arm) => sendsEmail(arm.flow));\n default:\n return false;\n }\n });\n}\n\nconst describeIssue = (issue: { path: PropertyKey[]; message: string }): string =>\n `${issue.path.join('.') || '(root)'}: ${issue.message}`;\n\n/** An event the host emits itself, checked against the contract like any ingest. */\nfunction checkedPayload(\n contract: Contract | undefined,\n event: string,\n payload: Record<string, unknown>\n): Record<string, unknown> {\n if (contract === undefined) return payload;\n const verdict = checkEvent(contract, event, payload);\n if (!verdict.ok) throw new ContractValidationError(event, verdict.issues);\n return verdict.data;\n}\n\nexport function createJourneyHost<Env extends JourneyEnv>(\n options: JourneyHostOptions<Env>,\n platform: JourneyPlatform = {}\n): JourneyHost<Env> {\n const { config } = options;\n const emailIds = indexTemplates('email', config.templates?.email);\n // A config still on `emails` (its sender would be silently dropped) fails here, not at a send\n assertEmailConfig(config, emailIds.values());\n let compiled: BundleIR | undefined;\n const bundle = () => (compiled ??= compileConfig(config));\n // A journey that emails with nowhere to send fails here too, not at its first email\n const emailing = bundle().workflows.filter((workflow) => sendsEmail(workflow.flow));\n if (options.email === undefined && options.messages === undefined && emailing.length > 0) {\n throw new TypeError(\n `workflows ${emailing.map((w) => `'${w.id}'`).join(', ')} send email, but the host has no ` +\n '`email` transport: pass `email: (env) => cloudflareEmailTransport(...)` or `sesEmailTransport(...)`'\n );\n }\n // Parsed once per env object: the runtime hands the same one to every request of an isolate\n const checked = new WeakMap<object, Env>();\n const checkEnv = (env: Env): Env => {\n if (options.env === undefined) return env;\n const cached = checked.get(env);\n if (cached !== undefined) return cached;\n const result = z.safeParse(options.env, env);\n // Names and reasons only: the values are secrets\n if (!result.success) throw new EnvValidationError(result.error.issues.map(describeIssue));\n const merged: Env = { ...env, ...result.data };\n checked.set(env, merged);\n return merged;\n };\n const emails = config.templates?.email;\n const database = { schema: config.database?.schema ?? DEFAULT_SCHEMA };\n // In fast-forward, segment windows scale with the journeys reading them (time-scale.ts)\n const lease = (env: Env): Required<StoreLease> => {\n const opened = options.store(env, database);\n const noop = async () => {};\n const factor = runtimeFromConfig(config, env).timeScale;\n return 'store' in opened\n ? { store: scaleSegmentWindows(opened.store, factor), close: opened.close ?? noop }\n : { store: scaleSegmentWindows(opened, factor), close: noop };\n };\n\n // The provider clients (APNs JWT, FCM access token) live with the host, not with a run: a\n // run's env is a fresh object on every Lambda invocation, so nothing keyed on it would be reused\n const providers = createProviderCache();\n const messages = (env: Env, store: JourneyStore): MessageSender => {\n if (options.messages !== undefined) return options.messages(env, store);\n return channelsFromConfig(config, env, {\n store,\n email: options.email?.(env),\n apnsFetch: platform.apnsFetch,\n providers,\n });\n };\n\n const observer = (env: Env, store: JourneyStore): JourneyObserver => {\n if (options.observer !== undefined) return options.observer(env, store);\n return reportingObserver(store.reporting, env.WORKFLOW_ORIGIN);\n };\n\n const runParts = (env: Env, store: JourneyStore, params: JourneyParams): JourneyRunParts => ({\n store,\n env,\n runtime:\n options.runtime === undefined ? runtimeFromConfig(config, env) : options.runtime(env, params),\n messages: messages(env, store),\n // An action ref is a registered action structurally: the same object carries name, code and hash\n actions: new RegistryActionInvoker(config.actions ?? []),\n contract: config.contract,\n validator: registryValidator(config.templates ?? {}),\n observer: observer(env, store),\n });\n\n const preview = emails === undefined ? undefined : registryRenderer(emails, config.email);\n\n /**\n * The recipients' routes (`/t/*`): a push tapped open is recorded as the config's\n * `webPush.openedEvent` — keyed by the copy, so a second tap of the same notification is the\n * same event — and an unsubscribe from all email wakes the waits reading email reachability\n * (`reachable('email')` subscribes to the email property's channel), so an `exitWhen:\n * not(reachable('email'))` takes effect without waiting for a timeout. An unsubscribe from one\n * topic wakes nothing: the user is still reachable.\n */\n const tracking = (env: Env, store: JourneyStore): TrackingOptions => {\n const opened = config.webPush?.openedEvent;\n return {\n product: config.title,\n quickMs: config.email?.tracking?.quickClickMs,\n pushOpened:\n opened === undefined\n ? undefined\n : async ({ messageId, userId, at }) => {\n const payload = checkedPayload(config.contract, opened, { message_id: messageId });\n const input = {\n id: `${opened}:${messageId}`,\n userId,\n event: opened,\n payload,\n ts: at,\n };\n await ingestEvent(env, input, store);\n },\n onUnsubscribed: async ({ userId, all }) => {\n // A topic opt-out leaves the user reachable by email: only the channel-wide one wakes\n if (!all) return;\n const email = profileUpdatedEvent(RECIPIENT_PROPERTY.email ?? 'email');\n await wakeSubscribers(env, store, userId, [email]);\n },\n };\n };\n\n const handle = (\n request: Request,\n env: Env,\n store: JourneyStore,\n version?: BundleVersion\n ): Promise<Response> =>\n serve(request, {\n env,\n store,\n validation: { contract: config.contract, mode: options.contractMode },\n authorize: options.authorize as Authorize<JourneyEnv> | undefined,\n host: {\n bundle,\n bundleVersion: version,\n preview:\n preview === undefined ? undefined : { has: (id) => emailIds.has(id), render: preview },\n tracking: tracking(env, store),\n sweep: () => sweepSegments(env, store),\n },\n });\n\n const sweep = async (env: Env) => {\n const { store, close } = lease(env);\n try {\n return await sweepSegments(env, store);\n } finally {\n await close();\n }\n };\n\n // The bundle compiled into this version goes live on its own, once per process: the first\n // request or scheduled run reads which version's bundle is live (one row) and applies this one\n // only over an older version, so one still serving during a rollout can never put its routes\n // back. A failure (say the migrations have not run) is logged and retried a minute later. Only\n // the request that starts the check gets its promise back: a request context cannot wait on\n // another's I/O.\n let applying: Promise<void> | undefined;\n let retryAt = 0;\n const applyOwnBundle = (env: Env, version: BundleVersion | undefined) => {\n if (version === undefined || version.id === '' || Number.isNaN(version.createdAt)) {\n return undefined;\n }\n if (applying !== undefined || Date.now() < retryAt) return undefined;\n applying = (async () => {\n const { store, close } = lease(env);\n try {\n const live = await store.liveBundleVersion();\n if (live !== undefined && (live.id === version.id || live.createdAt >= version.createdAt))\n return;\n const result = await deployBundle(env, bundle(), store, version);\n if (result.applied) console.log(`[workflow] version ${version.id} is live`);\n } finally {\n await close();\n }\n })().catch((error: unknown) => {\n applying = undefined;\n retryAt = Date.now() + 60_000;\n console.error(`[workflow] could not make version ${version.id} live`, error);\n });\n return applying;\n };\n\n return { config, checkEnv, lease, bundle, runParts, handle, sweep, applyOwnBundle };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"journey-host.mjs","names":[],"sources":["../../src/host/journey-host.ts"],"sourcesContent":["import * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport { type JourneyConfig, compileConfig } from '../config';\nimport { type Contract, ContractValidationError, checkEvent } from '../contract';\nimport { assertEmailConfig } from '../email';\nimport type { EmailTransport } from '../email-sender';\nimport { RegistryActionInvoker } from '../engine/actions';\nimport { profileUpdatedEvent } from '../engine/condition';\nimport type { JourneyObserver, MessageSender } from '../engine/ports';\nimport { RECIPIENT_PROPERTY } from '../engine/recipient';\nimport { scaleSegmentWindows } from '../engine/time-scale';\nimport type { BundleIR, NodeIR } from '../ir';\nimport { registryRenderer } from '../react-email';\nimport { reportingObserver } from '../reporting/observer';\nimport type { TrackingOptions } from '../reporting/tracking';\nimport type { JourneyRuntimeOptions } from '../runtime';\nimport type { BundleVersion, JourneyStore, StoreLease } from '../store/index';\nimport { DEFAULT_SCHEMA } from '../store/tables';\nimport { indexTemplates, registryValidator } from '../templates';\nimport type { Authorize } from './auth';\nimport { type QueueMessageLike, consumeBatches } from './batch';\nimport { channelsFromConfig, createProviderCache, runtimeFromConfig } from './channels';\nimport type { JourneyRunParts } from './execute';\nimport { serve } from './http';\nimport {\n type SweepResult,\n deployBundle,\n ingestEvent,\n sweepSegments,\n wakeSubscribers,\n} from './ingest';\nimport type { JourneyEnv, JourneyParams } from './platform';\n\n/**\n * What a journey host is, apart from the runtime it runs on: the validated environment, the\n * store lease, the outlets a run is wired to, the HTTP API (`http.ts`, with the host's parts\n * plugged in) and the bundle that goes live with the code. `journeyWorker` (Cloudflare) and\n * `journeyLambda` (AWS) are this plus how their platform starts, wakes and stops instances.\n */\n\nexport interface JourneyHostOptions<Env extends JourneyEnv> {\n /**\n * The project's `workflow.config.ts` (`defineConfig`): what the host deploys (`GET /bundle`,\n * `POST /bundle/deploy` serve `compileConfig`), the template registries the senders, the\n * validator and `/preview` render from, the contract every `/ingest` is\n * checked against, the email defaults, actions, and the runtime / APNs / FCM settings.\n * Secrets come from the env under the names `JourneyEnv` documents.\n */\n config: JourneyConfig;\n /**\n * The data plane, opened per request and per workflow run. Return a `StoreLease` when the\n * client must be released afterwards (a Postgres connection). `database` is the config's\n * (`schema` defaulting to `workflow`):\n *\n * store: (env, database) => postgresJsStore(env.DATABASE_URL, database)\n */\n store: (env: Env, database: { schema: string }) => JourneyStore | StoreLease;\n /**\n * Where journey emails go: a transport built from the validated env — `sesEmailTransport(...)`,\n * `cloudflareEmailTransport(...)`, or your own. Which one is independent of where the workflow\n * runs. Without it the email channel fails its sends, saying so.\n */\n email?: (env: Env) => EmailTransport;\n /**\n * Reporting hooks: node arrivals, every copy sent (with the tracking it should carry — click\n * links and an open pixel for email, payload data for a push), and how each instance ended.\n * By default the store records them itself, and the host serves the email tracking routes\n * (`/t/...`, on `WORKFLOW_ORIGIN`); override to report elsewhere.\n */\n observer?: (env: Env, store: JourneyStore) => JourneyObserver;\n /** Message outlet override (tests, a custom gateway): replaces every channel, `email` included. */\n messages?: (env: Env, store: JourneyStore) => MessageSender;\n /** Per-instance runtime options override (a tester in fast-forward); by default `config.runtime` for `ENVIRONMENT`. */\n runtime?: (env: Env, params: JourneyParams) => JourneyRuntimeOptions;\n /**\n * The host's environment, as a zod schema over its vars and secrets (bindings pass through\n * untouched): checked the first time the host runs, so a missing or malformed variable fails\n * there — every problem listed — not at the send that needs it. Webhook templates read their\n * URL from the parsed values: `webhook: (env: Env) => env.WEBHOOK_DISCORD_ALERTS`.\n */\n env?: core.$ZodType<object>;\n /** 'enforce' (default) answers an event or patch the contract refuses with a 400; 'report' only logs it. */\n contractMode?: 'enforce' | 'report';\n /**\n * Who may call the API routes, in whatever form the host chooses: an HMAC over the body, a\n * JWT, a header its proxy adds. Receives a copy of the request and the env; true lets it\n * through, false answers 401, a throw answers 500. The tracking routes and `/health` stay\n * public. Without it, the env's `API_TOKEN` as a bearer (`bearerMatches`), which production\n * then requires.\n *\n * authorize: async (request, env) =>\n * timingSafeEqual(request.headers.get('x-signature') ?? '', await hmac(env.SECRET, await request.text()))\n */\n authorize?: Authorize<Env>;\n}\n\n/** The host's environment failed its schema (`env` option). */\nexport class EnvValidationError extends Error {\n override readonly name = 'EnvValidationError';\n constructor(readonly issues: readonly string[]) {\n super(`the host's environment is invalid:\\n ${issues.join('\\n ')}`);\n }\n}\n\nexport interface JourneyHost<Env extends JourneyEnv> {\n config: JourneyConfig;\n /** The env, parsed by the schema once per env object (throws EnvValidationError). */\n checkEnv(env: Env): Env;\n /** A store for one request or run, its segment windows scaled with the runtime. */\n lease(env: Env): Required<StoreLease>;\n /** The bundle compiled into this code. */\n bundle(): BundleIR;\n /** The outlets one run is wired to (the store is the run's). */\n runParts(env: Env, store: JourneyStore, params: JourneyParams): JourneyRunParts;\n /**\n * The HTTP API over one store: the ingest API plus tracking, the sweep, the bundle and previews.\n * `version` is the code version serving the request, recorded by `POST /bundle/deploy`.\n */\n handle(\n request: Request,\n env: Env,\n store: JourneyStore,\n version?: BundleVersion\n ): Promise<Response>;\n /** The scheduled sweep (`sweepSegments`) on a store of its own. */\n sweep(env: Env): Promise<SweepResult>;\n /** Delivered `JourneyBatch` messages over one store, acknowledged or retried one by one. */\n consume(messages: readonly QueueMessageLike[], env: Env, store: JourneyStore): Promise<void>;\n /**\n * Make the bundle compiled into `version` live, once per process, only over an older live\n * version (the store records it). Undefined when there is nothing to do; a failure is logged\n * and retried a minute later.\n */\n applyOwnBundle(env: Env, version: BundleVersion | undefined): Promise<void> | undefined;\n}\n\n/** What the platform adapter itself contributes. */\nexport interface JourneyPlatform {\n /** An HTTP/2 fetch for APNs, where the runtime's own fetch is HTTP/1.1 (Node). */\n apnsFetch?: typeof fetch;\n}\n\nfunction sendsEmail(nodes: readonly NodeIR[]): boolean {\n return nodes.some((node) => {\n switch (node.type) {\n case 'message':\n return node.channel === 'email';\n case 'wait_until':\n return Array.isArray(node.onTimeout) && sendsEmail(node.onTimeout);\n case 'branch':\n return (\n node.cases.some((branch) => sendsEmail(branch.flow)) || sendsEmail(node.otherwise ?? [])\n );\n case 'cohort':\n return node.arms.some((arm) => sendsEmail(arm.flow));\n default:\n return false;\n }\n });\n}\n\nconst describeIssue = (issue: { path: PropertyKey[]; message: string }): string =>\n `${issue.path.join('.') || '(root)'}: ${issue.message}`;\n\n/** An event the host emits itself, checked against the contract like any ingest. */\nfunction checkedPayload(\n contract: Contract | undefined,\n event: string,\n payload: Record<string, unknown>\n): Record<string, unknown> {\n if (contract === undefined) return payload;\n const verdict = checkEvent(contract, event, payload);\n if (!verdict.ok) throw new ContractValidationError(event, verdict.issues);\n return verdict.data;\n}\n\nexport function createJourneyHost<Env extends JourneyEnv>(\n options: JourneyHostOptions<Env>,\n platform: JourneyPlatform = {}\n): JourneyHost<Env> {\n const { config } = options;\n const emailIds = indexTemplates('email', config.templates?.email);\n // A config still on `emails` (its sender would be silently dropped) fails here, not at a send\n assertEmailConfig(config, emailIds.values());\n let compiled: BundleIR | undefined;\n const bundle = () => (compiled ??= compileConfig(config));\n // A journey that emails with nowhere to send fails here too, not at its first email\n const emailing = bundle().workflows.filter((workflow) => sendsEmail(workflow.flow));\n if (options.email === undefined && options.messages === undefined && emailing.length > 0) {\n throw new TypeError(\n `workflows ${emailing.map((w) => `'${w.id}'`).join(', ')} send email, but the host has no ` +\n '`email` transport: pass `email: (env) => cloudflareEmailTransport(...)` or `sesEmailTransport(...)`'\n );\n }\n // Parsed once per env object: the runtime hands the same one to every request of an isolate\n const checked = new WeakMap<object, Env>();\n const checkEnv = (env: Env): Env => {\n if (options.env === undefined) return env;\n const cached = checked.get(env);\n if (cached !== undefined) return cached;\n const result = z.safeParse(options.env, env);\n // Names and reasons only: the values are secrets\n if (!result.success) throw new EnvValidationError(result.error.issues.map(describeIssue));\n const merged: Env = { ...env, ...result.data };\n checked.set(env, merged);\n return merged;\n };\n const emails = config.templates?.email;\n const database = { schema: config.database?.schema ?? DEFAULT_SCHEMA };\n // In fast-forward, segment windows scale with the journeys reading them (time-scale.ts)\n const lease = (env: Env): Required<StoreLease> => {\n const opened = options.store(env, database);\n const noop = async () => {};\n const factor = runtimeFromConfig(config, env).timeScale;\n return 'store' in opened\n ? { store: scaleSegmentWindows(opened.store, factor), close: opened.close ?? noop }\n : { store: scaleSegmentWindows(opened, factor), close: noop };\n };\n\n // The provider clients (APNs JWT, FCM access token) live with the host, not with a run: a\n // run's env is a fresh object on every Lambda invocation, so nothing keyed on it would be reused\n const providers = createProviderCache();\n const messages = (env: Env, store: JourneyStore): MessageSender => {\n if (options.messages !== undefined) return options.messages(env, store);\n return channelsFromConfig(config, env, {\n store,\n email: options.email?.(env),\n apnsFetch: platform.apnsFetch,\n providers,\n });\n };\n\n const observer = (env: Env, store: JourneyStore): JourneyObserver => {\n if (options.observer !== undefined) return options.observer(env, store);\n return reportingObserver(store.reporting, env.WORKFLOW_ORIGIN);\n };\n\n const runParts = (env: Env, store: JourneyStore, params: JourneyParams): JourneyRunParts => ({\n store,\n env,\n runtime:\n options.runtime === undefined ? runtimeFromConfig(config, env) : options.runtime(env, params),\n messages: messages(env, store),\n // An action ref is a registered action structurally: the same object carries name, code and hash\n actions: new RegistryActionInvoker(config.actions ?? []),\n contract: config.contract,\n validator: registryValidator(config.templates ?? {}),\n observer: observer(env, store),\n });\n\n const preview = emails === undefined ? undefined : registryRenderer(emails, config.email);\n\n /**\n * The recipients' routes (`/t/*`): a push tapped open is recorded as the config's\n * `webPush.openedEvent` — keyed by the copy, so a second tap of the same notification is the\n * same event — and an unsubscribe from all email wakes the waits reading email reachability\n * (`reachable('email')` subscribes to the email property's channel), so an `exitWhen:\n * not(reachable('email'))` takes effect without waiting for a timeout. An unsubscribe from one\n * topic wakes nothing: the user is still reachable.\n */\n const tracking = (env: Env, store: JourneyStore): TrackingOptions => {\n const opened = config.webPush?.openedEvent;\n return {\n product: config.title,\n quickMs: config.email?.tracking?.quickClickMs,\n pushOpened:\n opened === undefined\n ? undefined\n : async ({ messageId, userId, at }) => {\n const payload = checkedPayload(config.contract, opened, { message_id: messageId });\n const input = {\n id: `${opened}:${messageId}`,\n userId,\n event: opened,\n payload,\n ts: at,\n };\n await ingestEvent(env, input, store);\n },\n onUnsubscribed: async ({ userId, all }) => {\n // A topic opt-out leaves the user reachable by email: only the channel-wide one wakes\n if (!all) return;\n const email = profileUpdatedEvent(RECIPIENT_PROPERTY.email ?? 'email');\n await wakeSubscribers(env, store, userId, [email]);\n },\n };\n };\n\n const handle = (\n request: Request,\n env: Env,\n store: JourneyStore,\n version?: BundleVersion\n ): Promise<Response> =>\n serve(request, {\n env,\n store,\n validation: { contract: config.contract, mode: options.contractMode },\n authorize: options.authorize as Authorize<JourneyEnv> | undefined,\n host: {\n bundle,\n bundleVersion: version,\n preview:\n preview === undefined ? undefined : { has: (id) => emailIds.has(id), render: preview },\n tracking: tracking(env, store),\n sweep: () => sweepSegments(env, store),\n },\n });\n\n const sweep = async (env: Env) => {\n const { store, close } = lease(env);\n try {\n return await sweepSegments(env, store);\n } finally {\n await close();\n }\n };\n\n // The bundle compiled into this version goes live on its own, once per process: the first\n // request or scheduled run reads which version's bundle is live (one row) and applies this one\n // only over an older version, so one still serving during a rollout can never put its routes\n // back. A failure (say the migrations have not run) is logged and retried a minute later. Only\n // the request that starts the check gets its promise back: a request context cannot wait on\n // another's I/O.\n let applying: Promise<void> | undefined;\n let retryAt = 0;\n const applyOwnBundle = (env: Env, version: BundleVersion | undefined) => {\n if (version === undefined || version.id === '' || Number.isNaN(version.createdAt)) {\n return undefined;\n }\n if (applying !== undefined || Date.now() < retryAt) return undefined;\n applying = (async () => {\n const { store, close } = lease(env);\n try {\n const live = await store.liveBundleVersion();\n if (live !== undefined && (live.id === version.id || live.createdAt >= version.createdAt))\n return;\n const result = await deployBundle(env, bundle(), store, version);\n if (result.applied) console.log(`[workflow] version ${version.id} is live`);\n } finally {\n await close();\n }\n })().catch((error: unknown) => {\n applying = undefined;\n retryAt = Date.now() + 60_000;\n console.error(`[workflow] could not make version ${version.id} live`, error);\n });\n return applying;\n };\n\n const consume: JourneyHost<Env>['consume'] = (messages, env, store) =>\n consumeBatches(env, messages, store, {\n contract: config.contract,\n mode: options.contractMode,\n });\n\n return { config, checkEnv, lease, bundle, runParts, handle, sweep, consume, applyOwnBundle };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiGA,IAAa,qBAAb,cAAwC,MAAM;CAEvB;CADrB,OAAyB;CACzB,YAAY,QAAoC;EAC9C,MAAM,yCAAyC,OAAO,KAAK,MAAM,GAAG;EADjD,KAAA,SAAA;CAErB;AACF;AAwCA,SAAS,WAAW,OAAmC;CACrD,OAAO,MAAM,MAAM,SAAS;EAC1B,QAAQ,KAAK,MAAb;GACE,KAAK,WACH,OAAO,KAAK,YAAY;GAC1B,KAAK,cACH,OAAO,MAAM,QAAQ,KAAK,SAAS,KAAK,WAAW,KAAK,SAAS;GACnE,KAAK,UACH,OACE,KAAK,MAAM,MAAM,WAAW,WAAW,OAAO,IAAI,CAAC,KAAK,WAAW,KAAK,aAAa,CAAC,CAAC;GAE3F,KAAK,UACH,OAAO,KAAK,KAAK,MAAM,QAAQ,WAAW,IAAI,IAAI,CAAC;GACrD,SACE,OAAO;EACX;CACF,CAAC;AACH;AAEA,MAAM,iBAAiB,UACrB,GAAG,MAAM,KAAK,KAAK,GAAG,KAAK,SAAS,IAAI,MAAM;;AAGhD,SAAS,eACP,UACA,OACA,SACyB;CACzB,IAAI,aAAa,KAAA,GAAW,OAAO;CACnC,MAAM,UAAU,WAAW,UAAU,OAAO,OAAO;CACnD,IAAI,CAAC,QAAQ,IAAI,MAAM,IAAI,wBAAwB,OAAO,QAAQ,MAAM;CACxE,OAAO,QAAQ;AACjB;AAEA,SAAgB,kBACd,SACA,WAA4B,CAAC,GACX;CAClB,MAAM,EAAE,WAAW;CACnB,MAAM,WAAW,eAAe,SAAS,OAAO,WAAW,KAAK;CAEhE,kBAAkB,QAAQ,SAAS,OAAO,CAAC;CAC3C,IAAI;CACJ,MAAM,eAAgB,aAAa,cAAc,MAAM;CAEvD,MAAM,WAAW,OAAO,CAAC,CAAC,UAAU,QAAQ,aAAa,WAAW,SAAS,IAAI,CAAC;CAClF,IAAI,QAAQ,UAAU,KAAA,KAAa,QAAQ,aAAa,KAAA,KAAa,SAAS,SAAS,GACrF,MAAM,IAAI,UACR,aAAa,SAAS,KAAK,MAAM,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,2IAE3D;CAGF,MAAM,0BAAU,IAAI,QAAqB;CACzC,MAAM,YAAY,QAAkB;EAClC,IAAI,QAAQ,QAAQ,KAAA,GAAW,OAAO;EACtC,MAAM,SAAS,QAAQ,IAAI,GAAG;EAC9B,IAAI,WAAW,KAAA,GAAW,OAAO;EACjC,MAAM,SAAS,EAAE,UAAU,QAAQ,KAAK,GAAG;EAE3C,IAAI,CAAC,OAAO,SAAS,MAAM,IAAI,mBAAmB,OAAO,MAAM,OAAO,IAAI,aAAa,CAAC;EACxF,MAAM,SAAc;GAAE,GAAG;GAAK,GAAG,OAAO;EAAK;EAC7C,QAAQ,IAAI,KAAK,MAAM;EACvB,OAAO;CACT;CACA,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,WAAW,EAAE,QAAQ,OAAO,UAAU,UAAA,WAAyB;CAErE,MAAM,SAAS,QAAmC;EAChD,MAAM,SAAS,QAAQ,MAAM,KAAK,QAAQ;EAC1C,MAAM,OAAO,YAAY,CAAC;EAC1B,MAAM,SAAS,kBAAkB,QAAQ,GAAG,CAAC,CAAC;EAC9C,OAAO,WAAW,SACd;GAAE,OAAO,oBAAoB,OAAO,OAAO,MAAM;GAAG,OAAO,OAAO,SAAS;EAAK,IAChF;GAAE,OAAO,oBAAoB,QAAQ,MAAM;GAAG,OAAO;EAAK;CAChE;CAIA,MAAM,YAAY,oBAAoB;CACtC,MAAM,YAAY,KAAU,UAAuC;EACjE,IAAI,QAAQ,aAAa,KAAA,GAAW,OAAO,QAAQ,SAAS,KAAK,KAAK;EACtE,OAAO,mBAAmB,QAAQ,KAAK;GACrC;GACA,OAAO,QAAQ,QAAQ,GAAG;GAC1B,WAAW,SAAS;GACpB;EACF,CAAC;CACH;CAEA,MAAM,YAAY,KAAU,UAAyC;EACnE,IAAI,QAAQ,aAAa,KAAA,GAAW,OAAO,QAAQ,SAAS,KAAK,KAAK;EACtE,OAAO,kBAAkB,MAAM,WAAW,IAAI,eAAe;CAC/D;CAEA,MAAM,YAAY,KAAU,OAAqB,YAA4C;EAC3F;EACA;EACA,SACE,QAAQ,YAAY,KAAA,IAAY,kBAAkB,QAAQ,GAAG,IAAI,QAAQ,QAAQ,KAAK,MAAM;EAC9F,UAAU,SAAS,KAAK,KAAK;EAE7B,SAAS,IAAI,sBAAsB,OAAO,WAAW,CAAC,CAAC;EACvD,UAAU,OAAO;EACjB,WAAW,kBAAkB,OAAO,aAAa,CAAC,CAAC;EACnD,UAAU,SAAS,KAAK,KAAK;CAC/B;CAEA,MAAM,UAAU,WAAW,KAAA,IAAY,KAAA,IAAY,iBAAiB,QAAQ,OAAO,KAAK;;;;;;;;;CAUxF,MAAM,YAAY,KAAU,UAAyC;EACnE,MAAM,SAAS,OAAO,SAAS;EAC/B,OAAO;GACL,SAAS,OAAO;GAChB,SAAS,OAAO,OAAO,UAAU;GACjC,YACE,WAAW,KAAA,IACP,KAAA,IACA,OAAO,EAAE,WAAW,QAAQ,SAAS;IACnC,MAAM,UAAU,eAAe,OAAO,UAAU,QAAQ,EAAE,YAAY,UAAU,CAAC;IACjF,MAAM,QAAQ;KACZ,IAAI,GAAG,OAAO,GAAG;KACjB;KACA,OAAO;KACP;KACA,IAAI;IACN;IACA,MAAM,YAAY,KAAK,OAAO,KAAK;GACrC;GACN,gBAAgB,OAAO,EAAE,QAAQ,UAAU;IAEzC,IAAI,CAAC,KAAK;IACV,MAAM,QAAQ,oBAAoB,mBAAmB,SAAS,OAAO;IACrE,MAAM,gBAAgB,KAAK,OAAO,QAAQ,CAAC,KAAK,CAAC;GACnD;EACF;CACF;CAEA,MAAM,UACJ,SACA,KACA,OACA,YAEA,MAAM,SAAS;EACb;EACA;EACA,YAAY;GAAE,UAAU,OAAO;GAAU,MAAM,QAAQ;EAAa;EACpE,WAAW,QAAQ;EACnB,MAAM;GACJ;GACA,eAAe;GACf,SACE,YAAY,KAAA,IAAY,KAAA,IAAY;IAAE,MAAM,OAAO,SAAS,IAAI,EAAE;IAAG,QAAQ;GAAQ;GACvF,UAAU,SAAS,KAAK,KAAK;GAC7B,aAAa,cAAc,KAAK,KAAK;EACvC;CACF,CAAC;CAEH,MAAM,QAAQ,OAAO,QAAa;EAChC,MAAM,EAAE,OAAO,UAAU,MAAM,GAAG;EAClC,IAAI;GACF,OAAO,MAAM,cAAc,KAAK,KAAK;EACvC,UAAU;GACR,MAAM,MAAM;EACd;CACF;CAQA,IAAI;CACJ,IAAI,UAAU;CACd,MAAM,kBAAkB,KAAU,YAAuC;EACvE,IAAI,YAAY,KAAA,KAAa,QAAQ,OAAO,MAAM,OAAO,MAAM,QAAQ,SAAS,GAC9E;EAEF,IAAI,aAAa,KAAA,KAAa,KAAK,IAAI,IAAI,SAAS,OAAO,KAAA;EAC3D,YAAY,YAAY;GACtB,MAAM,EAAE,OAAO,UAAU,MAAM,GAAG;GAClC,IAAI;IACF,MAAM,OAAO,MAAM,MAAM,kBAAkB;IAC3C,IAAI,SAAS,KAAA,MAAc,KAAK,OAAO,QAAQ,MAAM,KAAK,aAAa,QAAQ,YAC7E;IAEF,KAAI,MADiB,aAAa,KAAK,OAAO,GAAG,OAAO,OAAO,EAAA,CACpD,SAAS,QAAQ,IAAI,sBAAsB,QAAQ,GAAG,SAAS;GAC5E,UAAU;IACR,MAAM,MAAM;GACd;EACF,EAAA,CAAG,CAAC,CAAC,OAAO,UAAmB;GAC7B,WAAW,KAAA;GACX,UAAU,KAAK,IAAI,IAAI;GACvB,QAAQ,MAAM,qCAAqC,QAAQ,GAAG,QAAQ,KAAK;EAC7E,CAAC;EACD,OAAO;CACT;CAEA,MAAM,WAAwC,UAAU,KAAK,UAC3D,eAAe,KAAK,UAAU,OAAO;EACnC,UAAU,OAAO;EACjB,MAAM,QAAQ;CAChB,CAAC;CAEH,OAAO;EAAE;EAAQ;EAAU;EAAO;EAAQ;EAAU;EAAQ;EAAO;EAAS;CAAe;AAC7F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.cjs","names":[],"sources":["../../src/host/platform.ts"],"sourcesContent":["import type { EmailBindingLike } from '../cloudflare/bindings';\nimport type { ExecutionStart } from '../store/index';\n\n/**\n * The runtime port: what the host needs from the platform it runs on, whichever one that is.\n * Cloudflare's Workflow binding satisfies it (`cloudflareJourneyBinding`), the AWS adapter's\n * `AwsInstances` implements it over durable executions.\n */\n\nexport interface WorkflowInstanceLike {\n id: string;\n sendEvent(event: { type: string; payload?: unknown }): Promise<void>;\n /** Stop the instance for good (Cloudflare Workflows' `terminate`). */\n terminate?(): Promise<void>;\n /** Where it stands (`complete`, `errored`, `terminated`, `running`, …). */\n status?(): Promise<{ status: string }>;\n}\n\n/** How the host starts, wakes and stops instances. */\nexport interface WorkflowBindingLike {\n create(options: { id: string; params: unknown }): Promise<WorkflowInstanceLike>;\n /** The instance, or an `InstanceNotFoundError` when the runtime does not know the id. */\n get(id: string): Promise<WorkflowInstanceLike>;\n /**\n * For a runtime whose start is a submit that can be lost in flight (AWS): the start to record\n * with the entry, in one transaction. A failed `create` then keeps the entry — its recorded\n * start is retried by the next ingest for the pair (`resume`) or by maintenance, never rolled\n * back and re-entered as a second execution.\n */\n plan?(options: {\n id: string;\n params: JourneyParams;\n ts: number;\n /** The code version whose routes named `params.contentHash` (absent after a forced deploy). */\n version?: string | undefined;\n }): Promise<ExecutionStart>;\n /** Retry the recorded, unconfirmed start of the (workflow, user) entry still running, if any. */\n resume?(entry: { workflow: string; userId: string }): Promise<void>;\n /**\n * Maintenance the sweep runs first: old unconfirmed starts retried (or given up on), and\n * executions the platform ended on its own (timeout, stop, crash) reconciled into the ledger.\n */\n maintain?(): Promise<void>;\n}\n\n/**\n * The runtime does not know the instance: the only lookup failure that lets the host drop the\n * instance's wakes, or count it as stopped. Every `WorkflowBindingLike.get` answers it this way.\n */\nexport class InstanceNotFoundError extends Error {\n override readonly name = 'InstanceNotFoundError';\n constructor(\n readonly instanceId: string,\n options?: ErrorOptions\n ) {\n super(`no such instance: ${instanceId}`, options);\n }\n}\n\n/** The event type the host uses to wake waiting instances (sendEvent's `type`). */\nexport const WAKE_EVENT_TYPE = 'wake';\n\n/** Journey instance parameters, passed by the host when it creates one. */\nexport interface JourneyParams {\n workflowId: string;\n /** The IR version pinned at entry. */\n contentHash: string;\n userId: string;\n trigger: { event: string; payload: Record<string, unknown
|
|
1
|
+
{"version":3,"file":"platform.cjs","names":[],"sources":["../../src/host/platform.ts"],"sourcesContent":["import type { EmailBindingLike } from '../cloudflare/bindings';\nimport type { ExecutionStart } from '../store/index';\n\n/**\n * The runtime port: what the host needs from the platform it runs on, whichever one that is.\n * Cloudflare's Workflow binding satisfies it (`cloudflareJourneyBinding`), the AWS adapter's\n * `AwsInstances` implements it over durable executions.\n */\n\nexport interface WorkflowInstanceLike {\n id: string;\n sendEvent(event: { type: string; payload?: unknown }): Promise<void>;\n /** Stop the instance for good (Cloudflare Workflows' `terminate`). */\n terminate?(): Promise<void>;\n /** Where it stands (`complete`, `errored`, `terminated`, `running`, …). */\n status?(): Promise<{ status: string }>;\n}\n\n/** How the host starts, wakes and stops instances. */\nexport interface WorkflowBindingLike {\n create(options: { id: string; params: unknown }): Promise<WorkflowInstanceLike>;\n /** The instance, or an `InstanceNotFoundError` when the runtime does not know the id. */\n get(id: string): Promise<WorkflowInstanceLike>;\n /**\n * For a runtime whose start is a submit that can be lost in flight (AWS): the start to record\n * with the entry, in one transaction. A failed `create` then keeps the entry — its recorded\n * start is retried by the next ingest for the pair (`resume`) or by maintenance, never rolled\n * back and re-entered as a second execution.\n */\n plan?(options: {\n id: string;\n params: JourneyParams;\n ts: number;\n /** The code version whose routes named `params.contentHash` (absent after a forced deploy). */\n version?: string | undefined;\n }): Promise<ExecutionStart>;\n /** Retry the recorded, unconfirmed start of the (workflow, user) entry still running, if any. */\n resume?(entry: { workflow: string; userId: string }): Promise<void>;\n /**\n * Maintenance the sweep runs first: old unconfirmed starts retried (or given up on), and\n * executions the platform ended on its own (timeout, stop, crash) reconciled into the ledger.\n */\n maintain?(): Promise<void>;\n}\n\n/**\n * The runtime does not know the instance: the only lookup failure that lets the host drop the\n * instance's wakes, or count it as stopped. Every `WorkflowBindingLike.get` answers it this way.\n */\nexport class InstanceNotFoundError extends Error {\n override readonly name = 'InstanceNotFoundError';\n constructor(\n readonly instanceId: string,\n options?: ErrorOptions\n ) {\n super(`no such instance: ${instanceId}`, options);\n }\n}\n\n/** The event type the host uses to wake waiting instances (sendEvent's `type`). */\nexport const WAKE_EVENT_TYPE = 'wake';\n\n/** Journey instance parameters, passed by the host when it creates one. */\nexport interface JourneyParams {\n workflowId: string;\n /** The IR version pinned at entry. */\n contentHash: string;\n userId: string;\n /** `ts`: when the trigger happened, which can be long before a delayed delivery created this. */\n trigger: { event: string; payload: Record<string, unknown>; ts?: number };\n [key: string]: unknown;\n}\n\n/** Every binding, variable and secret the journey host reads (names match the wrangler config). */\nexport interface JourneyEnv {\n /** The runtime port: creating instances, waking and stopping them. */\n JOURNEY: WorkflowBindingLike;\n /** Optional message-delivery webhook (defaults to the console logging sender). */\n MESSAGE_WEBHOOK_URL?: string;\n /**\n * Bearer token required on every mutating HTTP endpoint (`Authorization:\n * Bearer <token>`). Unset = open — acceptable only for local dev; /deploy in\n * particular rewrites the whole routing table, so production must set this.\n */\n API_TOKEN?: string;\n /**\n * The host's public origin. With a store that keeps reporting (Postgres), journey emails'\n * links and open pixel point at its `/t/...` routes; without it emails go out untracked.\n */\n WORKFLOW_ORIGIN?: string;\n /** Which `workflow.config.ts` entry applies (`development`, `production`, …); production when unset. */\n ENVIRONMENT?: string;\n /** Cloudflare Email Sending, when the config has `emails`. */\n EMAIL?: EmailBindingLike;\n /** The APNs auth key (`.p8`, base64) named by the config's `apns.keyId`. */\n APNS_PRIVATE_KEY_BASE64?: string;\n /** The Firebase service account (base64 JSON) the config's `fcm` sends with. */\n GOOGLE_APPLICATION_CREDENTIALS_BASE64?: string;\n /** The VAPID key pair web pushes are signed with (`generateVapidKeys`); the config's `webPush` turns the channel on. */\n VAPID_PUBLIC_KEY?: string;\n VAPID_PRIVATE_KEY?: string;\n /**\n * Cloudflare's version metadata binding (wrangler `\"version_metadata\": { \"binding\":\n * \"CF_VERSION_METADATA\" }`). With it, and a store that records the live version (Postgres),\n * journeyWorker makes the bundle compiled into each new version live by itself: `wrangler\n * deploy` is the whole deploy.\n */\n CF_VERSION_METADATA?: { id: string; tag?: string; timestamp: string };\n /** Development only: the HTTP/2 relay `wrangler dev` sends APNs requests through (see `@shware/workflow/apns-relay`). */\n APNS_RELAY_ORIGIN?: string;\n}\n"],"mappings":";;;;;;AAiDA,IAAa,wBAAb,cAA2C,MAAM;CAGpC;CAFX,OAAyB;CACzB,YACE,YACA,SACA;EACA,MAAM,qBAAqB,cAAc,OAAO;EAHvC,KAAA,aAAA;CAIX;AACF;;AAGA,MAAa,kBAAkB"}
|
package/dist/host/platform.d.cts
CHANGED
|
@@ -67,9 +67,11 @@ export interface JourneyParams {
|
|
|
67
67
|
/** The IR version pinned at entry. */
|
|
68
68
|
contentHash: string;
|
|
69
69
|
userId: string;
|
|
70
|
+
/** `ts`: when the trigger happened, which can be long before a delayed delivery created this. */
|
|
70
71
|
trigger: {
|
|
71
72
|
event: string;
|
|
72
73
|
payload: Record<string, unknown>;
|
|
74
|
+
ts?: number;
|
|
73
75
|
};
|
|
74
76
|
[key: string]: unknown;
|
|
75
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.cts","names":[],"sources":["../../src/host/platform.ts"],"mappings":";;;;;;;iBASiB;EACf;EACA,UAAU;IAAS;IAAc;MAAsB;;EAEvD,cAAc;;EAEd,WAAW;IAAU;;;;iBAIN;EACf,OAAO;IAAW;IAAY;MAAoB,QAAQ;;EAE1D,IAAI,aAAa,QAAQ;;;;;;;EAOzB,MAAM;IACJ;IACA,QAAQ;IACR;;IAEA;MACE,QAAQ;;EAEZ,QAAQ;IAAS;IAAkB;MAAmB;;;;;EAKtD,aAAa;;;;;;qBAOF,8BAA8B;WAG9B;WAFO;EAClB,YACW,oBACT,UAAU;;;qBAOD;;iBAGI;EACf;;EAEA;EACA;
|
|
1
|
+
{"version":3,"file":"platform.d.cts","names":[],"sources":["../../src/host/platform.ts"],"mappings":";;;;;;;iBASiB;EACf;EACA,UAAU;IAAS;IAAc;MAAsB;;EAEvD,cAAc;;EAEd,WAAW;IAAU;;;;iBAIN;EACf,OAAO;IAAW;IAAY;MAAoB,QAAQ;;EAE1D,IAAI,aAAa,QAAQ;;;;;;;EAOzB,MAAM;IACJ;IACA,QAAQ;IACR;;IAEA;MACE,QAAQ;;EAEZ,QAAQ;IAAS;IAAkB;MAAmB;;;;;EAKtD,aAAa;;;;;;qBAOF,8BAA8B;WAG9B;WAFO;EAClB,YACW,oBACT,UAAU;;;qBAOD;;iBAGI;EACf;;EAEA;EACA;;EAEA;IAAW;IAAe,SAAS;IAAyB;;GAC3D;;;iBAIc;;EAEf,SAAS;;EAET;;;;;;EAMA;;;;;EAKA;;EAEA;;EAEA,QAAQ;;EAER;;EAEA;;EAEA;EACA;;;;;;;EAOA;IAAwB;IAAY;IAAc;;;EAElD"}
|
package/dist/host/platform.d.mts
CHANGED
|
@@ -67,9 +67,11 @@ export interface JourneyParams {
|
|
|
67
67
|
/** The IR version pinned at entry. */
|
|
68
68
|
contentHash: string;
|
|
69
69
|
userId: string;
|
|
70
|
+
/** `ts`: when the trigger happened, which can be long before a delayed delivery created this. */
|
|
70
71
|
trigger: {
|
|
71
72
|
event: string;
|
|
72
73
|
payload: Record<string, unknown>;
|
|
74
|
+
ts?: number;
|
|
73
75
|
};
|
|
74
76
|
[key: string]: unknown;
|
|
75
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.mts","names":[],"sources":["../../src/host/platform.ts"],"mappings":";;;;;;;iBASiB;EACf;EACA,UAAU;IAAS;IAAc;MAAsB;;EAEvD,cAAc;;EAEd,WAAW;IAAU;;;;iBAIN;EACf,OAAO;IAAW;IAAY;MAAoB,QAAQ;;EAE1D,IAAI,aAAa,QAAQ;;;;;;;EAOzB,MAAM;IACJ;IACA,QAAQ;IACR;;IAEA;MACE,QAAQ;;EAEZ,QAAQ;IAAS;IAAkB;MAAmB;;;;;EAKtD,aAAa;;;;;;qBAOF,8BAA8B;WAG9B;WAFO;EAClB,YACW,oBACT,UAAU;;;qBAOD;;iBAGI;EACf;;EAEA;EACA;
|
|
1
|
+
{"version":3,"file":"platform.d.mts","names":[],"sources":["../../src/host/platform.ts"],"mappings":";;;;;;;iBASiB;EACf;EACA,UAAU;IAAS;IAAc;MAAsB;;EAEvD,cAAc;;EAEd,WAAW;IAAU;;;;iBAIN;EACf,OAAO;IAAW;IAAY;MAAoB,QAAQ;;EAE1D,IAAI,aAAa,QAAQ;;;;;;;EAOzB,MAAM;IACJ;IACA,QAAQ;IACR;;IAEA;MACE,QAAQ;;EAEZ,QAAQ;IAAS;IAAkB;MAAmB;;;;;EAKtD,aAAa;;;;;;qBAOF,8BAA8B;WAG9B;WAFO;EAClB,YACW,oBACT,UAAU;;;qBAOD;;iBAGI;EACf;;EAEA;EACA;;EAEA;IAAW;IAAe,SAAS;IAAyB;;GAC3D;;;iBAIc;;EAEf,SAAS;;EAET;;;;;;EAMA;;;;;EAKA;;EAEA;;EAEA,QAAQ;;EAER;;EAEA;;EAEA;EACA;;;;;;;EAOA;IAAwB;IAAY;IAAc;;;EAElD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.mjs","names":[],"sources":["../../src/host/platform.ts"],"sourcesContent":["import type { EmailBindingLike } from '../cloudflare/bindings';\nimport type { ExecutionStart } from '../store/index';\n\n/**\n * The runtime port: what the host needs from the platform it runs on, whichever one that is.\n * Cloudflare's Workflow binding satisfies it (`cloudflareJourneyBinding`), the AWS adapter's\n * `AwsInstances` implements it over durable executions.\n */\n\nexport interface WorkflowInstanceLike {\n id: string;\n sendEvent(event: { type: string; payload?: unknown }): Promise<void>;\n /** Stop the instance for good (Cloudflare Workflows' `terminate`). */\n terminate?(): Promise<void>;\n /** Where it stands (`complete`, `errored`, `terminated`, `running`, …). */\n status?(): Promise<{ status: string }>;\n}\n\n/** How the host starts, wakes and stops instances. */\nexport interface WorkflowBindingLike {\n create(options: { id: string; params: unknown }): Promise<WorkflowInstanceLike>;\n /** The instance, or an `InstanceNotFoundError` when the runtime does not know the id. */\n get(id: string): Promise<WorkflowInstanceLike>;\n /**\n * For a runtime whose start is a submit that can be lost in flight (AWS): the start to record\n * with the entry, in one transaction. A failed `create` then keeps the entry — its recorded\n * start is retried by the next ingest for the pair (`resume`) or by maintenance, never rolled\n * back and re-entered as a second execution.\n */\n plan?(options: {\n id: string;\n params: JourneyParams;\n ts: number;\n /** The code version whose routes named `params.contentHash` (absent after a forced deploy). */\n version?: string | undefined;\n }): Promise<ExecutionStart>;\n /** Retry the recorded, unconfirmed start of the (workflow, user) entry still running, if any. */\n resume?(entry: { workflow: string; userId: string }): Promise<void>;\n /**\n * Maintenance the sweep runs first: old unconfirmed starts retried (or given up on), and\n * executions the platform ended on its own (timeout, stop, crash) reconciled into the ledger.\n */\n maintain?(): Promise<void>;\n}\n\n/**\n * The runtime does not know the instance: the only lookup failure that lets the host drop the\n * instance's wakes, or count it as stopped. Every `WorkflowBindingLike.get` answers it this way.\n */\nexport class InstanceNotFoundError extends Error {\n override readonly name = 'InstanceNotFoundError';\n constructor(\n readonly instanceId: string,\n options?: ErrorOptions\n ) {\n super(`no such instance: ${instanceId}`, options);\n }\n}\n\n/** The event type the host uses to wake waiting instances (sendEvent's `type`). */\nexport const WAKE_EVENT_TYPE = 'wake';\n\n/** Journey instance parameters, passed by the host when it creates one. */\nexport interface JourneyParams {\n workflowId: string;\n /** The IR version pinned at entry. */\n contentHash: string;\n userId: string;\n trigger: { event: string; payload: Record<string, unknown
|
|
1
|
+
{"version":3,"file":"platform.mjs","names":[],"sources":["../../src/host/platform.ts"],"sourcesContent":["import type { EmailBindingLike } from '../cloudflare/bindings';\nimport type { ExecutionStart } from '../store/index';\n\n/**\n * The runtime port: what the host needs from the platform it runs on, whichever one that is.\n * Cloudflare's Workflow binding satisfies it (`cloudflareJourneyBinding`), the AWS adapter's\n * `AwsInstances` implements it over durable executions.\n */\n\nexport interface WorkflowInstanceLike {\n id: string;\n sendEvent(event: { type: string; payload?: unknown }): Promise<void>;\n /** Stop the instance for good (Cloudflare Workflows' `terminate`). */\n terminate?(): Promise<void>;\n /** Where it stands (`complete`, `errored`, `terminated`, `running`, …). */\n status?(): Promise<{ status: string }>;\n}\n\n/** How the host starts, wakes and stops instances. */\nexport interface WorkflowBindingLike {\n create(options: { id: string; params: unknown }): Promise<WorkflowInstanceLike>;\n /** The instance, or an `InstanceNotFoundError` when the runtime does not know the id. */\n get(id: string): Promise<WorkflowInstanceLike>;\n /**\n * For a runtime whose start is a submit that can be lost in flight (AWS): the start to record\n * with the entry, in one transaction. A failed `create` then keeps the entry — its recorded\n * start is retried by the next ingest for the pair (`resume`) or by maintenance, never rolled\n * back and re-entered as a second execution.\n */\n plan?(options: {\n id: string;\n params: JourneyParams;\n ts: number;\n /** The code version whose routes named `params.contentHash` (absent after a forced deploy). */\n version?: string | undefined;\n }): Promise<ExecutionStart>;\n /** Retry the recorded, unconfirmed start of the (workflow, user) entry still running, if any. */\n resume?(entry: { workflow: string; userId: string }): Promise<void>;\n /**\n * Maintenance the sweep runs first: old unconfirmed starts retried (or given up on), and\n * executions the platform ended on its own (timeout, stop, crash) reconciled into the ledger.\n */\n maintain?(): Promise<void>;\n}\n\n/**\n * The runtime does not know the instance: the only lookup failure that lets the host drop the\n * instance's wakes, or count it as stopped. Every `WorkflowBindingLike.get` answers it this way.\n */\nexport class InstanceNotFoundError extends Error {\n override readonly name = 'InstanceNotFoundError';\n constructor(\n readonly instanceId: string,\n options?: ErrorOptions\n ) {\n super(`no such instance: ${instanceId}`, options);\n }\n}\n\n/** The event type the host uses to wake waiting instances (sendEvent's `type`). */\nexport const WAKE_EVENT_TYPE = 'wake';\n\n/** Journey instance parameters, passed by the host when it creates one. */\nexport interface JourneyParams {\n workflowId: string;\n /** The IR version pinned at entry. */\n contentHash: string;\n userId: string;\n /** `ts`: when the trigger happened, which can be long before a delayed delivery created this. */\n trigger: { event: string; payload: Record<string, unknown>; ts?: number };\n [key: string]: unknown;\n}\n\n/** Every binding, variable and secret the journey host reads (names match the wrangler config). */\nexport interface JourneyEnv {\n /** The runtime port: creating instances, waking and stopping them. */\n JOURNEY: WorkflowBindingLike;\n /** Optional message-delivery webhook (defaults to the console logging sender). */\n MESSAGE_WEBHOOK_URL?: string;\n /**\n * Bearer token required on every mutating HTTP endpoint (`Authorization:\n * Bearer <token>`). Unset = open — acceptable only for local dev; /deploy in\n * particular rewrites the whole routing table, so production must set this.\n */\n API_TOKEN?: string;\n /**\n * The host's public origin. With a store that keeps reporting (Postgres), journey emails'\n * links and open pixel point at its `/t/...` routes; without it emails go out untracked.\n */\n WORKFLOW_ORIGIN?: string;\n /** Which `workflow.config.ts` entry applies (`development`, `production`, …); production when unset. */\n ENVIRONMENT?: string;\n /** Cloudflare Email Sending, when the config has `emails`. */\n EMAIL?: EmailBindingLike;\n /** The APNs auth key (`.p8`, base64) named by the config's `apns.keyId`. */\n APNS_PRIVATE_KEY_BASE64?: string;\n /** The Firebase service account (base64 JSON) the config's `fcm` sends with. */\n GOOGLE_APPLICATION_CREDENTIALS_BASE64?: string;\n /** The VAPID key pair web pushes are signed with (`generateVapidKeys`); the config's `webPush` turns the channel on. */\n VAPID_PUBLIC_KEY?: string;\n VAPID_PRIVATE_KEY?: string;\n /**\n * Cloudflare's version metadata binding (wrangler `\"version_metadata\": { \"binding\":\n * \"CF_VERSION_METADATA\" }`). With it, and a store that records the live version (Postgres),\n * journeyWorker makes the bundle compiled into each new version live by itself: `wrangler\n * deploy` is the whole deploy.\n */\n CF_VERSION_METADATA?: { id: string; tag?: string; timestamp: string };\n /** Development only: the HTTP/2 relay `wrangler dev` sends APNs requests through (see `@shware/workflow/apns-relay`). */\n APNS_RELAY_ORIGIN?: string;\n}\n"],"mappings":";;;;;AAiDA,IAAa,wBAAb,cAA2C,MAAM;CAGpC;CAFX,OAAyB;CACzB,YACE,YACA,SACA;EACA,MAAM,qBAAqB,cAAc,OAAO;EAHvC,KAAA,aAAA;CAIX;AACF;;AAGA,MAAa,kBAAkB"}
|
package/dist/ir.cjs
CHANGED
|
@@ -172,7 +172,8 @@ const TriggerIR = zod_mini.discriminatedUnion("type", [
|
|
|
172
172
|
type: zod_mini.literal("event"),
|
|
173
173
|
event: zod_mini.string(),
|
|
174
174
|
where: zod_mini.optional(ConditionIR),
|
|
175
|
-
filter: zod_mini.optional(ConditionIR)
|
|
175
|
+
filter: zod_mini.optional(ConditionIR),
|
|
176
|
+
visitors: zod_mini.optional(zod_mini.literal(true))
|
|
176
177
|
}),
|
|
177
178
|
zod_mini.object({
|
|
178
179
|
type: zod_mini.literal("segment"),
|
package/dist/ir.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir.cjs","names":["z"],"sources":["../src/ir.ts"],"sourcesContent":["import * as z from 'zod/mini';\n\n/**\n * IR (Intermediate Representation) — what the DSL compiles to: serializable\n * JSON. It is the three-way contract between the engine that interprets it,\n * the UI (react-flow) that renders it, and diff / audit tooling.\n *\n * Validation strategy: the zod/mini schemas are the authoritative definition.\n * TS types are either inferred from a schema or hand-written under the same\n * name (zod cannot infer recursive types; the hand-written type is anchored\n * with a ZodMiniType<T> annotation, so any drift fails to compile). IR crosses\n * process boundaries (database, network, UI), so runtime validation is a must.\n *\n * ## Versioning (two independent axes)\n *\n * 1. IR format version (irVersion): evolution of the IR schema itself. Adding\n * optional fields does not bump it; only structurally incompatible changes\n * do (changing a discriminant, dropping a field, altering semantics).\n * Readers dispatch on irVersion to the matching schema/migrator; writers\n * always write the current version.\n * 2. Content version (contentHash): a content-addressed id for a single\n * definition's **execution semantics** — the canonical-JSON hash after\n * metadata (meta / label / reason) is stripped, see hash.ts. It is used to:\n * - pin a version when a user enters, so an in-flight journey runs the\n * pinned version to completion (the default policy; compatible hot\n * updates are a future explicit-migration topic). Known exception:\n * segment conditions are resolved **by id at evaluation time** — a\n * segment redeploy changes behaviour of in-flight journeys that\n * reference it. Pinning covers the workflow's own structure; per-hash\n * segment snapshots are future work;\n * - diff on deploy: same hash means unchanged, skip publishing;\n * - audit: answer \"which version was this user on\" at any point.\n *\n * ## Node ids\n *\n * Derived from the structural path at compile time — stable and deterministic:\n * root level '0', '1', …; the j-th child of a branch's i-th case is\n * '{branchId}.c{i}.{j}', the default arm '{branchId}.o.{j}'; cohort arms use\n * the arm name '{cohortId}.{armName}.{j}'; a waitUntil timeout flow '{id}.t.{j}'.\n * Editing a node's parameters keeps its id; inserting a node shifts the ids of\n * its later siblings (a known trade-off — locating in-flight users relies on\n * the pinned version rather than cross-version id alignment, and cross-version\n * mapping is an explicit migrator's job).\n */\n\nexport const IR_VERSION = 2;\n\n/* -------------------------------- Base values ------------------------------- */\n\nexport const ScalarIR = z.union([z.string(), z.number(), z.boolean()]);\nexport type ScalarIR = z.infer<typeof ScalarIR>;\n\n/** Duration: `value` keeps the DSL literal ('23 hours', for display), `ms` is what the engine uses. */\n/*\n * TODO: `value` is hashed alongside `ms`, so '7 days' / '1 week' / '168 hours'\n * — identical to the engine, which only reads ms — produce three different\n * contentHashes. Rewording a duration therefore reports as a semantic change in\n * plan and repins in-flight users. Moving `value` out of the hash (it is a\n * display string) would fix it; deferred because it changes existing hashes.\n */\nexport const DurationIR = z.object({ value: z.string(), ms: z.number() });\nexport type DurationIR = z.infer<typeof DurationIR>;\n\n/** User-property reference — isomorphic to the DSL's UserPropertyRef once serialized (phantom type dropped). */\nexport const UserPropertyRefIR = z.object({\n type: z.literal('user_property'),\n path: z.string(),\n});\nexport type UserPropertyRefIR = z.infer<typeof UserPropertyRefIR>;\n\n/** A message prop / event payload value: either a literal or a user-property reference. */\nexport const PropValueIR = z.union([ScalarIR, UserPropertyRefIR]);\nexport type PropValueIR = z.infer<typeof PropValueIR>;\n\nexport const ChannelIR = z.enum(['email', 'sms', 'push', 'in_app', 'slack', 'discord', 'survey']);\nexport type ChannelIR = z.infer<typeof ChannelIR>;\n\nexport const WeekdayIR = z.enum(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']);\nexport type WeekdayIR = z.infer<typeof WeekdayIR>;\n\n/* --------------------------------- Provenance -------------------------------- */\n\n/**\n * Source location of the builder call that produced a definition — the IR's\n * source map. Lives under `meta`, so it is **excluded from contentHash**\n * (see hash.ts): provenance never affects execution identity or plan.\n * `file` is cwd-relative when compiled under Node, a URL under Vite dev.\n */\nexport const SourceLocIR = z.object({\n file: z.string(),\n line: z.number(),\n column: z.number(),\n});\nexport type SourceLocIR = z.infer<typeof SourceLocIR>;\n\n/** Node-level metadata: provenance only, for now. Excluded from contentHash. */\nexport const NodeMetaIR = z.object({\n loc: z.optional(SourceLocIR),\n});\nexport type NodeMetaIR = z.infer<typeof NodeMetaIR>;\n\n/* -------------------------------- Conditions -------------------------------- */\n\nexport const PropertyOperatorIR = z.enum([\n 'eq',\n 'ne',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'between',\n 'not_between',\n 'in_array',\n 'not_in_array',\n 'exists',\n 'not_exists',\n 'contains',\n 'not_contains',\n]);\nexport type PropertyOperatorIR = z.infer<typeof PropertyOperatorIR>;\n\n/**\n * Condition expression tree. The shape of value/values follows `op`:\n * eq/ne/gt/gte/lt/lte/contains/not_contains → value; in_array/not_in_array →\n * values; between/not_between → values = [min, max]; exists/not_exists → neither.\n *\n * `payload` predicates test the payload of an event (dotted paths reach nested\n * fields). They are only valid inside a `where` subtree — `performed.where`\n * and the event trigger's `where` — where an event row is in scope; the\n * compiler (compileBundle) and the evaluator both enforce the placement.\n * TODO: runtime refinement narrowing by op.\n */\n/**\n * Provenance rides on conditions too, so the studio can point at the exact\n * `eq(...)` / `and(...)` that produced a node — same rule as nodes: `meta` is\n * dropped before hashing, so carrying it never affects execution identity.\n */\ninterface ConditionBaseIR {\n meta?: NodeMetaIR | undefined;\n}\n\nexport type ConditionIR =\n | (ConditionBaseIR & { type: 'and'; conditions: ConditionIR[] })\n | (ConditionBaseIR & { type: 'or'; conditions: ConditionIR[] })\n | (ConditionBaseIR & { type: 'not'; condition: ConditionIR })\n /** Reference a segment by id (defined in SegmentIR, versioned separately). */\n | (ConditionBaseIR & { type: 'segment'; segment: string })\n | (ConditionBaseIR & {\n type: 'property';\n path: string;\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n })\n | (ConditionBaseIR & {\n /** Event payload predicate; `path` is dotted for nested fields ('tags.utm_source'). */\n type: 'payload';\n path: string;\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n })\n | (ConditionBaseIR & {\n type: 'performed';\n event: string;\n /** Payload filter: only events matching it count (a payload-only subtree). */\n where?: ConditionIR | undefined;\n within?: DurationIR | undefined;\n count?: number | undefined;\n })\n /**\n * The user can be reached on the channel right now: for push, the host's\n * device directory lists at least one device; for the others, the channel's\n * recipient property (email, phone, …) is set.\n */\n | (ConditionBaseIR & { type: 'reachable'; channel: ChannelIR });\n\nconst conditionBase = { meta: z.optional(NodeMetaIR) };\n\nexport const ConditionIR: z.ZodMiniType<ConditionIR> = z.lazy(() =>\n z.discriminatedUnion('type', [\n z.object({ ...conditionBase, type: z.literal('and'), conditions: z.array(ConditionIR) }),\n z.object({ ...conditionBase, type: z.literal('or'), conditions: z.array(ConditionIR) }),\n z.object({ ...conditionBase, type: z.literal('not'), condition: ConditionIR }),\n z.object({ ...conditionBase, type: z.literal('segment'), segment: z.string() }),\n z.object({\n ...conditionBase,\n type: z.literal('property'),\n path: z.string(),\n op: PropertyOperatorIR,\n value: z.optional(ScalarIR),\n values: z.optional(z.array(ScalarIR)),\n }),\n z.object({\n ...conditionBase,\n type: z.literal('payload'),\n path: z.string(),\n op: PropertyOperatorIR,\n value: z.optional(ScalarIR),\n values: z.optional(z.array(ScalarIR)),\n }),\n z.object({\n ...conditionBase,\n type: z.literal('performed'),\n event: z.string(),\n where: z.optional(ConditionIR),\n within: z.optional(DurationIR),\n count: z.optional(z.number()),\n }),\n z.object({ ...conditionBase, type: z.literal('reachable'), channel: ChannelIR }),\n ])\n);\n\n/* --------------------------------- Triggers --------------------------------- */\n\n/**\n * Trigger shapes. The event trigger has two independent gates: `where` tests\n * the triggering event's payload (payload-only subtree, evaluated against the\n * incoming event before anything else), `filter` tests the user's profile.\n */\nexport const TriggerIR = z.discriminatedUnion('type', [\n z.object({\n type: z.literal('event'),\n event: z.string(),\n where: z.optional(ConditionIR),\n filter: z.optional(ConditionIR),\n }),\n z.object({ type: z.literal('segment'), segment: z.string() }),\n z.object({ type: z.literal('date'), at: z.string() }),\n z.object({ type: z.literal('webhook') }),\n]);\nexport type TriggerIR = z.infer<typeof TriggerIR>;\n\n/* ----------------------------------- Nodes ---------------------------------- */\n\ninterface NodeBaseIR {\n /** Stable id derived from the structural path (rules in the file header). */\n id: string;\n /** Optional node name: UI title / observability handle (branch's optional first argument lands here). */\n label?: string | undefined;\n /** Metadata (provenance); excluded from contentHash. */\n meta?: NodeMetaIR | undefined;\n}\n\nexport type NodeIR =\n | (NodeBaseIR & {\n type: 'message';\n channel: ChannelIR;\n template: string;\n props: Record<string, PropValueIR>;\n /**\n * Push only: which of the user's devices get a copy. Absent means every\n * device the directory lists; 'latest' only the most recently registered.\n */\n devices?: 'latest' | undefined;\n /**\n * Email and push: the template's subscription topic, or null for a transactional message\n * (sent whatever the user opted out of). Absent for other channels, and in IR compiled\n * before topics existed: such a step checks the channel-level opt-out only, as it always did.\n */\n topic?: string | null | undefined;\n })\n | (NodeBaseIR & { type: 'delay'; duration: DurationIR })\n | (NodeBaseIR & { type: 'random_delay'; min: DurationIR; max: DurationIR })\n | (NodeBaseIR & {\n type: 'time_window';\n days: WeekdayIR[];\n between: [string, string];\n tz: string;\n })\n | (NodeBaseIR & {\n type: 'wait_until';\n condition: ConditionIR;\n timeout: DurationIR;\n onTimeout: 'continue' | 'exit' | NodeIR[];\n })\n | (NodeBaseIR & {\n type: 'branch';\n cases: { label?: string | undefined; condition: ConditionIR; flow: NodeIR[] }[];\n /** Default arm (the DSL's bare tail argument). Absent = fall through to the main line. */\n otherwise?: NodeIR[] | undefined;\n })\n | (NodeBaseIR & {\n type: 'filter';\n /** Audit-log line for the users this gate drops. Metadata: excluded from contentHash. */\n reason?: string | undefined;\n condition: ConditionIR;\n })\n | (NodeBaseIR & {\n type: 'cohort';\n /**\n * Explicit experiment key: bucketing hashes `userId:key` instead of the\n * structural node id, so the assignment survives node insertion/moves and\n * the experiment keeps one identity across workflow versions. Absent →\n * the node id is used (fine for one-off splits).\n */\n key?: string | undefined;\n /**\n * Arms in authoring order — the order the interpreter accumulates weights\n * in, so it decides which bucket range each arm owns. The DSL keeps that\n * promise by rejecting integer-like arm names, which JavaScript would\n * hoist to the front of the object in numeric order. Weights sum to 100.\n */\n arms: { name: string; weight: number; flow: NodeIR[] }[];\n })\n | (NodeBaseIR & {\n /** `reason` is the audit-log line. Metadata: excluded from contentHash. */\n type: 'exit';\n reason?: string | undefined;\n })\n | (NodeBaseIR & { type: 'send_event'; event: string; payload: Record<string, PropValueIR> })\n | (NodeBaseIR & {\n type: 'action';\n /** Registry name of the custom action; the code itself lives in the deployed Worker bundle. */\n action: string;\n /** Argument values; user_property references are resolved at run time, like message props. */\n args: Record<string, PropValueIR>;\n });\n\nconst nodeBase = { id: z.string(), label: z.optional(z.string()), meta: z.optional(NodeMetaIR) };\n\nexport const NodeIR: z.ZodMiniType<NodeIR> = z.lazy(() =>\n z.discriminatedUnion('type', [\n z.object({\n ...nodeBase,\n type: z.literal('message'),\n channel: ChannelIR,\n template: z.string(),\n props: z.record(z.string(), PropValueIR),\n devices: z.optional(z.literal('latest')),\n topic: z.optional(z.nullable(z.string())),\n }),\n z.object({ ...nodeBase, type: z.literal('delay'), duration: DurationIR }),\n z.object({ ...nodeBase, type: z.literal('random_delay'), min: DurationIR, max: DurationIR }),\n z.object({\n ...nodeBase,\n type: z.literal('time_window'),\n days: z.array(WeekdayIR),\n between: z.tuple([z.string(), z.string()]),\n tz: z.string(),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('wait_until'),\n condition: ConditionIR,\n timeout: DurationIR,\n onTimeout: z.union([z.literal('continue'), z.literal('exit'), z.array(NodeIR)]),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('branch'),\n cases: z.array(\n z.object({\n label: z.optional(z.string()),\n condition: ConditionIR,\n flow: z.array(NodeIR),\n })\n ),\n otherwise: z.optional(z.array(NodeIR)),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('filter'),\n condition: ConditionIR,\n reason: z.optional(z.string()),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('cohort'),\n key: z.optional(z.string()),\n arms: z.array(z.object({ name: z.string(), weight: z.number(), flow: z.array(NodeIR) })),\n }),\n z.object({ ...nodeBase, type: z.literal('exit'), reason: z.optional(z.string()) }),\n z.object({\n ...nodeBase,\n type: z.literal('send_event'),\n event: z.string(),\n payload: z.record(z.string(), PropValueIR),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('action'),\n action: z.string(),\n args: z.record(z.string(), PropValueIR),\n }),\n ])\n);\n\n/* ---------------------- Top-level definitions and bundle --------------------- */\n\nexport const GoalIR = z.object({\n condition: ConditionIR,\n within: z.optional(DurationIR),\n exitOnMatch: z.boolean(),\n});\nexport type GoalIR = z.infer<typeof GoalIR>;\n\n/**\n * Human-facing metadata: **excluded from contentHash** (see hash.ts).\n * Editing a name or description does not touch execution semantics, so it never\n * invalidates the version in-flight users are pinned to, nor shows up in plan.\n */\nexport const MetaIR = z.object({\n /**\n * Human label. The asset's `id` is its identity — a wire key that routing\n * tables and the entry ledger are built on — so it is not something to\n * rename for readability. This is: it is metadata, dropped before hashing,\n * and the studio shows it in place of the key wherever it is set.\n */\n name: z.optional(z.string()),\n description: z.optional(z.string()),\n loc: z.optional(SourceLocIR),\n});\nexport type MetaIR = z.infer<typeof MetaIR>;\n\nexport const WorkflowIR = z.object({\n irVersion: z.literal(IR_VERSION),\n /** Wire identity (`workflow({ id })`): routing tables, the entry ledger and reporting are keyed on it. */\n id: z.string(),\n contentHash: z.string(),\n meta: z.optional(MetaIR),\n trigger: TriggerIR,\n goal: z.optional(GoalIR),\n exitWhen: z.optional(ConditionIR),\n flow: z.array(NodeIR),\n});\nexport type WorkflowIR = z.infer<typeof WorkflowIR>;\n\nexport const SegmentIR = z.object({\n irVersion: z.literal(IR_VERSION),\n /** Wire identity (`segment({ id })`): segment references and the membership store are keyed on it. */\n id: z.string(),\n contentHash: z.string(),\n /** Provenance plus the human label — MetaIR, same as a workflow's. */\n meta: z.optional(MetaIR),\n condition: ConditionIR,\n});\nexport type SegmentIR = z.infer<typeof SegmentIR>;\n\n/** Template manifest entry: IR records only the reference; the body lives in the template system. */\nexport const TemplateIR = z.object({\n irVersion: z.literal(IR_VERSION),\n key: z.string(),\n channel: ChannelIR,\n});\nexport type TemplateIR = z.infer<typeof TemplateIR>;\n\n/**\n * Action manifest entry: IR records the name; the implementation solidifies\n * into the Worker bundle at deploy time, never into IR (custom code stays out\n * of the data plane by design).\n */\nexport const ActionIR = z.object({\n irVersion: z.literal(IR_VERSION),\n name: z.string(),\n});\nexport type ActionIR = z.infer<typeof ActionIR>;\n\n/**\n * Deployment unit: the complete snapshot one `deploy` produces (terraform\n * apply, same mental model). The server diffs each definition's contentHash to\n * decide publish / skip / retire.\n */\n/**\n * The subscription topics the contract declares, carried by the bundle so the engine validates\n * `/subscriptions` and the templates' topics against the deployed list, not the SDK's word.\n */\nexport const SubscriptionsIR = z.object({\n topics: z.array(z.string()),\n});\nexport type SubscriptionsIR = z.infer<typeof SubscriptionsIR>;\n\nexport const BundleIR = z.object({\n irVersion: z.literal(IR_VERSION),\n workflows: z.array(WorkflowIR),\n segments: z.array(SegmentIR),\n templates: z.array(TemplateIR),\n actions: z.array(ActionIR),\n /** Absent in a bundle compiled before topics existed: no topics. */\n subscriptions: z.optional(SubscriptionsIR),\n});\nexport type BundleIR = z.infer<typeof BundleIR>;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ir.cjs","names":["z"],"sources":["../src/ir.ts"],"sourcesContent":["import * as z from 'zod/mini';\n\n/**\n * IR (Intermediate Representation) — what the DSL compiles to: serializable\n * JSON. It is the three-way contract between the engine that interprets it,\n * the UI (react-flow) that renders it, and diff / audit tooling.\n *\n * Validation strategy: the zod/mini schemas are the authoritative definition.\n * TS types are either inferred from a schema or hand-written under the same\n * name (zod cannot infer recursive types; the hand-written type is anchored\n * with a ZodMiniType<T> annotation, so any drift fails to compile). IR crosses\n * process boundaries (database, network, UI), so runtime validation is a must.\n *\n * ## Versioning (two independent axes)\n *\n * 1. IR format version (irVersion): evolution of the IR schema itself. Adding\n * optional fields does not bump it; only structurally incompatible changes\n * do (changing a discriminant, dropping a field, altering semantics).\n * Readers dispatch on irVersion to the matching schema/migrator; writers\n * always write the current version.\n * 2. Content version (contentHash): a content-addressed id for a single\n * definition's **execution semantics** — the canonical-JSON hash after\n * metadata (meta / label / reason) is stripped, see hash.ts. It is used to:\n * - pin a version when a user enters, so an in-flight journey runs the\n * pinned version to completion (the default policy; compatible hot\n * updates are a future explicit-migration topic). Known exception:\n * segment conditions are resolved **by id at evaluation time** — a\n * segment redeploy changes behaviour of in-flight journeys that\n * reference it. Pinning covers the workflow's own structure; per-hash\n * segment snapshots are future work;\n * - diff on deploy: same hash means unchanged, skip publishing;\n * - audit: answer \"which version was this user on\" at any point.\n *\n * ## Node ids\n *\n * Derived from the structural path at compile time — stable and deterministic:\n * root level '0', '1', …; the j-th child of a branch's i-th case is\n * '{branchId}.c{i}.{j}', the default arm '{branchId}.o.{j}'; cohort arms use\n * the arm name '{cohortId}.{armName}.{j}'; a waitUntil timeout flow '{id}.t.{j}'.\n * Editing a node's parameters keeps its id; inserting a node shifts the ids of\n * its later siblings (a known trade-off — locating in-flight users relies on\n * the pinned version rather than cross-version id alignment, and cross-version\n * mapping is an explicit migrator's job).\n */\n\nexport const IR_VERSION = 2;\n\n/* -------------------------------- Base values ------------------------------- */\n\nexport const ScalarIR = z.union([z.string(), z.number(), z.boolean()]);\nexport type ScalarIR = z.infer<typeof ScalarIR>;\n\n/** Duration: `value` keeps the DSL literal ('23 hours', for display), `ms` is what the engine uses. */\n/*\n * TODO: `value` is hashed alongside `ms`, so '7 days' / '1 week' / '168 hours'\n * — identical to the engine, which only reads ms — produce three different\n * contentHashes. Rewording a duration therefore reports as a semantic change in\n * plan and repins in-flight users. Moving `value` out of the hash (it is a\n * display string) would fix it; deferred because it changes existing hashes.\n */\nexport const DurationIR = z.object({ value: z.string(), ms: z.number() });\nexport type DurationIR = z.infer<typeof DurationIR>;\n\n/** User-property reference — isomorphic to the DSL's UserPropertyRef once serialized (phantom type dropped). */\nexport const UserPropertyRefIR = z.object({\n type: z.literal('user_property'),\n path: z.string(),\n});\nexport type UserPropertyRefIR = z.infer<typeof UserPropertyRefIR>;\n\n/** A message prop / event payload value: either a literal or a user-property reference. */\nexport const PropValueIR = z.union([ScalarIR, UserPropertyRefIR]);\nexport type PropValueIR = z.infer<typeof PropValueIR>;\n\nexport const ChannelIR = z.enum(['email', 'sms', 'push', 'in_app', 'slack', 'discord', 'survey']);\nexport type ChannelIR = z.infer<typeof ChannelIR>;\n\nexport const WeekdayIR = z.enum(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']);\nexport type WeekdayIR = z.infer<typeof WeekdayIR>;\n\n/* --------------------------------- Provenance -------------------------------- */\n\n/**\n * Source location of the builder call that produced a definition — the IR's\n * source map. Lives under `meta`, so it is **excluded from contentHash**\n * (see hash.ts): provenance never affects execution identity or plan.\n * `file` is cwd-relative when compiled under Node, a URL under Vite dev.\n */\nexport const SourceLocIR = z.object({\n file: z.string(),\n line: z.number(),\n column: z.number(),\n});\nexport type SourceLocIR = z.infer<typeof SourceLocIR>;\n\n/** Node-level metadata: provenance only, for now. Excluded from contentHash. */\nexport const NodeMetaIR = z.object({\n loc: z.optional(SourceLocIR),\n});\nexport type NodeMetaIR = z.infer<typeof NodeMetaIR>;\n\n/* -------------------------------- Conditions -------------------------------- */\n\nexport const PropertyOperatorIR = z.enum([\n 'eq',\n 'ne',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'between',\n 'not_between',\n 'in_array',\n 'not_in_array',\n 'exists',\n 'not_exists',\n 'contains',\n 'not_contains',\n]);\nexport type PropertyOperatorIR = z.infer<typeof PropertyOperatorIR>;\n\n/**\n * Condition expression tree. The shape of value/values follows `op`:\n * eq/ne/gt/gte/lt/lte/contains/not_contains → value; in_array/not_in_array →\n * values; between/not_between → values = [min, max]; exists/not_exists → neither.\n *\n * `payload` predicates test the payload of an event (dotted paths reach nested\n * fields). They are only valid inside a `where` subtree — `performed.where`\n * and the event trigger's `where` — where an event row is in scope; the\n * compiler (compileBundle) and the evaluator both enforce the placement.\n * TODO: runtime refinement narrowing by op.\n */\n/**\n * Provenance rides on conditions too, so the studio can point at the exact\n * `eq(...)` / `and(...)` that produced a node — same rule as nodes: `meta` is\n * dropped before hashing, so carrying it never affects execution identity.\n */\ninterface ConditionBaseIR {\n meta?: NodeMetaIR | undefined;\n}\n\nexport type ConditionIR =\n | (ConditionBaseIR & { type: 'and'; conditions: ConditionIR[] })\n | (ConditionBaseIR & { type: 'or'; conditions: ConditionIR[] })\n | (ConditionBaseIR & { type: 'not'; condition: ConditionIR })\n /** Reference a segment by id (defined in SegmentIR, versioned separately). */\n | (ConditionBaseIR & { type: 'segment'; segment: string })\n | (ConditionBaseIR & {\n type: 'property';\n path: string;\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n })\n | (ConditionBaseIR & {\n /** Event payload predicate; `path` is dotted for nested fields ('tags.utm_source'). */\n type: 'payload';\n path: string;\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n })\n | (ConditionBaseIR & {\n type: 'performed';\n event: string;\n /** Payload filter: only events matching it count (a payload-only subtree). */\n where?: ConditionIR | undefined;\n within?: DurationIR | undefined;\n count?: number | undefined;\n })\n /**\n * The user can be reached on the channel right now: for push, the host's\n * device directory lists at least one device; for the others, the channel's\n * recipient property (email, phone, …) is set.\n */\n | (ConditionBaseIR & { type: 'reachable'; channel: ChannelIR });\n\nconst conditionBase = { meta: z.optional(NodeMetaIR) };\n\nexport const ConditionIR: z.ZodMiniType<ConditionIR> = z.lazy(() =>\n z.discriminatedUnion('type', [\n z.object({ ...conditionBase, type: z.literal('and'), conditions: z.array(ConditionIR) }),\n z.object({ ...conditionBase, type: z.literal('or'), conditions: z.array(ConditionIR) }),\n z.object({ ...conditionBase, type: z.literal('not'), condition: ConditionIR }),\n z.object({ ...conditionBase, type: z.literal('segment'), segment: z.string() }),\n z.object({\n ...conditionBase,\n type: z.literal('property'),\n path: z.string(),\n op: PropertyOperatorIR,\n value: z.optional(ScalarIR),\n values: z.optional(z.array(ScalarIR)),\n }),\n z.object({\n ...conditionBase,\n type: z.literal('payload'),\n path: z.string(),\n op: PropertyOperatorIR,\n value: z.optional(ScalarIR),\n values: z.optional(z.array(ScalarIR)),\n }),\n z.object({\n ...conditionBase,\n type: z.literal('performed'),\n event: z.string(),\n where: z.optional(ConditionIR),\n within: z.optional(DurationIR),\n count: z.optional(z.number()),\n }),\n z.object({ ...conditionBase, type: z.literal('reachable'), channel: ChannelIR }),\n ])\n);\n\n/* --------------------------------- Triggers --------------------------------- */\n\n/**\n * Trigger shapes. The event trigger has two independent gates: `where` tests\n * the triggering event's payload (payload-only subtree, evaluated against the\n * incoming event before anything else), `filter` tests the user's profile.\n */\nexport const TriggerIR = z.discriminatedUnion('type', [\n z.object({\n type: z.literal('event'),\n event: z.string(),\n where: z.optional(ConditionIR),\n filter: z.optional(ConditionIR),\n visitors: z.optional(z.literal(true)),\n }),\n z.object({ type: z.literal('segment'), segment: z.string() }),\n z.object({ type: z.literal('date'), at: z.string() }),\n z.object({ type: z.literal('webhook') }),\n]);\nexport type TriggerIR = z.infer<typeof TriggerIR>;\n\n/* ----------------------------------- Nodes ---------------------------------- */\n\ninterface NodeBaseIR {\n /** Stable id derived from the structural path (rules in the file header). */\n id: string;\n /** Optional node name: UI title / observability handle (branch's optional first argument lands here). */\n label?: string | undefined;\n /** Metadata (provenance); excluded from contentHash. */\n meta?: NodeMetaIR | undefined;\n}\n\nexport type NodeIR =\n | (NodeBaseIR & {\n type: 'message';\n channel: ChannelIR;\n template: string;\n props: Record<string, PropValueIR>;\n /**\n * Push only: which of the user's devices get a copy. Absent means every\n * device the directory lists; 'latest' only the most recently registered.\n */\n devices?: 'latest' | undefined;\n /**\n * Email and push: the template's subscription topic, or null for a transactional message\n * (sent whatever the user opted out of). Absent for other channels, and in IR compiled\n * before topics existed: such a step checks the channel-level opt-out only, as it always did.\n */\n topic?: string | null | undefined;\n })\n | (NodeBaseIR & { type: 'delay'; duration: DurationIR })\n | (NodeBaseIR & { type: 'random_delay'; min: DurationIR; max: DurationIR })\n | (NodeBaseIR & {\n type: 'time_window';\n days: WeekdayIR[];\n between: [string, string];\n tz: string;\n })\n | (NodeBaseIR & {\n type: 'wait_until';\n condition: ConditionIR;\n timeout: DurationIR;\n onTimeout: 'continue' | 'exit' | NodeIR[];\n })\n | (NodeBaseIR & {\n type: 'branch';\n cases: { label?: string | undefined; condition: ConditionIR; flow: NodeIR[] }[];\n /** Default arm (the DSL's bare tail argument). Absent = fall through to the main line. */\n otherwise?: NodeIR[] | undefined;\n })\n | (NodeBaseIR & {\n type: 'filter';\n /** Audit-log line for the users this gate drops. Metadata: excluded from contentHash. */\n reason?: string | undefined;\n condition: ConditionIR;\n })\n | (NodeBaseIR & {\n type: 'cohort';\n /**\n * Explicit experiment key: bucketing hashes `userId:key` instead of the\n * structural node id, so the assignment survives node insertion/moves and\n * the experiment keeps one identity across workflow versions. Absent →\n * the node id is used (fine for one-off splits).\n */\n key?: string | undefined;\n /**\n * Arms in authoring order — the order the interpreter accumulates weights\n * in, so it decides which bucket range each arm owns. The DSL keeps that\n * promise by rejecting integer-like arm names, which JavaScript would\n * hoist to the front of the object in numeric order. Weights sum to 100.\n */\n arms: { name: string; weight: number; flow: NodeIR[] }[];\n })\n | (NodeBaseIR & {\n /** `reason` is the audit-log line. Metadata: excluded from contentHash. */\n type: 'exit';\n reason?: string | undefined;\n })\n | (NodeBaseIR & { type: 'send_event'; event: string; payload: Record<string, PropValueIR> })\n | (NodeBaseIR & {\n type: 'action';\n /** Registry name of the custom action; the code itself lives in the deployed Worker bundle. */\n action: string;\n /** Argument values; user_property references are resolved at run time, like message props. */\n args: Record<string, PropValueIR>;\n });\n\nconst nodeBase = { id: z.string(), label: z.optional(z.string()), meta: z.optional(NodeMetaIR) };\n\nexport const NodeIR: z.ZodMiniType<NodeIR> = z.lazy(() =>\n z.discriminatedUnion('type', [\n z.object({\n ...nodeBase,\n type: z.literal('message'),\n channel: ChannelIR,\n template: z.string(),\n props: z.record(z.string(), PropValueIR),\n devices: z.optional(z.literal('latest')),\n topic: z.optional(z.nullable(z.string())),\n }),\n z.object({ ...nodeBase, type: z.literal('delay'), duration: DurationIR }),\n z.object({ ...nodeBase, type: z.literal('random_delay'), min: DurationIR, max: DurationIR }),\n z.object({\n ...nodeBase,\n type: z.literal('time_window'),\n days: z.array(WeekdayIR),\n between: z.tuple([z.string(), z.string()]),\n tz: z.string(),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('wait_until'),\n condition: ConditionIR,\n timeout: DurationIR,\n onTimeout: z.union([z.literal('continue'), z.literal('exit'), z.array(NodeIR)]),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('branch'),\n cases: z.array(\n z.object({\n label: z.optional(z.string()),\n condition: ConditionIR,\n flow: z.array(NodeIR),\n })\n ),\n otherwise: z.optional(z.array(NodeIR)),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('filter'),\n condition: ConditionIR,\n reason: z.optional(z.string()),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('cohort'),\n key: z.optional(z.string()),\n arms: z.array(z.object({ name: z.string(), weight: z.number(), flow: z.array(NodeIR) })),\n }),\n z.object({ ...nodeBase, type: z.literal('exit'), reason: z.optional(z.string()) }),\n z.object({\n ...nodeBase,\n type: z.literal('send_event'),\n event: z.string(),\n payload: z.record(z.string(), PropValueIR),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('action'),\n action: z.string(),\n args: z.record(z.string(), PropValueIR),\n }),\n ])\n);\n\n/* ---------------------- Top-level definitions and bundle --------------------- */\n\nexport const GoalIR = z.object({\n condition: ConditionIR,\n within: z.optional(DurationIR),\n exitOnMatch: z.boolean(),\n});\nexport type GoalIR = z.infer<typeof GoalIR>;\n\n/**\n * Human-facing metadata: **excluded from contentHash** (see hash.ts).\n * Editing a name or description does not touch execution semantics, so it never\n * invalidates the version in-flight users are pinned to, nor shows up in plan.\n */\nexport const MetaIR = z.object({\n /**\n * Human label. The asset's `id` is its identity — a wire key that routing\n * tables and the entry ledger are built on — so it is not something to\n * rename for readability. This is: it is metadata, dropped before hashing,\n * and the studio shows it in place of the key wherever it is set.\n */\n name: z.optional(z.string()),\n description: z.optional(z.string()),\n loc: z.optional(SourceLocIR),\n});\nexport type MetaIR = z.infer<typeof MetaIR>;\n\nexport const WorkflowIR = z.object({\n irVersion: z.literal(IR_VERSION),\n /** Wire identity (`workflow({ id })`): routing tables, the entry ledger and reporting are keyed on it. */\n id: z.string(),\n contentHash: z.string(),\n meta: z.optional(MetaIR),\n trigger: TriggerIR,\n goal: z.optional(GoalIR),\n exitWhen: z.optional(ConditionIR),\n flow: z.array(NodeIR),\n});\nexport type WorkflowIR = z.infer<typeof WorkflowIR>;\n\nexport const SegmentIR = z.object({\n irVersion: z.literal(IR_VERSION),\n /** Wire identity (`segment({ id })`): segment references and the membership store are keyed on it. */\n id: z.string(),\n contentHash: z.string(),\n /** Provenance plus the human label — MetaIR, same as a workflow's. */\n meta: z.optional(MetaIR),\n condition: ConditionIR,\n});\nexport type SegmentIR = z.infer<typeof SegmentIR>;\n\n/** Template manifest entry: IR records only the reference; the body lives in the template system. */\nexport const TemplateIR = z.object({\n irVersion: z.literal(IR_VERSION),\n key: z.string(),\n channel: ChannelIR,\n});\nexport type TemplateIR = z.infer<typeof TemplateIR>;\n\n/**\n * Action manifest entry: IR records the name; the implementation solidifies\n * into the Worker bundle at deploy time, never into IR (custom code stays out\n * of the data plane by design).\n */\nexport const ActionIR = z.object({\n irVersion: z.literal(IR_VERSION),\n name: z.string(),\n});\nexport type ActionIR = z.infer<typeof ActionIR>;\n\n/**\n * Deployment unit: the complete snapshot one `deploy` produces (terraform\n * apply, same mental model). The server diffs each definition's contentHash to\n * decide publish / skip / retire.\n */\n/**\n * The subscription topics the contract declares, carried by the bundle so the engine validates\n * `/subscriptions` and the templates' topics against the deployed list, not the SDK's word.\n */\nexport const SubscriptionsIR = z.object({\n topics: z.array(z.string()),\n});\nexport type SubscriptionsIR = z.infer<typeof SubscriptionsIR>;\n\nexport const BundleIR = z.object({\n irVersion: z.literal(IR_VERSION),\n workflows: z.array(WorkflowIR),\n segments: z.array(SegmentIR),\n templates: z.array(TemplateIR),\n actions: z.array(ActionIR),\n /** Absent in a bundle compiled before topics existed: no topics. */\n subscriptions: z.optional(SubscriptionsIR),\n});\nexport type BundleIR = z.infer<typeof BundleIR>;\n"],"mappings":"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"}
|
package/dist/ir.d.cts
CHANGED
|
@@ -194,6 +194,7 @@ export declare const TriggerIR: z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
|
|
|
194
194
|
event: z.ZodMiniString<string>;
|
|
195
195
|
where: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
196
196
|
filter: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
197
|
+
visitors: z.ZodMiniOptional<z.ZodMiniLiteral<true>>;
|
|
197
198
|
}, z.core.$strip>, z.ZodMiniObject<{
|
|
198
199
|
type: z.ZodMiniLiteral<"segment">;
|
|
199
200
|
segment: z.ZodMiniString<string>;
|
|
@@ -337,6 +338,7 @@ export declare const WorkflowIR: z.ZodMiniObject<{
|
|
|
337
338
|
event: z.ZodMiniString<string>;
|
|
338
339
|
where: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
339
340
|
filter: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
341
|
+
visitors: z.ZodMiniOptional<z.ZodMiniLiteral<true>>;
|
|
340
342
|
}, z.core.$strip>, z.ZodMiniObject<{
|
|
341
343
|
type: z.ZodMiniLiteral<"segment">;
|
|
342
344
|
segment: z.ZodMiniString<string>;
|
|
@@ -432,6 +434,7 @@ export declare const BundleIR: z.ZodMiniObject<{
|
|
|
432
434
|
event: z.ZodMiniString<string>;
|
|
433
435
|
where: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
434
436
|
filter: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
437
|
+
visitors: z.ZodMiniOptional<z.ZodMiniLiteral<true>>;
|
|
435
438
|
}, z.core.$strip>, z.ZodMiniObject<{
|
|
436
439
|
type: z.ZodMiniLiteral<"segment">;
|
|
437
440
|
segment: z.ZodMiniString<string>;
|
package/dist/ir.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir.d.cts","names":[],"sources":["../src/ir.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA6Ca;qBAIA,UAAQ,EAAA,uBAAA,EAAA,uBAAA,EAAA,uBAAA,EAAA;YACT,WAAW,EAAE,aAAa;;qBAUzB,YAAU,EAAA;;;GAAkD,EAAA,KAAA;YAC7D,aAAa,EAAE,aAAa;;qBAG3B,mBAAiB,EAAA;;;GAG5B,EAAA,KAAA;YACU,oBAAoB,EAAE,aAAa;;qBAGlC,aAAW,EAAA,uBAAA,EAAA,uBAAA,EAAA,uBAAA,EAAA,uBAAA,EAAA,2BAAA,EAAA;;;GAAyC,EAAA,KAAA;YACrD,cAAc,EAAE,aAAa;qBAE5B,WAAS,EAAA;;;;;;;;;YACV,YAAY,EAAE,aAAa;qBAE1B,WAAS,EAAA;;;;;;;;;YACV,YAAY,EAAE,aAAa;;;;;;;qBAU1B,aAAW,EAAA;;;;GAItB,EAAA,KAAA;YACU,cAAc,EAAE,aAAa;;qBAG5B,YAAU,EAAA;;;;;;GAErB,EAAA,KAAA;YACU,aAAa,EAAE,aAAa;qBAI3B,oBAAkB,EAAA;;;;;;;;;;;;;;;;YAgBnB,qBAAqB,EAAE,aAAa;;;;;;;;;;;;;;;;;UAkBtC;EACR,OAAO;;YAGG,eACP;EAAoB;EAAa,YAAY;MAC7C;EAAoB;EAAY,YAAY;MAC5C;EAAoB;EAAa,WAAW;;;CAE5C;EAAoB;EAAiB;MACrC;EACC;EACA;EACA,IAAI;EACJ,QAAQ;EACR,SAAS;MAEV;;EAEC;EACA;EACA,IAAI;EACJ,QAAQ;EACR,SAAS;MAEV;EACC;EACA;;EAEA,QAAQ;EACR,SAAS;EACT;;;;;;;CAOD;EAAoB;EAAmB,SAAS;;qBAIxC,aAAa,EAAE,YAAY;;;;;;qBAyC3B,WAAS,EAAA,2BAAA,EAAA
|
|
1
|
+
{"version":3,"file":"ir.d.cts","names":[],"sources":["../src/ir.ts"],"mappings":"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"}
|
package/dist/ir.d.mts
CHANGED
|
@@ -194,6 +194,7 @@ export declare const TriggerIR: z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
|
|
|
194
194
|
event: z.ZodMiniString<string>;
|
|
195
195
|
where: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
196
196
|
filter: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
197
|
+
visitors: z.ZodMiniOptional<z.ZodMiniLiteral<true>>;
|
|
197
198
|
}, z.core.$strip>, z.ZodMiniObject<{
|
|
198
199
|
type: z.ZodMiniLiteral<"segment">;
|
|
199
200
|
segment: z.ZodMiniString<string>;
|
|
@@ -337,6 +338,7 @@ export declare const WorkflowIR: z.ZodMiniObject<{
|
|
|
337
338
|
event: z.ZodMiniString<string>;
|
|
338
339
|
where: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
339
340
|
filter: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
341
|
+
visitors: z.ZodMiniOptional<z.ZodMiniLiteral<true>>;
|
|
340
342
|
}, z.core.$strip>, z.ZodMiniObject<{
|
|
341
343
|
type: z.ZodMiniLiteral<"segment">;
|
|
342
344
|
segment: z.ZodMiniString<string>;
|
|
@@ -432,6 +434,7 @@ export declare const BundleIR: z.ZodMiniObject<{
|
|
|
432
434
|
event: z.ZodMiniString<string>;
|
|
433
435
|
where: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
434
436
|
filter: z.ZodMiniOptional<z.ZodMiniType<ConditionIR, unknown, z.core.$ZodTypeInternals<ConditionIR, unknown>>>;
|
|
437
|
+
visitors: z.ZodMiniOptional<z.ZodMiniLiteral<true>>;
|
|
435
438
|
}, z.core.$strip>, z.ZodMiniObject<{
|
|
436
439
|
type: z.ZodMiniLiteral<"segment">;
|
|
437
440
|
segment: z.ZodMiniString<string>;
|
package/dist/ir.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir.d.mts","names":[],"sources":["../src/ir.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA6Ca;qBAIA,UAAQ,EAAA,uBAAA,EAAA,uBAAA,EAAA,uBAAA,EAAA;YACT,WAAW,EAAE,aAAa;;qBAUzB,YAAU,EAAA;;;GAAkD,EAAA,KAAA;YAC7D,aAAa,EAAE,aAAa;;qBAG3B,mBAAiB,EAAA;;;GAG5B,EAAA,KAAA;YACU,oBAAoB,EAAE,aAAa;;qBAGlC,aAAW,EAAA,uBAAA,EAAA,uBAAA,EAAA,uBAAA,EAAA,uBAAA,EAAA,2BAAA,EAAA;;;GAAyC,EAAA,KAAA;YACrD,cAAc,EAAE,aAAa;qBAE5B,WAAS,EAAA;;;;;;;;;YACV,YAAY,EAAE,aAAa;qBAE1B,WAAS,EAAA;;;;;;;;;YACV,YAAY,EAAE,aAAa;;;;;;;qBAU1B,aAAW,EAAA;;;;GAItB,EAAA,KAAA;YACU,cAAc,EAAE,aAAa;;qBAG5B,YAAU,EAAA;;;;;;GAErB,EAAA,KAAA;YACU,aAAa,EAAE,aAAa;qBAI3B,oBAAkB,EAAA;;;;;;;;;;;;;;;;YAgBnB,qBAAqB,EAAE,aAAa;;;;;;;;;;;;;;;;;UAkBtC;EACR,OAAO;;YAGG,eACP;EAAoB;EAAa,YAAY;MAC7C;EAAoB;EAAY,YAAY;MAC5C;EAAoB;EAAa,WAAW;;;CAE5C;EAAoB;EAAiB;MACrC;EACC;EACA;EACA,IAAI;EACJ,QAAQ;EACR,SAAS;MAEV;;EAEC;EACA;EACA,IAAI;EACJ,QAAQ;EACR,SAAS;MAEV;EACC;EACA;;EAEA,QAAQ;EACR,SAAS;EACT;;;;;;;CAOD;EAAoB;EAAmB,SAAS;;qBAIxC,aAAa,EAAE,YAAY;;;;;;qBAyC3B,WAAS,EAAA,2BAAA,EAAA
|
|
1
|
+
{"version":3,"file":"ir.d.mts","names":[],"sources":["../src/ir.ts"],"mappings":"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"}
|
package/dist/ir.mjs
CHANGED
|
@@ -169,7 +169,8 @@ const TriggerIR = z.discriminatedUnion("type", [
|
|
|
169
169
|
type: z.literal("event"),
|
|
170
170
|
event: z.string(),
|
|
171
171
|
where: z.optional(ConditionIR),
|
|
172
|
-
filter: z.optional(ConditionIR)
|
|
172
|
+
filter: z.optional(ConditionIR),
|
|
173
|
+
visitors: z.optional(z.literal(true))
|
|
173
174
|
}),
|
|
174
175
|
z.object({
|
|
175
176
|
type: z.literal("segment"),
|
package/dist/ir.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir.mjs","names":[],"sources":["../src/ir.ts"],"sourcesContent":["import * as z from 'zod/mini';\n\n/**\n * IR (Intermediate Representation) — what the DSL compiles to: serializable\n * JSON. It is the three-way contract between the engine that interprets it,\n * the UI (react-flow) that renders it, and diff / audit tooling.\n *\n * Validation strategy: the zod/mini schemas are the authoritative definition.\n * TS types are either inferred from a schema or hand-written under the same\n * name (zod cannot infer recursive types; the hand-written type is anchored\n * with a ZodMiniType<T> annotation, so any drift fails to compile). IR crosses\n * process boundaries (database, network, UI), so runtime validation is a must.\n *\n * ## Versioning (two independent axes)\n *\n * 1. IR format version (irVersion): evolution of the IR schema itself. Adding\n * optional fields does not bump it; only structurally incompatible changes\n * do (changing a discriminant, dropping a field, altering semantics).\n * Readers dispatch on irVersion to the matching schema/migrator; writers\n * always write the current version.\n * 2. Content version (contentHash): a content-addressed id for a single\n * definition's **execution semantics** — the canonical-JSON hash after\n * metadata (meta / label / reason) is stripped, see hash.ts. It is used to:\n * - pin a version when a user enters, so an in-flight journey runs the\n * pinned version to completion (the default policy; compatible hot\n * updates are a future explicit-migration topic). Known exception:\n * segment conditions are resolved **by id at evaluation time** — a\n * segment redeploy changes behaviour of in-flight journeys that\n * reference it. Pinning covers the workflow's own structure; per-hash\n * segment snapshots are future work;\n * - diff on deploy: same hash means unchanged, skip publishing;\n * - audit: answer \"which version was this user on\" at any point.\n *\n * ## Node ids\n *\n * Derived from the structural path at compile time — stable and deterministic:\n * root level '0', '1', …; the j-th child of a branch's i-th case is\n * '{branchId}.c{i}.{j}', the default arm '{branchId}.o.{j}'; cohort arms use\n * the arm name '{cohortId}.{armName}.{j}'; a waitUntil timeout flow '{id}.t.{j}'.\n * Editing a node's parameters keeps its id; inserting a node shifts the ids of\n * its later siblings (a known trade-off — locating in-flight users relies on\n * the pinned version rather than cross-version id alignment, and cross-version\n * mapping is an explicit migrator's job).\n */\n\nexport const IR_VERSION = 2;\n\n/* -------------------------------- Base values ------------------------------- */\n\nexport const ScalarIR = z.union([z.string(), z.number(), z.boolean()]);\nexport type ScalarIR = z.infer<typeof ScalarIR>;\n\n/** Duration: `value` keeps the DSL literal ('23 hours', for display), `ms` is what the engine uses. */\n/*\n * TODO: `value` is hashed alongside `ms`, so '7 days' / '1 week' / '168 hours'\n * — identical to the engine, which only reads ms — produce three different\n * contentHashes. Rewording a duration therefore reports as a semantic change in\n * plan and repins in-flight users. Moving `value` out of the hash (it is a\n * display string) would fix it; deferred because it changes existing hashes.\n */\nexport const DurationIR = z.object({ value: z.string(), ms: z.number() });\nexport type DurationIR = z.infer<typeof DurationIR>;\n\n/** User-property reference — isomorphic to the DSL's UserPropertyRef once serialized (phantom type dropped). */\nexport const UserPropertyRefIR = z.object({\n type: z.literal('user_property'),\n path: z.string(),\n});\nexport type UserPropertyRefIR = z.infer<typeof UserPropertyRefIR>;\n\n/** A message prop / event payload value: either a literal or a user-property reference. */\nexport const PropValueIR = z.union([ScalarIR, UserPropertyRefIR]);\nexport type PropValueIR = z.infer<typeof PropValueIR>;\n\nexport const ChannelIR = z.enum(['email', 'sms', 'push', 'in_app', 'slack', 'discord', 'survey']);\nexport type ChannelIR = z.infer<typeof ChannelIR>;\n\nexport const WeekdayIR = z.enum(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']);\nexport type WeekdayIR = z.infer<typeof WeekdayIR>;\n\n/* --------------------------------- Provenance -------------------------------- */\n\n/**\n * Source location of the builder call that produced a definition — the IR's\n * source map. Lives under `meta`, so it is **excluded from contentHash**\n * (see hash.ts): provenance never affects execution identity or plan.\n * `file` is cwd-relative when compiled under Node, a URL under Vite dev.\n */\nexport const SourceLocIR = z.object({\n file: z.string(),\n line: z.number(),\n column: z.number(),\n});\nexport type SourceLocIR = z.infer<typeof SourceLocIR>;\n\n/** Node-level metadata: provenance only, for now. Excluded from contentHash. */\nexport const NodeMetaIR = z.object({\n loc: z.optional(SourceLocIR),\n});\nexport type NodeMetaIR = z.infer<typeof NodeMetaIR>;\n\n/* -------------------------------- Conditions -------------------------------- */\n\nexport const PropertyOperatorIR = z.enum([\n 'eq',\n 'ne',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'between',\n 'not_between',\n 'in_array',\n 'not_in_array',\n 'exists',\n 'not_exists',\n 'contains',\n 'not_contains',\n]);\nexport type PropertyOperatorIR = z.infer<typeof PropertyOperatorIR>;\n\n/**\n * Condition expression tree. The shape of value/values follows `op`:\n * eq/ne/gt/gte/lt/lte/contains/not_contains → value; in_array/not_in_array →\n * values; between/not_between → values = [min, max]; exists/not_exists → neither.\n *\n * `payload` predicates test the payload of an event (dotted paths reach nested\n * fields). They are only valid inside a `where` subtree — `performed.where`\n * and the event trigger's `where` — where an event row is in scope; the\n * compiler (compileBundle) and the evaluator both enforce the placement.\n * TODO: runtime refinement narrowing by op.\n */\n/**\n * Provenance rides on conditions too, so the studio can point at the exact\n * `eq(...)` / `and(...)` that produced a node — same rule as nodes: `meta` is\n * dropped before hashing, so carrying it never affects execution identity.\n */\ninterface ConditionBaseIR {\n meta?: NodeMetaIR | undefined;\n}\n\nexport type ConditionIR =\n | (ConditionBaseIR & { type: 'and'; conditions: ConditionIR[] })\n | (ConditionBaseIR & { type: 'or'; conditions: ConditionIR[] })\n | (ConditionBaseIR & { type: 'not'; condition: ConditionIR })\n /** Reference a segment by id (defined in SegmentIR, versioned separately). */\n | (ConditionBaseIR & { type: 'segment'; segment: string })\n | (ConditionBaseIR & {\n type: 'property';\n path: string;\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n })\n | (ConditionBaseIR & {\n /** Event payload predicate; `path` is dotted for nested fields ('tags.utm_source'). */\n type: 'payload';\n path: string;\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n })\n | (ConditionBaseIR & {\n type: 'performed';\n event: string;\n /** Payload filter: only events matching it count (a payload-only subtree). */\n where?: ConditionIR | undefined;\n within?: DurationIR | undefined;\n count?: number | undefined;\n })\n /**\n * The user can be reached on the channel right now: for push, the host's\n * device directory lists at least one device; for the others, the channel's\n * recipient property (email, phone, …) is set.\n */\n | (ConditionBaseIR & { type: 'reachable'; channel: ChannelIR });\n\nconst conditionBase = { meta: z.optional(NodeMetaIR) };\n\nexport const ConditionIR: z.ZodMiniType<ConditionIR> = z.lazy(() =>\n z.discriminatedUnion('type', [\n z.object({ ...conditionBase, type: z.literal('and'), conditions: z.array(ConditionIR) }),\n z.object({ ...conditionBase, type: z.literal('or'), conditions: z.array(ConditionIR) }),\n z.object({ ...conditionBase, type: z.literal('not'), condition: ConditionIR }),\n z.object({ ...conditionBase, type: z.literal('segment'), segment: z.string() }),\n z.object({\n ...conditionBase,\n type: z.literal('property'),\n path: z.string(),\n op: PropertyOperatorIR,\n value: z.optional(ScalarIR),\n values: z.optional(z.array(ScalarIR)),\n }),\n z.object({\n ...conditionBase,\n type: z.literal('payload'),\n path: z.string(),\n op: PropertyOperatorIR,\n value: z.optional(ScalarIR),\n values: z.optional(z.array(ScalarIR)),\n }),\n z.object({\n ...conditionBase,\n type: z.literal('performed'),\n event: z.string(),\n where: z.optional(ConditionIR),\n within: z.optional(DurationIR),\n count: z.optional(z.number()),\n }),\n z.object({ ...conditionBase, type: z.literal('reachable'), channel: ChannelIR }),\n ])\n);\n\n/* --------------------------------- Triggers --------------------------------- */\n\n/**\n * Trigger shapes. The event trigger has two independent gates: `where` tests\n * the triggering event's payload (payload-only subtree, evaluated against the\n * incoming event before anything else), `filter` tests the user's profile.\n */\nexport const TriggerIR = z.discriminatedUnion('type', [\n z.object({\n type: z.literal('event'),\n event: z.string(),\n where: z.optional(ConditionIR),\n filter: z.optional(ConditionIR),\n }),\n z.object({ type: z.literal('segment'), segment: z.string() }),\n z.object({ type: z.literal('date'), at: z.string() }),\n z.object({ type: z.literal('webhook') }),\n]);\nexport type TriggerIR = z.infer<typeof TriggerIR>;\n\n/* ----------------------------------- Nodes ---------------------------------- */\n\ninterface NodeBaseIR {\n /** Stable id derived from the structural path (rules in the file header). */\n id: string;\n /** Optional node name: UI title / observability handle (branch's optional first argument lands here). */\n label?: string | undefined;\n /** Metadata (provenance); excluded from contentHash. */\n meta?: NodeMetaIR | undefined;\n}\n\nexport type NodeIR =\n | (NodeBaseIR & {\n type: 'message';\n channel: ChannelIR;\n template: string;\n props: Record<string, PropValueIR>;\n /**\n * Push only: which of the user's devices get a copy. Absent means every\n * device the directory lists; 'latest' only the most recently registered.\n */\n devices?: 'latest' | undefined;\n /**\n * Email and push: the template's subscription topic, or null for a transactional message\n * (sent whatever the user opted out of). Absent for other channels, and in IR compiled\n * before topics existed: such a step checks the channel-level opt-out only, as it always did.\n */\n topic?: string | null | undefined;\n })\n | (NodeBaseIR & { type: 'delay'; duration: DurationIR })\n | (NodeBaseIR & { type: 'random_delay'; min: DurationIR; max: DurationIR })\n | (NodeBaseIR & {\n type: 'time_window';\n days: WeekdayIR[];\n between: [string, string];\n tz: string;\n })\n | (NodeBaseIR & {\n type: 'wait_until';\n condition: ConditionIR;\n timeout: DurationIR;\n onTimeout: 'continue' | 'exit' | NodeIR[];\n })\n | (NodeBaseIR & {\n type: 'branch';\n cases: { label?: string | undefined; condition: ConditionIR; flow: NodeIR[] }[];\n /** Default arm (the DSL's bare tail argument). Absent = fall through to the main line. */\n otherwise?: NodeIR[] | undefined;\n })\n | (NodeBaseIR & {\n type: 'filter';\n /** Audit-log line for the users this gate drops. Metadata: excluded from contentHash. */\n reason?: string | undefined;\n condition: ConditionIR;\n })\n | (NodeBaseIR & {\n type: 'cohort';\n /**\n * Explicit experiment key: bucketing hashes `userId:key` instead of the\n * structural node id, so the assignment survives node insertion/moves and\n * the experiment keeps one identity across workflow versions. Absent →\n * the node id is used (fine for one-off splits).\n */\n key?: string | undefined;\n /**\n * Arms in authoring order — the order the interpreter accumulates weights\n * in, so it decides which bucket range each arm owns. The DSL keeps that\n * promise by rejecting integer-like arm names, which JavaScript would\n * hoist to the front of the object in numeric order. Weights sum to 100.\n */\n arms: { name: string; weight: number; flow: NodeIR[] }[];\n })\n | (NodeBaseIR & {\n /** `reason` is the audit-log line. Metadata: excluded from contentHash. */\n type: 'exit';\n reason?: string | undefined;\n })\n | (NodeBaseIR & { type: 'send_event'; event: string; payload: Record<string, PropValueIR> })\n | (NodeBaseIR & {\n type: 'action';\n /** Registry name of the custom action; the code itself lives in the deployed Worker bundle. */\n action: string;\n /** Argument values; user_property references are resolved at run time, like message props. */\n args: Record<string, PropValueIR>;\n });\n\nconst nodeBase = { id: z.string(), label: z.optional(z.string()), meta: z.optional(NodeMetaIR) };\n\nexport const NodeIR: z.ZodMiniType<NodeIR> = z.lazy(() =>\n z.discriminatedUnion('type', [\n z.object({\n ...nodeBase,\n type: z.literal('message'),\n channel: ChannelIR,\n template: z.string(),\n props: z.record(z.string(), PropValueIR),\n devices: z.optional(z.literal('latest')),\n topic: z.optional(z.nullable(z.string())),\n }),\n z.object({ ...nodeBase, type: z.literal('delay'), duration: DurationIR }),\n z.object({ ...nodeBase, type: z.literal('random_delay'), min: DurationIR, max: DurationIR }),\n z.object({\n ...nodeBase,\n type: z.literal('time_window'),\n days: z.array(WeekdayIR),\n between: z.tuple([z.string(), z.string()]),\n tz: z.string(),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('wait_until'),\n condition: ConditionIR,\n timeout: DurationIR,\n onTimeout: z.union([z.literal('continue'), z.literal('exit'), z.array(NodeIR)]),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('branch'),\n cases: z.array(\n z.object({\n label: z.optional(z.string()),\n condition: ConditionIR,\n flow: z.array(NodeIR),\n })\n ),\n otherwise: z.optional(z.array(NodeIR)),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('filter'),\n condition: ConditionIR,\n reason: z.optional(z.string()),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('cohort'),\n key: z.optional(z.string()),\n arms: z.array(z.object({ name: z.string(), weight: z.number(), flow: z.array(NodeIR) })),\n }),\n z.object({ ...nodeBase, type: z.literal('exit'), reason: z.optional(z.string()) }),\n z.object({\n ...nodeBase,\n type: z.literal('send_event'),\n event: z.string(),\n payload: z.record(z.string(), PropValueIR),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('action'),\n action: z.string(),\n args: z.record(z.string(), PropValueIR),\n }),\n ])\n);\n\n/* ---------------------- Top-level definitions and bundle --------------------- */\n\nexport const GoalIR = z.object({\n condition: ConditionIR,\n within: z.optional(DurationIR),\n exitOnMatch: z.boolean(),\n});\nexport type GoalIR = z.infer<typeof GoalIR>;\n\n/**\n * Human-facing metadata: **excluded from contentHash** (see hash.ts).\n * Editing a name or description does not touch execution semantics, so it never\n * invalidates the version in-flight users are pinned to, nor shows up in plan.\n */\nexport const MetaIR = z.object({\n /**\n * Human label. The asset's `id` is its identity — a wire key that routing\n * tables and the entry ledger are built on — so it is not something to\n * rename for readability. This is: it is metadata, dropped before hashing,\n * and the studio shows it in place of the key wherever it is set.\n */\n name: z.optional(z.string()),\n description: z.optional(z.string()),\n loc: z.optional(SourceLocIR),\n});\nexport type MetaIR = z.infer<typeof MetaIR>;\n\nexport const WorkflowIR = z.object({\n irVersion: z.literal(IR_VERSION),\n /** Wire identity (`workflow({ id })`): routing tables, the entry ledger and reporting are keyed on it. */\n id: z.string(),\n contentHash: z.string(),\n meta: z.optional(MetaIR),\n trigger: TriggerIR,\n goal: z.optional(GoalIR),\n exitWhen: z.optional(ConditionIR),\n flow: z.array(NodeIR),\n});\nexport type WorkflowIR = z.infer<typeof WorkflowIR>;\n\nexport const SegmentIR = z.object({\n irVersion: z.literal(IR_VERSION),\n /** Wire identity (`segment({ id })`): segment references and the membership store are keyed on it. */\n id: z.string(),\n contentHash: z.string(),\n /** Provenance plus the human label — MetaIR, same as a workflow's. */\n meta: z.optional(MetaIR),\n condition: ConditionIR,\n});\nexport type SegmentIR = z.infer<typeof SegmentIR>;\n\n/** Template manifest entry: IR records only the reference; the body lives in the template system. */\nexport const TemplateIR = z.object({\n irVersion: z.literal(IR_VERSION),\n key: z.string(),\n channel: ChannelIR,\n});\nexport type TemplateIR = z.infer<typeof TemplateIR>;\n\n/**\n * Action manifest entry: IR records the name; the implementation solidifies\n * into the Worker bundle at deploy time, never into IR (custom code stays out\n * of the data plane by design).\n */\nexport const ActionIR = z.object({\n irVersion: z.literal(IR_VERSION),\n name: z.string(),\n});\nexport type ActionIR = z.infer<typeof ActionIR>;\n\n/**\n * Deployment unit: the complete snapshot one `deploy` produces (terraform\n * apply, same mental model). The server diffs each definition's contentHash to\n * decide publish / skip / retire.\n */\n/**\n * The subscription topics the contract declares, carried by the bundle so the engine validates\n * `/subscriptions` and the templates' topics against the deployed list, not the SDK's word.\n */\nexport const SubscriptionsIR = z.object({\n topics: z.array(z.string()),\n});\nexport type SubscriptionsIR = z.infer<typeof SubscriptionsIR>;\n\nexport const BundleIR = z.object({\n irVersion: z.literal(IR_VERSION),\n workflows: z.array(WorkflowIR),\n segments: z.array(SegmentIR),\n templates: z.array(TemplateIR),\n actions: z.array(ActionIR),\n /** Absent in a bundle compiled before topics existed: no topics. */\n subscriptions: z.optional(SubscriptionsIR),\n});\nexport type BundleIR = z.infer<typeof BundleIR>;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ir.mjs","names":[],"sources":["../src/ir.ts"],"sourcesContent":["import * as z from 'zod/mini';\n\n/**\n * IR (Intermediate Representation) — what the DSL compiles to: serializable\n * JSON. It is the three-way contract between the engine that interprets it,\n * the UI (react-flow) that renders it, and diff / audit tooling.\n *\n * Validation strategy: the zod/mini schemas are the authoritative definition.\n * TS types are either inferred from a schema or hand-written under the same\n * name (zod cannot infer recursive types; the hand-written type is anchored\n * with a ZodMiniType<T> annotation, so any drift fails to compile). IR crosses\n * process boundaries (database, network, UI), so runtime validation is a must.\n *\n * ## Versioning (two independent axes)\n *\n * 1. IR format version (irVersion): evolution of the IR schema itself. Adding\n * optional fields does not bump it; only structurally incompatible changes\n * do (changing a discriminant, dropping a field, altering semantics).\n * Readers dispatch on irVersion to the matching schema/migrator; writers\n * always write the current version.\n * 2. Content version (contentHash): a content-addressed id for a single\n * definition's **execution semantics** — the canonical-JSON hash after\n * metadata (meta / label / reason) is stripped, see hash.ts. It is used to:\n * - pin a version when a user enters, so an in-flight journey runs the\n * pinned version to completion (the default policy; compatible hot\n * updates are a future explicit-migration topic). Known exception:\n * segment conditions are resolved **by id at evaluation time** — a\n * segment redeploy changes behaviour of in-flight journeys that\n * reference it. Pinning covers the workflow's own structure; per-hash\n * segment snapshots are future work;\n * - diff on deploy: same hash means unchanged, skip publishing;\n * - audit: answer \"which version was this user on\" at any point.\n *\n * ## Node ids\n *\n * Derived from the structural path at compile time — stable and deterministic:\n * root level '0', '1', …; the j-th child of a branch's i-th case is\n * '{branchId}.c{i}.{j}', the default arm '{branchId}.o.{j}'; cohort arms use\n * the arm name '{cohortId}.{armName}.{j}'; a waitUntil timeout flow '{id}.t.{j}'.\n * Editing a node's parameters keeps its id; inserting a node shifts the ids of\n * its later siblings (a known trade-off — locating in-flight users relies on\n * the pinned version rather than cross-version id alignment, and cross-version\n * mapping is an explicit migrator's job).\n */\n\nexport const IR_VERSION = 2;\n\n/* -------------------------------- Base values ------------------------------- */\n\nexport const ScalarIR = z.union([z.string(), z.number(), z.boolean()]);\nexport type ScalarIR = z.infer<typeof ScalarIR>;\n\n/** Duration: `value` keeps the DSL literal ('23 hours', for display), `ms` is what the engine uses. */\n/*\n * TODO: `value` is hashed alongside `ms`, so '7 days' / '1 week' / '168 hours'\n * — identical to the engine, which only reads ms — produce three different\n * contentHashes. Rewording a duration therefore reports as a semantic change in\n * plan and repins in-flight users. Moving `value` out of the hash (it is a\n * display string) would fix it; deferred because it changes existing hashes.\n */\nexport const DurationIR = z.object({ value: z.string(), ms: z.number() });\nexport type DurationIR = z.infer<typeof DurationIR>;\n\n/** User-property reference — isomorphic to the DSL's UserPropertyRef once serialized (phantom type dropped). */\nexport const UserPropertyRefIR = z.object({\n type: z.literal('user_property'),\n path: z.string(),\n});\nexport type UserPropertyRefIR = z.infer<typeof UserPropertyRefIR>;\n\n/** A message prop / event payload value: either a literal or a user-property reference. */\nexport const PropValueIR = z.union([ScalarIR, UserPropertyRefIR]);\nexport type PropValueIR = z.infer<typeof PropValueIR>;\n\nexport const ChannelIR = z.enum(['email', 'sms', 'push', 'in_app', 'slack', 'discord', 'survey']);\nexport type ChannelIR = z.infer<typeof ChannelIR>;\n\nexport const WeekdayIR = z.enum(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']);\nexport type WeekdayIR = z.infer<typeof WeekdayIR>;\n\n/* --------------------------------- Provenance -------------------------------- */\n\n/**\n * Source location of the builder call that produced a definition — the IR's\n * source map. Lives under `meta`, so it is **excluded from contentHash**\n * (see hash.ts): provenance never affects execution identity or plan.\n * `file` is cwd-relative when compiled under Node, a URL under Vite dev.\n */\nexport const SourceLocIR = z.object({\n file: z.string(),\n line: z.number(),\n column: z.number(),\n});\nexport type SourceLocIR = z.infer<typeof SourceLocIR>;\n\n/** Node-level metadata: provenance only, for now. Excluded from contentHash. */\nexport const NodeMetaIR = z.object({\n loc: z.optional(SourceLocIR),\n});\nexport type NodeMetaIR = z.infer<typeof NodeMetaIR>;\n\n/* -------------------------------- Conditions -------------------------------- */\n\nexport const PropertyOperatorIR = z.enum([\n 'eq',\n 'ne',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'between',\n 'not_between',\n 'in_array',\n 'not_in_array',\n 'exists',\n 'not_exists',\n 'contains',\n 'not_contains',\n]);\nexport type PropertyOperatorIR = z.infer<typeof PropertyOperatorIR>;\n\n/**\n * Condition expression tree. The shape of value/values follows `op`:\n * eq/ne/gt/gte/lt/lte/contains/not_contains → value; in_array/not_in_array →\n * values; between/not_between → values = [min, max]; exists/not_exists → neither.\n *\n * `payload` predicates test the payload of an event (dotted paths reach nested\n * fields). They are only valid inside a `where` subtree — `performed.where`\n * and the event trigger's `where` — where an event row is in scope; the\n * compiler (compileBundle) and the evaluator both enforce the placement.\n * TODO: runtime refinement narrowing by op.\n */\n/**\n * Provenance rides on conditions too, so the studio can point at the exact\n * `eq(...)` / `and(...)` that produced a node — same rule as nodes: `meta` is\n * dropped before hashing, so carrying it never affects execution identity.\n */\ninterface ConditionBaseIR {\n meta?: NodeMetaIR | undefined;\n}\n\nexport type ConditionIR =\n | (ConditionBaseIR & { type: 'and'; conditions: ConditionIR[] })\n | (ConditionBaseIR & { type: 'or'; conditions: ConditionIR[] })\n | (ConditionBaseIR & { type: 'not'; condition: ConditionIR })\n /** Reference a segment by id (defined in SegmentIR, versioned separately). */\n | (ConditionBaseIR & { type: 'segment'; segment: string })\n | (ConditionBaseIR & {\n type: 'property';\n path: string;\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n })\n | (ConditionBaseIR & {\n /** Event payload predicate; `path` is dotted for nested fields ('tags.utm_source'). */\n type: 'payload';\n path: string;\n op: PropertyOperatorIR;\n value?: ScalarIR | undefined;\n values?: ScalarIR[] | undefined;\n })\n | (ConditionBaseIR & {\n type: 'performed';\n event: string;\n /** Payload filter: only events matching it count (a payload-only subtree). */\n where?: ConditionIR | undefined;\n within?: DurationIR | undefined;\n count?: number | undefined;\n })\n /**\n * The user can be reached on the channel right now: for push, the host's\n * device directory lists at least one device; for the others, the channel's\n * recipient property (email, phone, …) is set.\n */\n | (ConditionBaseIR & { type: 'reachable'; channel: ChannelIR });\n\nconst conditionBase = { meta: z.optional(NodeMetaIR) };\n\nexport const ConditionIR: z.ZodMiniType<ConditionIR> = z.lazy(() =>\n z.discriminatedUnion('type', [\n z.object({ ...conditionBase, type: z.literal('and'), conditions: z.array(ConditionIR) }),\n z.object({ ...conditionBase, type: z.literal('or'), conditions: z.array(ConditionIR) }),\n z.object({ ...conditionBase, type: z.literal('not'), condition: ConditionIR }),\n z.object({ ...conditionBase, type: z.literal('segment'), segment: z.string() }),\n z.object({\n ...conditionBase,\n type: z.literal('property'),\n path: z.string(),\n op: PropertyOperatorIR,\n value: z.optional(ScalarIR),\n values: z.optional(z.array(ScalarIR)),\n }),\n z.object({\n ...conditionBase,\n type: z.literal('payload'),\n path: z.string(),\n op: PropertyOperatorIR,\n value: z.optional(ScalarIR),\n values: z.optional(z.array(ScalarIR)),\n }),\n z.object({\n ...conditionBase,\n type: z.literal('performed'),\n event: z.string(),\n where: z.optional(ConditionIR),\n within: z.optional(DurationIR),\n count: z.optional(z.number()),\n }),\n z.object({ ...conditionBase, type: z.literal('reachable'), channel: ChannelIR }),\n ])\n);\n\n/* --------------------------------- Triggers --------------------------------- */\n\n/**\n * Trigger shapes. The event trigger has two independent gates: `where` tests\n * the triggering event's payload (payload-only subtree, evaluated against the\n * incoming event before anything else), `filter` tests the user's profile.\n */\nexport const TriggerIR = z.discriminatedUnion('type', [\n z.object({\n type: z.literal('event'),\n event: z.string(),\n where: z.optional(ConditionIR),\n filter: z.optional(ConditionIR),\n visitors: z.optional(z.literal(true)),\n }),\n z.object({ type: z.literal('segment'), segment: z.string() }),\n z.object({ type: z.literal('date'), at: z.string() }),\n z.object({ type: z.literal('webhook') }),\n]);\nexport type TriggerIR = z.infer<typeof TriggerIR>;\n\n/* ----------------------------------- Nodes ---------------------------------- */\n\ninterface NodeBaseIR {\n /** Stable id derived from the structural path (rules in the file header). */\n id: string;\n /** Optional node name: UI title / observability handle (branch's optional first argument lands here). */\n label?: string | undefined;\n /** Metadata (provenance); excluded from contentHash. */\n meta?: NodeMetaIR | undefined;\n}\n\nexport type NodeIR =\n | (NodeBaseIR & {\n type: 'message';\n channel: ChannelIR;\n template: string;\n props: Record<string, PropValueIR>;\n /**\n * Push only: which of the user's devices get a copy. Absent means every\n * device the directory lists; 'latest' only the most recently registered.\n */\n devices?: 'latest' | undefined;\n /**\n * Email and push: the template's subscription topic, or null for a transactional message\n * (sent whatever the user opted out of). Absent for other channels, and in IR compiled\n * before topics existed: such a step checks the channel-level opt-out only, as it always did.\n */\n topic?: string | null | undefined;\n })\n | (NodeBaseIR & { type: 'delay'; duration: DurationIR })\n | (NodeBaseIR & { type: 'random_delay'; min: DurationIR; max: DurationIR })\n | (NodeBaseIR & {\n type: 'time_window';\n days: WeekdayIR[];\n between: [string, string];\n tz: string;\n })\n | (NodeBaseIR & {\n type: 'wait_until';\n condition: ConditionIR;\n timeout: DurationIR;\n onTimeout: 'continue' | 'exit' | NodeIR[];\n })\n | (NodeBaseIR & {\n type: 'branch';\n cases: { label?: string | undefined; condition: ConditionIR; flow: NodeIR[] }[];\n /** Default arm (the DSL's bare tail argument). Absent = fall through to the main line. */\n otherwise?: NodeIR[] | undefined;\n })\n | (NodeBaseIR & {\n type: 'filter';\n /** Audit-log line for the users this gate drops. Metadata: excluded from contentHash. */\n reason?: string | undefined;\n condition: ConditionIR;\n })\n | (NodeBaseIR & {\n type: 'cohort';\n /**\n * Explicit experiment key: bucketing hashes `userId:key` instead of the\n * structural node id, so the assignment survives node insertion/moves and\n * the experiment keeps one identity across workflow versions. Absent →\n * the node id is used (fine for one-off splits).\n */\n key?: string | undefined;\n /**\n * Arms in authoring order — the order the interpreter accumulates weights\n * in, so it decides which bucket range each arm owns. The DSL keeps that\n * promise by rejecting integer-like arm names, which JavaScript would\n * hoist to the front of the object in numeric order. Weights sum to 100.\n */\n arms: { name: string; weight: number; flow: NodeIR[] }[];\n })\n | (NodeBaseIR & {\n /** `reason` is the audit-log line. Metadata: excluded from contentHash. */\n type: 'exit';\n reason?: string | undefined;\n })\n | (NodeBaseIR & { type: 'send_event'; event: string; payload: Record<string, PropValueIR> })\n | (NodeBaseIR & {\n type: 'action';\n /** Registry name of the custom action; the code itself lives in the deployed Worker bundle. */\n action: string;\n /** Argument values; user_property references are resolved at run time, like message props. */\n args: Record<string, PropValueIR>;\n });\n\nconst nodeBase = { id: z.string(), label: z.optional(z.string()), meta: z.optional(NodeMetaIR) };\n\nexport const NodeIR: z.ZodMiniType<NodeIR> = z.lazy(() =>\n z.discriminatedUnion('type', [\n z.object({\n ...nodeBase,\n type: z.literal('message'),\n channel: ChannelIR,\n template: z.string(),\n props: z.record(z.string(), PropValueIR),\n devices: z.optional(z.literal('latest')),\n topic: z.optional(z.nullable(z.string())),\n }),\n z.object({ ...nodeBase, type: z.literal('delay'), duration: DurationIR }),\n z.object({ ...nodeBase, type: z.literal('random_delay'), min: DurationIR, max: DurationIR }),\n z.object({\n ...nodeBase,\n type: z.literal('time_window'),\n days: z.array(WeekdayIR),\n between: z.tuple([z.string(), z.string()]),\n tz: z.string(),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('wait_until'),\n condition: ConditionIR,\n timeout: DurationIR,\n onTimeout: z.union([z.literal('continue'), z.literal('exit'), z.array(NodeIR)]),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('branch'),\n cases: z.array(\n z.object({\n label: z.optional(z.string()),\n condition: ConditionIR,\n flow: z.array(NodeIR),\n })\n ),\n otherwise: z.optional(z.array(NodeIR)),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('filter'),\n condition: ConditionIR,\n reason: z.optional(z.string()),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('cohort'),\n key: z.optional(z.string()),\n arms: z.array(z.object({ name: z.string(), weight: z.number(), flow: z.array(NodeIR) })),\n }),\n z.object({ ...nodeBase, type: z.literal('exit'), reason: z.optional(z.string()) }),\n z.object({\n ...nodeBase,\n type: z.literal('send_event'),\n event: z.string(),\n payload: z.record(z.string(), PropValueIR),\n }),\n z.object({\n ...nodeBase,\n type: z.literal('action'),\n action: z.string(),\n args: z.record(z.string(), PropValueIR),\n }),\n ])\n);\n\n/* ---------------------- Top-level definitions and bundle --------------------- */\n\nexport const GoalIR = z.object({\n condition: ConditionIR,\n within: z.optional(DurationIR),\n exitOnMatch: z.boolean(),\n});\nexport type GoalIR = z.infer<typeof GoalIR>;\n\n/**\n * Human-facing metadata: **excluded from contentHash** (see hash.ts).\n * Editing a name or description does not touch execution semantics, so it never\n * invalidates the version in-flight users are pinned to, nor shows up in plan.\n */\nexport const MetaIR = z.object({\n /**\n * Human label. The asset's `id` is its identity — a wire key that routing\n * tables and the entry ledger are built on — so it is not something to\n * rename for readability. This is: it is metadata, dropped before hashing,\n * and the studio shows it in place of the key wherever it is set.\n */\n name: z.optional(z.string()),\n description: z.optional(z.string()),\n loc: z.optional(SourceLocIR),\n});\nexport type MetaIR = z.infer<typeof MetaIR>;\n\nexport const WorkflowIR = z.object({\n irVersion: z.literal(IR_VERSION),\n /** Wire identity (`workflow({ id })`): routing tables, the entry ledger and reporting are keyed on it. */\n id: z.string(),\n contentHash: z.string(),\n meta: z.optional(MetaIR),\n trigger: TriggerIR,\n goal: z.optional(GoalIR),\n exitWhen: z.optional(ConditionIR),\n flow: z.array(NodeIR),\n});\nexport type WorkflowIR = z.infer<typeof WorkflowIR>;\n\nexport const SegmentIR = z.object({\n irVersion: z.literal(IR_VERSION),\n /** Wire identity (`segment({ id })`): segment references and the membership store are keyed on it. */\n id: z.string(),\n contentHash: z.string(),\n /** Provenance plus the human label — MetaIR, same as a workflow's. */\n meta: z.optional(MetaIR),\n condition: ConditionIR,\n});\nexport type SegmentIR = z.infer<typeof SegmentIR>;\n\n/** Template manifest entry: IR records only the reference; the body lives in the template system. */\nexport const TemplateIR = z.object({\n irVersion: z.literal(IR_VERSION),\n key: z.string(),\n channel: ChannelIR,\n});\nexport type TemplateIR = z.infer<typeof TemplateIR>;\n\n/**\n * Action manifest entry: IR records the name; the implementation solidifies\n * into the Worker bundle at deploy time, never into IR (custom code stays out\n * of the data plane by design).\n */\nexport const ActionIR = z.object({\n irVersion: z.literal(IR_VERSION),\n name: z.string(),\n});\nexport type ActionIR = z.infer<typeof ActionIR>;\n\n/**\n * Deployment unit: the complete snapshot one `deploy` produces (terraform\n * apply, same mental model). The server diffs each definition's contentHash to\n * decide publish / skip / retire.\n */\n/**\n * The subscription topics the contract declares, carried by the bundle so the engine validates\n * `/subscriptions` and the templates' topics against the deployed list, not the SDK's word.\n */\nexport const SubscriptionsIR = z.object({\n topics: z.array(z.string()),\n});\nexport type SubscriptionsIR = z.infer<typeof SubscriptionsIR>;\n\nexport const BundleIR = z.object({\n irVersion: z.literal(IR_VERSION),\n workflows: z.array(WorkflowIR),\n segments: z.array(SegmentIR),\n templates: z.array(TemplateIR),\n actions: z.array(ActionIR),\n /** Absent in a bundle compiled before topics existed: no topics. */\n subscriptions: z.optional(SubscriptionsIR),\n});\nexport type BundleIR = z.infer<typeof BundleIR>;\n"],"mappings":"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"}
|