<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chuck Carpenter</title>
    <description>The latest articles on DEV Community by Chuck Carpenter (@chuckcarpenter).</description>
    <link>https://dev.to/chuckcarpenter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F270355%2F10dadb8d-7395-45c3-a480-436e11f57d96.png</url>
      <title>DEV Community: Chuck Carpenter</title>
      <link>https://dev.to/chuckcarpenter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chuckcarpenter"/>
    <language>en</language>
    <item>
      <title>Exciting New Features in Shepherd Pro's Latest Beta Release</title>
      <dc:creator>Chuck Carpenter</dc:creator>
      <pubDate>Tue, 13 Aug 2024 22:00:12 +0000</pubDate>
      <link>https://dev.to/chuckcarpenter/exciting-new-features-in-shepherd-pros-latest-beta-release-3g22</link>
      <guid>https://dev.to/chuckcarpenter/exciting-new-features-in-shepherd-pros-latest-beta-release-3g22</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://blog.shepherdpro.com/blog/exciting-new-features-in-our-latest-beta-release/" rel="noopener noreferrer"&gt;blog.shepherdpro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We’re super excited to announce that Shepherd Pro has leveled up from alpha to beta. Work has been ongoing since our alpha and this release is packed with powerful new features designed to enhance your user journey configurations. &lt;/p&gt;

&lt;p&gt;If you’ve been tuned in, you know we initially launched with analytics tied into the inner workings of our core open source library. This allows Shepherd to better inform how users of your tour are progressing and illuminating how successful the onboarding, or whatever outcome you’ve designed for, has worked. You can see a basic form of this in the Pro application and we’ve offered integrations into some popular analytics providers, like PostHog &amp;amp; Amplitude. This enables you to track user engagement step-by-step, gaining valuable insights to optimize your user journeys. &lt;/p&gt;

&lt;p&gt;Since our alpha launch, we’ve worked with our users and developers to learn more about what additional features are the next most important for managing onboarding flows. We ran a Quira quest (hackathon with technology specific requirements) to work with developers implementing open source into their projects to get more feedback on the learning curve to get started. &lt;/p&gt;

&lt;p&gt;I’ve also built journeys directly for our customers and have worked on more advanced use cases where the end user’s journey experience (e.g. whether they see a journey at all or even just particular steps along that flow) are dictated by that user’s specific criteria and activities.&lt;/p&gt;

&lt;p&gt;It’s really exciting to see all the complex situations and ideas for journeys we’ve discovered in this time!&lt;/p&gt;

&lt;p&gt;To build on these discoveries, we’re launching two major features as part of our beta application and javascript library updates. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced Targeting Options&lt;/strong&gt;&lt;br&gt;
First are advanced targeting options where you can now add options in the application to determine if a journey is shown to your user.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User Details: Personalize journeys based on specific user attributes, such as a user’s email address or specific user id (all based on any user data you send as properties when initializing Shepherd).&lt;/li&gt;
&lt;li&gt;Journey Interaction: Target users who have not previously seen a journey, or who may not have completed that particular journey to the final step.&lt;/li&gt;
&lt;li&gt;URL-Specific Targeting: Trigger journeys by specific URLs for contextual relevance. For example, users who end up on a specific landing page may have a different funnel than those who are accessing your site from the home page and therefore you may want to show them different journeys that lead to different conclusions or have varying steps based on where they are currently in your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Seamless Journey Creation&lt;/strong&gt;&lt;br&gt;
Next, we’re introducing the ability to have your entire journey configured within the Shepherd application, and no longer limiting the configuration experience to within your application only. This is starting with simply using the same JSON configuration as would be applied directly in code, but not requiring deployments of your application for those changes to be active. &lt;/p&gt;

&lt;p&gt;Say goodbye to a code only experience! You can now create, update, and configure journeys entirely within the Shepherd application, streamlining the process for a smoother, more intuitive experience. You no longer need to make deployments for changing text, content, order of steps, and much more. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looking Ahead&lt;/strong&gt;&lt;br&gt;
Our journey from alpha to beta showcases our commitment to continually improving and expanding Shepherd Pro. While these updates are exciting, they’re just a glimpse into our future plans as we explore new SDKs, targeting options, and more detailed journey configurations. If there’s something you’d like to see in Shepherd Pro, please let us know. &lt;/p&gt;

&lt;p&gt;Stay tuned for more updates, and happy journey building!&lt;/p&gt;

&lt;p&gt;If you’re interested in learning more, &lt;a href="https://shepherdpro.com/demo-request" rel="noopener noreferrer"&gt;schedule a demo&lt;/a&gt; to chat with me directly! Or join our growing &lt;a href="https://discord.gg/EGcDW5NSud" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; and see how other developers are building with Shepherd.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Introducing Shepherd Pro: Adding User Journey Analytics</title>
      <dc:creator>Chuck Carpenter</dc:creator>
      <pubDate>Thu, 18 Apr 2024 04:24:05 +0000</pubDate>
      <link>https://dev.to/chuckcarpenter/introducing-shepherd-pro-adding-user-journey-analytics-4agl</link>
      <guid>https://dev.to/chuckcarpenter/introducing-shepherd-pro-adding-user-journey-analytics-4agl</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://blog.shepherdpro.com/blog/alpha-launch-pro/" rel="noopener noreferrer"&gt;blog.shepherdpro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hey there, it’s me Chuck Carpenter. Today I want to talk about Shepherd.js, the open source library and to announce Shepherd Pro, a way to take things further to create user journeys that are informed and effective. The TL;DR is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An alpha launch of the Pro platform and adding analytics capabilities to the library&lt;/li&gt;
&lt;li&gt;Chuck is building the Pro product, with continued support and help from Robbie and the OS community for the library&lt;/li&gt;
&lt;li&gt;Sign up today at &lt;a href="https://shepherdpro.com" rel="noopener noreferrer"&gt;https://shepherdpro.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Everything remains open source, MIT licensed, and we are looking for feedback on the product to help us drive things forward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foc6ny2ed1h4c9u0axga0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foc6ny2ed1h4c9u0axga0.png" alt="Image description" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As many of you know, I’ve been a maintainer of the Shepherd library for about 7 years or so. What originally started as an Ember.js add-on of an existing tour library, has been rewritten a number of times and morphed into one of the most popular user journey libraries (thanks to our 90+ contributors) with almost 12k stars! We now believe our users are looking for more while supporting the ethos of open source (we are committed to keeping the library OSS).&lt;/p&gt;

&lt;p&gt;We’ve worked to make software that is stable, accessible, and very composable in order to allow developers to create some clever ways to help their users find functionality and value for their time (companies and projects such as Drupal, Google, and recently our friends at Codepen.io). Much of that work has also allowed us to learn a lot about maintaining/publishing dependencies and work through a few interesting patterns such as attaching an element to another, highlighting it, and programmatically reacting to user events.&lt;/p&gt;

&lt;p&gt;Today, we’re excited to announce the alpha release of Shepherd Pro, a robust new addition to our trusted Shepherd library. Shepherd has gained and maintained popularity for its openness and expandability, and now we’re dialing it up by integrating powerful analytics capabilities. While there exists paid software where you can create onboarding tours for your application, they seem to be very focused on WYSIWYG creation and customization. Our users are very technical and are able to control all these things quite comfortably in code. Why not empower them to and give them a professional product that allows them to bring in feedback and data?  These new features will transform how you understand and improve user interactions throughout their journeys.&lt;/p&gt;

&lt;p&gt;With Shepherd Pro, you’ll have deep insights into how users interact with your guided journeys. Our new analytics tools allow you to track user engagement step-by-step, pinpointing exactly where users might disengage or need extra help. This data isn’t just informative—it’s crucial for enhancing the effectiveness of each journey and identifying areas for improvement. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc0t67lt8vli5sau35fu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc0t67lt8vli5sau35fu.png" alt="Image description" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While other tools exist in the market to address some of these areas, we feel that a completely open source approach that allows end product users to not only provide valuable insights but also actively contribute is critical in our path to deliver the most impactful solutions. Additionally, there are features that aren't available in other products such as:&lt;/p&gt;

&lt;p&gt;On premise/self hosted solutions&lt;br&gt;
Accessibility focus&lt;br&gt;
Catering to highly technical and high performing engineering teams&lt;/p&gt;

&lt;p&gt;The ultimate goal? To make your tours not only more engaging but also easier to navigate, boosting product adoption and user satisfaction. We aim to get users to the "aha!" moment of your application more smoothly and quickly, because a well-informed user is a happy user. Integrations extend beyond just our own tools as well. Now, Shepherd Pro easily connects with top analytics platforms like Posthog, with more integrations on the horizon. This seamless connection ensures that you can manage and analyze your user journey data effortlessly within a familiar environment.&lt;/p&gt;

&lt;p&gt;We’re thrilled to see how Shepherd Pro will empower you to craft even more engaging and effective user journeys and we’re launching all of this to alpha users under our open source project that is MIT licensed. As we progress toward the next stages of release, we plan to add targeting options, allow you to sync your cohorts from our integrations, and eventually add ways to more easily define the configurations of a journey.  Join us at &lt;a href="https://shepherdpro.com" rel="noopener noreferrer"&gt;https://shepherdpro.com&lt;/a&gt; to sign up or feel free to reach out to me directly, &lt;a href="mailto:hello@shepherdpro.com"&gt;hello@shepherdpro.com&lt;/a&gt;. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Secret Handshakes and Hidden Passages: Using Redwood Functions</title>
      <dc:creator>Chuck Carpenter</dc:creator>
      <pubDate>Wed, 28 Feb 2024 00:13:22 +0000</pubDate>
      <link>https://dev.to/chuckcarpenter/secret-handshakes-and-hidden-passages-using-redwood-functions-2ci1</link>
      <guid>https://dev.to/chuckcarpenter/secret-handshakes-and-hidden-passages-using-redwood-functions-2ci1</guid>
      <description>&lt;p&gt;Redwood.js has served as a great choice in terms of a fully featured web application stack, covering lots of my needs such as authentication and mailing capabilities right out of the box. &lt;/p&gt;

&lt;p&gt;For your main application, using and managing the API for your admin users is very straightforward and uses known conventions. Let's say you have perhaps another use case though? One that creates data via an action or even takes in events from another site? Enter Redwood functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Redwood Functions
&lt;/h2&gt;

&lt;p&gt;Redwood functions are the backbone of your serverless architecture, enabling you to execute server-side logic without the overhead of managing special access points in the GraphQL that have their own custom directives and you avoid enforcing GraphQL style queries where it might be unfamilar. These functions can range from processing data, handling requests, to interacting with databases. However, without proper protection, they're like open doors to your data treasury.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing API Key Authentication
&lt;/h2&gt;

&lt;p&gt;To secure these precious endpoints, we turn to API keys—a unique string that a client must send to access the function. Think of it as a special passcode that unlocks your application's capabilities. Implementing API key authentication in Redwood functions ensures that only requests with the correct key can invoke your business logic.&lt;/p&gt;

&lt;p&gt;Here’s how you might implement such a check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid API Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Do your business here, good people&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access granted: Welcome to the secret club!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might notice the event arguments on the handler function look familiar if you've worked in serverless environments before. As noted in the Redwood.js documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally, Redwood apps were intended to be deployed as serverless functions to AWS Lambda. Whenever a Redwood app is deployed to a "serverful" environment such as Fly or Render, a Fastify server is started and your Redwood app's functions in api/src/functions are automatically registered onto the server. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Meaning, they have a similar signature to Lambdas even when in a containerized application. Huzzah!&lt;/p&gt;

&lt;p&gt;In the above example, the function first checks if the incoming request contains an 'api-key' header that matches a predefined key stored in the environment variables. If not, it responds with a 403 Forbidden status, effectively barring the door against unauthorized access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why API Keys?
&lt;/h2&gt;

&lt;p&gt;API keys are simple yet effective in controlling access to your functions. They help you track and control how the API is being used, preventing unauthorized use and safeguarding your application from unwanted guests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tale Ends Here
&lt;/h2&gt;

&lt;p&gt;Incorporating API key authentication into your Redwood functions is like equipping your digital fortress with a sophisticated locking mechanism. It ensures that your functions, whether they're updating user data, processing payments, or querying databases, remain secure and accessible only to those with the correct key.&lt;/p&gt;

&lt;p&gt;As you continue to develop and deploy, remember that the security of your functions is integral to the integrity of your application. Secure your Redwood functions with API keys (or any other way you wish), and rest easy knowing that your back-end operations are guarded against the wild west of the web.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>redwoodjs</category>
    </item>
    <item>
      <title>A Tale of Frameworks: Our Quirky Quest from Next.js to Redwood.js</title>
      <dc:creator>Chuck Carpenter</dc:creator>
      <pubDate>Tue, 20 Feb 2024 21:05:37 +0000</pubDate>
      <link>https://dev.to/chuckcarpenter/a-tale-of-frameworks-our-quirky-quest-from-nextjs-to-redwoodjs-5eca</link>
      <guid>https://dev.to/chuckcarpenter/a-tale-of-frameworks-our-quirky-quest-from-nextjs-to-redwoodjs-5eca</guid>
      <description>&lt;h2&gt;
  
  
  And so it begins
&lt;/h2&gt;

&lt;p&gt;In the realm of web development, choosing the right framework can feel like finding a needle in a digital haystack. Our latest project, an application for managing user journeys and getting analytics for them, started with a dive into &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; version 14 ("blessed" with React Server Components), paired with a separate API using &lt;a href="https://elysiajs.com/" rel="noopener noreferrer"&gt;ElysiaJS&lt;/a&gt;. However, as the project continued and the complexity grew, so did our needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Started with Next.js and ElysiaJS
&lt;/h2&gt;

&lt;p&gt;Next.js, with its robust features and scalability, seemed like a natural choice. We've used it quite successfully plenty in the past and felt like an old friend to help us in our own project now, rather than enterprise client projects. React can be the wild west in some ways when it comes to compiling together all the dependencies to make it a full app framework suite, Next.js makes many of those choices for you and provides nice defaults. &lt;/p&gt;

&lt;p&gt;Coupled with ElysiaJS for our API needs (which to be fair is quite bleeding edge and built on the newly released &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;Bun&lt;/a&gt; runtime), we embarked on this journey with high hopes. Next.js offered us a modern framework with great performance, while ElysiaJS promised a flexible and fast API layer. Bun is just fast, no question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Twist in the Tale: Shifting to RedwoodJS
&lt;/h2&gt;

&lt;p&gt;However, as we navigated through development, we encountered a few hitches. The separate management of front-end and API layers started to feel like juggling apples and oranges – doable, but unnecessarily complex. Bleeding edge tech isn't always the most documented and while open source folks are quite helpful, it's time consuming to ask questions in multiple projects and/or Discord servers. Enter RedwoodJS.&lt;/p&gt;

&lt;p&gt;RedwoodJS offered us an integrated solution, blending the front-end and API seamlessly. It was like finding a tool that could juggle for us. The allure of its full-stack capabilities, coupled with a simpler, more cohesive development experience, was too good to pass up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the App with RedwoodJS
&lt;/h2&gt;

&lt;p&gt;With RedwoodJS, our development process became smoother, akin to a well-oiled machine. Its opinionated structure provided a clear path forward, reducing the overhead of decision-making. The integrated GraphQL API meant that our data management became more streamlined and all the work happened in one monorepo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In the end, our journey led us to RedwoodJS, a framework that aligned perfectly with our vision for the journey management application. It reminded us that in the world of web development, flexibility and adaptability are just as important as the features a framework offers. There's something to be said for relying on web technologies that have worked well for years on end. Join us as we continue to explore and share our experiences in this ever-evolving landscape.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Innovating in the Open: Building a Product with Shepherd.js</title>
      <dc:creator>Chuck Carpenter</dc:creator>
      <pubDate>Tue, 20 Feb 2024 21:02:54 +0000</pubDate>
      <link>https://dev.to/chuckcarpenter/innovating-in-the-open-building-a-product-with-shepherdjs-3efm</link>
      <guid>https://dev.to/chuckcarpenter/innovating-in-the-open-building-a-product-with-shepherdjs-3efm</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://shepherdjs.dev/blog/shepherd-the-product/" rel="noopener noreferrer"&gt;shepherdjs.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Web development isn’t just about slinging code – it’s about creating experiences that don’t make users want to pull their hair out. Enter Shepherd.js, our guiding light in this adventure, and our choice for steering users through journeys in our apps without getting them lost and helping to drive value every time they interact. What makes this development story unique is our approach: building in public with open source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Shepherd.js?
&lt;/h2&gt;

&lt;p&gt;It’s like having a GPS for your app - but without the annoying “recalculating” voice. Shepherd.js is lightweight, flexible, and doesn’t hog the spotlight, ensuring our app remains the star of the show. With this library our product isn’t just functional; it becomes more accessible and user-friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building in Public
&lt;/h2&gt;

&lt;p&gt;We’re embracing the “building in public” approach. It’s like cooking with the kitchen door open – everyone sees the mess, but they also see the masterpiece in the making. This transparency invites valuable feedback and keeps us honest (and a little bit on edge).&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source: A Collaborative Effort
&lt;/h2&gt;

&lt;p&gt;Open source is at the core of our development strategy. By making our code available to the community, we’re not just sharing our work; we’re inviting collaboration. This collective wisdom not only enhances our product but also enriches the wider open-source ecosystem. Let’s be honest, we don’t always have the best or right answers for what our customers will need and we’re happy to let them help drive us in the right direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shepherd Pro: The Journey Leading Superstar
&lt;/h2&gt;

&lt;p&gt;We've worked to create and iterate over this library over a number of years, and now we are taking the next step to unlock the potential capabilities of advanced user journeys with Pro. Shepherd Pro is like Shepherd.js on a caffeine boost. This enhanced offering is designed to unlock a plethora of capabilities and craft more full-featured user journeys.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Customizable Tour Templates&lt;/strong&gt;: Like fashion templates, but for software. Dress your app in the best style that feels seemless to what your visitors are used to seeing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics Integration&lt;/strong&gt;: Understanding user engagement is crucial. Shepherd Pro integrates analytics to provide insights into the effectiveness of the journeys, helping us make data-driven decisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Language Support&lt;/strong&gt;: Parlez-vous tour? ¡Claro que sí!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Behavior Tracking&lt;/strong&gt;: By tracking how users interact with the tours, we gain invaluable insights. This helps us continuously optimize the user experience, making it more intuitive and effective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Capabilities&lt;/strong&gt;: Plays well with others. No sandbox squabbles here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback Mechanisms&lt;/strong&gt;: Because we actually want to know what you think.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Branching Logic&lt;/strong&gt;: It's like choose-your-own-adventure, but for user journeys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Flexible Hosting Options: Your Place or Ours
&lt;/h2&gt;

&lt;p&gt;With Shepherd Pro, we’re not just handing you the keys to a sleek car; we’re giving you the choice of where to park it. Whether you’re more comfortable with us hosting, or hosting the application on your own premises or in your cloud account, Shepherd Pro adapts to your environment. Whether you’re guiding users through a complex application or a simple interface, the choice of hosting is yours. Host Shepherd Pro on your premises or in your cloud account to seamlessly integrate these journeys into your environment. It’s like choosing the perfect setting for a story – whether it’s in your own home or a cloud in the sky, we ensure the user journey remains uninterrupted and secure. This flexibility ensures that your data sovereignty and security preferences are always top priority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Join us on this quirky, code-filled journey. Whether you're a developer, a tech aficionado, or just someone who appreciates a great user experience, your insights and involvement are what make this a thrilling adventure.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Simplify using MirageJS with GraphQL</title>
      <dc:creator>Chuck Carpenter</dc:creator>
      <pubDate>Mon, 13 Jul 2020 04:25:57 +0000</pubDate>
      <link>https://dev.to/chuckcarpenter/simplify-using-miragejs-with-graphql-g54</link>
      <guid>https://dev.to/chuckcarpenter/simplify-using-miragejs-with-graphql-g54</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://shipshape.io/blog/miragejs-graphql-tools/"&gt;shipshape.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://miragejs.com/"&gt;Mirage&lt;/a&gt; can be very handy to allow app developers to build features early and not be blocked by an API that doesn't exist yet or that is still in progress. This way you can have a proof of concept or can work on features that simply need to be put in place for persistence somewhere later on.&lt;/p&gt;

&lt;p&gt;While this works across many different application stacks, it has traditionally been used expecting a REST style API and it's not completely turn-key for some technologies such as GraphQL. A number of folks, including the Mirage core team, have been working on the best workflows to make that an easier experience, but it is not yet &lt;a href="https://miragejs.com/docs/comparison-with-other-tools/#graphql-query-mocking"&gt;robustly documented&lt;/a&gt;. While that is underway, this is how we decided to improve our workflow as the code base grows.&lt;/p&gt;

&lt;p&gt;Recently, we needed to apply simulation for a Javascript application using GraphQL. As part of the implementation, we worked on some utilities to streamline the developer experience for updates and maintenance. The examples for dealing with this are for a very basic use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;miragejs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graphqlSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
  type Query {
    movies: [Movie]
  }
  type Movie {
    id: ID!
    title: String!
  }
`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="nf"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;movie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Interstellar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;movie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inception&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;movie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The Dark Knight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requestJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requestJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;graphql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;graphqlSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how the basic recommended configuration is when working with GraphQl. This can work early on, but become problematic to manage when you start to have a much larger schema and need to maintain way more models and relationships for your mocking. Given the following schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Movies&lt;/span&gt;&lt;span class="p"&gt;]!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Distributor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;]!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;actors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;]!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;distributor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Distributor&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing we can do is automate adding models to our config at build time. This can be done by parsing our schema and some traversing of the parsed AST.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
  // ...schema
`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// get the object definitions and fields&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodeTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;definitions&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;def&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ObjectTypeDefinition&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;def&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filteredDef&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filteredDef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filteredDef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// output an object with model mapping&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modelMaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodeTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;modelAccumulator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;modelAccumulator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;modelAccumulator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then add that to the configuration for Mirage as &lt;code&gt;models: modelMaps&lt;/code&gt; and we'll get that automatically registered as we add to our schema. This does get more complicated though, when we start to add associations in our objects and need Mirage to see that as a relationship for queries that load all that data. Ideally, the graph can work for a query like so on the UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ListAllMovies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;actors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;      
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;distributor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first want to identify all the model names (variable &lt;code&gt;modelNames&lt;/code&gt;). Also, we'll want to reduce the fields we're checking against to only fields that are confirmed to be other object types (variable &lt;code&gt;modelsReducedFields&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modelNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodeTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modelsReducedFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodeTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodeFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodeFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isNestedType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isNestedType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_getRootType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isListType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ListType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rootField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;isListType&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, what we're doing here with &lt;code&gt;modelsReducedFields()&lt;/code&gt; is taking each node and reducing the fields down to other models and determining if they are a belongs-to or has-many kind of association. You might have noticed the call to &lt;code&gt;_getRootType()&lt;/code&gt;, which is just a recursive function to go through nested objects in the AST and get the deepest node's name. I'm showing it independently in the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;_getRootType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;_getRootType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now use this improved array for the &lt;code&gt;modelMaps&lt;/code&gt; value to get models that have the associations automatically created.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>miragejs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Shepherd 6.0: One Dependency, Smaller Bundle, and New Look!</title>
      <dc:creator>Chuck Carpenter</dc:creator>
      <pubDate>Mon, 18 Nov 2019 16:49:00 +0000</pubDate>
      <link>https://dev.to/chuckcarpenter/shepherd-6-0-one-dependency-smaller-bundle-and-new-look-2j7</link>
      <guid>https://dev.to/chuckcarpenter/shepherd-6-0-one-dependency-smaller-bundle-and-new-look-2j7</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://shipshape.io/blog/shepherd-6-smaller-faster-tether/"&gt;shipshape.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Return to Tether
&lt;/h2&gt;

&lt;p&gt;As Robert wrote about in a previous &lt;a href="https://shipshape.io/blog/shepherd-popper-to-tether/"&gt;post&lt;/a&gt;, we moved back to the original element attachment and positioning library Shepherd.js shipped with. This was with the intent of not only controlling the overal bundle size (which I'll discuss next), but also about being able to control those dependencies over time. Since Ship Shape is now maintaining the &lt;a href="https://github.com/shipshapecode/tether"&gt;Tether&lt;/a&gt; code base, we have a chance to ensure that it stays lean and performant for it's users, which are also ourselves for the future of Shepherd.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smaller Size
&lt;/h2&gt;

&lt;p&gt;Getting back to the point on overall size. For implementers of Shepherd, we find it important to make sure we're able to ship a library that adds only what is needed for the Tour API and allow engineers to create rich experiences with little impact to their application's performance. We think we've done quite a bit by reducing the overall bundle size down to &lt;code&gt;~15kb (min + gzip)&lt;/code&gt; and removing all dependencies other than Tether.&lt;/p&gt;

&lt;h2&gt;
  
  
  Website Update
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6sD35zyM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://shipshape.io/img/blog/shepherd6-site4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6sD35zyM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://shipshape.io/img/blog/shepherd6-site4.png" alt="Image of Latest Shepherd Website" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we continued to iterate, on improving the library, we decided to work with the team at &lt;a href="https://www.makemodel.co"&gt;make&amp;amp;model&lt;/a&gt; to create branding to help Shepherd be more memorable and unique. This now combines the demo site and branding to help show the many benefits of the library and highlight companies already using it. It also helps to show the seemingly limitless level of customization that can be achieved with Shepherd tours.&lt;/p&gt;

&lt;p&gt;We hope this latest release provides a speedy, low impact, and easy to use way to bring application tours to your users! Feedback is appreciated, as always.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
