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 🙏

© 2024 – Pkg Stats / Ryan Hefner

hypermedia-transitions

v1.3.1

Published

Module to specify state transitions and transform them into hypermedia format

Downloads

19

Readme

hypermedia-transitions

npm version CircleCI Coverage Status Dependency Status

This module is designed to help you to add hypermedia support for you API.

Features

This module consists of :

  • a fixed data structure to store your API state transitions (more details in the next section)
  • parsers for many different hypermedia-compliant media types exploiting this data structure to build the response.

Use as Express middleware

In your Express file, you need to add a couple lines :

  • setup all your state transitions using the "addTransition" function
  • register the interceptor in your middlewares.

All set ! If no header is specified, your API responses won't change. But if a client requires a particular format using the "Accept" header, and if you chose to support it, he will receive it in the response !

Example :


//
// state_transitions.js 
//

const list_transitions = [
  {
    rel: "resource_list", 
    target: "resource list",
    accessibleFrom: [{ state: "home" }],
    href: "/resources",
    method: "get"
  },
  {
    rel: "resource", 
    target: "resource",
    accessibleFrom: [{ state: "resource list" }],
    href: "/resources/{id}",
    isUrlTemplate: true,
    method: "get"
  },
  {
    rel: "resource_delete", 
    target: "resource",
    accessibleFrom: [{ state: "resource list" }],
    href: "/resources/{id}",
    isUrlTemplate: true,
    method: "delete",
    authRequired: true
  }
]

exports.listTr = { list_transitions }

//
// app.js
//

const express = require('express')
const transitions = require('hypermedia-transitions')
const listTr  = require('./state_transitions').listTr

var app = express()

// add the middleware that need to be set early 

transitions.addTransitionList(listTr)

app.use(transitions.halInterceptor)

// define your routes, your error handlers, and start your server. 

Use as HAPI plugin


const hypermediaOptions = {
  mediaTypes: ['hal', 'siren'],
  transitions: require('../list_transitions.json')
}

server.register([{
    register: require('../hypermedia-transitions').hapiRegister,
    options: hypermediaOptions
  }, {
// ...
}
], function (err) {
    if (err) { return console.log(err); }

    server.route(require('./routes'));

    server.start(function () {
      console.log('API up and running at:', server.info.uri);
    });
});

Data structure

The state transitions you define should be objects defining the following properties :

  • rel : the name for your transition

Warning : in some formats, the "rel" attribute becomes a Javascript object key. Hence, using characters such as "." or "-" can cause an error.

  • target : the target state of your transition
  • accessibleFrom : a list of objects describing how your transition can be triggered (more details in next section)
  • href : the URL to trigger your transition (relative from domain name)
  • isUrlTemplate : whether the URL written in the previous field can be used "as is" or is a URL template that needs to be filled in
  • method : the HTTP method to trigger your transition
  • authRequired : whether or not the client needs to be authenticated to be able to trigger that transition
  • template : a template for what kind of data should be sent (POST or PUT methods for example)

Example :

{
  rel: "update_task",
  target: "task",
  accessibleFrom: [
    { state: "home" }, 
    { state: "task list" },
    { state: "task", fillTemplateWith: {task_id: "id"} }
  ],
  href: "/tasks/{task_id}",
  isUrlTemplate: true,
  method: "post",
  authRequired: false,
  template: {
    name: "string",
    completed: "bool",
    description: "string"
  }
}

// "id" here is a data element that is included in your API response when displaying a task resource

Objects in "accessibleFrom" list

They can have the following properties:

  • state : the state from which it can be triggered (required)
  • fillTemplateWith : a dictionnary describing how to fill the URL template, when the transition is available from this state, using data included in your response. Must be formatted this way :
{ url_template_parameter: "data_corresponding_parameter" }
  • eachItem : set to true if your data is a list of elements and the URL template must be filled with a different value for each element. Example :
{
// ...
  accessibleFrom: [{state: "resource list", fillTemplateWith: {id: "id"}, eachItem: true}]
// ...
}

// Will result in (HAL for example) : 

[
  { 
    // ...
    _links: {
      resource: {
        href: "http://example.org/resources/1"
      }
    }
  },
  { 
    // ...
    _links: {
      resource: {
        href: "http://example.org/resources/2"
      }
    }
  }
]
  
  • withSelfRel : You can specify this attribute if you want a link to be the "self" relationship, in embedded resources or not. Example :
{
// ...
  accessibleFrom: [{state: "list resources", fillTemplateWith: {id: "id"}, eachItem: true, withSelfRel: true}]
// ...
}

// Will result in (HAL for example) : 

[
  { 
    // ...
    _links: {
      self: {
        href: "http://example.org/resources/1"
      }
    }
  },
  { 
    // ...
    _links: {
      self: {
        href: "http://example.org/resources/2"
      }
    }
  }
]
  

"state" and "target"

The "state" property from the "accessibleFrom" objects and the "target" property from the transition work together. When a transition is triggered, your "state" become its "target", and this state is used to figure out which other transition are available. I advise you to write down a graph representing your API state and transitions to be sure not to have forgotten any. Your state names will most of the times consist in resources names. For naming consistency, put the resource name in first position and before a space or a "_" in the "state" string since it is used in parsing for naming lists. Example in HAL :

// Current state : "task_list"
// Original API data :
[ 
  //... some objects ... 
]

// Becomes in HAL : 
{ _embedded: {
  task: [
    // objects...
    ]
  }
}

Authentication

To be able to display results that are only visible by authenticated users, you will have to add a req.isAuth property. If it is not set or false, the translators will consider that the user is not authenticated.

Warning : for this to work you must verify if your user is authenticated for EACH request and a client that requires authentication for only some actions MUST authenticate for each request. Otherwise the transitions that are "authentication-protected" won't be visible.

Featured media types