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

sails-hook-slipway

v0.0.13

Published

Connect Sails apps to Slipway with Lookout, Bridge support views, Wake analytics, release flags, and Bearing feedback.

Readme

sails-hook-slipway

The Sails hook that connects an application to Slipway.

It currently provides:

  • automatic request, exception, Waterline, Quest, and cache telemetry for Lookout;
  • app-scoped boolean release flags with deterministic rollouts;
  • app-owned Bearing feedback, roadmap, updates, and an optional widget; and
  • a secure app-local /bridge entry point for people who manage application data without receiving Slipway infrastructure access.

Installation

Requires Node.js 22 or newer.

npm install sails-hook-slipway

Slipway detects the hook and injects its deployment credentials automatically. Do not copy Bridge or telemetry credentials into config/slipway.js.

App-local Bridge

Enable Bridge for an app from App → Bridge access in Slipway, then redeploy the app. Slipway injects a dedicated, app-scoped exchange credential into that deployment.

People can enter Bridge through either URL without changing origin mid-session:

https://your-app.example.com/bridge
https://slipway.example.com/projects/<project>/environments/<environment>/apps/<app>/bridge

The public URL keeps navigation, search, actions, and assets under the app's own /bridge path. The Slipway URL is the operator entry point. If the app is mounted below a route prefix, that prefix comes before /bridge; a root app uses exactly /bridge.

The hook uses the app's existing authenticated user as the identity. Slipway only activates an invitation when the signed-in user's verified email exactly matches the invited email. The account may be created after the invitation is sent, but it must exist and verify that address before Bridge opens. Bridge users are not added to the Slipway team.

The default Boring Stack conventions work without app code:

  • model: User
  • session key: userId
  • email: email
  • name: fullName
  • verification: emailStatus is verified or confirmed

Persist that provider-neutral verification state when Wish, a redeemed magic link, or another authentication flow proves ownership of the exact email stored on the user. A provider that verifies a different candidate email must not confirm the current address. Opening /bridge must never call GitHub, Google, or another identity provider, and Bridge does not need stored OAuth access tokens.

Declarative identity mapping

For conventional model-backed sessions with different names, map the attributes instead:

module.exports.slipway = {
  bridge: {
    loginPath: '/login',
    identity: {
      model: 'member',
      sessionKey: 'memberId',
      emailAttribute: 'emailAddress',
      nameAttribute: 'name',
      emailVerifiedAttribute: 'hasVerifiedEmail'
    }
  }
}

Declarative host authorization

Bridge invitation roles are a ceiling. Narrow them with the host user's persisted role without an application authorization helper:

module.exports.slipway = {
  bridge: {
    authorization: {
      roleAttribute: 'role',
      roles: {
        admin: ['*'],
        editor: ['viewAny', 'view', 'create']
      },
      default: []
    },
    resources: {
      purchase: {
        authorization: {
          roles: {
            admin: ['viewAny', 'view'],
            editor: []
          }
        }
      }
    }
  }
}

Slipway resolves the host user by the stable ID established during the Bridge exchange and reads the configured role once per authorization pass. Unknown users, roles, actions, and invalid configuration fail closed.

Direct domain actions

An action can invoke an explicitly allowlisted application domain helper with validated fields as named inputs:

issueLicense: {
  scope: 'resource',
  helper: {
    identity: 'license.createLicense',
    inputs: 'values',
    result: {
      message: 'License issued for {{email}}. Copy this key now: {{key}}'
    }
  },
  fields: {
    email: { type: 'email', required: true },
    maxUses: { type: 'number', required: true, min: 1, max: 2 }
  }
}

Sails still validates the domain helper's declared inputs. The result template is rendered inside the target app and only the bounded message crosses into a one-time Bridge flash. Add context: ['actor', 'recordId'] only when the domain helper explicitly declares those inputs. The browser can never choose a helper identity.

Custom authentication escape hatch

Use an identity helper only when authentication is not model-backed:

// config/slipway.js
module.exports.slipway = {
  bridge: {
    loginPath: '/sign-in',
    identity: {
      helper: 'bridge.identity'
    }
  }
}
// api/helpers/bridge/identity.js
module.exports = {
  friendlyName: 'Resolve Bridge identity',

  inputs: {
    req: { type: 'ref', required: true }
  },

  exits: {
    success: { outputType: 'ref' }
  },

  fn: async function ({ req }) {
    const member = await Member.findOne({ id: req.session.memberId })
    if (!member) return null

    return {
      id: member.id,
      email: member.email,
      fullName: member.name,
      emailVerified: member.emailVerified === true
    }
  }
}

The helper must return emailVerified: true. Bridge fails closed when it cannot prove email verification.

Bearing

Bearing gives each deployed app feedback, roadmap, and update pages on the app's own domain. Enable it from App → Bearing, choose who may participate, then redeploy once so Slipway can inject the app-scoped exchange credential.

https://your-app.example.com/bearing/feedback
https://your-app.example.com/bearing/roadmap
https://your-app.example.com/bearing/updates

/bearing redirects to /bearing/feedback. Keeping every public surface below the Bearing namespace prevents Slipway from claiming app routes such as /feedback, /roadmap, or /updates.

When the widget is enabled, the hook adds one same-origin, asynchronous bootstrap script to successful HTML responses. It does not edit the app's templates, weaken its Content Security Policy, block a request on Slipway, or render anything while the control plane says the widget is off. The capability document is refreshed in the background with release flags.

The widget says What's new only when a published update is newer than the last update that visitor opened. Opening it stores that update's public ID as a local seen watermark. Its quiet lower-corner trigger opens a compact panel and gets out of the way while that panel is open. Escape, an outside click, or the panel's close control dismisses it and returns focus. The trigger stays hidden until another update is published. No published or unseen update means no injected UI is visible.

The host app can open the same panel on any surface from explicit actions in a menu or toolbar:

<button type="button" data-slipway-bearing-open="feedback">
  Share feedback
</button>
<button type="button" data-slipway-bearing-open="roadmap">Roadmap</button>
<button type="button" data-slipway-bearing-open="updates">What's new</button>

Bearing listens with event delegation, so buttons rendered after the bootstrap loads work too. Use feedback, roadmap, or updates as the requested surface. Unknown or disabled surfaces do nothing safely.

Ordinary links remain ordinary navigation and are never intercepted. This makes it safe to offer the full Bearing page elsewhere, such as in the app footer:

<a href="https://your-app.example.com/bearing/feedback">Feedback</a>

For a programmatic action, dispatch the equivalent event:

window.dispatchEvent(
  new CustomEvent('slipway:bearing:open', {
    detail: { surface: 'updates' }
  })
)

Set surface to feedback, roadmap, or updates. The requested surface is temporary widget state: it does not change the host app URL or persist in localStorage. Escape and an outside click close the panel even after the user moves into the embedded surface, host-page scrolling stays locked while it is open, and focus returns to the host control that invoked it.

Opening Feedback from the host app does not mark a product update as seen. The unread watermark changes only when the Updates surface is actually opened.

One host identity contract

Bearing resolves the same verified host-app identity as Bridge, but creates a customer participant—not a Slipway user or Bridge access grant. Configure the identity mapping once at slipway.identity; either feature may still override it for an unusual app. Existing slipway.bridge.identity configuration remains the backward-compatible fallback.

module.exports.slipway = {
  identity: {
    model: 'member',
    sessionKey: 'memberId',
    emailAttribute: 'emailAddress',
    nameAttribute: 'name',
    emailVerifiedAttribute: 'hasVerifiedEmail',
    loginPath: '/sign-in'
  }
}

For tenant-aware or external authentication, let the app compute the login URL instead of teaching Slipway whether the route is /login or /signin:

module.exports.slipway = {
  identity: {
    helper: 'slipway.identity',
    loginHelper: 'slipway.loginUrl'
  }
}

The login helper receives req, returnUrl, and feature, and returns a safe local URL containing whatever redirect state the app needs. A static loginPath remains the conventional fallback.

Security model

  • Bridge is disabled per app by default.
  • The exchange credential is encrypted by Slipway, scoped to one app, kept server-side, and separate from the Lookout telemetry token.
  • Enabling Bridge rotates the credential, so an old container cannot activate access before the required redeploy.
  • Invitations expire after seven days, store only a SHA-256 token hash, and activate atomically against one host-app identity.
  • Launch codes expire after two minutes and are consumed atomically once.
  • The handoff regenerates the session before adding Bridge authorization.
  • Disabling Bridge, revoking a grant, changing the app credential, or exceeding the eight-hour Bridge session lifetime invalidates access server-side.
  • Bridge roles (viewer, editor, and administrator) are a ceiling. The target app's configured resource authorization can only narrow that access.
  • Declarative host authorization loads the host user by its server-established ID, never by a client-supplied email.
  • OAuth provider tokens stay inside authentication and are not required by Bridge.

Lookout telemetry

When Lookout is configured by Slipway, the hook also sends a lightweight startup registration and a bounded heartbeat over the same authenticated telemetry endpoint. This lets Lookout distinguish a connected but quiet app from a missing or stale hook without keeping a socket open or retaining empty request spans. Slipway supplies the app and deployment identity automatically.

Slipway automatically injects the telemetry endpoint, scoped token, app ID, and deployment ID during deployment. Apps do not need to configure or expose those values themselves.

Optional settings live under lookout:

// config/slipway.js
module.exports.slipway = {
  lookout: {
    enabled: true,
    batchSize: 50,
    flushInterval: 10000,
    captureQueries: true,
    captureExceptions: true,
    captureQuestEvents: true,
    captureCache: true,
    slowQueryThreshold: 100
  }
}

Telemetry failures never break the host application.

With sails-hook-quest 0.0.5 or newer, failed job output continues streaming to the application log while its final bounded diagnostic is attached to the Lookout exception. The Slipway hook removes terminal formatting and redacts known secret values before telemetry leaves the application. Older Quest payloads remain supported and use their runner stack when one is available.

Resident Quest workspace (0.0.12)

The new Quest workspace connects to the application's running Sails process to inspect source jobs, review typed inputs, run a registered job, and pause or resume its scheduler. Results and logs are shown separately. Scripts and config/quest.js remain the source of truth for inputs and schedules.

This requires sails-hook-quest 0.0.6 or newer and sails-hook-slipway 0.0.12 or newer. Quest 0.0.5 and Slipway hook 0.0.11 do not include this contract. Older hooks retain bounded history in the dashboard, with live state and resident controls marked unavailable. The Quest upgrade guide tracks the compatible pair and required verification; a version string alone never enables unsupported controls.

Quest 0.0.6 currently needs the Sails ORM hook enabled. Database-free apps that exclude ORM can fail to finish startup; this is tracked in Quest issue #16.

After upgrading the two hooks and reviewing the application's registered jobs, explicitly enable the integration and deploy through the normal app workflow:

// config/slipway.js
module.exports.slipway = {
  quest: { enabled: true }
}

The setting defaults to false. Slipway verifies the app/deployment identity and the resident's capabilities before enabling controls. On Linux, the connection uses an app-scoped private Unix socket; it adds no public HTTP listener or new credential. Worker apps are supported. Multiple matching resident processes make controls unavailable because Slipway cannot choose a scheduler safely.

Pause prevents new runs in the current process and leaves active work running. It resets on app restart; change source configuration for a persistent schedule change. Closing or reconnecting the dashboard does not execute a job again. Use Run again to review and submit a deliberate new invocation. Completed means the child process exited successfully; a named Sails exit or business result can still describe an application-level failure.

Run history is bounded to seven days. Logs load on demand from retained tails; there is no live log replay, cancellation, durable delivery queue, automatic retry, or distributed overlap guarantee. Telemetry is best effort. A verified resident can reconcile a receipt it still holds after a connection loss, but a resident restart or receipt eviction can leave the outcome unconfirmed.

Release flags

Slipway injects the private flag endpoint and app identity during deployments and rollbacks. Evaluate a flag with an explicit safe default:

const enabled = await sails.helpers.flags.enabled.with({
  key: 'new-checkout',
  req: this.req,
  defaultValue: false
})

For a background job, pass a stable context containing a user, account, tenant, or team identifier. Configuration is cached and refreshed in the background; an unavailable control plane never fails or delays the host-app request. flags.enabled is a regular Sails helper machine, so Sails validates its declared inputs before evaluation. An app-defined helper at the same identity is preserved. See the release flag guide for rollout behavior and Lookout comparisons.

Wake and read-only Bridge support views

Version 0.0.10 adds the full Wake collection/goals/revenue runtime and opt-in read-only support sessions. Both capabilities remain disabled until explicitly configured. See Wake setup, privacy, revenue replay and retention and Bridge support configuration and safety boundaries. Custom session mappings are inherited from slipway.identity; Wake also supports anonymous sessionless apps.

Quest reconnect and control (0.0.13)

This release integrates Quest 0.0.8 opt-in live logs and verified cancellation, plus bounded private receipt persistence. Node 22+ and Sails ^1.5.0 are required. Upgrade both hooks, retain existing telemetry configuration and explicitly configure the Linux controls and private persistent receipt directory. See CHANGELOG.md for the complete app upgrade instructions, bounds and uncertain-outcome behavior.