npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@modular-vue/journeys

v1.4.0

Published

Vue 3 journeys for @modular-vue: JourneyProvider, instance composables (useJourneyState / useActiveLeafJourneyInstance), and the journeys registry plugin over @modular-frontend/journeys-engine.

Readme

@modular-vue/journeys

Vue 3 journeys for @modular-vue. Journeys are typed, serializable workflows that compose multiple modules: a journey declares entry/exit transitions between modules and owns shared state, while the modules stay journey-unaware.

This package is the Vue binding over the framework-neutral @modular-frontend/journeys-engine. It re-exports the engine's authoring surface (defineJourney, handles, persistence, transition helpers) and adds the Vue-specific pieces: the provider, the composables, the outlet, and the registry plugin.

The React binding is @modular-react/journeys; the two are kept at parity, and its README carries the long-form guides (authoring patterns, invoke/resume, persistence) that apply to both.

Installation

npm install @modular-vue/journeys

What's included

  • <JourneyProvider :runtime="…"> — provides the journey runtime to descendant journey hosts and mounts <ModuleExitProvider> so module exits fired outside a step reach the shell's onModuleExit.
  • <JourneyOutlet :instance-id="…"> — renders the current step of a running instance: mount-kind checks, loading, error boundary + policy, terminal, idle preload, and abandon-on-unmount. Walks the active call chain to the leaf by default.
  • <JourneyHost :handle="…" :input="…"> — mounts a journey in one line: starts it on mount, renders its step, ends + forgets the instance on unmount. Outlet props pass through as attrs, in either spelling (:on-finished and :onFinished both reach the outlet); the default scoped slot receives { instanceId, instance, runtime, stepIndex, outlet } for chrome. outlet is a functional component — render it with <component :is="outlet" />, or spell it yourself as <JourneyOutlet :instance-id="instanceId" :runtime="runtime" />.
  • useJourneyHost(handle, input, options?) — the lifecycle without the rendering. Returns { instanceId, instance, stepIndex } as refs, plus the plain runtime it resolved at setup — the one instanceId is valid on.
  • useJourneySync(id, port, options?) — keeps a journey and the URL in step both ways: the journey advances and the URL follows; Back/Forward drive rewindTo / goForward. You supply a small JourneySyncPort for vue-router.
  • useJourneyState(id) / useJourneyInstance(id) — subscribe to one journey instance; return a reactive ref of its state (or full snapshot).
  • useActiveLeafJourneyState(rootId) / useActiveLeafJourneyInstance(rootId) — walk the activeChildId chain and track the deepest active leaf, so a parent host reads the child sub-flow's state without knowing the depth.
  • <ModuleTab> — renders a single module entry outside a route and forwards its exits to the shell. The non-journey counterpart to the outlet.
  • useWaitForExit(exit, channels) — for step components that wait on an async backend event: races a push subscribe channel, a poll channel, and a timeout arm with first-wins-latched dispatch.
  • journeysPlugin(options?) — pass to createRegistry({ plugins: [...] }) to contribute registerJourney(...), validate journey contracts against registered modules, produce the JourneyRuntime on manifest.extensions.journeys, and wrap the provider stack in <JourneyProvider>. In the router-owning install path it also threads the runtime app-wide via its appProvides hook, so app.use(manifest) provides journeyKey with no shell wiring.
  • createPiniaJourneyPersistence(options) — a JourneyPersistence backed by a Pinia store you own (keyed like createWebStoragePersistence), so in-flight journeys live in your Pinia tree and resume across a modal close/reopen. Structural store shape — takes no pinia dependency.
  • provideJourneyRuntime(app, runtime, options?) — the app-level twin of <JourneyProvider> (analog of provideNavigation / provideSlots), for wiring a hand-built runtime without the plugin. Usually unnecessary — the plugin auto-threads via app.use(manifest).

Usage

// app-shared: register the plugin and journeys
import { createRegistry } from "@modular-vue/runtime";
import { journeysPlugin, defineJourney } from "@modular-vue/journeys";

const registry = createRegistry<AppDeps, AppSlots>({ stores, services, slots });
const journeys = journeysPlugin();
registry.use(journeys);

registry.registerJourney(checkoutJourney, {
  nav: { label: "Checkout", group: "flows" },
});
<!-- a host component reading the active leaf's state -->
<script setup lang="ts">
import { useActiveLeafJourneyInstance } from "@modular-vue/journeys";

const props = defineProps<{ rootId: string }>();
const leaf = useActiveLeafJourneyInstance(props.rootId);
</script>

<template>
  <span>{{ leaf?.step?.moduleId }} — {{ leaf?.status }}</span>
</template>
<!-- a journey route: mounted, deep-linked, cleaned up -->
<script setup lang="ts">
import { useRoute, useRouter } from "vue-router";
import { JourneyOutlet, useJourneyHost, useJourneySync } from "@modular-vue/journeys";
import type { JourneySyncPort } from "@modular-vue/journeys";
import { checkoutHandle } from "@app/journeys-checkout";

const props = defineProps<{ cartId: string }>();
const router = useRouter();
const route = useRoute();

const { instanceId, stepIndex } = useJourneyHost(checkoutHandle, { cartId: props.cartId });

const port: JourneySyncPort = {
  read: () => String(route.params.step ?? ""),
  push: (path) => void router.push({ name: "checkout", params: { step: path } }),
  replace: (path) => void router.replace({ name: "checkout", params: { step: path } }),
  go: (delta) => router.go(delta),
  subscribe: (listener) => router.afterEach(() => listener()),
};

useJourneySync(instanceId, port, { stepToPath: (step) => step.entry });
</script>

<template>
  <p>Step {{ stepIndex + 1 }}</p>
  <JourneyOutlet v-if="instanceId" :instance-id="instanceId" />
</template>

useJourneyHost starts the journey on mount and ends + forgets it on unmount; useJourneySync owns the URL. Neither knows about the other, and instanceId is null for the first render (the journey is started from onMounted, so the start is guaranteed to be paired with an unmount that ends it).

A URL cannot navigate a journey to an arbitrary step — a step is derived from state, so the only positions a location can select are ones the journey has already been to. See Deep-linking steps in the React README for the full model; the reconciler is the same code.

Reactivity

The composables mirror the React hooks but return Vue refs. A single-instance composable subscribes at setup and pushes fresh snapshots into a shallowRef on every runtime change; the leaf-walking composables re-subscribe as the active chain grows (a parent invokes a child) or shrinks (a child terminates and the parent resumes). Read .value at the call site, or bind directly in templates.