@modular-react/journeys 1.0.0 → 1.0.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-DPPLlqbu.js","names":[],"sources":["../src/semver.ts","../src/validation.ts","../src/runtime.ts"],"sourcesContent":["/**\n * Minimal semver subset for module-compatibility checks.\n *\n * Why not the `semver` package?\n * -----------------------------\n * `semver` is the canonical JavaScript implementation of the full semver-2.0\n * specification: prerelease tags, build metadata, hyphen ranges, partial\n * versions, loose mode, range intersection algebra, etc. Two costs come with\n * that surface:\n *\n * 1. **Parse cost.** `new SemVer(v)` and `new Range(r)` allocate several\n * objects per call — a `SemVer` for each comparator, regex matches over\n * a hot path. For a startup-time validation that runs once per\n * registered journey × module, the parse cost is fine; for long-running\n * checks (test suites that resolve hundreds of manifests, hot-reload\n * cycles in dev) it adds up.\n * 2. **Range expansion.** `^1.2.3` parses into a `Range` with two\n * `Comparator` objects wrapping two `SemVer` objects; satisfaction is\n * then checked by walking comparator sets. It works, but for the\n * narrow set of ranges this codebase actually uses (caret/tilde/exact\n * from `package.json` and authoring sugar on journey declarations) the\n * math collapses into \"is `[major, minor, patch]` in `[lo, hi)`?\" — a\n * handful of integer comparisons.\n *\n * What this implementation supports\n * ---------------------------------\n * - exact: `1.2.3` (and `=1.2.3`)\n * - caret: `^1.2.3`, `^0.2.3`, `^0.0.3`\n * - tilde: `~1.2.3`, `~1.2`, `~1`\n * - x-range: `1.x`, `1.x.x`, `1.2.x`, `*`, `x`, `1`, `1.2`\n * - bounded: `>=1.2.3`, `>1.2.3`, `<1.2.3`, `<=1.2.3`, `=1.2.3`\n * - hyphen: `1.2.3 - 2.0.0`\n * - AND: whitespace-separated comparators (e.g. `>=1.0.0 <2.0.0`)\n * - OR: `||`-separated comparator sets\n *\n * What it does NOT support\n * ------------------------\n * - prerelease tags (`1.0.0-rc.1`) — we treat anything past `MAJOR.MINOR.PATCH`\n * as an error. Module versions in this framework are stable releases by\n * contract; if that ever changes, we add prerelease handling here.\n * - build metadata (`1.0.0+abc`) — same reasoning.\n * - loose mode / version coercion — versions must be strict\n * `MAJOR.MINOR.PATCH`.\n *\n * If a module declares a range or version this parser can't handle, the\n * relevant function throws a `SemverParseError` synchronously so the failure\n * shows up at registration time rather than as a silent \"no match\".\n */\n\n/** Triple of integers — `[major, minor, patch]`. */\nexport type SemverTriple = readonly [number, number, number];\n\nconst enum Op {\n GT,\n GTE,\n LT,\n LTE,\n EQ,\n}\n\ninterface Comparator {\n readonly op: Op;\n readonly v: SemverTriple;\n}\n\n/**\n * Pre-parsed range. A range is a disjunction (`||`) of conjunctions (` `).\n * Each conjunction is a list of comparators all of which must hold for the\n * version to match the conjunction; the range matches if any one\n * conjunction matches. The `*` / empty range parses to a single empty\n * conjunction (vacuously true).\n */\nexport interface ParsedRange {\n readonly sets: readonly (readonly Comparator[])[];\n}\n\nexport class SemverParseError extends Error {\n constructor(message: string) {\n super(`[@modular-react/journeys] ${message}`);\n this.name = \"SemverParseError\";\n }\n}\n\n/**\n * Parse a strict `MAJOR.MINOR.PATCH` version. Leading `v`/`=` is tolerated\n * (matching `npm` and `semver`'s \"loose\" trim, which is universal in the\n * wild) but any prerelease/build suffix is rejected so a typo doesn't\n * silently match nothing.\n */\nexport function parseVersion(input: string): SemverTriple {\n // `parseVersion` is the only entry point that takes raw user/author input\n // — internal callers receive tokens that the conjunction parser already\n // split on whitespace. Trim here so `stripVersionPrefix` itself stays a\n // pure prefix strip on the hot internal paths.\n const trimmed = stripVersionPrefix(input.trim());\n const triple = parseTriple(trimmed);\n if (!triple) throw new SemverParseError(`invalid version \"${input}\"`);\n return triple;\n}\n\n/**\n * Parse a range string into a {@link ParsedRange}. Throws\n * {@link SemverParseError} on syntactically invalid input. Cache the result\n * when validating against the same range repeatedly — `satisfies` itself is\n * pure integer arithmetic but the parser walks the string.\n */\nexport function parseRange(input: string): ParsedRange {\n const raw = input.trim();\n if (raw === \"\" || raw === \"*\" || raw === \"x\" || raw === \"X\") {\n return { sets: [[]] };\n }\n const orParts = raw.split(\"||\");\n const sets: Comparator[][] = [];\n for (const orPart of orParts) {\n const conj = parseConjunction(orPart);\n sets.push(conj);\n }\n return { sets };\n}\n\n/**\n * Test a concrete version against a parsed range. The hot path is a few\n * integer compares per comparator with no allocations.\n */\nexport function satisfiesParsed(version: SemverTriple, range: ParsedRange): boolean {\n for (const set of range.sets) {\n let ok = true;\n for (const cmp of set) {\n if (!checkComparator(version, cmp)) {\n ok = false;\n break;\n }\n }\n if (ok) return true;\n }\n return false;\n}\n\n/**\n * Convenience wrapper that parses both inputs and runs satisfaction. Use\n * this for one-shot checks; for hot loops, parse once and pass the cached\n * {@link ParsedRange} to {@link satisfiesParsed}.\n */\nexport function satisfies(version: string, range: string): boolean {\n return satisfiesParsed(parseVersion(version), parseRange(range));\n}\n\n/**\n * Compare two version triples lexicographically by `(major, minor, patch)`.\n * Returns -1 / 0 / 1.\n */\nexport function compareTriples(a: SemverTriple, b: SemverTriple): number {\n if (a[0] !== b[0]) return a[0] < b[0] ? -1 : 1;\n if (a[1] !== b[1]) return a[1] < b[1] ? -1 : 1;\n if (a[2] !== b[2]) return a[2] < b[2] ? -1 : 1;\n return 0;\n}\n\n/**\n * Convenience wrapper for ordering two version strings. Returns -1 if `a`\n * sorts before `b`, 1 if after, 0 if equal. Throws {@link SemverParseError}\n * if either input is not a strict `MAJOR.MINOR.PATCH`.\n *\n * Useful for persisted-data compatibility checks: \"is the version recorded\n * on this saved blob older than the cutoff at which we changed the on-disk\n * shape?\". For matching against a range (`^1.0.0`, `>=1.5 <2`, …) use\n * {@link satisfies} instead.\n */\nexport function compareVersions(a: string, b: string): number {\n return compareTriples(parseVersion(a), parseVersion(b));\n}\n\n// -----------------------------------------------------------------------------\n// Internals\n// -----------------------------------------------------------------------------\n\nfunction checkComparator(v: SemverTriple, cmp: Comparator): boolean {\n const c = compareTriples(v, cmp.v);\n switch (cmp.op) {\n case Op.GT:\n return c > 0;\n case Op.GTE:\n return c >= 0;\n case Op.LT:\n return c < 0;\n case Op.LTE:\n return c <= 0;\n case Op.EQ:\n return c === 0;\n }\n}\n\nfunction parseConjunction(input: string): Comparator[] {\n const out: Comparator[] = [];\n // Detect hyphen ranges (`A - B`) before splitting on whitespace. We\n // require whitespace around the hyphen so `1.2.3-rc.1` (which we reject\n // anyway) and bare numerics in a partial don't collide.\n const hyphen = matchHyphenRange(input);\n if (hyphen) {\n expandHyphen(hyphen[0], hyphen[1], out);\n return out;\n }\n const tokens = input.split(/\\s+/).filter((t) => t.length > 0);\n if (tokens.length === 0) return out;\n for (const tok of tokens) expandToken(tok, out);\n return out;\n}\n\nfunction matchHyphenRange(input: string): [string, string] | null {\n // Pattern: <token> <whitespace> \"-\" <whitespace> <token>\n // We don't allow `1.2.3-2.0.0` (no whitespace) — it'd ambiguate with\n // prerelease syntax. Strict whitespace-padded form only.\n const m = input.match(/^\\s*(\\S+)\\s+-\\s+(\\S+)\\s*$/);\n if (!m) return null;\n return [m[1]!, m[2]!];\n}\n\nfunction expandHyphen(loRaw: string, hiRaw: string, out: Comparator[]): void {\n // Lower bound: partial → fill with zero. `1` → `>=1.0.0`; `1.2` → `>=1.2.0`.\n const loPartial = parsePartial(stripVersionPrefix(loRaw));\n if (!loPartial) throw new SemverParseError(`invalid lower bound in hyphen range \"${loRaw}\"`);\n out.push({ op: Op.GTE, v: fillPartial(loPartial, 0) });\n\n // Upper bound: partial → bump the most-specific declared component and\n // use `<` (so `1.2.3 - 2.0.0` becomes `<=2.0.0`, but `1.2.3 - 2` becomes\n // `<3.0.0`). This matches npm semver's hyphen semantics.\n const hiPartial = parsePartial(stripVersionPrefix(hiRaw));\n if (!hiPartial) throw new SemverParseError(`invalid upper bound in hyphen range \"${hiRaw}\"`);\n if (hiPartial.major === null)\n throw new SemverParseError(`invalid upper bound in hyphen range \"${hiRaw}\"`);\n if (hiPartial.minor === null) {\n out.push({ op: Op.LT, v: [hiPartial.major + 1, 0, 0] });\n } else if (hiPartial.patch === null) {\n out.push({ op: Op.LT, v: [hiPartial.major, hiPartial.minor + 1, 0] });\n } else {\n out.push({ op: Op.LTE, v: [hiPartial.major, hiPartial.minor, hiPartial.patch] });\n }\n}\n\nfunction expandToken(token: string, out: Comparator[]): void {\n if (token === \"*\" || token === \"x\" || token === \"X\") return; // no-op (vacuous)\n if (token.startsWith(\"^\")) {\n expandCaret(token.slice(1), out);\n return;\n }\n if (token.startsWith(\"~\")) {\n expandTilde(token.slice(1), out);\n return;\n }\n if (token.startsWith(\">=\")) {\n out.push({ op: Op.GTE, v: parseStrict(token.slice(2), token) });\n return;\n }\n if (token.startsWith(\"<=\")) {\n out.push({ op: Op.LTE, v: parseStrict(token.slice(2), token) });\n return;\n }\n if (token.startsWith(\">\")) {\n out.push({ op: Op.GT, v: parseStrict(token.slice(1), token) });\n return;\n }\n if (token.startsWith(\"<\")) {\n out.push({ op: Op.LT, v: parseStrict(token.slice(1), token) });\n return;\n }\n if (token.startsWith(\"=\")) {\n expandPlainOrXRange(token.slice(1), token, out);\n return;\n }\n expandPlainOrXRange(token, token, out);\n}\n\nfunction expandCaret(rest: string, out: Comparator[]): void {\n const partial = parsePartial(stripVersionPrefix(rest));\n if (!partial) throw new SemverParseError(`invalid caret range \"^${rest}\"`);\n const { major, minor, patch } = partial;\n if (major === null) throw new SemverParseError(`invalid caret range \"^${rest}\"`);\n // `^1` / `^1.x` / `^1.2.x` all collapse to \"any version with the same\n // most-significant non-zero component\"; the runtime treatment is uniform\n // with the explicit forms above.\n if (major > 0) {\n out.push({ op: Op.GTE, v: [major, minor ?? 0, patch ?? 0] });\n out.push({ op: Op.LT, v: [major + 1, 0, 0] });\n return;\n }\n // major === 0: the next significant level becomes the cap.\n if (minor === null) {\n // `^0` → `>=0.0.0 <1.0.0` (matches `semver` behaviour)\n out.push({ op: Op.GTE, v: [0, 0, 0] });\n out.push({ op: Op.LT, v: [1, 0, 0] });\n return;\n }\n if (minor > 0) {\n out.push({ op: Op.GTE, v: [0, minor, patch ?? 0] });\n out.push({ op: Op.LT, v: [0, minor + 1, 0] });\n return;\n }\n // major === 0, minor === 0\n if (patch === null) {\n out.push({ op: Op.GTE, v: [0, 0, 0] });\n out.push({ op: Op.LT, v: [0, 1, 0] });\n return;\n }\n out.push({ op: Op.GTE, v: [0, 0, patch] });\n out.push({ op: Op.LT, v: [0, 0, patch + 1] });\n}\n\nfunction expandTilde(rest: string, out: Comparator[]): void {\n const partial = parsePartial(stripVersionPrefix(rest));\n if (!partial) throw new SemverParseError(`invalid tilde range \"~${rest}\"`);\n const { major, minor, patch } = partial;\n if (major === null) throw new SemverParseError(`invalid tilde range \"~${rest}\"`);\n if (minor === null) {\n // `~1` → `>=1.0.0 <2.0.0` (treat as caret on major).\n out.push({ op: Op.GTE, v: [major, 0, 0] });\n out.push({ op: Op.LT, v: [major + 1, 0, 0] });\n return;\n }\n out.push({ op: Op.GTE, v: [major, minor, patch ?? 0] });\n out.push({ op: Op.LT, v: [major, minor + 1, 0] });\n}\n\nfunction expandPlainOrXRange(rest: string, token: string, out: Comparator[]): void {\n const stripped = stripVersionPrefix(rest);\n const partial = parsePartial(stripped);\n if (!partial) throw new SemverParseError(`invalid version or range \"${token}\"`);\n const { major, minor, patch } = partial;\n if (major === null) return; // `*` / `x` already handled above; defensive.\n if (minor === null) {\n out.push({ op: Op.GTE, v: [major, 0, 0] });\n out.push({ op: Op.LT, v: [major + 1, 0, 0] });\n return;\n }\n if (patch === null) {\n out.push({ op: Op.GTE, v: [major, minor, 0] });\n out.push({ op: Op.LT, v: [major, minor + 1, 0] });\n return;\n }\n out.push({ op: Op.EQ, v: [major, minor, patch] });\n}\n\ninterface PartialVersion {\n readonly major: number | null;\n readonly minor: number | null;\n readonly patch: number | null;\n}\n\nfunction parsePartial(s: string): PartialVersion | null {\n if (s === \"\" || s === \"*\" || s === \"x\" || s === \"X\") {\n return { major: null, minor: null, patch: null };\n }\n const parts = s.split(\".\");\n if (parts.length > 3) return null;\n const out: (number | null)[] = [];\n for (const part of parts) {\n if (part === \"\" || part === \"x\" || part === \"X\" || part === \"*\") {\n out.push(null);\n continue;\n }\n if (!/^\\d+$/.test(part)) return null;\n const n = Number(part);\n if (!Number.isFinite(n)) return null;\n out.push(n);\n }\n while (out.length < 3) out.push(null);\n // Reject partials like `1.x.2` (a wildcard followed by a concrete number)\n // — this is malformed in semver and the silent \"treat trailing as\n // wildcard\" behaviour is more confusing than a clean error.\n let sawWildcard = false;\n for (const v of out) {\n if (v === null) sawWildcard = true;\n else if (sawWildcard) return null;\n }\n return { major: out[0]!, minor: out[1]!, patch: out[2]! };\n}\n\nfunction fillPartial(p: PartialVersion, fill: number): SemverTriple {\n return [p.major ?? fill, p.minor ?? fill, p.patch ?? fill];\n}\n\nfunction parseStrict(s: string, original: string): SemverTriple {\n const triple = parseTriple(stripVersionPrefix(s));\n if (!triple) throw new SemverParseError(`invalid version in \"${original}\"`);\n return triple;\n}\n\nfunction parseTriple(s: string): SemverTriple | null {\n const m = /^(\\d+)\\.(\\d+)\\.(\\d+)$/.exec(s);\n if (!m) return null;\n const triple: SemverTriple = [Number(m[1]), Number(m[2]), Number(m[3])];\n // Reject components that overflow `Number` to `Infinity`. This only catches\n // truly absurd inputs (~309+ digits): the smaller \"lots of digits\" cases\n // (17–308 digits) coerce to a finite Number with silent precision loss but\n // pass this check — they're unreachable from real package.json / authoring\n // surfaces, so we accept the precision-loss tail rather than imposing a\n // tighter cap that would surprise authors writing a legitimate large\n // major number.\n if (!triple.every((n) => Number.isFinite(n))) return null;\n return triple;\n}\n\nfunction stripVersionPrefix(s: string): string {\n // Tolerate `v`, `V`, or `=` as a leading prefix on a strict\n // `MAJOR.MINOR.PATCH` — matching the npm-semver \"loose\" trim that's\n // universal in the wild (`v1.2.3`, `=1.2.3`, both pasted from\n // `package.json` or release notes). Strips a single character so\n // doubled forms (`==1.2.3`, `vv1.2.3`) still fail loudly as\n // malformed input.\n if (s.length === 0) return s;\n const c = s.charCodeAt(0);\n if (c === 118 /* 'v' */ || c === 86 /* 'V' */ || c === 61 /* '=' */) return s.slice(1);\n return s;\n}\n","import { isExitContract } from \"@modular-react/core\";\nimport type { ExitContract, ModuleDescriptor } from \"@modular-react/core\";\nimport type { AnyJourneyDefinition, RegisteredJourney } from \"./types.js\";\nimport { parseRange, parseVersion, satisfiesParsed, SemverParseError } from \"./semver.js\";\n\n/**\n * Aggregated error thrown when one or more registered journeys reference\n * module ids, entry names, or exit names that do not exist (or that\n * disagree on `allowBack`). Mirrors the style of core's\n * `validateDependencies` — accumulate all issues, throw once.\n */\nexport class JourneyValidationError extends Error {\n readonly issues: readonly string[];\n constructor(issues: readonly string[]) {\n super(`[@modular-react/journeys] Invalid journey registration:\\n - ${issues.join(\"\\n - \")}`);\n this.name = \"JourneyValidationError\";\n this.issues = issues;\n }\n}\n\nexport class JourneyHydrationError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(`[@modular-react/journeys] ${message}`, options);\n this.name = \"JourneyHydrationError\";\n }\n}\n\n/**\n * Thrown when `runtime.start()` / `runtime.hydrate()` is called with a\n * journey id that is not registered. Distinct class so shells can\n * discriminate \"this journey is gone after an upgrade, drop the tab\"\n * from transient or validation failures.\n */\nexport class UnknownJourneyError extends Error {\n readonly journeyId: string;\n constructor(journeyId: string, registered: readonly string[]) {\n super(\n `[@modular-react/journeys] Unknown journey id \"${journeyId}\". Registered: ${\n registered.join(\", \") || \"(none)\"\n }`,\n );\n this.name = \"UnknownJourneyError\";\n this.journeyId = journeyId;\n }\n}\n\nexport function validateJourneyContracts(\n journeys: readonly RegisteredJourney[],\n modules: readonly ModuleDescriptor<any, any, any, any>[],\n): void {\n const issues: string[] = [];\n const moduleById = new Map<string, ModuleDescriptor<any, any, any, any>>();\n for (const mod of modules) moduleById.set(mod.id, mod);\n\n // Run the dependency-graph cycle check first — its issues read more\n // diagnostically when the graph is sane structurally, so emit them\n // alongside any structural problems below.\n for (const issue of detectInvokeGraphIssues(journeys)) issues.push(issue);\n\n // Guard against a module declaring an exit literally named `allowBack`.\n // Per-entry transitions on a journey use `allowBack: boolean` as a\n // control key and an exit of the same name would be silently skipped by\n // the per-exit iteration below. Fail loudly at registration time instead.\n for (const mod of modules) {\n if (mod.exitPoints && Object.prototype.hasOwnProperty.call(mod.exitPoints, \"allowBack\")) {\n issues.push(\n `module \"${mod.id}\" declares an exit named \"allowBack\", which collides with the reserved ` +\n `per-entry transition control key. Rename the exit (e.g. \"allowBackExit\").`,\n );\n }\n }\n\n const seenIds = new Set<string>();\n for (const reg of journeys) {\n const def = reg.definition;\n if (seenIds.has(def.id)) {\n issues.push(`journey \"${def.id}\" is registered more than once`);\n }\n seenIds.add(def.id);\n\n // Validate transitions map. The inner objects must be non-null — we\n // accept `AnyJourneyDefinition`, so a caller that sidesteps the typed\n // `defineJourney` helper can hand us `{ transitions: { foo: null } }`\n // or `{ bar: { baz: null } }`; we want those to become an accumulated\n // issue instead of a TypeError that short-circuits the loop.\n const transitions = (def.transitions ?? {}) as Record<string, unknown>;\n for (const [moduleId, perModule] of Object.entries(transitions)) {\n const mod = moduleById.get(moduleId);\n if (!mod) {\n issues.push(\n `journey \"${def.id}\" references unknown module id \"${moduleId}\" in transitions`,\n );\n continue;\n }\n if (!perModule || typeof perModule !== \"object\") {\n issues.push(\n `journey \"${def.id}\" has malformed transitions for module \"${moduleId}\" (expected an object)`,\n );\n continue;\n }\n for (const [entryName, perEntry] of Object.entries(perModule as Record<string, unknown>)) {\n const entry = mod.entryPoints?.[entryName];\n if (!entry) {\n issues.push(`journey \"${def.id}\" references unknown entry \"${moduleId}.${entryName}\"`);\n continue;\n }\n if (!perEntry || typeof perEntry !== \"object\") {\n issues.push(\n `journey \"${def.id}\" has malformed transitions for entry \"${moduleId}.${entryName}\" (expected an object)`,\n );\n continue;\n }\n const perEntryObj = perEntry as Record<string, unknown>;\n for (const exitName of Object.keys(perEntryObj)) {\n if (exitName === \"allowBack\") continue;\n if (!mod.exitPoints || !(exitName in mod.exitPoints)) {\n issues.push(\n `journey \"${def.id}\" references unknown exit \"${moduleId}.${entryName}.${exitName}\"`,\n );\n }\n }\n if (perEntryObj.allowBack === true) {\n const descriptorAllowBack = entry.allowBack;\n if (descriptorAllowBack !== \"preserve-state\" && descriptorAllowBack !== \"rollback\") {\n issues.push(\n `journey \"${def.id}\" sets allowBack on \"${moduleId}.${entryName}\" but the module entry does not declare allowBack`,\n );\n }\n }\n }\n }\n\n validateWildcardTransitions(def, modules, moduleById, issues);\n\n // Validate `moduleCompat`: each declared range must parse, the named\n // module must be registered, and the registered module's `version`\n // must satisfy the range. We accumulate every issue (rather than\n // throwing on the first) so a deployment with several mismatched\n // teams sees the full list in one CI run.\n if (def.moduleCompat) {\n for (const [moduleId, rangeRaw] of Object.entries(def.moduleCompat)) {\n if (typeof rangeRaw !== \"string\") {\n issues.push(\n `journey \"${def.id}\" declares a non-string version range for module \"${moduleId}\" in moduleCompat`,\n );\n continue;\n }\n // Trim and check separately from the typeof guard so a whitespace-only\n // value (e.g. `\" \"`) gets a message that matches the actual problem,\n // and so it can't slip past length===0 and then be treated as the\n // wildcard range by `parseRange` (which would silently disable compat\n // enforcement for that module).\n const rangeNormalized = rangeRaw.trim();\n if (rangeNormalized.length === 0) {\n issues.push(\n `journey \"${def.id}\" declares an empty version range for module \"${moduleId}\" in moduleCompat`,\n );\n continue;\n }\n const mod = moduleById.get(moduleId);\n if (!mod) {\n issues.push(\n `journey \"${def.id}\" requires module \"${moduleId}\" (range \"${rangeNormalized}\") in moduleCompat but it is not registered`,\n );\n continue;\n }\n let parsedRange;\n try {\n parsedRange = parseRange(rangeNormalized);\n } catch (err) {\n const message = err instanceof SemverParseError ? err.message : String(err);\n issues.push(\n `journey \"${def.id}\" has an unparseable moduleCompat range for \"${moduleId}\": ${message}`,\n );\n continue;\n }\n let modVersion;\n try {\n modVersion = parseVersion(mod.version);\n } catch (err) {\n const message = err instanceof SemverParseError ? err.message : String(err);\n issues.push(\n `module \"${moduleId}\" declares an unparseable version \"${mod.version}\" (referenced by journey \"${def.id}\"): ${message}`,\n );\n continue;\n }\n if (!satisfiesParsed(modVersion, parsedRange)) {\n issues.push(\n `journey \"${def.id}\" requires module \"${moduleId}\" \"${rangeNormalized}\" but registered version is \"${mod.version}\"`,\n );\n }\n }\n }\n\n // Validate the sibling `resumes` map. Like `transitions`, the runtime\n // tolerates a malformed value (handlers are looked up by name at child\n // terminal time), but spelling errors at authoring time are easier to\n // diagnose here than as a generic \"invoke-unknown-resume\" abort.\n const resumes = (def.resumes ?? {}) as Record<string, unknown>;\n for (const [moduleId, perModule] of Object.entries(resumes)) {\n const mod = moduleById.get(moduleId);\n if (!mod) {\n issues.push(`journey \"${def.id}\" references unknown module id \"${moduleId}\" in resumes`);\n continue;\n }\n if (!perModule || typeof perModule !== \"object\") {\n issues.push(\n `journey \"${def.id}\" has malformed resumes for module \"${moduleId}\" (expected an object)`,\n );\n continue;\n }\n for (const [entryName, perEntry] of Object.entries(perModule as Record<string, unknown>)) {\n if (!mod.entryPoints?.[entryName]) {\n issues.push(\n `journey \"${def.id}\" references unknown entry \"${moduleId}.${entryName}\" in resumes`,\n );\n continue;\n }\n if (!perEntry || typeof perEntry !== \"object\") {\n issues.push(\n `journey \"${def.id}\" has malformed resumes for entry \"${moduleId}.${entryName}\" (expected an object)`,\n );\n continue;\n }\n // Resume names live in their own keyspace, but a name that collides\n // with a module exit on the same entry is almost certainly an error\n // (the author probably meant a transition handler, not a resume).\n // Surface it loudly.\n for (const resumeName of Object.keys(perEntry as Record<string, unknown>)) {\n if (mod.exitPoints && Object.prototype.hasOwnProperty.call(mod.exitPoints, resumeName)) {\n issues.push(\n `journey \"${def.id}\" declares resume \"${moduleId}.${entryName}.${resumeName}\" but \"${resumeName}\" is also an exit name on that module — rename one to avoid silent confusion`,\n );\n }\n const handler = (perEntry as Record<string, unknown>)[resumeName];\n if (typeof handler !== \"function\") {\n issues.push(\n `journey \"${def.id}\" has non-function resume \"${moduleId}.${entryName}.${resumeName}\"`,\n );\n }\n }\n }\n }\n }\n\n if (issues.length > 0) throw new JourneyValidationError(issues);\n}\n\n/**\n * Verify the directed graph of journey-to-journey invocations, derived\n * from each registered journey's `invokes` declaration, contains no\n * cycles. A cycle in the static graph would, at runtime, manifest as\n * either an infinite chain (depth-limited by `maxCallStackDepth`) or a\n * same-id-on-stack abort — both are recoverable but late. The graph\n * check turns the same mistake into a registration-time error citing\n * the cycle path.\n *\n * Run automatically as part of {@link validateJourneyContracts}. Exposed\n * separately so shells that compose registrations (e.g. plugin chaining)\n * can run the graph check on a partial slice without invoking the full\n * contracts validator. Throws {@link JourneyValidationError} when one or\n * more cycles are detected.\n *\n * **What's checked.** The graph only spans journeys whose `invokes`\n * field is declared as an array. Edges to journey ids that are not\n * present in `journeys` are ignored — those will fail at runtime with\n * `invoke-unknown-journey`, not as cycle reports. Self-loops (a journey\n * that lists its own handle) are reported as a one-cycle.\n *\n * **What's NOT checked.** Journeys that omit `invokes` contribute no\n * edges; a cycle that runs through such a journey will not be caught\n * statically, and the runtime guards (`invoke-cycle`,\n * `invoke-stack-overflow`) remain the safety net. Authors who want\n * full static coverage should declare `invokes` on every journey that\n * uses `invoke()`.\n */\nexport function validateJourneyGraph(journeys: readonly RegisteredJourney[]): void {\n const issues = detectInvokeGraphIssues(journeys);\n if (issues.length > 0) throw new JourneyValidationError(issues);\n}\n\n/**\n * DFS-based cycle finder over the static `invokes` graph. Returns one\n * issue per *distinct* cycle (canonicalized by rotating the cycle so\n * its lexicographically smallest id leads, so `A→B→A` and `B→A→B`\n * collapse to one report).\n *\n * Worst-case O(V + E) per starting node, V*E total — perfectly fine for\n * the tens-of-journeys scale this targets. We deliberately do not use\n * Tarjan's SCC algorithm here: enumerating an SCC's members is less\n * actionable to authors than a concrete cycle path, and DFS path\n * extraction gives that for free.\n */\nfunction detectInvokeGraphIssues(journeys: readonly RegisteredJourney[]): string[] {\n const idSet = new Set<string>();\n for (const reg of journeys) idSet.add(reg.definition.id);\n\n const graph = new Map<string, string[]>();\n for (const reg of journeys) {\n const out: string[] = [];\n const declared = reg.definition.invokes;\n if (Array.isArray(declared)) {\n for (const handle of declared) {\n if (!handle || typeof handle.id !== \"string\") continue;\n // Edges to journeys outside the registration set are ignored —\n // their absence is reported by other paths (UnknownJourneyError\n // at runtime, or the registry's own missing-id check). The\n // cycle search only operates on the closed graph.\n if (!idSet.has(handle.id)) continue;\n out.push(handle.id);\n }\n }\n graph.set(reg.definition.id, out);\n }\n\n const issues: string[] = [];\n const reportedCycles = new Set<string>();\n // `fullyExplored` short-circuits nodes whose subtree we've already fully\n // explored. Once a node's subtree is known cycle-free (in either of two\n // senses: no cycles found, or all cycles through it already reported),\n // we skip it on subsequent DFS roots. The path-membership state\n // (`onPath`) is recomputed from each root.\n const fullyExplored = new Set<string>();\n const onPath = new Set<string>();\n const path: string[] = [];\n\n // Iterative DFS — a recursive version is more obvious to read but\n // overflows V8's stack on pathologically deep invoke chains (~10k\n // nodes deep). The iterative form preserves the same observable\n // behaviour: a frame is `{ id, nextIndex }` (a cursor over the node's\n // outgoing edges), which is exactly the state the recursive version\n // kept implicitly between recursive calls.\n type Frame = { readonly id: string; nextIndex: number };\n const stack: Frame[] = [];\n\n for (const reg of journeys) {\n const rootId = reg.definition.id;\n if (fullyExplored.has(rootId)) continue;\n\n stack.push({ id: rootId, nextIndex: 0 });\n onPath.add(rootId);\n path.push(rootId);\n\n while (stack.length > 0) {\n const frame = stack[stack.length - 1]!;\n const neighbors = graph.get(frame.id);\n if (!neighbors || frame.nextIndex >= neighbors.length) {\n stack.pop();\n path.pop();\n onPath.delete(frame.id);\n fullyExplored.add(frame.id);\n continue;\n }\n const next = neighbors[frame.nextIndex]!;\n frame.nextIndex++;\n\n if (onPath.has(next)) {\n // Closed a cycle — extract the path from `next`'s first\n // occurrence through to the duplicate. e.g. for path [A, B, C]\n // re-entering A, the cycle is A → B → C → A.\n const startIdx = path.indexOf(next);\n const cycleNodes = path.slice(startIdx);\n const canonical = canonicalizeCycle(cycleNodes);\n if (!reportedCycles.has(canonical)) {\n reportedCycles.add(canonical);\n const display = [...cycleNodes, next].map(quote).join(\" → \");\n issues.push(`journey invoke cycle detected: ${display}`);\n }\n continue;\n }\n if (fullyExplored.has(next)) continue;\n\n stack.push({ id: next, nextIndex: 0 });\n onPath.add(next);\n path.push(next);\n }\n }\n return issues;\n}\n\nfunction canonicalizeCycle(nodes: readonly string[]): string {\n // Rotate so the lexicographically smallest id leads. Without this,\n // the same cycle reported from two DFS roots would yield two distinct\n // strings (e.g. `A→B→A` vs `B→A→B`) and slip past the dedup Set.\n let pivot = 0;\n for (let i = 1; i < nodes.length; i++) {\n if (nodes[i] < nodes[pivot]) pivot = i;\n }\n return nodes.slice(pivot).concat(nodes.slice(0, pivot)).join(\"→\");\n}\n\nfunction quote(id: string): string {\n return `\"${id}\"`;\n}\n\n/**\n * Plain-object guard. `typeof === \"object\"` alone admits `null` and\n * arrays, both of which are common malformed-payload shapes for the\n * wildcard transition map (`null` from a manual reset, arrays from\n * accidental `[handler]` instead of `{ name: handler }`). Reject both\n * here so the validator's branches don't have to repeat the check.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Wildcard transition validator — kept separate from the main\n * `validateJourneyContracts` body so the registration loop reads\n * cleanly and the wildcard-specific branching (two slots, three\n * checks per slot, shape guards at each level) lives in one place.\n *\n * Three checks per slot:\n * - **Live-key**: at least one *registered* module emits the named\n * exit (or entry+exit pair). Scoped to all registered modules\n * because a journey may legitimately reach a module via a `next`\n * transition we can't statically introspect — narrowing this to\n * `Object.keys(transitions)` would false-reject those wildcards.\n * - **Contract consistency**: scoped to the *journey's* own modules\n * (those keyed under `transitions`). The wildcard's typed `output`\n * is determined by the modules the journey actually navigates to;\n * unrelated modules in the registry that happen to share the exit\n * name with a different `ExitContract` instance must not produce a\n * spurious mismatch error.\n * - **Tier-overlap warning**: declaring the same exit under both\n * `byEntryAndExit[E][X]` and `byExit[X]` shadows the latter for\n * entry `E`. Console warning only — sometimes deliberate, but\n * worth surfacing.\n *\n * The two scopes (live-key = app-wide, consistency = journey-scoped)\n * are deliberately different. The runtime spot-check in `runtime.ts`\n * covers post-registration drift for both scopes by walking the\n * runtime's own `moduleMap` lazily on first dispatch.\n */\nfunction validateWildcardTransitions(\n def: AnyJourneyDefinition,\n modules: readonly ModuleDescriptor<any, any, any, any>[],\n moduleById: ReadonlyMap<string, ModuleDescriptor<any, any, any, any>>,\n issues: string[],\n): void {\n const rawWildcardTransitions = (def as { wildcardTransitions?: unknown }).wildcardTransitions;\n if (rawWildcardTransitions === undefined || rawWildcardTransitions === null) return;\n if (!isPlainObject(rawWildcardTransitions)) {\n issues.push(\n `journey \"${def.id}\" has malformed wildcardTransitions (expected an object, got ${describeKind(rawWildcardTransitions)})`,\n );\n return;\n }\n\n const wildcardTransitions = rawWildcardTransitions;\n const journeyModuleIds = new Set(Object.keys(def.transitions ?? {}));\n // Modules the journey directly references via its `transitions` map.\n // Used as the consistency scope so unrelated modules in the registry\n // can't produce false-positive contract mismatches.\n const journeyModules: ModuleDescriptor<any, any, any, any>[] = [];\n for (const id of journeyModuleIds) {\n const mod = moduleById.get(id);\n if (mod) journeyModules.push(mod);\n }\n\n const byEntryAndExit = parseWildcardSlot(\n wildcardTransitions.byEntryAndExit,\n `journey \"${def.id}\" has malformed wildcardTransitions.byEntryAndExit`,\n issues,\n );\n if (byEntryAndExit) {\n validateByEntryAndExit(def.id, byEntryAndExit, modules, journeyModules, issues);\n }\n\n const byExit = parseWildcardSlot(\n wildcardTransitions.byExit,\n `journey \"${def.id}\" has malformed wildcardTransitions.byExit`,\n issues,\n );\n if (byExit) {\n validateByExit(def.id, byExit, byEntryAndExit, modules, journeyModules, issues);\n }\n}\n\n/**\n * Extract a wildcard slot (`byEntryAndExit` or `byExit`) from the raw\n * map, accumulating a malformed-shape issue when the value is present\n * but not a plain object. Returns `undefined` for both the absent and\n * malformed cases so the caller can short-circuit uniformly.\n */\nfunction parseWildcardSlot(\n raw: unknown,\n malformedPrefix: string,\n issues: string[],\n): Record<string, unknown> | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (!isPlainObject(raw)) {\n issues.push(`${malformedPrefix} (expected an object, got ${describeKind(raw)})`);\n return undefined;\n }\n return raw;\n}\n\nfunction describeKind(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n return typeof value;\n}\n\nfunction validateByEntryAndExit(\n journeyId: string,\n byEntryAndExit: Record<string, unknown>,\n liveKeyModules: readonly ModuleDescriptor<any, any, any, any>[],\n consistencyModules: readonly ModuleDescriptor<any, any, any, any>[],\n issues: string[],\n): void {\n for (const [entryName, perEntry] of Object.entries(byEntryAndExit)) {\n if (!isPlainObject(perEntry)) {\n issues.push(\n `journey \"${journeyId}\" has malformed wildcardTransitions.byEntryAndExit[\"${entryName}\"] (expected an object, got ${describeKind(perEntry)})`,\n );\n continue;\n }\n for (const [exitName, handler] of Object.entries(perEntry)) {\n if (typeof handler !== \"function\") {\n issues.push(\n `journey \"${journeyId}\" has non-function wildcardTransitions.byEntryAndExit[\"${entryName}\"][\"${exitName}\"]`,\n );\n continue;\n }\n const liveKeyMatch = liveKeyModules.filter(\n (m) =>\n m.entryPoints?.[entryName] !== undefined &&\n m.exitPoints !== undefined &&\n exitName in m.exitPoints,\n );\n if (liveKeyMatch.length === 0) {\n issues.push(\n `journey \"${journeyId}\" declares wildcardTransitions.byEntryAndExit[\"${entryName}\"][\"${exitName}\"] but no registered module pairs that entry with that exit`,\n );\n continue;\n }\n // Consistency check scoped to journey-declared modules only.\n const consistencyMatch = consistencyModules.filter(\n (m) =>\n m.entryPoints?.[entryName] !== undefined &&\n m.exitPoints !== undefined &&\n exitName in m.exitPoints,\n );\n validateContractConsistency(\n issues,\n journeyId,\n `wildcardTransitions.byEntryAndExit[\"${entryName}\"][\"${exitName}\"]`,\n exitName,\n consistencyMatch,\n );\n }\n }\n}\n\nfunction validateByExit(\n journeyId: string,\n byExit: Record<string, unknown>,\n byEntryAndExit: Record<string, unknown> | undefined,\n liveKeyModules: readonly ModuleDescriptor<any, any, any, any>[],\n consistencyModules: readonly ModuleDescriptor<any, any, any, any>[],\n issues: string[],\n): void {\n for (const [exitName, handler] of Object.entries(byExit)) {\n if (typeof handler !== \"function\") {\n issues.push(\n `journey \"${journeyId}\" has non-function wildcardTransitions.byExit[\"${exitName}\"]`,\n );\n continue;\n }\n const liveKeyMatch = liveKeyModules.filter(\n (m) => m.exitPoints !== undefined && exitName in m.exitPoints,\n );\n if (liveKeyMatch.length === 0) {\n issues.push(\n `journey \"${journeyId}\" declares wildcardTransitions.byExit[\"${exitName}\"] but no registered module emits that exit`,\n );\n continue;\n }\n const consistencyMatch = consistencyModules.filter(\n (m) => m.exitPoints !== undefined && exitName in m.exitPoints,\n );\n validateContractConsistency(\n issues,\n journeyId,\n `wildcardTransitions.byExit[\"${exitName}\"]`,\n exitName,\n consistencyMatch,\n );\n\n // Tier-overlap warning: declaring the same exit under both slots\n // shadows byExit for the matching entry. Console-only because the\n // validator's contract is to throw on hard issues; warnings sit\n // beside them.\n if (byEntryAndExit) {\n const shadowing: string[] = [];\n for (const [entryName, perEntry] of Object.entries(byEntryAndExit)) {\n if (\n isPlainObject(perEntry) &&\n typeof (perEntry as Record<string, unknown>)[exitName] === \"function\"\n ) {\n shadowing.push(entryName);\n }\n }\n if (shadowing.length > 0) {\n console.warn(\n `[@modular-react/journeys] journey \"${journeyId}\" declares both wildcardTransitions.byExit[\"${exitName}\"] and wildcardTransitions.byEntryAndExit[${shadowing\n .map(quote)\n .join(\n \",\",\n )}][\"${exitName}\"]; the byExit handler is unreachable from those entries (the more specific one wins).`,\n );\n }\n }\n }\n}\n\n/**\n * Cross-module contract consistency check for a wildcard transition\n * slot. When any of the matching modules declare the exit via an\n * `ExitContract`, every other matching module must use the same\n * contract instance — otherwise the wildcard handler's `output` shape\n * silently differs between modules, which defeats the whole point of\n * the wildcard. Modules that declare the exit as a plain\n * `ExitPointSchema` (no contract) are tolerated alongside contract-\n * using modules in the warning-only sense (no issue), because authors\n * can opt into shape consistency incrementally.\n *\n * Issues are appended to the shared accumulator. The slot path\n * (`label`) is included verbatim in the message so authors can locate\n * the offending wildcard at a glance.\n */\nfunction validateContractConsistency(\n issues: string[],\n journeyId: string,\n label: string,\n exitName: string,\n matching: readonly ModuleDescriptor<any, any, any, any>[],\n): void {\n // Find the first contract-using module; it sets the expected identity.\n let expectedContract: ExitContract<unknown> | null = null;\n let expectedFromModule: string | null = null;\n for (const mod of matching) {\n const schema = mod.exitPoints?.[exitName];\n if (schema && isExitContract(schema)) {\n expectedContract = schema as ExitContract<unknown>;\n expectedFromModule = mod.id;\n break;\n }\n }\n if (!expectedContract) return;\n\n for (const mod of matching) {\n if (mod.id === expectedFromModule) continue;\n const schema = mod.exitPoints?.[exitName];\n if (!schema) continue;\n if (!isExitContract(schema)) continue;\n if (schema !== expectedContract) {\n issues.push(\n `journey \"${journeyId}\" ${label}: module \"${mod.id}\" declares exit \"${exitName}\" with a different ExitContract than module \"${expectedFromModule}\" (contract identity mismatch — share a single defineExitContract value across modules to make the wildcard's output shape uniform)`,\n );\n }\n }\n}\n\n/**\n * Shallow sanity check on a journey definition's own shape. Use this for\n * authoring ergonomics; structural contract checks live in\n * {@link validateJourneyContracts}.\n */\nexport function validateJourneyDefinition(def: AnyJourneyDefinition): readonly string[] {\n const issues: string[] = [];\n if (!def.id || typeof def.id !== \"string\") issues.push(\"journey is missing a string id\");\n if (!def.version || typeof def.version !== \"string\")\n issues.push(`journey \"${def.id ?? \"(unknown)\"}\" is missing a string version`);\n if (typeof def.initialState !== \"function\")\n issues.push(`journey \"${def.id}\" must declare initialState as a function`);\n if (typeof def.start !== \"function\")\n issues.push(`journey \"${def.id}\" must declare start as a function`);\n if (!def.transitions || typeof def.transitions !== \"object\")\n issues.push(`journey \"${def.id}\" must declare transitions`);\n return issues;\n}\n","import { isDevEnv, isExitContract } from \"@modular-react/core\";\nimport type {\n ExitContract,\n JourneyHandleRef,\n ModuleDescriptor,\n StandardSchemaIssue,\n StandardSchemaResult,\n} from \"@modular-react/core\";\nimport type {\n AnyJourneyDefinition,\n ChildOutcome,\n InstanceId,\n InvokeSpec,\n JourneyDefinition,\n JourneyDefinitionSummary,\n JourneyInstance,\n JourneyPersistence,\n JourneyRuntime,\n JourneyStatus,\n JourneyStep,\n ModuleTypeMap,\n PendingInvoke,\n RegisteredJourney,\n ResumeBounceCounter,\n ResumeHandler,\n ResumeMap,\n SerializedJourney,\n StepSpec,\n TransitionEvent,\n TransitionResult,\n} from \"./types.js\";\nimport { JourneyHydrationError, UnknownJourneyError } from \"./validation.js\";\n\n/**\n * Default cap on the depth of an in-flight invoke chain (root parent +\n * descendants). A journey's registration can override this via\n * `JourneyRegisterOptions.maxCallStackDepth`; the runtime takes the\n * minimum of all non-undefined settings across the chain so the most\n * cautious journey wins.\n *\n * The default of 16 is chosen to be larger than any reasonable\n * invocation chain we've seen (deepest real-world: 3) while still\n * catching unbounded recursion long before it becomes a memory or\n * call-stack hazard.\n */\nconst DEFAULT_MAX_CALL_STACK_DEPTH = 16;\n\n/**\n * Default cap on consecutive resume bounces at the same parent step.\n * A \"bounce\" is a resume that returns `{ invoke }` instead of advancing\n * the parent's step; the counter resets when the parent's step changes\n * for any reason. Override via\n * `JourneyRegisterOptions.maxResumeBouncesPerStep`.\n */\nconst DEFAULT_MAX_RESUME_BOUNCES_PER_STEP = 8;\n\nexport interface InstanceRecord<TState = unknown> {\n id: InstanceId;\n journeyId: string;\n status: JourneyStatus;\n step: JourneyStep | null;\n history: JourneyStep[];\n /** Snapshots captured per history entry — indexed alongside history. */\n rollbackSnapshots: (TState | undefined)[];\n /** True when any entry in `rollbackSnapshots` holds a real snapshot. */\n hasRollbackSnapshot: boolean;\n state: TState;\n terminalPayload: unknown;\n startedAt: string;\n updatedAt: string;\n /** Monotonically increasing token used to invalidate stale exit/goBack calls. */\n stepToken: number;\n /** Persistence key computed on start. Stable for the instance's lifetime. */\n persistenceKey: string | null;\n terminalFired: boolean;\n /** Total retries the outlet has consumed for this instance (across all steps). */\n retryCount: number;\n listeners: Set<() => void>;\n pendingSave: SerializedJourney<TState> | null;\n saveInFlight: boolean;\n /**\n * Set when this instance is a child invoked from a parent's transition.\n * On terminal, the runtime looks up the parent record and applies the\n * named resume handler with this instance's terminal outcome. Cleared\n * after the resume fires (or never set for root instances). Mirrored to\n * the persisted blob as `parentLink` so a child loaded out-of-order on\n * reload still knows its parent.\n */\n parent: { instanceId: InstanceId; resumeName: string } | null;\n /**\n * Set on a parent record when a child journey is in flight from this\n * record's current step. Cleared on resume (the named handler fires)\n * or when either side is force-terminated. Mirrored to the persisted\n * blob as `pendingInvoke` so reload can relink across processes.\n */\n activeChildId: InstanceId | null;\n /**\n * True when `removePersisted` fires while `saveInFlight` is still set:\n * the remove is deferred until the save settles so adapters that don't\n * serialize their own ops can't see remove→save reordering and leave\n * an orphaned blob in storage.\n */\n pendingRemove: boolean;\n /**\n * Monotonically incrementing revision bumped when an observable field\n * changes (status/step/state/history/terminalPayload). Used to memoize\n * the public `JourneyInstance` snapshot so that `getInstance(id)` returns\n * a stable reference between changes — a requirement of\n * `useSyncExternalStore`.\n */\n revision: number;\n /** Cached snapshot keyed by `revision`; rebuilt on the next read if stale. */\n cachedSnapshot: { revision: number; instance: JourneyInstance } | null;\n /** Cached exit/goBack closures keyed by stepToken. */\n cachedCallbacks: {\n stepToken: number;\n exit: (name: string, output?: unknown) => void;\n goBack: (() => void) | undefined;\n } | null;\n /**\n * Per-step bounce counter for the resume-bounce-limit guard. Set when\n * a resume on this record's current step returns `{ invoke }`;\n * incremented on each subsequent same-step resume that does the same;\n * cleared whenever the step actually advances. Mirrored to the\n * persisted blob as `resumeBouncesAtStep` so a hostile or accidental\n * reload-bounce-reload-bounce cannot reset the counter through\n * storage.\n */\n resumeBouncesAtStep: ResumeBounceCounter | null;\n}\n\nexport interface JourneyRuntimeOptions {\n readonly debug?: boolean;\n /**\n * Module descriptors keyed by id — the runtime needs them to resolve\n * `allowBack` mode ('preserve-state' | 'rollback' | false) at goBack time.\n * When omitted, `goBack` falls back to 'preserve-state' for any journey\n * transition that opts in via `allowBack: true`.\n */\n readonly modules?: Readonly<Record<string, ModuleDescriptor<any, any, any, any>>>;\n}\n\n/**\n * Module-private store of runtime internals. Keeps `__bindStepCallbacks`,\n * `__getRecord`, `__getRegistered`, and the bound module descriptor map off\n * the public `JourneyRuntime` surface (which would otherwise show up in\n * autocomplete, `Object.keys`, etc.). Access via {@link getInternals}.\n */\nconst INTERNALS = new WeakMap<JourneyRuntime, JourneyRuntimeInternals>();\n\n/**\n * Create a journey runtime bound to a set of registered journeys. The\n * registry integration assembles this once at resolve time; the runtime is\n * owned by the manifest and exposed as `manifest.journeys`.\n *\n * Passing an empty `registered` array yields a no-op runtime: every public\n * method is safe to call, and `start()` will throw \"unknown journey id\" —\n * matching the normal \"not registered\" failure mode and letting shells skip\n * null-guards on `manifest.journeys`.\n */\nexport function createJourneyRuntime(\n registered: readonly RegisteredJourney[],\n options: JourneyRuntimeOptions = {},\n): JourneyRuntime {\n const debug = options.debug ?? isDevEnv();\n const moduleMap = options.modules ?? {};\n /**\n * Module ids we've already warned about for missing-module dispatch.\n * Without this dedupe a long-running runtime would spam an identical\n * warning on every exit fired from the same step. Scoped to the\n * runtime closure so reset semantics fall out naturally with disposal.\n */\n const warnedMissingModule = new Set<string>();\n /**\n * Exit names we've already spot-checked for cross-module ExitContract\n * identity drift. `validateJourneyContracts` runs the equivalent check\n * at registration time against the modules the registry resolved with,\n * but the runtime can be built with a different module snapshot\n * (direct `createJourneyRuntime` use, plugin composition that adds\n * modules after the validator has already run, test mocks that swap\n * descriptors without a fresh resolve). One-shot per exit name keeps\n * the cost bounded — at most O(modules) once per unique exit fired,\n * cached forever after.\n */\n const spotCheckedExits = new Set<string>();\n const definitions = new Map<string, RegisteredJourney>();\n for (const entry of registered) definitions.set(entry.definition.id, entry);\n const instances = new Map<InstanceId, InstanceRecord>();\n // keyIndex is namespaced internally by journeyId so two journeys that\n // happen to return the same `keyFor` string do not alias onto the same\n // instance. The adapter sees only the user-defined portion; the prefix is\n // applied inside the runtime.\n const keyIndex = new Map<string, InstanceId>();\n /**\n * Reverse lookup from child instance id → parent instance id. Maintained\n * alongside `record.parent` so the child-terminal hook is O(1) and does\n * not have to scan all records to find an awaiting parent. Symmetric:\n * present iff `parent.activeChildId === childId`.\n */\n const childToParent = new Map<InstanceId, InstanceId>();\n\n function indexKey(journeyId: string, userKey: string): string {\n // Use a non-printable separator so a journey id containing the\n // separator can never collide with a user-defined key.\n return `${journeyId}\u0001${userKey}`;\n }\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function notify(record: InstanceRecord) {\n record.revision += 1;\n record.cachedSnapshot = null;\n for (const listener of record.listeners) {\n try {\n listener();\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] listener threw\", err);\n }\n }\n }\n\n function nowIso(): string {\n return new Date().toISOString();\n }\n\n function mintInstanceId(): InstanceId {\n try {\n const cryptoObj = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (cryptoObj?.randomUUID) return `ji_${cryptoObj.randomUUID()}`;\n } catch {\n // Fall through to the Math.random fallback.\n }\n const rand = Math.random().toString(36).slice(2, 10);\n return `ji_${Date.now().toString(36)}_${rand}`;\n }\n\n function summarize(reg: RegisteredJourney): JourneyDefinitionSummary {\n return {\n id: reg.definition.id,\n version: reg.definition.version,\n meta: reg.definition.meta,\n };\n }\n\n function assertKnown(journeyId: string): RegisteredJourney {\n const reg = definitions.get(journeyId);\n if (!reg) {\n throw new UnknownJourneyError(journeyId, [...definitions.keys()]);\n }\n return reg;\n }\n\n function stepFromSpec(spec: StepSpec<ModuleTypeMap>): JourneyStep {\n return { moduleId: spec.module, entry: spec.entry, input: spec.input };\n }\n\n function entryAllowBackMode(step: JourneyStep | null): \"preserve-state\" | \"rollback\" | false {\n if (!step) return false;\n const mod = moduleMap[step.moduleId];\n const entry = mod?.entryPoints?.[step.entry];\n const raw = entry?.allowBack;\n if (raw === \"rollback\" || raw === \"preserve-state\") return raw;\n return false;\n }\n\n function journeyAllowsBack(definition: AnyJourneyDefinition, step: JourneyStep | null): boolean {\n if (!step) return false;\n const perModule = (definition.transitions as Record<string, any> | undefined)?.[step.moduleId];\n const perEntry = perModule?.[step.entry];\n return perEntry?.allowBack === true;\n }\n\n function cloneSnapshot(state: unknown): unknown {\n if (state === null || typeof state !== \"object\") return state;\n const cloned: unknown = Array.isArray(state) ? [...state] : { ...(state as object) };\n // Freeze the snapshot so a transition that mutates rolled-back state\n // in place fails loudly instead of silently corrupting the history.\n // Done in dev *and* prod so the dev/prod behavior diff is just\n // \"console.error vs strict-mode throw,\" not \"silently corrupts vs\n // throws.\" The freeze is shallow — deep mutation still slips through\n // (documented limitation L8), but catches the most common footgun.\n try {\n Object.freeze(cloned);\n } catch {\n // Some engines reject freezing exotic objects; swallow.\n }\n return cloned;\n }\n\n function trimHistory(record: InstanceRecord, reg: RegisteredJourney) {\n const cap = reg.options?.maxHistory;\n // `undefined`, zero, and negative all mean \"unbounded\" — zero is treated\n // as the same escape hatch as a negative cap so a misconfigured `0`\n // cannot silently disable `goBack` by trimming history on every transition.\n if (cap === undefined || cap <= 0) return;\n while (record.history.length > cap) {\n record.history.shift();\n record.rollbackSnapshots.shift();\n }\n record.hasRollbackSnapshot = record.rollbackSnapshots.some((s) => s !== undefined);\n }\n\n // ---------------------------------------------------------------------------\n // Persistence save pipeline (§10.2)\n // ---------------------------------------------------------------------------\n\n function schedulePersist<TState>(\n record: InstanceRecord<TState>,\n persistence: JourneyPersistence<TState>,\n ) {\n const blob = serialize(record);\n if (record.saveInFlight) {\n record.pendingSave = blob;\n return;\n }\n void runSave(record, persistence, blob);\n }\n\n async function runSave<TState>(\n record: InstanceRecord<TState>,\n persistence: JourneyPersistence<TState>,\n blob: SerializedJourney<TState>,\n ) {\n record.saveInFlight = true;\n try {\n if (!record.persistenceKey) return;\n await persistence.save(record.persistenceKey, blob);\n } catch (err) {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Failed to persist \"${record.journeyId}\" instance ${record.id}`,\n err,\n );\n }\n } finally {\n record.saveInFlight = false;\n // A terminal transition arrived while the save was in flight. The\n // remove was deferred to this point so adapters that do not serialize\n // their own ops don't see remove → save reordering. Skip the pending\n // save — its blob is about to be obsolete anyway.\n if (record.pendingRemove) {\n record.pendingRemove = false;\n record.pendingSave = null;\n if (record.persistenceKey) fireAndForgetRemove(persistence, record.persistenceKey);\n } else if (record.pendingSave) {\n const next = record.pendingSave;\n record.pendingSave = null;\n void runSave(record, persistence, next);\n }\n }\n }\n\n function removePersisted<TState>(\n record: InstanceRecord<TState>,\n persistence: JourneyPersistence<TState>,\n ) {\n if (!record.persistenceKey) return;\n record.pendingSave = null;\n const key = record.persistenceKey;\n keyIndex.delete(indexKey(record.journeyId, key));\n if (record.saveInFlight) {\n // Defer the remove until the save settles. `runSave`'s finally block\n // picks this up and fires the remove with the same key.\n record.pendingRemove = true;\n return;\n }\n fireAndForgetRemove(persistence, key);\n }\n\n /**\n * Delete a blob we've decided to discard (terminal, corrupt, unmigrateable)\n * without mutating any live instance record. Used from the `start()` paths\n * where we've probed the adapter and then chosen to mint a fresh instance.\n */\n function discardBlob<TState>(persistence: JourneyPersistence<TState>, key: string) {\n fireAndForgetRemove(persistence, key);\n }\n\n function fireAndForgetRemove<TState>(persistence: JourneyPersistence<TState>, key: string) {\n try {\n const maybe = persistence.remove(key);\n if (maybe && typeof (maybe as Promise<void>).catch === \"function\") {\n (maybe as Promise<void>).catch((err) => {\n if (debug) console.error(\"[@modular-react/journeys] persistence.remove rejected\", err);\n });\n }\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] persistence.remove threw\", err);\n }\n }\n\n function serialize<TState>(record: InstanceRecord<TState>): SerializedJourney<TState> {\n let pendingInvoke: PendingInvoke | undefined;\n if (record.activeChildId) {\n const child = instances.get(record.activeChildId);\n // Best-effort: only emit pendingInvoke when the child is still in\n // memory and we can resolve it. If the child has been forgotten or\n // never linked, we skip serialization rather than emitting a dangling\n // link the hydrate path can't satisfy.\n if (child) {\n const link = child.parent;\n if (link && link.instanceId === record.id) {\n pendingInvoke = {\n childJourneyId: child.journeyId,\n childInstanceId: child.id,\n childPersistenceKey: child.persistenceKey,\n resumeName: link.resumeName,\n };\n }\n }\n }\n const parentLink = record.parent\n ? { parentInstanceId: record.parent.instanceId, resumeName: record.parent.resumeName }\n : undefined;\n return {\n definitionId: record.journeyId,\n version: definitions.get(record.journeyId)!.definition.version,\n instanceId: record.id,\n status:\n record.status === \"loading\" ? \"active\" : (record.status as SerializedJourney[\"status\"]),\n step: record.step,\n history: [...record.history],\n // Preserve alignment with `history` — map `undefined` to `null` so the\n // shape survives JSON. Only emit when we actually hold snapshots.\n rollbackSnapshots: record.hasRollbackSnapshot\n ? record.rollbackSnapshots.map((s) => (s === undefined ? null : s))\n : undefined,\n terminalPayload:\n record.status === \"completed\" || record.status === \"aborted\"\n ? record.terminalPayload\n : undefined,\n state: record.state,\n startedAt: record.startedAt,\n updatedAt: record.updatedAt,\n pendingInvoke,\n parentLink,\n resumeBouncesAtStep: record.resumeBouncesAtStep ?? undefined,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Hook firing\n // ---------------------------------------------------------------------------\n\n function fireOnTransition(\n reg: RegisteredJourney,\n record: InstanceRecord,\n from: JourneyStep | null,\n to: JourneyStep | null,\n exit: string | null,\n extras?: {\n readonly kind?: TransitionEvent[\"kind\"];\n readonly child?: TransitionEvent[\"child\"];\n readonly outcome?: TransitionEvent[\"outcome\"];\n readonly resume?: TransitionEvent[\"resume\"];\n },\n ) {\n const ev: TransitionEvent = {\n journeyId: record.journeyId,\n instanceId: record.id,\n from,\n to,\n exit,\n state: record.state,\n // Defensive copy — `record.history` is mutated in place on every\n // transition, and async consumers (analytics batchers, deferred\n // telemetry) would otherwise observe later mutations when they\n // finally inspect the event.\n history: [...record.history],\n kind: extras?.kind ?? \"step\",\n child: extras?.child,\n outcome: extras?.outcome,\n resume: extras?.resume,\n };\n try {\n reg.definition.onTransition?.(ev);\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] onTransition (definition) threw\", err);\n }\n try {\n reg.options?.onTransition?.(ev);\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] onTransition (registration) threw\", err);\n }\n }\n\n function fireOnComplete(reg: RegisteredJourney, record: InstanceRecord, result: unknown) {\n if (record.terminalFired) return;\n record.terminalFired = true;\n const ctx = {\n journeyId: record.journeyId,\n instanceId: record.id,\n state: record.state,\n history: record.history,\n };\n try {\n reg.definition.onComplete?.(ctx, result);\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] onComplete (definition) threw\", err);\n }\n try {\n reg.options?.onComplete?.(ctx, result);\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] onComplete (registration) threw\", err);\n }\n }\n\n function fireOnAbort(reg: RegisteredJourney, record: InstanceRecord, reason: unknown) {\n if (record.terminalFired) return;\n record.terminalFired = true;\n const ctx = {\n journeyId: record.journeyId,\n instanceId: record.id,\n state: record.state,\n history: record.history,\n };\n try {\n reg.definition.onAbort?.(ctx, reason);\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] onAbort (definition) threw\", err);\n }\n try {\n reg.options?.onAbort?.(ctx, reason);\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] onAbort (registration) threw\", err);\n }\n }\n\n function fireOnError(\n reg: RegisteredJourney,\n record: InstanceRecord,\n err: unknown,\n step: JourneyStep | null,\n phase: \"step\" | \"invoke\" | \"resume\" | \"abandon\" = \"step\",\n ) {\n try {\n reg.options?.onError?.(err, { step, phase });\n } catch (hookErr) {\n if (debug) console.error(\"[@modular-react/journeys] onError (registration) threw\", hookErr);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Invoke / resume helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Look up a resume handler on a record's current step. Resumes live in a\n * sibling map at the journey-definition level (`def.resumes[mod][entry]`)\n * so the `EntryTransitions` intersection stays clean — see the doc on\n * `EntryTransitions` in `journey-contracts.ts` for why.\n */\n function lookupResume(\n reg: RegisteredJourney,\n step: JourneyStep,\n resumeName: string,\n ): ResumeHandler<ModuleTypeMap, unknown, unknown, unknown, unknown> | undefined {\n const resumesMap = reg.definition.resumes as ResumeMap<ModuleTypeMap, unknown> | undefined;\n if (!resumesMap) return undefined;\n const perModule = resumesMap[step.moduleId];\n if (!perModule) return undefined;\n const perEntry = perModule[step.entry];\n if (!perEntry) return undefined;\n return perEntry[resumeName] as\n | ResumeHandler<ModuleTypeMap, unknown, unknown, unknown, unknown>\n | undefined;\n }\n\n /**\n * Walk a record's ancestor chain via `parent` links, returning every\n * ancestor in root → immediate-parent order (excluding `record`\n * itself). Used by the cycle and depth guards in {@link beginInvoke}.\n * Stops at the first dangling pointer (an ancestor that has been\n * `forget()`-ed mid-flight) — the chain we have is what we work with;\n * we do not synthesize phantom links.\n */\n function ancestorChain(record: InstanceRecord): InstanceRecord[] {\n const chain: InstanceRecord[] = [];\n let cur = record.parent;\n // Cap the walk at MAX_ANCESTOR_WALK so a corrupted in-memory cycle\n // (parent pointer landing back on a descendant after a forget +\n // re-hydrate race) cannot lock the runtime in an infinite loop.\n // The cap is far above any realistic chain depth and any finite\n // guard limit.\n const seen = new Set<InstanceId>([record.id]);\n while (cur) {\n if (seen.has(cur.instanceId)) break;\n const ancestor = instances.get(cur.instanceId);\n if (!ancestor) break;\n seen.add(ancestor.id);\n chain.unshift(ancestor);\n cur = ancestor.parent;\n }\n return chain;\n }\n\n /**\n * Resolve the effective `maxResumeBouncesPerStep` for a record. Unlike\n * `maxCallStackDepth`, the bounce counter is per-record (per-step,\n * actually) and can only be sensibly governed by the journey that\n * owns the resume returning the bounce — i.e. the parent's own\n * registration. Other journeys in the call chain don't see the\n * resumes; they would have no business voting on the cap.\n */\n function resolveBounceCap(reg: RegisteredJourney): number {\n const opt = reg.options?.maxResumeBouncesPerStep;\n if (typeof opt === \"number\" && Number.isFinite(opt) && opt > 0) return opt;\n return DEFAULT_MAX_RESUME_BOUNCES_PER_STEP;\n }\n\n /**\n * Resolve the effective `maxCallStackDepth` for an invoke about to\n * happen. Walks the chain (ancestors + parent + would-be child) and\n * picks the **minimum** non-undefined option, falling back to the\n * library default. Any journey in the chain can lower the cap; none\n * can quietly raise it.\n *\n * `0` and negative values are treated as \"no opinion\" (consistent with\n * `maxHistory` semantics) so a misconfigured `0` cannot silently\n * disable invoke from this journey.\n */\n function resolveMaxCallStackDepth(\n chain: readonly InstanceRecord[],\n parentReg: RegisteredJourney,\n childReg: RegisteredJourney | undefined,\n ): number {\n let cap = DEFAULT_MAX_CALL_STACK_DEPTH;\n let sawOverride = false;\n const visit = (reg: RegisteredJourney | undefined) => {\n const opt = reg?.options?.maxCallStackDepth;\n if (typeof opt === \"number\" && Number.isFinite(opt) && opt > 0) {\n cap = sawOverride ? Math.min(cap, opt) : opt;\n sawOverride = true;\n }\n };\n for (const ancestor of chain) visit(definitions.get(ancestor.journeyId));\n visit(parentReg);\n visit(childReg);\n return cap;\n }\n\n /**\n * Open a child journey from a parent's transition. Validates the handle\n * up-front; on any failure (unknown journey id, missing resume on the\n * parent step), fires `onError` and drives the parent into `abort` so\n * the failure mode is surfaced uniformly with other transition errors.\n * Returns the live child `InstanceRecord` when the invoke was committed,\n * or `false` when it fell through to abort (and the caller must skip\n * the post-transition persistence/notify pass).\n *\n * The cycle / depth / undeclared-child guards run *before* the handle\n * is dispatched to `runtime.start`, so a guard rejection never\n * materialises a child record. This keeps the failure path symmetric\n * with the existing unknown-journey / missing-resume validations.\n */\n function beginInvoke(\n parent: InstanceRecord,\n parentReg: RegisteredJourney,\n spec: InvokeSpec<unknown, unknown>,\n exitName: string | null,\n ): InstanceRecord | false {\n const parentStep = parent.step;\n if (!parentStep) {\n // Should not happen (we only reach the invoke arm with an active step),\n // but guard so a corrupted call site can't crash the runtime.\n applyTransitionLocal(\n parent,\n parentReg,\n { abort: { reason: \"invoke-without-step\", exit: exitName } },\n { kind: \"invoke\" },\n );\n return false;\n }\n if (!spec || typeof spec !== \"object\") {\n applyTransitionLocal(\n parent,\n parentReg,\n { abort: { reason: \"invoke-missing-spec\", exit: exitName } },\n { kind: \"invoke\" },\n );\n return false;\n }\n const childJourneyId = spec.handle?.id;\n if (!childJourneyId || !definitions.has(childJourneyId)) {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Invoke from \"${parent.journeyId}.${parentStep.moduleId}.${parentStep.entry}\" references unknown child journey id \"${childJourneyId}\".`,\n );\n }\n fireOnError(\n parentReg,\n parent,\n new Error(`Unknown child journey \"${childJourneyId}\"`),\n parentStep,\n \"invoke\",\n );\n applyTransitionLocal(\n parent,\n parentReg,\n {\n abort: { reason: \"invoke-unknown-journey\", journeyId: childJourneyId, exit: exitName },\n },\n { kind: \"invoke\" },\n );\n return false;\n }\n if (typeof spec.resume !== \"string\" || spec.resume.length === 0) {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Invoke from \"${parent.journeyId}.${parentStep.moduleId}.${parentStep.entry}\" is missing a resume name.`,\n );\n }\n fireOnError(parentReg, parent, new Error(\"Invoke missing resume name\"), parentStep, \"invoke\");\n applyTransitionLocal(\n parent,\n parentReg,\n { abort: { reason: \"invoke-missing-resume\", exit: exitName } },\n { kind: \"invoke\" },\n );\n return false;\n }\n if (!lookupResume(parentReg, parentStep, spec.resume)) {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Invoke from \"${parent.journeyId}.${parentStep.moduleId}.${parentStep.entry}\" names resume \"${spec.resume}\" but no such handler is declared on def.resumes[${parentStep.moduleId}][${parentStep.entry}].`,\n );\n }\n fireOnError(\n parentReg,\n parent,\n new Error(\n `Resume \"${spec.resume}\" not declared on ${parentStep.moduleId}.${parentStep.entry}`,\n ),\n parentStep,\n \"invoke\",\n );\n applyTransitionLocal(\n parent,\n parentReg,\n { abort: { reason: \"invoke-unknown-resume\", resume: spec.resume, exit: exitName } },\n { kind: \"invoke\" },\n );\n return false;\n }\n\n // ---------------------------------------------------------------------\n // Cycle / depth / declared-set guards (cycle-safety net).\n //\n // All three run before we dispatch to `runtime.start` so a guard\n // rejection never materialises a child record nor touches the child's\n // persistence. The order is deliberate:\n // 1. undeclared-child: cheapest, catches dynamic-dispatch typos\n // whose blast radius is the parent's own definition.\n // 2. cycle: catches \"this exact id is already on the active chain\"\n // with the most actionable error message (a printed chain).\n // 3. depth: backstop for graphs that cycle through ids the\n // runtime has not seen yet (e.g. ABCABC where each link is a\n // different journey).\n // ---------------------------------------------------------------------\n\n const childReg = definitions.get(childJourneyId);\n const declaredInvokes = parentReg.definition.invokes;\n if (Array.isArray(declaredInvokes)) {\n let allowed = false;\n for (const handle of declaredInvokes) {\n if (handle?.id === childJourneyId) {\n allowed = true;\n break;\n }\n }\n if (!allowed) {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Invoke from \"${parent.journeyId}.${parentStep.moduleId}.${parentStep.entry}\" dispatched handle \"${childJourneyId}\" which is not in the parent's declared invokes[]. Add the handle to the parent journey's \\`invokes\\` array, or remove the declaration to opt out of static checking.`,\n );\n }\n fireOnError(\n parentReg,\n parent,\n new Error(`Child journey \"${childJourneyId}\" is not in \"${parent.journeyId}\".invokes[]`),\n parentStep,\n \"invoke\",\n );\n applyTransitionLocal(\n parent,\n parentReg,\n {\n abort: {\n reason: \"invoke-undeclared-child\",\n parentJourneyId: parent.journeyId,\n childJourneyId,\n exit: exitName,\n },\n },\n { kind: \"invoke\" },\n );\n return false;\n }\n }\n\n const chain = ancestorChain(parent);\n // Same-id guard. Walks every ancestor + the parent itself; if the\n // target id is anywhere on the active chain we abort with the\n // closing-cycle path so the author can see exactly where recursion\n // closed. The `chain` payload mirrors the printed display (cycle\n // portion only — pre-cycle prefix is dropped) so telemetry consumers\n // and the human-readable warning agree.\n {\n const fullIds = chain.map((r) => r.journeyId).concat(parent.journeyId);\n const collisionIdx = fullIds.indexOf(childJourneyId);\n if (collisionIdx >= 0) {\n const cyclePath = [...fullIds.slice(collisionIdx), childJourneyId];\n const display = cyclePath.map((id) => `\"${id}\"`).join(\" → \");\n if (debug) {\n console.error(\n `[@modular-react/journeys] Invoke would re-enter journey \"${childJourneyId}\" already on the active chain: ${display}. Aborting parent \"${parent.id}\".`,\n );\n }\n fireOnError(\n parentReg,\n parent,\n new Error(`Invoke cycle on chain: ${display}`),\n parentStep,\n \"invoke\",\n );\n applyTransitionLocal(\n parent,\n parentReg,\n {\n abort: {\n reason: \"invoke-cycle\",\n childJourneyId,\n chain: cyclePath,\n exit: exitName,\n },\n },\n { kind: \"invoke\" },\n );\n return false;\n }\n }\n\n // Depth guard. The chain reaching `parent` is `chain.length + 1`\n // (ancestors + parent); the new child would push it to\n // `chain.length + 2`. Compare against the resolved cap.\n const cap = resolveMaxCallStackDepth(chain, parentReg, childReg);\n const depthAfterInvoke = chain.length + 2;\n if (depthAfterInvoke > cap) {\n const chainIds = chain.map((r) => r.journeyId).concat(parent.journeyId, childJourneyId);\n const display = chainIds.map((id) => `\"${id}\"`).join(\" → \");\n if (debug) {\n console.error(\n `[@modular-react/journeys] Invoke would exceed maxCallStackDepth (${cap}) — chain: ${display}. Aborting parent \"${parent.id}\".`,\n );\n }\n fireOnError(\n parentReg,\n parent,\n new Error(`Invoke would exceed depth cap ${cap} on chain ${display}`),\n parentStep,\n \"invoke\",\n );\n applyTransitionLocal(\n parent,\n parentReg,\n {\n abort: {\n reason: \"invoke-stack-overflow\",\n depth: depthAfterInvoke,\n cap,\n chain: chainIds,\n exit: exitName,\n },\n },\n { kind: \"invoke\" },\n );\n return false;\n }\n\n // Mint or load the child via the standard `start` path so persistence\n // and idempotency work uniformly. `start` returns an existing instance\n // id when the child's keyFor matches an in-flight one (rare here, but\n // fully supported).\n let childId: InstanceId;\n try {\n childId = runtime.start(spec.handle, spec.input as never);\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] runtime.start during invoke threw\", err);\n fireOnError(parentReg, parent, err, parentStep, \"invoke\");\n applyTransitionLocal(\n parent,\n parentReg,\n { abort: { reason: \"invoke-start-threw\", error: err, exit: exitName } },\n { kind: \"invoke\" },\n );\n return false;\n }\n const child = instances.get(childId);\n if (!child) {\n // Defensive: start should have populated `instances`. If not, abort.\n applyTransitionLocal(\n parent,\n parentReg,\n { abort: { reason: \"invoke-start-no-record\", exit: exitName } },\n { kind: \"invoke\" },\n );\n return false;\n }\n // `runtime.start` is idempotent on the persistence key, so it can hand\n // back a child that is already wired to a *different* parent — for\n // example, two parents invoke the same child journey with a `keyFor`\n // that resolves to the same string. Overwriting the link would steal\n // the child and leave the original parent stranded with a dangling\n // `activeChildId`. Reject the second invoke instead.\n if (child.parent && child.parent.instanceId !== parent.id) {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Invoke target \"${child.id}\" is already linked to parent \"${child.parent.instanceId}\". Aborting parent \"${parent.id}\".`,\n );\n }\n fireOnError(\n parentReg,\n parent,\n new Error(\n `Child instance \"${child.id}\" is already linked to parent \"${child.parent.instanceId}\"`,\n ),\n parentStep,\n \"invoke\",\n );\n applyTransitionLocal(\n parent,\n parentReg,\n {\n abort: {\n reason: \"invoke-child-already-linked\",\n childInstanceId: child.id,\n existingParentId: child.parent.instanceId,\n exit: exitName,\n },\n },\n { kind: \"invoke\" },\n );\n return false;\n }\n\n // Wire the link in both directions.\n parent.activeChildId = childId;\n child.parent = { instanceId: parent.id, resumeName: spec.resume };\n childToParent.set(childId, parent.id);\n // Persist the child *now* that its parentLink is set — otherwise the\n // blob written during `runtime.start(...)` above has parentLink=undefined,\n // and a reload would resurrect the child with no back-pointer to the\n // parent's resume. Reuses the `childReg` looked up by the cycle/depth\n // guards above; that lookup is by `childJourneyId === child.journeyId`,\n // so it is the same registration record.\n const childPersistence = childReg?.options?.persistence;\n if (childPersistence) schedulePersist(child, childPersistence);\n // Bump the child's revision so subscribers picking up `instance.parent`\n // see the link on the very first read after the invoke.\n notify(child);\n return child;\n }\n\n /**\n * Local short-cut for the abort branch used by `beginInvoke`'s validation\n * failures — calls into the same shared transition machinery without\n * re-entering the invoke arm. The \"Local\" suffix marks it as the inner\n * sibling of the public `applyTransition`; both eventually fall into the\n * same persistence/notify tail.\n */\n function applyTransitionLocal(\n record: InstanceRecord,\n reg: RegisteredJourney,\n result: TransitionResult<ModuleTypeMap, unknown>,\n eventExtras?: {\n readonly kind?: TransitionEvent[\"kind\"];\n readonly outcome?: TransitionEvent[\"outcome\"];\n readonly resume?: TransitionEvent[\"resume\"];\n },\n ) {\n applyTransition(record, reg, result, null, eventExtras);\n }\n\n /**\n * If `record` has a `parent` link AND has just reached a terminal status,\n * fire the parent's named resume handler with the child's outcome and\n * apply the result as the parent's next transition. Idempotent: clears\n * the parent's `activeChildId` and the `childToParent` entry so a second\n * call is a no-op.\n *\n * Edge: the parent may itself be terminal already (cascade-end raced\n * with a child completing) — in that case the resume is dropped, the\n * link is cleared, and the child's terminal stands on its own.\n */\n function applyResumeIfChild(child: InstanceRecord) {\n if (child.status !== \"completed\" && child.status !== \"aborted\") return;\n const parentLink = child.parent;\n if (!parentLink) return;\n const parentId = parentLink.instanceId;\n const parent = instances.get(parentId);\n // Always clear the bookkeeping, even if the parent is gone — leftover\n // entries would otherwise pin the child in `childToParent` forever.\n childToParent.delete(child.id);\n child.parent = null;\n // The terminal `notify(child)` in `applyTransition` already fired\n // before us with `child.parent` still set; subscribers cached that\n // snapshot. Re-notify so the cleared linkage lands in the public\n // snapshot — otherwise `getInstance(child.id).parent` keeps returning\n // the stale link until the next unrelated revision bump.\n notify(child);\n if (!parent) return;\n // Only fire the resume when this child is still the parent's *active*\n // child. A racey end()/forget on the parent could have set\n // `activeChildId` to null already; treat that as \"parent moved on\".\n if (parent.activeChildId !== child.id) return;\n parent.activeChildId = null;\n // Same staleness story for the parent: paths that fall through to\n // `applyTransition(parent, …)` get a notify there, but the early-\n // return arms below (terminal parent, missing reg/step) do not — so\n // notify here when we are about to bail. Paths that continue rely on\n // `applyTransition`'s tail notify, which clears `cachedSnapshot` and\n // emits a single combined snapshot for the link clear + transition.\n if (parent.status !== \"active\") {\n notify(parent);\n return;\n }\n const parentReg = definitions.get(parent.journeyId);\n const parentStep = parent.step;\n if (!parentReg || !parentStep) {\n notify(parent);\n return;\n }\n\n const handler = lookupResume(parentReg, parentStep, parentLink.resumeName);\n if (!handler) {\n // The parent's journey definition was upgraded between invoke and\n // resume so the resume name is gone. Surface as an abort with a\n // discoverable reason — the parent's onAbort/onError see it and the\n // shell can decide whether to restart or surrender.\n if (debug) {\n console.warn(\n `[@modular-react/journeys] Resume \"${parentLink.resumeName}\" no longer declared on ${parentStep.moduleId}.${parentStep.entry} — aborting parent ${parent.id}.`,\n );\n }\n fireOnError(\n parentReg,\n parent,\n new Error(`Resume \"${parentLink.resumeName}\" missing`),\n parentStep,\n \"resume\",\n );\n applyTransition(\n parent,\n parentReg,\n {\n abort: {\n reason: \"resume-missing\",\n resume: parentLink.resumeName,\n childJourneyId: child.journeyId,\n },\n },\n null,\n );\n return;\n }\n\n const outcome: ChildOutcome<unknown> =\n child.status === \"completed\"\n ? { status: \"completed\", payload: child.terminalPayload }\n : { status: \"aborted\", reason: child.terminalPayload };\n\n let result: TransitionResult<ModuleTypeMap, unknown>;\n try {\n result = handler({\n state: parent.state,\n input: parentStep.input,\n outcome,\n }) as TransitionResult<ModuleTypeMap, unknown>;\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] resume handler threw\", err);\n fireOnError(parentReg, parent, err, parentStep, \"resume\");\n applyTransition(\n parent,\n parentReg,\n {\n abort: {\n reason: \"resume-threw\",\n resume: parentLink.resumeName,\n error: err,\n },\n },\n null,\n );\n return;\n }\n if (result && typeof (result as { then?: unknown }).then === \"function\") {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Resume handler \"${parentLink.resumeName}\" on ${parentStep.moduleId}.${parentStep.entry} returned a Promise. Resumes must be synchronous and pure.`,\n );\n }\n applyTransition(\n parent,\n parentReg,\n { abort: { reason: \"resume-returned-promise\", resume: parentLink.resumeName } },\n null,\n { kind: \"resume\", outcome, resume: parentLink.resumeName },\n );\n return;\n }\n // Tag the parent's transition as \"resume\" — same flow through\n // applyTransition, but `onTransition` consumers can filter by\n // `kind === \"resume\"` and read `outcome` / `resume` to correlate.\n applyTransition(parent, parentReg, result, null, {\n kind: \"resume\",\n outcome,\n resume: parentLink.resumeName,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Transition application\n // ---------------------------------------------------------------------------\n\n function applyTransition(\n record: InstanceRecord,\n reg: RegisteredJourney,\n result: TransitionResult<ModuleTypeMap, unknown>,\n exitName: string | null,\n eventExtras?: {\n readonly kind?: TransitionEvent[\"kind\"];\n readonly outcome?: TransitionEvent[\"outcome\"];\n readonly resume?: TransitionEvent[\"resume\"];\n },\n ) {\n const previousStep = record.step;\n // Snapshot the *pre-transition* state (before any state update) — this\n // is what goBack should restore into the step we're about to leave.\n // \"state\" in result signals an explicit write, even if the new value is\n // `undefined` (legitimate for state types that allow it).\n const preState = record.state;\n if (\"state\" in result) {\n record.state = result.state;\n }\n\n // Any step change clears the per-step bounce counter — the counter is\n // scoped to the step that fired the bouncing resumes; once we move\n // off that step, we're in a fresh per-step budget. Cleared up front\n // so all three step-advancing arms (next / complete / abort) inherit\n // the reset uniformly.\n if (\"next\" in result || \"complete\" in result || \"abort\" in result) {\n record.resumeBouncesAtStep = null;\n }\n\n if (\"next\" in result) {\n const nextStep = stepFromSpec(result.next);\n if (debug) {\n // Validation at resolveManifest() catches static misconfiguration,\n // but transition handlers branch at runtime and can return a\n // dynamically-built `next` that points at a module or entry that\n // isn't registered. The outlet would then render its generic\n // \"no entry on the registered modules\" message with no hint about\n // which transition was responsible. Warn here so the authoring loop\n // surfaces the source.\n if (Object.keys(moduleMap).length > 0) {\n const mod = moduleMap[nextStep.moduleId];\n if (!mod) {\n console.warn(\n `[@modular-react/journeys] Transition on \"${previousStep?.moduleId}.${previousStep?.entry}\" returned next.module=\"${nextStep.moduleId}\" which is not in the runtime's module map — the outlet will render a \"no entry\" error.`,\n );\n } else if (!mod.entryPoints?.[nextStep.entry]) {\n console.warn(\n `[@modular-react/journeys] Transition on \"${previousStep?.moduleId}.${previousStep?.entry}\" returned next.entry=\"${nextStep.moduleId}.${nextStep.entry}\" which is not a declared entry on that module.`,\n );\n }\n }\n }\n if (previousStep) {\n record.history.push(previousStep);\n // Clone the pre-state snapshot only when the step we're entering\n // opts in to rollback — avoids unnecessary work for preserve-state\n // / no-back entries. Shallow clone keeps the snapshot stable against\n // accidental top-level mutation.\n const nextMode = entryAllowBackModeForStep(nextStep);\n if (nextMode === \"rollback\") {\n record.rollbackSnapshots.push(cloneSnapshot(preState));\n record.hasRollbackSnapshot = true;\n } else {\n record.rollbackSnapshots.push(undefined);\n }\n }\n record.step = nextStep;\n record.status = \"active\";\n record.stepToken += 1;\n record.updatedAt = nowIso();\n record.cachedCallbacks = null;\n trimHistory(record, reg);\n fireOnTransition(reg, record, previousStep, nextStep, exitName, eventExtras);\n } else if (\"complete\" in result) {\n if (previousStep) {\n record.history.push(previousStep);\n record.rollbackSnapshots.push(undefined);\n }\n record.step = null;\n record.status = \"completed\";\n record.terminalPayload = result.complete;\n record.stepToken += 1;\n record.updatedAt = nowIso();\n record.cachedCallbacks = null;\n trimHistory(record, reg);\n fireOnTransition(reg, record, previousStep, null, exitName, eventExtras);\n fireOnComplete(reg, record, result.complete);\n } else if (\"abort\" in result) {\n if (previousStep) {\n record.history.push(previousStep);\n record.rollbackSnapshots.push(undefined);\n }\n record.step = null;\n record.status = \"aborted\";\n record.terminalPayload = result.abort;\n record.stepToken += 1;\n record.updatedAt = nowIso();\n record.cachedCallbacks = null;\n trimHistory(record, reg);\n fireOnTransition(reg, record, previousStep, null, exitName, eventExtras);\n fireOnAbort(reg, record, result.abort);\n } else if (\"invoke\" in result) {\n // Invoke a child journey from this step. The parent's step does NOT\n // change — the parent stays \"active, with a child in flight.\" When\n // the child terminates, the runtime fires the parent's named resume\n // handler (declared on `def.resumes[mod][entry][name]`) with the\n // child's outcome and applies the result as the parent's next\n // transition.\n //\n // Bounce-limit guard: when this `{ invoke }` arm is the result of\n // a *resume* on the same step (kind === \"resume\"), the parent has\n // not advanced since the previous invoke at this step — that is a\n // \"bounce.\" Cap the consecutive bounces so a malformed\n // resume → invoke → resume → invoke loop cannot spin indefinitely.\n // The check runs before `beginInvoke` so a rejected bounce never\n // mints a child instance.\n if (eventExtras?.kind === \"resume\") {\n const bounceCap = resolveBounceCap(reg);\n const prior = record.resumeBouncesAtStep;\n const nextCount = prior && prior.stepToken === record.stepToken ? prior.count + 1 : 1;\n if (nextCount > bounceCap) {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Resume on \"${record.journeyId}.${previousStep?.moduleId}.${previousStep?.entry}\" would bounce ${nextCount} times in a row at the same step (cap ${bounceCap}). Aborting \"${record.id}\" to break the loop.`,\n );\n }\n fireOnError(\n reg,\n record,\n new Error(\n `Resume bounce limit exceeded (${bounceCap}) at ${previousStep?.moduleId}.${previousStep?.entry}`,\n ),\n previousStep,\n \"resume\",\n );\n // Re-enter the abort branch via the standard machinery — that\n // gives us the persist + notify + onAbort tail for free, and\n // fires `kind: \"resume\"` so telemetry filters work.\n applyTransition(\n record,\n reg,\n {\n abort: {\n reason: \"resume-bounce-limit\",\n cap: bounceCap,\n count: nextCount,\n resume: eventExtras.resume,\n },\n },\n null,\n { kind: \"resume\", outcome: eventExtras.outcome, resume: eventExtras.resume },\n );\n return;\n }\n record.resumeBouncesAtStep = { stepToken: record.stepToken, count: nextCount };\n }\n const childRecord = beginInvoke(record, reg, result.invoke, exitName);\n if (!childRecord) {\n // beginInvoke already applied the abort path on validation failure.\n // notify+persistence have run there too; bail without a second pass.\n return;\n }\n record.updatedAt = nowIso();\n // No step change → no stepToken bump → exit/goBack callbacks for the\n // parent step would still resolve, but `dispatchExit` short-circuits\n // on `record.activeChildId` (see the guard added there) so a stray\n // exit fires from the now-paused parent are dropped with a warning.\n fireOnTransition(reg, record, previousStep, previousStep, exitName, {\n kind: \"invoke\",\n child: { instanceId: childRecord.id, journeyId: childRecord.journeyId },\n });\n }\n\n const persistence = reg.options?.persistence;\n if (persistence) {\n if (record.status === \"active\") schedulePersist(record, persistence);\n else removePersisted(record, persistence);\n }\n\n notify(record);\n\n // After the parent's transition has settled (and any persistence has\n // been scheduled), check whether *this* record was a child whose\n // terminal we just applied — in which case the parent's named resume\n // fires now. Doing it after `notify(record)` keeps subscribers'\n // observed order intact: child's terminal first, parent's resume\n // second. `applyResumeIfChild` is a no-op for non-child or non-terminal\n // records.\n applyResumeIfChild(record);\n }\n\n function entryAllowBackModeForStep(\n step: JourneyStep | null,\n ): \"preserve-state\" | \"rollback\" | false {\n return entryAllowBackMode(step);\n }\n\n /**\n * Belt-and-suspenders contract-identity check scoped to the\n * dispatching journey's own modules — modules keyed under\n * `def.transitions`. `validateJourneyContracts` does the equivalent\n * at registration time, but the runtime's `moduleMap` can drift\n * from the validator's view: a direct `createJourneyRuntime` call,\n * plugin composition that adds modules after the registry has\n * resolved, or test mocks swapping descriptors all bypass the\n * registration-time snapshot. Run the check lazily on first\n * dispatch of each `[journeyId, exitName]` pair (cached for the\n * lifetime of the runtime), warn if we find two journey-modules\n * that declare the same exit via *different* `ExitContract`\n * instances. Cost: one walk of the journey's module set per unique\n * exit fired by that journey — bounded and amortized.\n *\n * Caller is responsible for gating on `debug` mode (so prod doesn't\n * pay even the function-call cost). The dedupe `Set` is keyed by\n * `journeyId\\0exitName` so two journeys sharing an exit name each\n * get their own first-fire check.\n */\n function spotCheckContractDrift(reg: RegisteredJourney, exitName: string) {\n const cacheKey = `${reg.definition.id}\u0000${exitName}`;\n if (spotCheckedExits.has(cacheKey)) return;\n spotCheckedExits.add(cacheKey);\n\n const journeyModuleIds = Object.keys(reg.definition.transitions ?? {});\n let canonical: { contract: ExitContract<unknown>; moduleId: string } | null = null;\n for (const moduleId of journeyModuleIds) {\n const mod = moduleMap[moduleId];\n if (!mod) continue;\n const schema = mod.exitPoints?.[exitName];\n if (!schema || !isExitContract(schema)) continue;\n if (!canonical) {\n canonical = { contract: schema as ExitContract<unknown>, moduleId };\n continue;\n }\n if (schema !== canonical.contract) {\n console.warn(\n `[@modular-react/journeys] Journey \"${reg.definition.id}\": modules ` +\n `\"${canonical.moduleId}\" and \"${moduleId}\" declare exit \"${exitName}\" via different ` +\n `ExitContract instances. Wildcard handlers may receive a non-uniform output shape. This ` +\n `drift was not caught at registration time — re-resolve the registry, or call ` +\n `validateJourneyContracts(...) after registering new modules.`,\n );\n // First mismatch is enough to flag the drift; further mismatches\n // on the same exit name would just repeat the warning.\n return;\n }\n }\n }\n\n function dispatchExit(\n record: InstanceRecord,\n reg: RegisteredJourney,\n stepToken: number,\n exitName: string,\n output: unknown,\n ) {\n if (record.status !== \"active\") {\n if (debug) {\n console.warn(\n `[@modular-react/journeys] Exit(\"${exitName}\") dropped on instance ${record.id} — status=${record.status}. ` +\n `(This is the expected no-op when an exit fires before the initial async load settles; ` +\n `await the load or subscribe for status changes before dispatching.)`,\n );\n }\n return;\n }\n if (record.activeChildId) {\n // Parent step is paused awaiting a child's resume. Exits from the\n // parent step are dropped — same semantics as a stale stepToken or\n // a terminal record. The parent advances only via the resume.\n if (debug) {\n console.warn(\n `[@modular-react/journeys] Exit(\"${exitName}\") dropped on instance ${record.id} — a child journey is in flight (activeChildId=${record.activeChildId}). The parent advances when the child resumes.`,\n );\n }\n return;\n }\n if (record.stepToken !== stepToken) {\n if (debug) {\n console.warn(\n `[@modular-react/journeys] Stale exit(\"${exitName}\") dropped on instance ${record.id}`,\n );\n }\n return;\n }\n const step = record.step;\n if (!step) return;\n\n // Phase 1 — payload validation. When the active step's module declares\n // this exit via an `ExitContract` carrying a Standard Schema, validate\n // the payload before any handler runs. Validation is contract-driven\n // (not transition-tier-driven) so exact and wildcard handlers see the\n // same already-validated payload. Synchronous schemas only — async\n // results abort with `exit-payload-invalid-async` so the misconfigured\n // schema surfaces at the authoring loop instead of being silently\n // swallowed (mirrors the same rule applied to transition handlers).\n let validatedOutput = output;\n const stepModule = moduleMap[step.moduleId];\n if (!stepModule && debug && !warnedMissingModule.has(step.moduleId)) {\n warnedMissingModule.add(step.moduleId);\n console.warn(\n `[@modular-react/journeys] No descriptor for module \"${step.moduleId}\" in the runtime's module map. ` +\n `ExitContract payload validation is skipped for any contract-based exit on this step. ` +\n `Pass it via createJourneyRuntime({ modules: ... }) to enable schema validation.`,\n );\n }\n const exitSchema = stepModule?.exitPoints?.[exitName];\n const isContract = exitSchema !== undefined && isExitContract(exitSchema);\n // Drift check covers contracts with or without schemas — typed\n // wildcard `output` narrowing applies to any contract identity.\n // Gated at the call site so prod doesn't pay even the function-call\n // cost; `spotCheckContractDrift` is a no-op outside debug mode.\n if (debug && isContract) {\n spotCheckContractDrift(reg, exitName);\n }\n if (isContract && (exitSchema as ExitContract<unknown>).schema) {\n const contract = exitSchema as ExitContract<unknown>;\n let validateResult: StandardSchemaResult<unknown> | Promise<StandardSchemaResult<unknown>>;\n try {\n validateResult = contract.schema![\"~standard\"].validate(output);\n } catch (err) {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Exit contract schema for \"${step.moduleId}.${exitName}\" threw during validation.`,\n err,\n );\n }\n fireOnError(reg, record, err, step);\n applyTransition(\n record,\n reg,\n { abort: { reason: \"transition-error\", exit: exitName, error: err } },\n exitName,\n );\n return;\n }\n if (validateResult && typeof (validateResult as { then?: unknown }).then === \"function\") {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Exit contract schema for \"${step.moduleId}.${exitName}\" returned a Promise. Schemas attached to ExitContracts must be synchronous — async refinements run in a loading entry point instead.`,\n );\n }\n // The journey is about to abort regardless of how the thenable\n // settles, but a rejection on the dropped promise would surface\n // as an unhandledRejection later. Attach a no-op catch to keep\n // the abort decision local to this call.\n (validateResult as Promise<unknown>).catch(() => {});\n applyTransition(\n record,\n reg,\n { abort: { reason: \"exit-payload-invalid-async\", exit: exitName } },\n exitName,\n );\n return;\n }\n const settled = validateResult as StandardSchemaResult<unknown>;\n if (settled.issues) {\n if (debug) {\n console.warn(\n `[@modular-react/journeys] Exit(\"${exitName}\") on ${step.moduleId} failed contract validation:`,\n settled.issues,\n );\n }\n applyTransition(\n record,\n reg,\n {\n abort: {\n reason: \"exit-payload-invalid\",\n exit: exitName,\n issues: settled.issues as ReadonlyArray<StandardSchemaIssue>,\n },\n },\n exitName,\n );\n return;\n }\n // Hand the schema's parsed output to handlers — coerced /\n // narrowed shape, not the raw emit payload.\n validatedOutput = settled.value;\n }\n\n // Phase 2 — three-tier handler lookup. Order encodes precedence: the\n // first hit wins, so an exact handler always preempts a wildcard, and\n // a more-specific wildcard preempts a less-specific one.\n const perModule = (reg.definition.transitions as Record<string, any> | undefined)?.[\n step.moduleId\n ];\n const perEntry = perModule?.[step.entry];\n const exact = perEntry?.[exitName];\n const wc = (reg.definition as { wildcardTransitions?: unknown }).wildcardTransitions as\n | {\n byEntryAndExit?: Record<string, Record<string, unknown>>;\n byExit?: Record<string, unknown>;\n }\n | undefined;\n const byEntryAndExit = wc?.byEntryAndExit?.[step.entry]?.[exitName];\n const byExit = wc?.byExit?.[exitName];\n\n const handler =\n typeof exact === \"function\"\n ? (exact as (ctx: {\n state: unknown;\n input: unknown;\n output: unknown;\n }) => TransitionResult<ModuleTypeMap, unknown>)\n : typeof byEntryAndExit === \"function\"\n ? (byEntryAndExit as (ctx: {\n state: unknown;\n input: unknown;\n output: unknown;\n }) => TransitionResult<ModuleTypeMap, unknown>)\n : typeof byExit === \"function\"\n ? (byExit as (ctx: {\n state: unknown;\n input: unknown;\n output: unknown;\n }) => TransitionResult<ModuleTypeMap, unknown>)\n : undefined;\n\n if (typeof handler !== \"function\") {\n if (debug) {\n console.warn(\n `[@modular-react/journeys] No transition for exit(\"${exitName}\") on ${step.moduleId}.${step.entry} — ignoring.`,\n );\n }\n return;\n }\n let result: TransitionResult<ModuleTypeMap, unknown>;\n try {\n result = handler({ state: record.state, input: step.input, output: validatedOutput });\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] transition handler threw\", err);\n fireOnError(reg, record, err, step);\n applyTransition(\n record,\n reg,\n { abort: { reason: \"transition-error\", exit: exitName, error: err } },\n exitName,\n );\n return;\n }\n // Transitions must be pure and synchronous. A handler that returns a\n // thenable almost certainly forgot to put the async work inside a loading\n // entry point — applying the thenable as the transition result would be\n // a silent no-op (it is not `{ next | complete | abort }`), so warn and\n // treat it as an abort.\n if (result && typeof (result as { then?: unknown }).then === \"function\") {\n if (debug) {\n console.error(\n `[@modular-react/journeys] Transition handler for ${step.moduleId}.${step.entry}.\"${exitName}\" returned a Promise. Transitions must be synchronous and pure — put async work inside a loading entry point on a module.`,\n );\n }\n applyTransition(\n record,\n reg,\n { abort: { reason: \"transition-returned-promise\", exit: exitName } },\n exitName,\n );\n return;\n }\n applyTransition(record, reg, result, exitName);\n }\n\n function dispatchGoBack(record: InstanceRecord, reg: RegisteredJourney, stepToken: number) {\n if (record.status !== \"active\") return;\n // Mirror dispatchExit: while a child journey is in flight the parent\n // step is paused, so a stale `goBack` closure must not rewind the\n // parent out from under it.\n if (record.activeChildId) return;\n if (record.stepToken !== stepToken) return;\n if (record.history.length === 0) return;\n\n const step = record.step;\n if (!step) return;\n // Journey-side opt-in\n if (!journeyAllowsBack(reg.definition, step)) return;\n\n const previousStep = record.history.pop()!;\n const snapshot = record.rollbackSnapshots.pop();\n const mode = entryAllowBackMode(step);\n if (mode === \"rollback\" && snapshot !== undefined) {\n record.state = snapshot;\n }\n record.hasRollbackSnapshot = record.rollbackSnapshots.some((s) => s !== undefined);\n record.step = previousStep;\n // Same per-step reset that applyTransition performs for next/complete/abort:\n // moving to a different step starts a fresh per-step bounce budget, and\n // leaving an old counter in place would let a serialize/hydrate cycle\n // restamp it onto the new step and abort incorrectly.\n record.resumeBouncesAtStep = null;\n record.stepToken += 1;\n record.updatedAt = nowIso();\n record.cachedCallbacks = null;\n fireOnTransition(reg, record, step, previousStep, null);\n const persistence = reg.options?.persistence;\n if (persistence) schedulePersist(record, persistence);\n notify(record);\n }\n\n function bindStepCallbacks(record: InstanceRecord, reg: RegisteredJourney) {\n if (record.cachedCallbacks && record.cachedCallbacks.stepToken === record.stepToken) {\n return record.cachedCallbacks;\n }\n const token = record.stepToken;\n const exit = (name: string, output?: unknown) => {\n dispatchExit(record, reg, token, name, output);\n };\n let mode = entryAllowBackMode(record.step);\n // Documented fallback (see `JourneyRuntimeOptions.modules`): when the\n // runtime is built without a module descriptor for this step but the\n // journey's transition opts in via `allowBack: true`, treat the mode as\n // 'preserve-state' so `goBack` stays wired. Without this fallback the\n // headless simulator (which never passes a moduleMap) and any runtime\n // created without module descriptors would see `goBack` silently\n // disappear, contradicting the documented behavior.\n if (\n mode === false &&\n record.step &&\n journeyAllowsBack(reg.definition, record.step) &&\n !moduleMap[record.step.moduleId]\n ) {\n mode = \"preserve-state\";\n }\n const canGoBack =\n mode !== false && journeyAllowsBack(reg.definition, record.step) && record.history.length > 0;\n const goBack = canGoBack\n ? () => {\n dispatchGoBack(record, reg, token);\n }\n : undefined;\n record.cachedCallbacks = { stepToken: token, exit, goBack };\n return record.cachedCallbacks;\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n function buildInstance(record: InstanceRecord): JourneyInstance {\n if (record.cachedSnapshot && record.cachedSnapshot.revision === record.revision) {\n return record.cachedSnapshot.instance;\n }\n // Copy-on-build: history is mutated in place on every transition, so\n // consumers that diff against a prior `instance.history` reference\n // (React deps, effect closures, useMemo) need a frozen-per-revision\n // snapshot. Cheap — bounded by the history cap, and only rebuilt when\n // the revision bumps.\n const historySnapshot: readonly JourneyStep[] = [...record.history];\n const instance: JourneyInstance = {\n id: record.id,\n journeyId: record.journeyId,\n status: record.status,\n step: record.step,\n history: historySnapshot,\n state: record.state,\n terminalPayload:\n record.status === \"completed\" || record.status === \"aborted\"\n ? record.terminalPayload\n : undefined,\n startedAt: record.startedAt,\n updatedAt: record.updatedAt,\n activeChildId: record.activeChildId,\n // Defensive copy — `record.parent` is mutated when invoke/resume cycles\n // clear the link, and consumers diffing against a previously-read\n // `instance.parent` reference would otherwise see the mutation.\n parent: record.parent ? { ...record.parent } : null,\n serialize: () => serialize(record),\n };\n record.cachedSnapshot = { revision: record.revision, instance };\n return instance;\n }\n\n function createRecord(\n reg: RegisteredJourney,\n instanceId: InstanceId,\n persistenceKey: string | null,\n initialState: unknown,\n ): InstanceRecord {\n const startedAt = nowIso();\n return {\n id: instanceId,\n journeyId: reg.definition.id,\n status: \"loading\",\n step: null,\n history: [],\n rollbackSnapshots: [],\n hasRollbackSnapshot: false,\n state: initialState,\n terminalPayload: undefined,\n startedAt,\n updatedAt: startedAt,\n stepToken: 0,\n persistenceKey,\n terminalFired: false,\n retryCount: 0,\n listeners: new Set(),\n pendingSave: null,\n saveInFlight: false,\n pendingRemove: false,\n revision: 0,\n cachedSnapshot: null,\n cachedCallbacks: null,\n parent: null,\n activeChildId: null,\n resumeBouncesAtStep: null,\n };\n }\n\n function startFresh(\n reg: RegisteredJourney,\n input: unknown,\n existingRecord?: InstanceRecord,\n ): InstanceId {\n const def = reg.definition as JourneyDefinition<any, any, unknown>;\n const record =\n existingRecord ?? createRecord(reg, mintInstanceId(), null, def.initialState(input));\n if (!existingRecord) {\n instances.set(record.id, record);\n } else {\n // Recycling a record — typically because an async probe failed or a\n // partial hydrate threw. Reset every field that could carry stale\n // state from the record's previous life. Without this, a hydrate that\n // populated `history` / `rollbackSnapshots` before throwing would\n // leak those entries into the \"fresh\" instance.\n record.state = def.initialState(input);\n record.history = [];\n record.rollbackSnapshots = [];\n record.hasRollbackSnapshot = false;\n }\n const startStep = stepFromSpec(def.start(record.state, input));\n record.step = startStep;\n record.status = \"active\";\n record.stepToken += 1;\n record.terminalFired = false;\n record.terminalPayload = undefined;\n // A recycled record can carry a retry count from its previous life (an\n // async-load failure that fell through to `startFresh`, for example).\n // The new run is a fresh journey — reset the budget.\n record.retryCount = 0;\n record.updatedAt = nowIso();\n record.cachedCallbacks = null;\n fireOnTransition(reg, record, null, startStep, null);\n const persistence = reg.options?.persistence;\n if (persistence) schedulePersist(record, persistence);\n notify(record);\n return record.id;\n }\n\n function hydrateInto(record: InstanceRecord, blob: SerializedJourney<unknown>) {\n const historyLen = blob.history.length;\n // Align rollbackSnapshots with history — mismatched lengths corrupt\n // `goBack` (pop() would take the wrong pair). Reject upfront instead of\n // silently misbehaving later.\n if (blob.rollbackSnapshots && blob.rollbackSnapshots.length !== historyLen) {\n throw new JourneyHydrationError(\n `Blob for journey \"${record.journeyId}\" has rollbackSnapshots.length=${blob.rollbackSnapshots.length} but history.length=${historyLen}. Fix the persisted blob (pad rollbackSnapshots with null for non-rollback entries) or provide onHydrate to migrate.`,\n );\n }\n record.state = blob.state;\n record.step = blob.step;\n record.history = [...blob.history];\n if (blob.rollbackSnapshots) {\n record.rollbackSnapshots = blob.rollbackSnapshots.map((s) =>\n s === null ? undefined : s,\n ) as (unknown | undefined)[];\n record.hasRollbackSnapshot = record.rollbackSnapshots.some((s) => s !== undefined);\n } else {\n // Legacy blobs without rollbackSnapshots — treat as if every history\n // entry had no snapshot. Keeps the two arrays length-aligned.\n record.rollbackSnapshots = Array.from({ length: historyLen }, () => undefined);\n record.hasRollbackSnapshot = false;\n }\n record.status = blob.status;\n record.terminalPayload = blob.terminalPayload;\n record.startedAt = blob.startedAt;\n record.updatedAt = blob.updatedAt;\n record.stepToken += 1;\n record.terminalFired = blob.status !== \"active\";\n record.cachedCallbacks = null;\n\n // Restore the parent ↔ child link state. The matching record on the\n // other side may not exist yet (cross-instance hydrate ordering varies),\n // but we lay down the local half so a subsequent hydrate of the other\n // half can finish the link via `relinkInvocations()`.\n if (blob.parentLink) {\n record.parent = {\n instanceId: blob.parentLink.parentInstanceId,\n resumeName: blob.parentLink.resumeName,\n };\n } else {\n record.parent = null;\n }\n if (blob.pendingInvoke) {\n // Adopt the recorded child id even if no live child record exists\n // yet — `relinkInvocations` will validate or rebuild on demand.\n record.activeChildId = blob.pendingInvoke.childInstanceId;\n } else {\n record.activeChildId = null;\n }\n // Restore the bounce counter. The persisted `stepToken` is from the\n // pre-hydrate runtime, so it cannot be compared against the new\n // `record.stepToken` (which we just bumped). We retain the count\n // and re-stamp the token to the *current* step so the counter\n // continues to police the same step. If the parent advances after\n // hydrate, the next applyTransition's reset-on-step-change clears\n // it — exactly matching pre-hydrate semantics.\n if (blob.resumeBouncesAtStep && Number.isFinite(blob.resumeBouncesAtStep.count)) {\n record.resumeBouncesAtStep = {\n stepToken: record.stepToken,\n count: Math.max(0, Math.floor(blob.resumeBouncesAtStep.count)),\n };\n } else {\n record.resumeBouncesAtStep = null;\n }\n }\n\n /**\n * Rebuild `childToParent` from records' in-memory `parent` /\n * `activeChildId` fields. Called after each hydrate path so a hydrate\n * that lands a child after its parent (or vice-versa) ends with a\n * consistent reverse map.\n *\n * Does NOT abort parents whose `activeChildId` points at a not-yet-loaded\n * child — the shell may be hydrating in stages, and a transient missing\n * child would otherwise eat the parent. Parents with a missing child\n * stay in `active` with the link set; their step exits are blocked\n * (see `dispatchExit`'s `activeChildId` guard), and the shell decides\n * whether to `runtime.end(parent)` or load the missing child later.\n * Idempotent — safe to call multiple times.\n */\n function relinkInvocations() {\n childToParent.clear();\n for (const child of instances.values()) {\n if (!child.parent) continue;\n const parent = instances.get(child.parent.instanceId);\n if (!parent) continue;\n if (parent.activeChildId !== child.id) continue;\n childToParent.set(child.id, parent.id);\n }\n }\n\n /**\n * Auto-rehydrate a child journey when a parent's blob carries a\n * `pendingInvoke.childPersistenceKey`. The runtime walks the persisted\n * link chain transparently so callers only need to `start()` the root —\n * the leaf comes back along with its parents. Recurses into the loaded\n * child's own `pendingInvoke` so deep chains (parent → child → grandchild)\n * restore in one sweep.\n *\n * Tolerant of missing pieces: a child blob that's gone from storage,\n * a child journey that's no longer registered, or a child journey\n * without persistence configured all leave the parent in the documented\n * \"active with activeChildId set, exits blocked\" state. The shell can\n * then `runtime.end(parentId)` to give up.\n *\n * Async loads return without resolving; the link is reconciled when the\n * persistence promise settles.\n */\n function tryRehydrateChild(parentRecord: InstanceRecord, parentBlob: SerializedJourney<unknown>) {\n const pi = parentBlob.pendingInvoke;\n if (!pi) return;\n if (instances.has(pi.childInstanceId)) return;\n if (!pi.childPersistenceKey) return;\n const childReg = definitions.get(pi.childJourneyId);\n if (!childReg) return;\n const childPersistence = childReg.options?.persistence as\n | JourneyPersistence<unknown>\n | undefined;\n if (!childPersistence) return;\n\n const childIndexed = indexKey(pi.childJourneyId, pi.childPersistenceKey);\n // If a different in-memory instance already owns this child key, leave\n // it alone — we don't want to clobber a user-driven `start()` of the\n // same child journey that happened to land first.\n if (keyIndex.has(childIndexed)) return;\n\n const loaded = probeLoad(childReg, childPersistence, pi.childPersistenceKey);\n\n const finishHydrate = (childBlob: SerializedJourney<unknown> | null) => {\n if (!childBlob) return;\n if (childBlob.status !== \"active\") return;\n // Race guards (matters for the async path): a parallel `start()` may\n // have populated the slot or the parent may have moved on.\n if (instances.has(pi.childInstanceId)) return;\n if (keyIndex.has(childIndexed)) return;\n if (parentRecord.activeChildId !== pi.childInstanceId) return;\n const childMigrated = migrateBlob(childReg, childBlob);\n if (!childMigrated.ok) return;\n const childRecord = createRecord(\n childReg,\n pi.childInstanceId,\n pi.childPersistenceKey,\n childMigrated.blob.state,\n );\n instances.set(pi.childInstanceId, childRecord);\n keyIndex.set(childIndexed, pi.childInstanceId);\n try {\n hydrateInto(childRecord, childMigrated.blob);\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] auto-rehydrate child failed\", err);\n instances.delete(pi.childInstanceId);\n keyIndex.delete(childIndexed);\n return;\n }\n // Recurse: the child may itself have a pendingInvoke (grandchild).\n tryRehydrateChild(childRecord, childMigrated.blob);\n relinkInvocations();\n notify(childRecord);\n notify(parentRecord);\n };\n\n if (loaded && typeof (loaded as Promise<unknown>).then === \"function\") {\n void (loaded as Promise<SerializedJourney<unknown> | null>).then(\n (childBlob) => finishHydrate(childBlob),\n (err) => {\n if (debug)\n console.error(\n \"[@modular-react/journeys] auto-rehydrate child persistence.load rejected\",\n err,\n );\n },\n );\n return;\n }\n finishHydrate(loaded as SerializedJourney<unknown> | null);\n }\n\n function probeLoad(\n reg: RegisteredJourney,\n persistence: JourneyPersistence<unknown>,\n key: string,\n ): SerializedJourney<unknown> | null | Promise<SerializedJourney<unknown> | null> {\n let loaded: SerializedJourney<unknown> | null | Promise<SerializedJourney<unknown> | null>;\n try {\n loaded = persistence.load(key) as\n | SerializedJourney<unknown>\n | null\n | Promise<SerializedJourney<unknown> | null>;\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] persistence.load threw\", err);\n return null;\n }\n if (loaded && typeof (loaded as Promise<unknown>).then === \"function\") {\n return loaded as Promise<SerializedJourney<unknown> | null>;\n }\n return loaded as SerializedJourney<unknown> | null;\n }\n\n type MigrateResult =\n | { ok: true; blob: SerializedJourney<unknown> }\n | { ok: false; reason: \"version-mismatch\" }\n | { ok: false; reason: \"on-hydrate-threw\"; cause: unknown };\n\n function migrateBlob(reg: RegisteredJourney, blob: SerializedJourney<unknown>): MigrateResult {\n let migrated: SerializedJourney<unknown> = blob;\n let ranAny = false;\n if (reg.definition.onHydrate) {\n ranAny = true;\n try {\n migrated = reg.definition.onHydrate(migrated) as SerializedJourney<unknown>;\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] onHydrate (definition) threw\", err);\n return { ok: false, reason: \"on-hydrate-threw\", cause: err };\n }\n }\n // Registration-level `onHydrate` runs after the definition's — shells can\n // layer environment-specific post-migration tweaks (redaction, id\n // rewriting) without touching journey authoring code.\n const regHydrate = reg.options?.onHydrate as\n | ((b: SerializedJourney<unknown>) => SerializedJourney<unknown>)\n | undefined;\n if (regHydrate) {\n ranAny = true;\n try {\n migrated = regHydrate(migrated);\n } catch (err) {\n if (debug) console.error(\"[@modular-react/journeys] onHydrate (registration) threw\", err);\n return { ok: false, reason: \"on-hydrate-threw\", cause: err };\n }\n }\n if (ranAny) {\n return { ok: true, blob: migrated };\n }\n if (blob.version !== reg.definition.version) {\n return { ok: false, reason: \"version-mismatch\" };\n }\n return { ok: true, blob };\n }\n\n // ---------------------------------------------------------------------------\n // Runtime surface\n // ---------------------------------------------------------------------------\n\n const runtime: JourneyRuntime = {\n start<TInput, TOutput>(\n journeyIdOrHandle: string | JourneyHandleRef<string, TInput, TOutput>,\n ...rest: [input?: TInput]\n ): InstanceId {\n const input = (rest.length > 0 ? rest[0] : undefined) as TInput;\n // Accept either a bare id or a `JourneyHandle`-shaped object. The\n // handle form is the `start<TId, TInput>(handle, input)` overload; it\n // only exists to type-check `input` — the runtime behaviour is\n // identical either way.\n const journeyId =\n typeof journeyIdOrHandle === \"string\" ? journeyIdOrHandle : journeyIdOrHandle.id;\n const reg = assertKnown(journeyId);\n const persistence = reg.options?.persistence;\n\n if (persistence) {\n const key = persistence.keyFor({\n journeyId: reg.definition.id,\n input,\n });\n const indexed = indexKey(reg.definition.id, key);\n // Idempotency: return the existing instance for this key whenever it\n // is still in flight — \"active\" OR \"loading\". Returning a fresh id\n // while a load is pending would orphan the loading instance and\n // trigger a second `load()`.\n const existingId = keyIndex.get(indexed);\n const existing = existingId ? instances.get(existingId) : null;\n if (existing && (existing.status === \"active\" || existing.status === \"loading\")) {\n return existing.id;\n }\n\n const def = reg.definition as JourneyDefinition<any, any, unknown>;\n const loaded = probeLoad(reg, persistence as JourneyPersistence<unknown>, key);\n\n if (loaded && typeof (loaded as Promise<unknown>).then === \"function\") {\n // Async probe — mint a placeholder instance in `loading` status,\n // but initialize `state` from `initialState(input)` immediately so\n // consumers reading state during loading never see `undefined`.\n // If the blob later hydrates, state is overwritten.\n const instanceId = mintInstanceId();\n const record = createRecord(reg, instanceId, key, def.initialState(input));\n instances.set(instanceId, record);\n keyIndex.set(indexed, instanceId);\n notify(record);\n\n void (loaded as Promise<SerializedJourney<unknown> | null>).then(\n (blob) => {\n // The caller may have ended the instance before the load\n // settled (tab closed, navigation, explicit `runtime.end`).\n // In that case the record is already terminal and we must\n // not resurrect it with startFresh or a hydrate.\n if (record.status !== \"loading\") return;\n if (!blob || blob.status !== \"active\") {\n // Discard terminal/missing blob and mint a fresh instance\n // under the same key. A terminal blob left in storage would\n // be re-fetched on every subsequent start().\n if (blob) discardBlob(persistence as JourneyPersistence<unknown>, key);\n startFresh(reg, input, record);\n return;\n }\n const migrated = migrateBlob(reg, blob);\n if (!migrated.ok) {\n discardBlob(persistence as JourneyPersistence<unknown>, key);\n startFresh(reg, input, record);\n return;\n }\n try {\n hydrateInto(record, migrated.blob);\n } catch (err) {\n if (debug)\n console.error(\"[@modular-react/journeys] hydrate after async load failed\", err);\n discardBlob(persistence as JourneyPersistence<unknown>, key);\n startFresh(reg, input, record);\n return;\n }\n tryRehydrateChild(record, migrated.blob);\n relinkInvocations();\n notify(record);\n },\n (err) => {\n if (debug) console.error(\"[@modular-react/journeys] persistence.load rejected\", err);\n if (record.status !== \"loading\") return;\n startFresh(reg, input, record);\n },\n );\n return instanceId;\n }\n\n const blob = loaded as SerializedJourney<unknown> | null;\n if (blob && blob.status === \"active\") {\n const migrated = migrateBlob(reg, blob);\n if (migrated.ok) {\n // Guard against a blob whose recorded id collides with a live\n // instance (corrupted / hand-edited blob, or two journeys sharing\n // a persistence keyspace). Mint a fresh id instead of clobbering\n // the existing entry — matches `hydrate()`'s existing rejection\n // of re-hydrate over an existing id.\n const instanceId =\n migrated.blob.instanceId && !instances.has(migrated.blob.instanceId)\n ? migrated.blob.instanceId\n : mintInstanceId();\n const record = createRecord(reg, instanceId, key, def.initialState(input));\n instances.set(instanceId, record);\n keyIndex.set(indexed, instanceId);\n try {\n hydrateInto(record, migrated.blob);\n } catch (err) {\n if (debug)\n console.error(\"[@modular-react/journeys] hydrate during start failed\", err);\n // Cleanup the half-built record and fall through to startFresh\n // under the same key.\n instances.delete(instanceId);\n keyIndex.delete(indexed);\n discardBlob(persistence as JourneyPersistence<unknown>, key);\n const freshId = mintInstanceId();\n const freshRecord = createRecord(reg, freshId, key, def.initialState(input));\n instances.set(freshId, freshRecord);\n keyIndex.set(indexed, freshId);\n return startFresh(reg, input, freshRecord);\n }\n tryRehydrateChild(record, migrated.blob);\n relinkInvocations();\n notify(record);\n return instanceId;\n }\n // Migration failed: discard the stale blob so it doesn't get\n // re-fetched forever.\n discardBlob(persistence as JourneyPersistence<unknown>, key);\n } else if (blob) {\n // Terminal blob — drop it before reusing the key for a fresh run.\n discardBlob(persistence as JourneyPersistence<unknown>, key);\n }\n\n // No blob / terminal blob / migration failed — mint a fresh instance\n // that still owns the key, so subsequent `start()` calls are\n // idempotent.\n const instanceId = mintInstanceId();\n const record = createRecord(reg, instanceId, key, def.initialState(input));\n instances.set(instanceId, record);\n keyIndex.set(indexed, instanceId);\n return startFresh(reg, input, record);\n }\n\n return startFresh(reg, input);\n },\n\n hydrate<TState>(journeyId: string, blob: SerializedJourney<TState>): InstanceId {\n const reg = assertKnown(journeyId);\n const migrated = migrateBlob(reg, blob as SerializedJourney<unknown>);\n if (!migrated.ok) {\n if (migrated.reason === \"on-hydrate-threw\") {\n // Surface the original throw via `.cause` so callers can\n // distinguish a migrator bug from a true version mismatch and\n // log the underlying error without losing the stack.\n throw new JourneyHydrationError(\n `onHydrate threw while migrating blob for \"${journeyId}\" (blob=${blob.version} def=${reg.definition.version}).`,\n { cause: migrated.cause },\n );\n }\n throw new JourneyHydrationError(\n `Hydrate version mismatch for \"${journeyId}\": blob=${blob.version} def=${reg.definition.version}. Provide onHydrate to migrate.`,\n );\n }\n const instanceId = migrated.blob.instanceId || mintInstanceId();\n // Guard against silent overwrite — two hydrates of the same blob would\n // otherwise clobber live state and orphan existing listeners.\n if (instances.has(instanceId)) {\n throw new JourneyHydrationError(\n `Cannot hydrate journey \"${journeyId}\" with instance id \"${instanceId}\" — an instance with the same id is already in memory. Call forget(id) first if you intend to replace it.`,\n );\n }\n\n // If the migrated blob has an input we can use to compute the key, we\n // could re-index — but `SerializedJourney` doesn't carry the original\n // `input`, so explicit hydrate stays persistence-unlinked. Callers\n // that want round-trip persistence should use `start()` which owns the\n // key lifecycle. Document this on the API.\n const record = createRecord(reg, instanceId, null, migrated.blob.state);\n instances.set(instanceId, record);\n try {\n hydrateInto(record, migrated.blob);\n } catch (err) {\n // Don't leak the half-built loading placeholder — otherwise\n // subsequent getInstance(id) returns partial state, a retry hits\n // the \"already in memory\" guard, and forget(id) is a no-op\n // (status never reached terminal). Mirror the sync-start path\n // which already cleans up in the same situation.\n instances.delete(instanceId);\n throw err;\n }\n tryRehydrateChild(record, migrated.blob);\n relinkInvocations();\n notify(record);\n return instanceId;\n },\n\n getInstance(id) {\n const record = instances.get(id);\n return record ? buildInstance(record) : null;\n },\n\n listInstances() {\n return [...instances.keys()];\n },\n\n listDefinitions() {\n return [...definitions.values()].map(summarize);\n },\n\n isRegistered(journeyId) {\n return definitions.has(journeyId);\n },\n\n subscribe(id, listener) {\n const record = instances.get(id);\n if (!record) return () => {};\n record.listeners.add(listener);\n return () => {\n record.listeners.delete(listener);\n };\n },\n\n end(id, reason) {\n const record = instances.get(id);\n if (!record) return;\n if (record.status === \"completed\" || record.status === \"aborted\") return;\n const reg = definitions.get(record.journeyId);\n if (!reg) return;\n // Cascade-end: a parent that gets force-terminated must take its\n // active child with it. Sever the parent ↔ child link first so the\n // child's terminal does NOT trigger the parent's resume (the parent\n // is already on its way out). Then end the child with a propagated\n // reason. Recurses naturally — a grandchild will be ended by the\n // child's `end()`.\n if (record.activeChildId) {\n const childId = record.activeChildId;\n record.activeChildId = null;\n const child = instances.get(childId);\n if (child && child.parent && child.parent.instanceId === record.id) {\n child.parent = null;\n }\n childToParent.delete(childId);\n // Use a distinct cascade reason so child telemetry can distinguish\n // \"user closed parent\" from \"child aborted on its own.\"\n runtime.end(childId, { reason: \"parent-ended\", parentId: record.id, cause: reason });\n }\n // An outlet that unmounts mid-load should still be able to tear the\n // placeholder instance down. The journey never \"started\" as far as the\n // author is concerned, so skip `onAbandon` (it would see a null step)\n // and transition straight to `aborted` with the supplied reason.\n // The reason is passed through directly (no `{ reason }` wrap) so\n // `terminalPayload` matches what callers handed in — important when\n // a parent's resume handler reads `outcome.reason` after a cascade.\n if (record.status === \"loading\") {\n applyTransition(record, reg, { abort: reason ?? \"abandoned\" }, null);\n return;\n }\n const defaultAbort: TransitionResult<ModuleTypeMap, unknown> = {\n abort: reason ?? \"abandoned\",\n };\n let result: TransitionResult<ModuleTypeMap, unknown> = defaultAbort;\n // Registration-level `onAbandon` overrides the definition's — shells\n // can swap the abandon outcome without modifying journey authoring code\n // (e.g. complete instead of abort on tab close). If absent, fall back\n // to the definition's handler.\n const abandonHandler = reg.options?.onAbandon ?? reg.definition.onAbandon;\n if (abandonHandler) {\n try {\n result = abandonHandler({\n journeyId: record.journeyId,\n instanceId: record.id,\n step: record.step,\n state: record.state,\n reason: reason ?? \"abandoned\",\n }) as TransitionResult<ModuleTypeMap, unknown>;\n } catch (err) {\n // Surface the handler crash through the registration-level onError\n // hook before falling back to the default abort. Preserve the\n // caller-supplied `reason` (and surface `onAbandon`'s own error as\n // `cause`) so a throw in a shell's onAbandon doesn't silently\n // erase the original abort context.\n if (debug) console.error(\"[@modular-react/journeys] onAbandon threw\", err);\n fireOnError(reg, record, err, record.step, \"abandon\");\n result = {\n abort: {\n reason: reason ?? \"abandoned\",\n cause: \"onAbandon-threw\",\n error: err,\n },\n };\n }\n }\n applyTransition(record, reg, result, null);\n },\n\n forget(id) {\n const record = instances.get(id);\n if (!record) return;\n if (record.status !== \"completed\" && record.status !== \"aborted\") return;\n if (record.persistenceKey) keyIndex.delete(indexKey(record.journeyId, record.persistenceKey));\n // A terminal child whose parent never picked up the resume (parent\n // already terminal, or detached) leaves the childToParent entry\n // dangling. Drop it on forget so the map cannot leak indefinitely.\n childToParent.delete(id);\n // If this terminal record still references a child (parent forgotten\n // before the child resumed back), null the orphan child's `parent`\n // pointer so a future child-terminal doesn't try to fire a resume\n // on a dropped instance.\n if (record.activeChildId) {\n const orphan = instances.get(record.activeChildId);\n if (orphan && orphan.parent && orphan.parent.instanceId === record.id) {\n orphan.parent = null;\n notify(orphan);\n }\n childToParent.delete(record.activeChildId);\n record.activeChildId = null;\n }\n record.listeners.clear();\n instances.delete(id);\n },\n\n forgetTerminal() {\n let removed = 0;\n for (const [id, record] of instances) {\n if (record.status === \"completed\" || record.status === \"aborted\") {\n if (record.persistenceKey) {\n keyIndex.delete(indexKey(record.journeyId, record.persistenceKey));\n }\n childToParent.delete(id);\n if (record.activeChildId) {\n const orphan = instances.get(record.activeChildId);\n if (orphan && orphan.parent && orphan.parent.instanceId === record.id) {\n orphan.parent = null;\n notify(orphan);\n }\n childToParent.delete(record.activeChildId);\n }\n record.listeners.clear();\n instances.delete(id);\n removed += 1;\n }\n }\n return removed;\n },\n };\n\n function dispatchComponentError(id: InstanceId, err: unknown, step: JourneyStep): void {\n const record = instances.get(id);\n if (!record) return;\n const reg = definitions.get(record.journeyId);\n if (!reg) return;\n // Component-level throws are step-phase errors — they happen during\n // a step component's render, not in the invoke / resume control plane.\n fireOnError(reg, record, err, step, \"step\");\n }\n\n // Internals used by the outlet and testing helpers — kept on a WeakMap\n // rather than on the runtime object to keep the public surface clean.\n const internals: JourneyRuntimeInternals = {\n __bindStepCallbacks: bindStepCallbacks,\n __getRecord: (id: InstanceId) => instances.get(id),\n __getRegistered: (id: string) => definitions.get(id),\n __moduleMap: moduleMap,\n __debug: debug,\n __fireComponentError: dispatchComponentError,\n __synthesizeCompletion: (childId: InstanceId, payload: unknown) => {\n const child = instances.get(childId);\n if (!child) return;\n const reg = definitions.get(child.journeyId);\n if (!reg) return;\n // Drive the child through the standard transition machinery so\n // onComplete, onTransition, persistence, AND the parent's resume\n // hook all fire exactly as they would for a real `{ complete }`\n // transition. The shape we hand `applyTransition` is the same one\n // a transition handler would have returned.\n applyTransition(child, reg, { complete: payload }, null);\n },\n __synthesizeAbort: (childId: InstanceId, reason: unknown) => {\n const child = instances.get(childId);\n if (!child) return;\n const reg = definitions.get(child.journeyId);\n if (!reg) return;\n // Mirror of `__synthesizeCompletion` but for the abort arm. Lets the\n // simulator's `abortChild()` deliver a clean reason to the parent's\n // resume handler — equivalent to what the child's own\n // `{ abort: reason }` transition would produce, without going through\n // `runtime.end()`'s onAbandon path.\n applyTransition(child, reg, { abort: reason }, null);\n },\n __consumeRetry: (id: InstanceId, retryLimit: number) => {\n // Centralized retry-budget check: the outlet calls in here so the\n // counter increments under the runtime's ownership, and a future\n // shell that wants to track retries via telemetry hooks has a\n // single place to do so.\n const record = instances.get(id);\n if (!record) return false;\n if (record.retryCount >= retryLimit) return false;\n record.retryCount += 1;\n return true;\n },\n };\n INTERNALS.set(runtime, internals);\n\n return runtime;\n}\n\nexport interface JourneyRuntimeInternals {\n __bindStepCallbacks(\n record: InstanceRecord,\n reg: RegisteredJourney,\n ): {\n exit: (name: string, output?: unknown) => void;\n goBack?: () => void;\n stepToken: number;\n };\n __getRecord(id: InstanceId): InstanceRecord | undefined;\n __getRegistered(id: string): RegisteredJourney | undefined;\n /** Module descriptors bound to this runtime — the `<JourneyOutlet>` reads\n * this to resolve step components without the caller threading `modules`\n * through as a prop. */\n __moduleMap: Readonly<Record<string, ModuleDescriptor<any, any, any, any>>>;\n /** Runtime's resolved debug flag — useful for dev-mode probes in the\n * outlet / module-tab. */\n __debug: boolean;\n /**\n * Fires the registration-level `onError` hook for a component-level throw\n * caught by the outlet's error boundary. Routed through the runtime so\n * the outlet never has to reach into `reg.options.onError` directly —\n * keeps the runtime the single owner of hook firing.\n */\n __fireComponentError(id: InstanceId, err: unknown, step: JourneyStep): void;\n /**\n * Drive a child instance to a `completed` terminal with a synthesized\n * payload. Test-only — the simulator's `completeChild()` uses this to\n * exercise a parent's resume handler in isolation without enumerating\n * the child's transition graph. Routes through the standard\n * `applyTransition` so onComplete / onTransition / persistence / the\n * parent's resume all fire as they would for a real `{ complete }`.\n * No-op for unknown ids.\n */\n __synthesizeCompletion(childId: InstanceId, payload: unknown): void;\n /**\n * Sibling of `__synthesizeCompletion` for the abort arm — the\n * simulator's `abortChild()` uses this so the parent's resume handler\n * receives the reason directly, without the `{ reason }` wrap that\n * `runtime.end()` previously applied.\n */\n __synthesizeAbort(childId: InstanceId, reason: unknown): void;\n /**\n * Atomically attempt to consume one retry slot for `id`. Returns\n * `true` if the budget allowed the retry (counter incremented), or\n * `false` if `retryLimit` was already hit. Centralizing this in the\n * runtime keeps the budget in one place — the outlet only decides\n * whether the policy was `\"retry\"` and trusts the runtime for the\n * cap check.\n */\n __consumeRetry(id: InstanceId, retryLimit: number): boolean;\n}\n\nexport function getInternals(runtime: JourneyRuntime): JourneyRuntimeInternals {\n const internals = INTERNALS.get(runtime);\n if (!internals) {\n throw new Error(\n \"[@modular-react/journeys] getInternals() called on a runtime that was not produced by createJourneyRuntime().\",\n );\n }\n return internals;\n}\n"],"mappings":"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"}
package/dist/testing.d.ts CHANGED
@@ -13,6 +13,7 @@ import { TerminalCtx } from '@modular-react/core';
13
13
  import { TransitionEvent as TransitionEvent_2 } from '@modular-react/core';
14
14
  import { TransitionMap } from '@modular-react/core';
15
15
  import { TransitionResult } from '@modular-react/core';
16
+ import { WildcardTransitionMap } from '@modular-react/core';
16
17
 
17
18
  /** Erased shape used by the registry — `any` on every generic lets the
18
19
  * registry store definitions from different journeys side-by-side.
@@ -53,6 +54,28 @@ declare interface JourneyDefinition<TModules extends ModuleTypeMap, TState, TInp
53
54
  readonly initialState: (input: TInput) => TState;
54
55
  readonly start: (state: TState, input: TInput) => StepSpec<TModules>;
55
56
  readonly transitions: TransitionMap<TModules, TState, TOutput>;
57
+ /**
58
+ * Wildcard transitions — fall-through handlers matched by exit name
59
+ * (and optionally entry name) rather than by full `[mod][entry][exit]`
60
+ * triple. Two precision tiers:
61
+ *
62
+ * - `byEntryAndExit[entry][exit]` — module unknown, entry + exit known.
63
+ * Fires when no exact `transitions[mod][entry][exit]` matches but
64
+ * the active step's `entry` and `exit` are both declared here.
65
+ * - `byExit[exit]` — module + entry both unknown. Fires when neither
66
+ * of the more specific tiers match.
67
+ *
68
+ * Resolution precedence at runtime is exact → byEntryAndExit → byExit;
69
+ * the more precise handler always wins because the lookup checks them
70
+ * in order and the first hit fires.
71
+ *
72
+ * Use cases: cross-cutting outcomes like `cancelled`, `error`, `back`,
73
+ * or `signedOut` that any module can emit — declare the handler once
74
+ * here instead of repeating it under every step. Pair with
75
+ * `defineExitContract` to enforce a uniform output shape across the
76
+ * modules that emit the exit.
77
+ */
78
+ readonly wildcardTransitions?: WildcardTransitionMap<TModules, TState, TOutput>;
56
79
  /**
57
80
  * Resume handlers fired when a child journey `invoke`d from a parent
58
81
  * step terminates. Keyed by `[moduleId][entryName][resumeName]` — the
package/dist/testing.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as e, t } from "./runtime-BUVl0_Ad.js";
1
+ import { n as e, t } from "./runtime-DPPLlqbu.js";
2
2
  //#region src/simulate-journey.ts
3
3
  function n(n, ...a) {
4
4
  let o = a.length > 0 ? a[0] : void 0, s = a.length > 1 ? a[1] : void 0, c = [], l = (e) => {