Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 54 additions & 46 deletions docs/vue-support-tracker.md

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions packages/journeys-engine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# @modular-frontend/journeys-engine

The framework-neutral engine behind [Journeys](https://github.com/kibertoad/modular-react#readme): typed, serializable workflows that compose several modules. It owns the journey runtime, validation, persistence, authoring helpers, handles, and the type surface, with no UI-framework dependency.

This is the shared foundation the framework bindings build on. The React binding (`@modular-react/journeys`) re-exports this package and adds the UI layer (outlet, provider, hooks); a future `@modular-vue/journeys` will do the same over the same engine.

## Installation

```bash
npm install @modular-frontend/journeys-engine
```

Most apps depend on a binding (`@modular-react/journeys`) rather than on this package directly. Use `@modular-frontend/journeys-engine` when building a new framework binding or framework-agnostic tooling over journeys.

## What's included

- **Runtime**: `createJourneyRuntime`, `getInternals` (low-level accessor the outlet and test harness drive), `JourneyRuntimeOptions`.
- **Validation**: `validateJourneyContracts`, `validateJourneyDefinition`, `validateJourneyGraph`, and the `JourneyValidationError` / `JourneyHydrationError` / `UnknownJourneyError` classes.
- **Persistence**: `defineJourneyPersistence`, `createWebStoragePersistence`, `createMemoryPersistence`.
- **Authoring helpers**: `defineJourney`, `defineTransition`, `isAnnotatedTransition`, `isTerminalSentinel`, `selectModule`, `selectModuleOrDefault`.
- **Handles**: `defineJourneyHandle`, `invoke`.
- **Types**: the full journey type surface (`JourneyDefinition`, `JourneyInstance`, `JourneyStep`, `TransitionMap`, wildcard maps, and the rest).

### `@modular-frontend/journeys-engine/testing`

Framework-neutral test helpers: `createTestHarness` (drive a live runtime from a test without mounting an outlet) and `simulateJourney` (pure-logic transition simulation).

## Full documentation

See the [main documentation](https://github.com/kibertoad/modular-react#readme) for the full guide.
48 changes: 48 additions & 0 deletions packages/journeys-engine/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@modular-frontend/journeys-engine",
"version": "1.7.1",
"description": "Framework-neutral journey engine: runtime, validation, persistence, authoring helpers, handles, and types for typed, serializable multi-module workflows. No UI-framework dependency — the outlet and hooks live in a binding package.",
"repository": {
"type": "git",
"url": "git://github.com/kibertoad/modular-react.git",
"directory": "packages/journeys-engine"
},
"files": [
"dist"
],
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./testing": {
"types": "./dist/testing.d.ts",
"import": "./dist/testing.js"
}
},
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "vite build",
"dev": "vite build --watch",
"test": "vitest run",
"prepublishOnly": "pnpm build",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@modular-frontend/core": "workspace:*"
},
"devDependencies": {
"happy-dom": "^20.9.0",
"oxfmt": "^0.51.0",
"oxlint": "^1.66.0",
"typescript": "^6.0.3",
"vite": "^8.0.11",
"vite-plugin-dts": "^4.5.4",
"vitest": "^4.1.5"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { buildInputFor, defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import {
buildInputFor,
defineEntry,
defineExit,
defineModule,
schema,
} from "@modular-frontend/core";

import { defineJourney } from "./define-journey.js";
import { defineJourneyHandle } from "./handle.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CatalogMeta } from "@modular-react/core";
import type { CatalogMeta } from "@modular-frontend/core";
import type { JourneyDefinition, ModuleTypeMap } from "./types.js";

/**
Expand Down Expand Up @@ -34,7 +34,7 @@ import type { JourneyDefinition, ModuleTypeMap } from "./types.js";
*
* Zero runtime cost — the definition is returned unchanged.
*
* @see `buildInputFor` in `@modular-react/core` — same visual two-call
* @see `buildInputFor` in `@modular-frontend/core` — same visual two-call
* curry, used for the entry-side `buildInput` factory.
*/
export const defineJourney =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
// returned handler shape.

import { expectTypeOf, test } from "vitest";
import { buildInputFor, defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import {
buildInputFor,
defineEntry,
defineExit,
defineModule,
schema,
} from "@modular-frontend/core";

import {
type AnnotatedTransitionHandler,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
ModuleTypeMap,
StepInputSlot,
TransitionResult,
} from "@modular-react/core";
} from "@modular-frontend/core";

/**
* Sentinel value declaring a non-`next` outcome on a wrapped transition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// `@ts-expect-error` directives and the explicit `expectTypeOf` checks.

import { expectTypeOf, test } from "vitest";
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import type { InstanceId } from "./types.js";
import { defineJourney } from "./define-journey.js";
import { defineJourneyHandle, type JourneyHandle } from "./handle.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import { defineJourney } from "./define-journey.js";
import { createJourneyRuntime, getInternals } from "./runtime.js";
import { defineJourneyHandle } from "./handle.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { InvokeSpec, JourneyHandleRef } from "@modular-react/core";
import type { InvokeSpec, JourneyHandleRef } from "@modular-frontend/core";
import type { JourneyDefinition, ModuleTypeMap } from "./types.js";

/**
* Lightweight token a journey exports so modules and shells can open it
* with a typed `input` (and a typed `outcome.payload` when invoked from a
* parent journey) without pulling in the journey's runtime code.
* Structurally identical to `JourneyHandleRef` in `@modular-react/core` —
* Structurally identical to `JourneyHandleRef` in `@modular-frontend/core` —
* re-exported here so authors have a single canonical name to import.
*
* The `__input` and `__output` fields are phantom: they never hold values
Expand Down
100 changes: 100 additions & 0 deletions packages/journeys-engine/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Public surface of @modular-frontend/journeys-engine.
//
// The framework-neutral journey engine: runtime, validation, persistence,
// authoring helpers, handles, and the full type surface. Nothing here depends
// on a UI framework. Framework bindings (@modular-react/journeys, a future
// @modular-vue/journeys) re-export the pieces their users need and add the UI
// layer (outlet, provider, hooks) on top.

export { defineJourney } from "./define-journey.js";
export {
defineJourneyPersistence,
createWebStoragePersistence,
createMemoryPersistence,
} from "./persistence.js";
export type {
WebStoragePersistenceOptions,
MemoryPersistenceOptions,
MemoryPersistence,
SyncJourneyPersistence,
} from "./persistence.js";

// `createJourneyRuntime` is the public factory; `getInternals` is the
// low-level accessor the outlet and the test harness drive. Bindings that
// render an outlet import `getInternals` from here; they do not re-export it.
export { createJourneyRuntime, getInternals, type JourneyRuntimeOptions } from "./runtime.js";
export {
JourneyValidationError,
JourneyHydrationError,
UnknownJourneyError,
validateJourneyContracts,
validateJourneyDefinition,
validateJourneyGraph,
} from "./validation.js";

// Authoring helpers — exhaustive (and fallback) state-driven dispatch.
export { selectModule, selectModuleOrDefault } from "./select-module.js";
export type { SelectModuleCases, SelectModuleCasesPartial } from "./select-module.js";

// Authoring helpers — annotate a transition handler with the entry points it
// can advance into.
export {
defineTransition,
isAnnotatedTransition,
isTerminalSentinel,
} from "./define-transition.js";
export type { AnnotatedTransitionHandler, StepRef, TerminalSentinel } from "./define-transition.js";

// Handles — open a journey with typed `input` without importing its runtime.
export { defineJourneyHandle, invoke } from "./handle.js";
export type { JourneyHandle } from "./handle.js";

export type {
AbandonCtx,
AnyJourneyDefinition,
ChildOutcome,
EntryExitWildcardMap,
EntryInputOf,
EntryNamesOf,
EntryTransitions,
ExitCtx,
ExitNamesOf,
ExitNamesPairedWithEntry,
ExitOnlyWildcardMap,
ExitOutputOf,
InstanceId,
InvokeSpec,
JourneyDefinition,
JourneyDefinitionSummary,
JourneyInstance,
JourneyNavContribution,
JourneyPersistence,
JourneyRegisterOptions,
JourneyRuntime,
JourneyStatus,
JourneyStep,
JourneyStepFor,
JourneySystemAbortReason,
JourneySystemAbortReasonCode,
MaybePromise,
ModuleTypeMap,
ParentLink,
PendingInvoke,
RegisteredJourney,
ResumeBounceCounter,
ResumeHandler,
ResumeMap,
SerializedJourney,
StepSpec,
TerminalCtx,
TerminalOutcome,
TransitionEvent,
TransitionMap,
TransitionResult,
WildcardEntryInputOf,
WildcardEntryNamesOf,
WildcardExitNamesOf,
WildcardExitOutputForEntry,
WildcardExitOutputOf,
WildcardTransitionMap,
} from "./types.js";
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
defineModule,
isJourneySystemAbort,
schema,
} from "@modular-react/core";
} from "@modular-frontend/core";
import { defineJourney } from "./define-journey.js";
import { defineJourneyHandle, invoke } from "./handle.js";
import { createJourneyRuntime } from "./runtime.js";
Expand Down Expand Up @@ -268,7 +268,7 @@
readonly hops: number;
}

function makeBouncyParent(opts: {

Check warning on line 271 in packages/journeys-engine/src/invoke-cycle-safety.test.ts

View workflow job for this annotation

GitHub Actions / Lint

eslint(no-unused-vars)

Function 'makeBouncyParent' is declared but never used.
readonly id: string;
readonly childId: string;
readonly declareInvokes: boolean;
Expand All @@ -283,7 +283,7 @@
transitions: {
m: {
step: {
go: ({ state }) =>

Check warning on line 286 in packages/journeys-engine/src/invoke-cycle-safety.test.ts

View workflow job for this annotation

GitHub Actions / Lint

eslint(no-unused-vars)

Parameter 'state' is declared but never used. Unused parameters should start with a '_'.
invoke({
handle: childHandle,
input: undefined,
Expand All @@ -300,7 +300,7 @@
// Default resume — a test can drive different parent definitions
// by extending. For the bounce tests we keep returning invoke
// until the limit forces an abort.
afterChild: ({ state }) =>

Check warning on line 303 in packages/journeys-engine/src/invoke-cycle-safety.test.ts

View workflow job for this annotation

GitHub Actions / Lint

eslint(no-unused-vars)

Parameter 'state' is declared but never used. Unused parameters should start with a '_'.
invoke({
handle: childHandle,
input: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
defineModule,
isJourneySystemAbort,
schema,
} from "@modular-react/core";
} from "@modular-frontend/core";
import { defineJourney } from "./define-journey.js";
import { defineJourneyHandle, invoke } from "./handle.js";
import { createJourneyRuntime } from "./runtime.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@
*/

import { describe, expectTypeOf, it } from "vitest";
import { defineEntry, defineExit, defineModule, schema, type StepSpec } from "@modular-react/core";
import {
defineEntry,
defineExit,
defineModule,
schema,
type StepSpec,
} from "@modular-frontend/core";

// ---------------------------------------------------------------------------
// Fixture: same shape as the compositions test, exercised against
Expand Down Expand Up @@ -159,5 +165,5 @@ describe("StepSpec mirrors the composition filter (across the boundary)", () =>
*
* To re-verify, flip any negative assertion (.not.toExtend ↔ .toExtend,
* .toEqualTypeOf<never> ↔ .toEqualTypeOf<typeof actual>) and rerun
* `pnpm -F @modular-react/journeys test`. Restore afterward.
* `pnpm -F @modular-frontend/journeys-engine test`. Restore afterward.
* ============================================================================ */
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import type { SerializedJourney } from "./types.js";
import { defineJourney } from "./define-journey.js";
import { createJourneyRuntime } from "./runtime.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expectTypeOf, test } from "vitest";
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import { defineJourney } from "./define-journey.js";
import { defineJourneyPersistence } from "./persistence.js";
import type { JourneyRegisterOptions } from "./types.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import { defineJourney } from "./define-journey.js";
import { createJourneyRuntime, getInternals } from "./runtime.js";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import { describe, expect, it } from "vitest";

import { defineJourney } from "./define-journey.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import { describe, expect, it, vi } from "vitest";

import { defineJourney } from "./define-journey.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import { describe, expect, it, vi } from "vitest";

import { defineJourney } from "./define-journey.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expectTypeOf, test } from "vitest";
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import { defineJourney } from "./define-journey.js";
import { defineJourneyHandle } from "./handle.js";
import { createJourneyRuntime } from "./runtime.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import { defineJourney } from "./define-journey.js";
import { createJourneyRuntime, getInternals } from "./runtime.js";
import type { RegisteredJourney } from "./types.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { isDevEnv, isExitContract } from "@modular-react/core";
import { isDevEnv, isExitContract } from "@modular-frontend/core";
import type {
ExitContract,
JourneyHandleRef,
ModuleDescriptor,
StandardSchemaIssue,
StandardSchemaResult,
} from "@modular-react/core";
} from "@modular-frontend/core";
import type {
AnyJourneyDefinition,
ChildOutcome,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
defineModule,
schema,
type StepSpec,
} from "@modular-react/core";
} from "@modular-frontend/core";
import { selectModule, selectModuleOrDefault } from "./select-module.js";

// -----------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { defineEntry, defineExit, defineModule, schema } from "@modular-react/core";
import { defineEntry, defineExit, defineModule, schema } from "@modular-frontend/core";
import { defineJourney } from "./define-journey.js";
import { simulateJourney } from "./simulate-journey.js";
import { selectModule, selectModuleOrDefault } from "./select-module.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { EntryNamesOf, ModuleTypeMap, StepInputSlot, StepSpec } from "@modular-react/core";
import type { EntryNamesOf, ModuleTypeMap, StepInputSlot, StepSpec } from "@modular-frontend/core";

/**
* One case in a `selectModule` map: an entry name on module `M` plus the
Expand Down
Loading
Loading