@modular-react/journeys 0.1.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +2367 -1669
- package/dist/index.d.ts +686 -28
- package/dist/index.js +0 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime-BUVl0_Ad.js +1422 -0
- package/dist/runtime-BUVl0_Ad.js.map +1 -0
- package/dist/testing.d.ts +155 -6
- package/dist/testing.js +62 -30
- package/dist/testing.js.map +1 -1
- package/package.json +7 -5
- package/dist/runtime-DyU_PmaC.js +0 -599
- package/dist/runtime-DyU_PmaC.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-BUVl0_Ad.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 type { 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 // 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 * 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 } from \"@modular-react/core\";\nimport type { JourneyHandleRef, ModuleDescriptor } 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 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 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 const perModule = (reg.definition.transitions as Record<string, any> | undefined)?.[\n step.moduleId\n ];\n const perEntry = perModule?.[step.entry];\n const handler = perEntry?.[exitName] as\n | ((ctx: {\n state: unknown;\n input: unknown;\n output: unknown;\n }) => TransitionResult<ModuleTypeMap, unknown>)\n | undefined;\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 });\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
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { AbandonCtx } from '@modular-react/core';
|
|
2
|
+
import { CatalogMeta } from '@modular-react/core';
|
|
2
3
|
import { InstanceId } from '@modular-react/core';
|
|
4
|
+
import { JourneyHandleRef } from '@modular-react/core';
|
|
3
5
|
import { JourneyRuntime } from '@modular-react/core';
|
|
4
6
|
import { JourneyStatus } from '@modular-react/core';
|
|
5
7
|
import { JourneyStep } from '@modular-react/core';
|
|
6
8
|
import { ModuleTypeMap } from '@modular-react/core';
|
|
9
|
+
import { ResumeMap } from '@modular-react/core';
|
|
7
10
|
import { SerializedJourney } from '@modular-react/core';
|
|
8
11
|
import { StepSpec } from '@modular-react/core';
|
|
9
12
|
import { TerminalCtx } from '@modular-react/core';
|
|
@@ -11,6 +14,19 @@ import { TransitionEvent as TransitionEvent_2 } from '@modular-react/core';
|
|
|
11
14
|
import { TransitionMap } from '@modular-react/core';
|
|
12
15
|
import { TransitionResult } from '@modular-react/core';
|
|
13
16
|
|
|
17
|
+
/** Erased shape used by the registry — `any` on every generic lets the
|
|
18
|
+
* registry store definitions from different journeys side-by-side.
|
|
19
|
+
* Tightening to `unknown` breaks variance: `initialState: (input: TInput)
|
|
20
|
+
* => TState` for a specific journey is not assignable to
|
|
21
|
+
* `(input: unknown) => unknown` because function parameters are
|
|
22
|
+
* contravariant, so the registry would reject any concrete definition.
|
|
23
|
+
*
|
|
24
|
+
* TModules is also `any` (rather than `ModuleTypeMap`) so the structural
|
|
25
|
+
* variance check on `ResumeMap`/`TransitionMap` does not strictly require
|
|
26
|
+
* the wide form to carry every specific module key — `any` short-circuits
|
|
27
|
+
* the property-by-property check and admits any concrete TModules. */
|
|
28
|
+
declare type AnyJourneyDefinition = JourneyDefinition<any, any, any, any, any>;
|
|
29
|
+
|
|
14
30
|
export declare function createTestHarness(runtime: JourneyRuntime): JourneyTestHarness;
|
|
15
31
|
|
|
16
32
|
/**
|
|
@@ -28,16 +44,102 @@ export declare interface InstanceSnapshot<TState = unknown> {
|
|
|
28
44
|
readonly retryCount: number;
|
|
29
45
|
}
|
|
30
46
|
|
|
31
|
-
declare interface JourneyDefinition<TModules extends ModuleTypeMap, TState, TInput = void
|
|
47
|
+
declare interface JourneyDefinition<TModules extends ModuleTypeMap, TState, TInput = void, TOutput = unknown, TMeta extends {
|
|
48
|
+
[K in keyof TMeta]: unknown;
|
|
49
|
+
} = Record<string, unknown>> {
|
|
32
50
|
readonly id: string;
|
|
33
51
|
readonly version: string;
|
|
34
|
-
readonly meta?: Readonly<
|
|
52
|
+
readonly meta?: Readonly<CatalogMeta & TMeta>;
|
|
35
53
|
readonly initialState: (input: TInput) => TState;
|
|
36
54
|
readonly start: (state: TState, input: TInput) => StepSpec<TModules>;
|
|
37
|
-
readonly transitions: TransitionMap<TModules, TState>;
|
|
55
|
+
readonly transitions: TransitionMap<TModules, TState, TOutput>;
|
|
56
|
+
/**
|
|
57
|
+
* Resume handlers fired when a child journey `invoke`d from a parent
|
|
58
|
+
* step terminates. Keyed by `[moduleId][entryName][resumeName]` — the
|
|
59
|
+
* runtime looks up `resumes[currentMod][currentEntry][invokeSpec.resume]`
|
|
60
|
+
* at child terminal time and applies the result as the parent's next
|
|
61
|
+
* transition. Optional — journeys that never invoke can omit it.
|
|
62
|
+
*/
|
|
63
|
+
readonly resumes?: ResumeMap<TModules, TState, TOutput>;
|
|
64
|
+
/**
|
|
65
|
+
* Closed set of journey handles this journey may invoke from any of its
|
|
66
|
+
* transitions (or from a resume that returns `{ invoke }`). Strongly
|
|
67
|
+
* recommended for any journey that uses `invoke`:
|
|
68
|
+
*
|
|
69
|
+
* 1. **Static cycle detection.** When every journey in a registration
|
|
70
|
+
* declares `invokes`, the registry runs a graph-level cycle check
|
|
71
|
+
* at validation time and rejects the registration with a path like
|
|
72
|
+
* `cycle detected: A → B → A` — far easier to diagnose than the
|
|
73
|
+
* runtime `invoke-cycle` abort.
|
|
74
|
+
* 2. **Runtime arrival check.** At invoke time the runtime verifies
|
|
75
|
+
* that the dispatched handle id appears in `invokes`; an unexpected
|
|
76
|
+
* handle aborts the parent with reason `invoke-undeclared-child`,
|
|
77
|
+
* catching dynamic dispatch bugs (a transition that branches on
|
|
78
|
+
* `output` and lands on a handle the author never intended).
|
|
79
|
+
*
|
|
80
|
+
* Omit only when the call set is genuinely dynamic (e.g. a host that
|
|
81
|
+
* receives child handles from a slot contribution at runtime). The
|
|
82
|
+
* runtime cycle / depth / bounce guards still apply in that case;
|
|
83
|
+
* they just no longer have a static graph to cross-check against.
|
|
84
|
+
*
|
|
85
|
+
* Self-loops (a journey listing its own handle) are reported as a
|
|
86
|
+
* cycle — by construction they would also blow the call-stack guard
|
|
87
|
+
* at runtime.
|
|
88
|
+
*/
|
|
89
|
+
readonly invokes?: ReadonlyArray<JourneyHandleRef<string, any, any>>;
|
|
90
|
+
/**
|
|
91
|
+
* Expected module version ranges, keyed by the `id` of a module declared
|
|
92
|
+
* in `TModules`. The keys are constrained to that map so a typo in a
|
|
93
|
+
* module id is a compile error rather than a registration-time issue
|
|
94
|
+
* ("requires module 'profil' but it is not registered"). The journeys
|
|
95
|
+
* plugin checks each entry at registry resolve time against the
|
|
96
|
+
* actually-registered module's `version` field; any incompatibility
|
|
97
|
+
* fails assembly with a {@link JourneyValidationError} listing every
|
|
98
|
+
* mismatch at once.
|
|
99
|
+
*
|
|
100
|
+
* **Why declare this even though the journey already references modules
|
|
101
|
+
* by id in `transitions`?** A journey that mixes in a module from another
|
|
102
|
+
* team is implicitly coupled to that module's exit-name and input-shape
|
|
103
|
+
* contract. The id-and-shape match holds today, but a backwards-
|
|
104
|
+
* incompatible bump on the other side ("we renamed the `success` exit
|
|
105
|
+
* to `done`") would otherwise only surface at runtime when the journey
|
|
106
|
+
* actually navigates to that step. Adding a compat declaration moves
|
|
107
|
+
* that failure to startup so an incompatible deployment refuses to come
|
|
108
|
+
* up at all, instead of breaking a single user mid-flow.
|
|
109
|
+
*
|
|
110
|
+
* The range syntax is an npm-style subset: caret, tilde, x-range,
|
|
111
|
+
* comparators, AND, OR, and hyphen — see the README's
|
|
112
|
+
* "Pattern - module compatibility (`moduleCompat`)" section for the
|
|
113
|
+
* exhaustive list. Pre-release tags and build metadata are not
|
|
114
|
+
* supported. Module ids in this map are typed against `TModules` so
|
|
115
|
+
* a typo is a compile error; an entry naming a module that genuinely
|
|
116
|
+
* isn't registered (e.g. via the erased `AnyJourneyDefinition` path)
|
|
117
|
+
* is reported with a dedicated "module not registered" issue.
|
|
118
|
+
*
|
|
119
|
+
* Optional. A journey that omits this field opts out of compatibility
|
|
120
|
+
* enforcement entirely; the existing structural validators
|
|
121
|
+
* (`transitions` referencing real modules / entries / exits) still run.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* defineJourney<OnboardingModules, OnboardingState>()({
|
|
126
|
+
* id: "onboarding",
|
|
127
|
+
* version: "1.0.0",
|
|
128
|
+
* moduleCompat: {
|
|
129
|
+
* profile: "^1.0.0",
|
|
130
|
+
* billing: "^2.0.0 || ^3.0.0",
|
|
131
|
+
* plan: ">=1.5.0 <2.0.0",
|
|
132
|
+
* },
|
|
133
|
+
* // ...
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
readonly moduleCompat?: {
|
|
138
|
+
readonly [K in keyof TModules & string]?: string;
|
|
139
|
+
};
|
|
38
140
|
readonly onTransition?: (ev: TransitionEvent_2<TModules, TState>) => void;
|
|
39
|
-
readonly onAbandon?: (ctx: AbandonCtx<TModules, TState>) => TransitionResult<TModules, TState>;
|
|
40
|
-
readonly onComplete?: (ctx: TerminalCtx<TState>, result:
|
|
141
|
+
readonly onAbandon?: (ctx: AbandonCtx<TModules, TState>) => TransitionResult<TModules, TState, TOutput>;
|
|
142
|
+
readonly onComplete?: (ctx: TerminalCtx<TState>, result: TOutput) => void;
|
|
41
143
|
readonly onAbort?: (ctx: TerminalCtx<TState>, reason: unknown) => void;
|
|
42
144
|
readonly onHydrate?: (blob: SerializedJourney<TState>) => SerializedJourney<TState>;
|
|
43
145
|
}
|
|
@@ -86,6 +188,32 @@ export declare interface JourneySimulator<_TModules extends ModuleTypeMap, TStat
|
|
|
86
188
|
* alignment with `history` without reaching into runtime internals.
|
|
87
189
|
*/
|
|
88
190
|
serialize(): SerializedJourney<TState>;
|
|
191
|
+
/**
|
|
192
|
+
* Active child instance id when this journey has invoked another. `null`
|
|
193
|
+
* when the journey is not currently awaiting a child.
|
|
194
|
+
*/
|
|
195
|
+
readonly activeChildId: InstanceId | null;
|
|
196
|
+
/**
|
|
197
|
+
* Sub-simulator for the currently-invoked child journey, if any. The
|
|
198
|
+
* returned simulator drives the child via the same runtime — once the
|
|
199
|
+
* child terminates, the parent's resume fires automatically and this
|
|
200
|
+
* sim's `state` / `step` reflect the post-resume position.
|
|
201
|
+
*
|
|
202
|
+
* `null` when no child is in flight (parent has not invoked, or the
|
|
203
|
+
* child has already resumed back into the parent).
|
|
204
|
+
*/
|
|
205
|
+
readonly activeChild: JourneySimulator<ModuleTypeMap, unknown> | null;
|
|
206
|
+
/**
|
|
207
|
+
* Synthesize a `{ status: "completed" }` outcome on the active child
|
|
208
|
+
* without enumerating its steps. Useful for unit-testing a parent's
|
|
209
|
+
* resume handler in isolation. Throws if no child is in flight.
|
|
210
|
+
*/
|
|
211
|
+
completeChild(payload: unknown): void;
|
|
212
|
+
/**
|
|
213
|
+
* Synthesize a `{ status: "aborted" }` outcome on the active child.
|
|
214
|
+
* Throws if no child is in flight.
|
|
215
|
+
*/
|
|
216
|
+
abortChild(reason?: unknown): void;
|
|
89
217
|
}
|
|
90
218
|
|
|
91
219
|
/**
|
|
@@ -115,7 +243,28 @@ export declare interface JourneyTestHarness {
|
|
|
115
243
|
* simulateJourney(noInputJourney); // no input required
|
|
116
244
|
* simulateJourney(inputJourney, { id: 1 }); // input required and typed
|
|
117
245
|
* ```
|
|
246
|
+
*
|
|
247
|
+
* Pass `options.children` when the primary journey can `invoke` child
|
|
248
|
+
* journeys — every reachable child must be registered or the parent
|
|
249
|
+
* will abort with `invoke-unknown-journey`.
|
|
118
250
|
*/
|
|
119
|
-
export declare function simulateJourney<TModules extends ModuleTypeMap, TState, TInput>(definition: JourneyDefinition<TModules, TState, TInput>, ...rest: [TInput] extends [void] ? [] | [input?: TInput] : [input: TInput]): JourneySimulator<TModules, TState>;
|
|
251
|
+
export declare function simulateJourney<TModules extends ModuleTypeMap, TState, TInput>(definition: JourneyDefinition<TModules, TState, TInput>, ...rest: [TInput] extends [void] ? [] | [input?: TInput] | [input: TInput, options: SimulateJourneyOptions] : [input: TInput] | [input: TInput, options: SimulateJourneyOptions]): JourneySimulator<TModules, TState>;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Options for {@link simulateJourney}. Pass `children` to register
|
|
255
|
+
* additional journey definitions reachable via `invoke` from the
|
|
256
|
+
* primary journey, so the simulator can drive child sub-flows
|
|
257
|
+
* end-to-end. The `mockChildOutcomes` shortcut is preferred when the
|
|
258
|
+
* child's transition path is irrelevant to the test.
|
|
259
|
+
*/
|
|
260
|
+
declare interface SimulateJourneyOptions {
|
|
261
|
+
/**
|
|
262
|
+
* Child journey definitions the primary journey can `invoke`. Without
|
|
263
|
+
* this, an `invoke` from the primary will abort with
|
|
264
|
+
* `invoke-unknown-journey`. Order does not matter; mutually-invoking
|
|
265
|
+
* journeys all go in here.
|
|
266
|
+
*/
|
|
267
|
+
readonly children?: readonly AnyJourneyDefinition[];
|
|
268
|
+
}
|
|
120
269
|
|
|
121
270
|
export { }
|