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

@lion/ajax

v2.1.0

Published

Thin wrapper around fetch with support for interceptors.

Downloads

105

Readme

Tools >> Ajax >> Overview ||10

Ajax is a small wrapper around fetch which:

  • Allows globally registering request and response interceptors
  • Throws on 4xx and 5xx status codes
  • Supports caching, so a request can be prevented from reaching to network, by returning the cached response.
  • Supports JSON with ajax.fetchJson by automatically serializing request body and deserializing response payload as JSON, and adding the correct Content-Type and Accept headers.
  • Adds accept-language header to requests based on application language
  • Adds XSRF header to request if the cookie is present and the request is for a mutable action (POST/PUT/PATCH/DELETE) and if the origin is the same as current origin or the request origin is in the xsrfTrustedOrigins list.

Installation

npm i --save @lion/ajax

Relation to fetch

Ajax delegates all requests to fetch. ajax.fetch and ajax.fetchJson have the same function signature as window.fetch, you can use any online resource to learn more about fetch. MDN is a great start.

ajax.fetch

The fetch method of ajax is a very small wrapper around native window.fetch and returns a native Response object, the main differences with native window.fetch are:

  • it will use any caching options that you've configured in the Ajax class
  • it will throw on response statuses between 400 and 600 (native fetch doesn't throw)
  • it will run any interceptors that you've configured
  • it will add a XSRF header to the request if the XSRF cookie is present

Otherwise, you can expect the same usage as from window.fetch. Here are some simple examples:

// A simple GET request
const response = await ajax.fetch('/api/foo');
const data = await response.json(); // or .text(), .clone(), .formData(), etc
// A simple POST request
const response = await ajax.fetch('/api/foo', {
  method: 'POST',
  body: JSON.stringify({ foo: 'bar' }),
});

ajax.fetchJson

The fetchJson method of ajax has some additional features, added for convenience and ease of use. For example, the fetchJson method:

  • adds the accept header with a value of application/json
  • adds the content-type header with a value of application/json, if a request body is provided
  • automatically JSON.stringifies the request body, if one is provided
  • will attempt to parse the response body as JSON if available
    • and also automatically remove a JSON prefix from the response body if one is configured

Note that instead of returning only a Response, fetchJson returns an object containing the Response and a JSON.parse'd body

// A simple GET request
const { response, body } = await ajax.fetchJson('/api/foo');
// body.foo === 'bar';
// A simple POST request
const { response, body } = await ajax.fetchJson('/api/foo', {
  method: 'POST',
  body: { foo: 'bar' },
});

Interceptors

Interceptors are functions that can be used to inspect or modify the Request or Response objects of a network request.

Request interceptors

A request interceptor is a function that takes a Request object, and returns a Request object, or a Response object, and runs before the native window.fetch call is done, allowing you to modify or inspect a request before it's made.

If you return a Response object, the response will be returned by the fetch or fetchJson methods, instead of passing the Request to the native window.fetch function.

Returning a Request:

function addAcceptLanguage(request) {
  request.headers.set('accept-language', 'EN_GB');
  return request;
}

ajax.addRequestInterceptor(addAcceptLanguage);

Returning a Response:

function interceptFooRequest(request) {
  if (request.headers.get('foo')) {
    return Response.json({ foo: 'bar' });
  }

  return request;
}

ajax.addRequestInterceptor(interceptFooRequest);

Request interceptors can be async and will be awaited.

Response interceptors

A response interceptor is a function that takes a Response object, and returns a Response object, and runs after the native window.fetch call is done, allowing you to modify or inspect the response before it's returned by fetch/fetchJson.

async function rewriteFoo(response) {
  const body = await response.clone().text();

  return new Response(body.replaceAll('foo', 'bar'), response);
}

ajax.addResponseInterceptor(rewriteFoo);

Response interceptors can be async and will be awaited.

Response JSON object interceptors

A response JSON object interceptor is a function that takes a successfully parsed response JSON object and response object and returns a new response JSON object. It's used only when the request is made with the fetchJson method, providing a convenience API to directly modify or inspect the parsed JSON without the need to parse it and handle errors manually.

async function interceptJson(jsonObject, response) {
  if (response.url === '/my-api') {
    return {
      ...jsonObject,
      changed: true,
    };
  }
  return jsonObject;
}

ajax.addResponseJsonInterceptor(interceptJson);

Response JSON object interceptors can be async and will be awaited.

Ajax class options

| Property | Type | Default Value | Description | | -------------------------------- | -------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | addAcceptLanguage | boolean | true | Whether to add the Accept-Language header from the data-localize-lang document property | | addCaching | boolean | false | Whether to add the cache interceptor and start storing responses in the cache, even if cacheOptions.useCache is false | | xsrfCookieName | string | "XSRF-TOKEN" | The name for the Cross Site Request Forgery cookie | | xsrfHeaderName | string | "X-XSRF-TOKEN" | The name for the Cross Site Request Forgery header | | xsrfTrustedOrigins | string[] | [] | List of trusted origins, the XSRF header will also be added if the origin is in this list. | | jsonPrefix | string | "" | The prefix to add to add to responses for the .fetchJson functions | | cacheOptions.useCache | boolean | false | Whether to use the default cache interceptors to cache requests | | cacheOptions.getCacheIdentifier | function | a function returning the string _default. | A function to determine the cache that should be used for each request; used to make sure responses for one session are not used in the next. Can be async. | | cacheOptions.methods | string[] | ["get"] | The HTTP methods to cache reponses for. Any other method will invalidate the cache for this request, see "Invalidating cache", below | | cacheOptions.maxAge | number | 360000 | The time to keep a response in the cache before invalidating it automatically | | cacheOptions.invalidateUrls | string[] | undefined | Urls to invalidate each time a method not in cacheOptions.methods is encountered, see "Invalidating cache", below | | cacheOptions.invalidateUrlsRegex | regex | undefined | Regular expression matching urls to invalidate each time a method not in cacheOptions.methods is encountered, see "Invalidating cache", below | | cacheOptions.requestIdFunction | function | a function returning the base url and serialized search parameters | Function to determine what defines a unique URL | | cacheOptions.contentTypes | string[] | undefined | Whitelist of content types that will be stored to or retrieved from the cache | | cacheOptions.maxResponseSize | number | undefined | The maximum response size in bytes that will be stored to or retrieved from the cache | | cacheOptions.maxCacheSize | number | undefined | The maxiumum total size in bytes of the cache; when the cache gets larger it is truncated |

Caching

import { ajax, createCacheInterceptors } from '@lion/ajax';

// Note: getCacheIdentifier can be async
const getCacheIdentifier = () => {
  let userId = localStorage.getItem('lion-ajax-cache-demo-user-id');
  if (!userId) {
    localStorage.setItem('lion-ajax-cache-demo-user-id', '1');
    userId = '1';
  }
  return userId;
};

const TEN_MINUTES = 1000 * 60 * 10; // in milliseconds

const cacheOptions = {
  useCache: true,
  maxAge: TEN_MINUTES,
};

const [cacheRequestInterceptor, cacheResponseInterceptor] = createCacheInterceptors(
  getCacheIdentifier,
  cacheOptions,
);

ajax.addRequestInterceptor(cacheRequestInterceptor);
ajax.addResponseInterceptor(cacheResponseInterceptor);

Or use a custom cache object and add the cache config to the constructor:

import { Ajax } from '@lion/ajax';

const storeButDontRetrieveByDefaultConfig = {
  addCaching: true,
  cacheOptions: {
    getCacheIdentifier,
    useCache: false,
    maxAge: TEN_MINUTES,
  },
};

const customAjax = new Ajax(storeButDontRetrieveByDefaultConfig);

Invalidating the cache

Invalidating the cache, or cache busting, can be done in multiple ways:

  • Going past the maxAge of the cache object
  • Changing cache identifier (e.g. user session or active profile changes)
  • Doing a non GET request to the cached endpoint
    • Invalidates the cache of that endpoint
    • Invalidates the cache of all other endpoints matching invalidatesUrls and invalidateUrlsRegex

Restricting what to cache

The library has a number of options available to restrict what should be cached. They include:

By content type

cacheOptions.contentTypes

If this option is set, it is interpreted as a whitelist for which content types to cache. The content types of a given response is derived from its Content-Type header. If this option is set, responses that do not have a Content-Type header are never added to or retrieved from the cache.

By response size

cacheOptions.maxResponseSize

This option sets a maximum size (in bytes) for a single response to be cached. The size of the response is determined first by looking at the Content-Length header; if this header is not available, the response is inspected (through the blob() function) and its size retrieved.

Limiting the cache size

cacheOptions.maxCacheSize

This option sets a maximum size (in bytes) for the whole cache. The size of a response is determined first by looking at the Content-Length header; if this header is not available, the response is inspected (through the blob() function) and its size retrieved.

If the cache grows larger than the maxCacheSize option, the cache is truncated according to a First-In-First-Out (FIFO) algorithm that simply removes the oldest entries until the cache is smaller than options.maxCacheSize.