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

@muze-nl/metro

v0.3.3

Published

http client with middleware support

Downloads

16

Readme

MetroJS: HTTPS Client with middleware

Project stage: Experimental

import * as metro from '@muze-nl/metro'

const client = metro.client({
  url: 'https://github.com/'
}).with((req,next) => {
  req = req.with({
    headers: {
      'Content-Type':'application/json',
      'Accept':'application/json'
    }
  })
  if (typeof req.body == 'object') {
    req = req.with({
      body: JSON.stringify(req.body)
    })
  }
  let res = await next(req)
  let body = await res.json()
  return res.with({ body })
})

MetroJS is an HTTPS client with support for middleware. Similar to ExpressJS, but for the client.

You add middleware with the with() function, as shown above.

The signature for a middleware function is:

async (request, next) => {
   // alter request
   let response = await next(request)
   // alter response
   return response
}

However, both request and response are immutable. You can not change them. You can however create a copy with some values different, using the with() function.

Both metro.request() and metro.response() are compatible with the normal Request and Response objects, used by the Fetch API. Any code that works with those, will work with the request and response objects in MetroJS.

Install / Usage

npm install @muze-nl/metro

In the browser, using a cdn:

<script src="https://cdn.jsdelivr.net/npm/@muze-nl/[email protected]/dist/browser.js"></script>
<script>
  async function main() {
    const client = metro.client('https://example.com/')
    const result = await client.get('folder/page.html')
  }
  main()
</script>

Using ES6 modules, in the browser or Node:

import * as metro from '@muze-nl/metro'

async function main() {
  const client = metro.client('https://example.com/')
  const result = await client.get('folder/page.html')
}

Using middleware

A middleware is a function with (request, next) as parameters, returning a response. Both request and response adhere to the Fetch API Request and Response standard.

next is a function that takes a request and returns a Promise<Response>. This function is defined by MetroJS and automatically passed to your middleware function. The idea is that your middleware function can change the request and pass it on to the next middleware or the actual fetch() call, then intercept the response and change that and return it:

async function myMiddleware(req,next) {
  req = req.with('?foo=bar')
  let res = await next(req)
  if (res.ok) {
    res = res.with({headers:{'X-Foo':'bar'}})
  }
  return res
}

Both request and response have a with function. This allows you to create a new request or response, from the existing one, with one or more options added or changed. The original request or response is not changed.

Debugging

Middleware is powerful, but can also be difficult to debug. For this reason MetroJS adds a trace feature. This allows you to add a request and response tracer function, which is called before and after each middleware call:

const client = metro.client()
metro.trace.add('mytracer', {
  request: (req) => {
    console.log('request',req)
  },
  response: (res) => {
    console.log('response',res)
  }
})

There is a default trace function that shows the call request/response in a nested fashion:

metro.trace.add('group', metro.trace.group())

Creating middleware

You can just create a async function with (req,next) => res as its signature. But often it is important to be able to set options specific for that middleware. The best way to do this is to create a module like so:

export default function myMiddleware(options)
{
  return async (req,next) => {
    // alter request, using options
    let res = await next(req)
    // alter response, using options
    return res
  }
}

See for example the jsonmw middleware.

metro.assert

For more complex middleware code, it can be very helpful to check any number of preconditions and give helpful error messages to developers. However, in production such code only slows down the experience, and the error messages don't mean anything to normal users. For this use MetroJS includes a simple assert module. This allows you to add assertions, which only get checked if it is enabled. Something that a developer can decide to do while developing code using your middleware.

To use this in your middleware code, do this:

import * as metro from '@muze-nl/metro'
import * as assert from '@muze-nl/metro/src/assert.mjs'

export default function myMiddleware(options) {

  assert.check(options, {
    'foo':'bar',
    'bar':assert.optional(assert.oneOf('bar','baz')),
    'baz':/b.+/
  })

  return async (req,next) => {

    assert.check(req.headers, {
      'X-Foo':'bar'
    }

    return await next(req)
  }
}

A developer may now enable assertion checking by calling assert.enable():

import * as metro from '@muze-nl/metro'
import * as assert from '@muze-nl/metro/src/assert'

async function main() {
  const client = metro.client(myMiddleware(options))
  assert.enable()
  let result = await client.get('foo/')
}

If any assertion fails, it will throw an Error with a list of assertions that failed.