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

inertia-sails

v1.3.3

Published

The Sails adapter for Inertia.

Readme

inertia-sails

The official Inertia.js adapter for Sails.js, powering The Boring JavaScript Stack.

Installation

npm install inertia-sails

Or use create-sails to scaffold a complete app:

npx create-sails my-app

Quick Start

1. Configure Inertia

// config/inertia.js
module.exports.inertia = {
  rootView: 'app', // views/app.ejs
  version: 1 // Asset version for cache busting
}

2. Create a root view

<!-- views/app.ejs -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <%- shipwright.styles() %>
</head>
<body>
  <div id="app" data-page="<%- JSON.stringify(page) %>"></div>
  <%- shipwright.scripts() %>
</body>
</html>

3. Create an action

// api/controllers/dashboard/view-dashboard.js
module.exports = {
  exits: {
    success: { responseType: 'inertia' }
  },
  fn: async function () {
    return {
      page: 'dashboard/index',
      props: {
        stats: await Stats.find()
      }
    }
  }
}

API Reference

Responses

responseType: 'inertia'

Return an Inertia page response:

return {
  page: 'users/index',       // Component name
  props: { users: [...] },   // Props passed to component
  locals: { title: '...' }   // Locals for root EJS template
}

responseType: 'inertiaRedirect'

Return a URL string to redirect:

return '/dashboard'

Sharing Data

share(key, value)

Share data with the current request (request-scoped):

sails.inertia.share('flash', { success: 'Saved!' })

shareGlobally(key, value)

Share data across all requests (app-wide):

// In hook initialization
sails.inertia.shareGlobally('appName', 'My App')

local(key, value)

Set a local variable for the root EJS template:

sails.inertia.local('title', 'Dashboard')

Once Props (Cached)

Cache expensive props across navigations. The client tracks cached props and skips re-fetching.

once(callback)

// In custom hook
sails.inertia.share(
  'loggedInUser',
  sails.inertia.once(async () => {
    return await User.findOne({ id: req.session.userId })
  })
)

Chainable methods:

  • .as(key) - Custom cache key
  • .until(seconds) - TTL expiration
  • .fresh(condition) - Force refresh
sails.inertia
  .once(() => fetchPermissions())
  .as('user-permissions')
  .until(3600) // Cache for 1 hour
  .fresh(req.query.refresh === 'true')

shareOnce(key, callback)

Shorthand for share() + once():

sails.inertia.shareOnce('countries', () => Country.find())

refreshOnce(keys)

Force refresh cached props from an action:

// After updating user profile
await User.updateOne({ id: userId }).set({ fullName })
sails.inertia.refreshOnce('loggedInUser')

Flash Messages

One-time messages that don't persist in browser history:

sails.inertia.flash('success', 'Profile updated!')
sails.inertia.flash({ error: 'Failed', field: 'email' })

Access in your frontend via page.props.flash.

Deferred Props

Load props after initial page render:

return {
  page: 'dashboard',
  props: {
    // Loads immediately
    user: currentUser,
    // Loads after render
    analytics: sails.inertia.defer(async () => {
      return await Analytics.getExpensiveReport()
    })
  }
}

Merge Props

Merge with existing client-side data (useful for infinite scroll):

// Shallow merge
messages: sails.inertia.merge(() => newMessages)

// Deep merge (nested objects)
settings: sails.inertia.deepMerge(() => updatedSettings)

Infinite Scroll

Paginate data with automatic merge behavior. Works with Inertia.js v2's <InfiniteScroll> component:

// Controller
const page = this.req.param('page', 0)
const perPage = 20
const invoices = await Invoice.find().paginate(page, perPage)
const total = await Invoice.count()

return {
  page: 'invoices/index',
  props: {
    invoices: sails.inertia.scroll(() => invoices, {
      page,
      perPage,
      total,
      wrapper: 'data' // Wraps in { data: [...], meta: {...} }
    })
  }
}
<!-- Vue component -->
<script setup>
import { InfiniteScroll } from '@inertiajs/vue3'

defineProps({ invoices: Object })
</script>

<template>
  <InfiniteScroll data="invoices">
    <div v-for="invoice in invoices.data" :key="invoice.id">
      {{ invoice.invoiceNumber }}
    </div>
  </InfiniteScroll>
</template>

History Encryption

Encrypt sensitive data in browser history:

sails.inertia.encryptHistory() // Enable for current request
sails.inertia.clearHistory() // Clear history state

Root View

Change the root template per-request:

sails.inertia.setRootView('auth') // Use views/auth.ejs

Back Navigation

Get the referrer URL for redirects:

return sails.inertia.back('/dashboard') // Fallback if no referrer

Optional Props

Props only included when explicitly requested via partial reload:

categories: sails.inertia.optional(() => Category.find())

Always Props

Props included even in partial reloads:

csrf: sails.inertia.always(() => this.req.csrfToken())

Custom Hook Example

Share user data across all authenticated pages using once() for caching:

// api/hooks/custom/index.js
module.exports = function defineCustomHook(sails) {
  return {
    routes: {
      before: {
        'GET /*': {
          skipAssets: true,
          fn: async function (req, res, next) {
            if (req.session.userId) {
              sails.inertia.share(
                'loggedInUser',
                sails.inertia.once(async () => {
                  return await User.findOne({ id: req.session.userId }).select([
                    'id',
                    'email',
                    'fullName',
                    'avatarUrl'
                  ])
                })
              )
            }
            return next()
          }
        }
      }
    }
  }
}

Custom Responses

Copy these to api/responses/:

  • inertia.js - Handle Inertia page responses
  • inertiaRedirect.js - Handle Inertia redirects
  • badRequest.js - Validation errors with redirect back
  • serverError.js - Error modal in dev, graceful redirect in prod

Architecture

inertia-sails uses AsyncLocalStorage for request-scoped state, preventing data leaks between concurrent requests. This is critical for share(), flash(), setRootView(), and other per-request APIs.

Configuration

// config/inertia.js
module.exports.inertia = {
  // Root EJS template (default: 'app')
  rootView: 'app',

  // Asset version for cache busting (optional - auto-detected by default)
  // version: 'custom-version',

  // History encryption settings
  history: {
    encrypt: false
  }
}

Automatic Asset Versioning

inertia-sails automatically handles asset versioning:

  1. With Shipwright: Reads .tmp/public/manifest.json and generates an MD5 hash. Version changes when any bundled asset changes.

  2. Without Shipwright: Falls back to server startup timestamp, ensuring fresh assets on each restart.

You can override this with a custom version if needed:

// config/inertia.js
module.exports.inertia = {
  version: 'v2.1.0' // Or a function: () => myCustomVersion()
}

References