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

@orniz/simple-hal-client

v2.1.0

Published

Simple HAL(-FORMS) client

Downloads

15

Readme

Simple HAL client

This is a simple implementation of HAL-FORMS client. It is mostly tested against Spring HATEOS.

Basic usage

Simply create a client and start fetching resources from your remote backend. The starting point of every interaction is fetch, which you should use to get the root of the API, supposed to provide links to every part of it.

const client = new SimpleHalClient('http://my-server.example/api');
const response = await client.fetch();

Response can be used as a regular fetch response. It just provides an additional hal() method whose goal is to parse the returned document as HAL(-FORMS). If the document cannot be parsed, the method will throw (as well as if the returned response is not valid json). Otherwise, you get back an HalFormsResource instance allowing you to navigate through links and relationships.

Accessing to resource properties

Except for _links, _embedded and _templates, all properties of the resources are accessible through the property data of an HalFormsResource. The class is parametrized to describe the type of the data. e.g.:

interface People {
	lastName: string;
	firstName: string;
}

…

const resource: HalFormsResource<People> = await response.hal();
const people = resource.data; // people: People

Navigating links

One of the benefits of using HAL is to attach links to resources (through the _links property of each HAL resource. Using SimpleHalClient, fetching and following a link is as simple as:

let resource: HalFormsResource<any>;
…

const ancestorLink: HalLink = resource.link('ancestor')!;
const ancestorResource = await ancestorLink.follow('ancestor');

const childrenLinks: HalLink[] = resource.links('children')!;
const childrenResource = childrenLinks.map(l => l.follow());

Links can be mono or multivalued. Trying to get multivalued link through link will throw and so will trying to get a monovalued link through links.

You can fetch all links for a given resource using links() (without argument). The relationship between the link and its resource is then accessible through the rel property.

Using embedded

Another feature of HAL is to embedded resources in an enclosing resource. The goal is, among others, to prevent multiple fetches or to give a consistent view of a resource (think about database transaction). Each embedded element can be the canonical representation of the embedded resource, a partial representation or something having nothing to do with this canonical representation. If the embedded resource has a link with self key, you should be able to fetch its canonical representation by following it.

Getting an embedded resource is as simple as:

let resource: HalFormsResource<any>;
…

const ancestorResource = resource.embedded('ancestor')!;
const childrenResource = resource.embeddeds('children')!;

Note that, as for the links, attempting to get a multivalued embedded with embedded will throw and so will fetching a monovalued embedded through embedded.

An embedded resource is a full HAL-FORMS resource, it can in turn have links, embedded and templates.

Using templates

Templates is an HAL-FORMS feature. They describe actions that can be performed on a resource. Each template has an identifying name and may describe the needed properties to send in the request.

Using templates with this lib looks like:

let resource: HalFormsResource<any>;
…

const addChildTemplate = resource.template('addChild')!;
const childResource = addChildTemplate.invoke(JSON.stringify({ name: 'Foo', age: 28 }));

The spec does not specify (sic) whether templates names are unique. This library assumes they are.

Filters

You can add filters to the client, in order to pre-process request or post-process response. This can be useful for e.g. authentication handling (if server answer 401, you may want to renew an access token or redirect user to login) or object creation.

Here is an example of filter that turns 204 (no content) responses into the resource pointed by the returned location header (you can test it with Spring HATEOAS examples:

const client = new SimpleHalClient('https://simple-hal-client.example/employees');
this.client.appendFilter(async (params: FilterParams) => {
  const response = await params.next(params.request);
  if(response.status === 204 && response.headers.has('location')) {
    const body: ReadableStream = response.body as ReadableStream;
    return params.client.fetch(response.headers.get('location')!);
  }
  return response;
});

…


const response = await client.fetch();
const employees = await response.hal();
const template = employees.template('default')!;
const newEmployeeResource = await template.invoke(JSON.stringify({lastName: 'Foo'})).hal();