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

wamjs

v0.3.8

Published

Wam is a koa and next.js inspired middleware for node

Downloads

17

Readme

wam.js

npm licence dependencies

Wam is a small koa and next.js inspired middleware framework for node the following block of code is a quick run down on what Wam supports out of the box, following that i will try to explain the details of it all.

const Wam = require('wamjs');

const app = new Wam();

// on the fly(as opposed to cached) compression i.e json requests
app.use(app.compress());

// serve static files, cached compression
app.use(app.static('examples/public'));

// component architecture v1 (directory)
app.use(app.components('examples/views', {optDependency: 1}));

// component architecture v2 (custom mappings)
app.use(app.components({'examples/views/index.js': 'index'}, {optDependency: 1}));

// middleware
app.use((context, next) => {});

// route for GET methods on /user/:id
app.use('/user/:id', 'get', (context, next) => { context.params.id === ':id' });

// route for ALL methods on /user/:id
app.use('/user/:id', (context, next) => { context.params.id === ':id' });

// start server
app.listen(3000);

Application

When you execute new Wam() a new application is created. This application object is the main point of entry for communicating with Wam api's. The following is the shape of the object returned by new Wam();

{
	use, listen, callback, create, error, static, 
	compress, router, components, statuses, mimes
}

Use

app.use is used to push a middleware to the stack where context is the object exposing the request and response objects and next is a function that is used to jump to the next middlware in the chain.

app.use(function (context, next) {
	// we will cover in full detail what the context object contains
	// but if you have used Koa before this should be all to familiar
	context.response.body === context.body === this.body;
	context.request.url = context.url === this.url;

	// where req and res represent the default 
	// request and response stream object from node
	this.req === context.req === context.response.req;
	this.res === context.res === context.response.res;

	// if there is another middlware this will execute that
	// if this is the last middlware the response will end
	next();

	// anything after next() call will execute 
	// after all middlwares in the chain
	// have resolved but before the response has been sent

	console.log('hello world');
});

Since routes are just middlwares the app.use function doubles as a route register when the first argument is a String/RegExp

app.use('/user/:name', function (ctx, next) {
	// where the .params property hold the variables
	// found in the url from the defined route
	ctx.params.name;
});

As you may have notices a method has not been specified for the request. When left blank the method resolves to handling all methods of that route but you could also be explicit about it and do the following

app.use('/user/:name', 'ALL' function (ctx, next) {
	// where `ALL` could be `GET` or `POST` or any method
	// or event an array ['get', 'post'] with 
	// just the methods the middlware should handle
});

If you wanted to be very explicit with routes you could also do the following, which is how Wam handles this under the hood.

app.use(app.route('/user/:name', 'ALL', function (ctx, next) {
	// ...
}))

Listen

Creates a server and starts listening for requests on the port 3000

app.listen(3000);

// this is just a short hand of what is internally being handled as
http.createServer(app.callback()).listen(3000);

Static

Creates a middleware that handles requests for static files

// the following will do a couple of things
app.use(app.static('examples/public'));

First it will resolve to the folder and create a tree of all the assets that residing, in the process this will also gzip any compressible assets using file type and file size as an indicator. The results of the above are cached - paths, file stats, and gzip payloads.

When a request is received the middleware will try to determine if the request is for a static asset, if it is and the resource is available, and the body has not been set it will serve the compressed asset as a stream and assign all the necessary headers.

Note that following this flow successfully will result in middlewares after the Static to be bypassed when a static file is request, found and served.

When not in a production enviroment the static files are watched for changes and updated as needed.

Components

Creates a middleware for all the components/files found in the specified directory and passing any dependencies if any the returned function is called like any other middleware when the components route is matched.

// index.js
app.use(app.components('examples/views', {id: 1}));

// examples/views/index.js
module.exports = function ({id}) {
	return function () {
		this.body = id;
	}
}

// when a request is made to `/` the function
// returned in `examples/views/index.js` will handle it.
// the step of calling next is handled internally
// if next is not specified as an argument of the middlware.

You could also be more specific and pass an object of the files - to - route matches to be used, for example...

app.use(app.components({'examples/views': '/'}, {id: 1}));

Compress

Creates a middleware that provides on the fly(non cached) compression for example for json requests. If you wanted to serve static content use Static instead.

app.use(app.compress());

Context

The context object and next function are the two arguments passed to every middlware function. We will start with context and what you can do with it, though if you've used Koa before you are probably familiar with this.

{
	response: {
		// methods
		is:  (types),
		get: (name),
		set: (name, value),
		remove: (name),

		// getters and setters
		type: {(getter|setter)},
		body: {(getter|setter)},
		length: {(getter|setter)},
		message: {(getter|setter)},
		status: {(getter|setter)},
		header: {(getter)},
		lastModified: {(getter|setter)},
		socket: {getter},
		writable: {getter},
		headerSent: {getter},
		etag: {(getter|setter)},
	},

	request: {
		// methods
		is: is(types),
		get: get(name),

	 	// getters and setters
		header: {getter},
		url: {(getter|setter)},
		method: {(getter|setter)},
		path: {(getter|setter)},
		query: {(getter|setter)},
		querystring: {(getter|setter)},
		search: {(getter|setter)},
		origin: {getter},
		href: {getter},
		ext: {getter},
		socket: {getter},
		length: {getter},
		protocol: {getter},
		secure: {getter},
		type: {getter},
		host: {getter},
		ips: {getter},
	},

	// aliased to response 
	remove, set, status, message, body, length, type, 
	lastModified, etag, headerSent, writable

	// aliased to request
	accepts, get, is, querystring, socket, search, method, query, path,
	url, origin, href, protocol, host, hostname, header, secure, ext, ips
}

Next

The next function is a central piece that connects middlewares together, though unlike Koa the next function accepts an optional argument that is used internally but one that you could make use of when needed.

app.use((ctx, next) => {
	// 1. if i pass 0 to next(0)
	// this will immediatly bypass all middelwares 
	// after this middleware and send the response	next(0);
});

app.use((ctx, next) => {
	// 2. if i pass 1 to next(1)
	// this will do the above plus opt out of the built
	// in response handling
	next(1);
});

app.use((ctx) => {
	// 3. if i don't define the next argument
	// next() will automatically run at the end of this function
});

Under The Hood

  1. Wam checks if a stream has a _type property to determine and set the response type if it has one.

  2. stores caches for the .static middlware in the directory provided within a .cache folder. This cache is updated as needed i.e if an asset is removed its gzipped resource is also removed, though this syncing is only in place when not in production mode.

  3. If your filesystem supports the / characters in filenames then you could potentially create a routing solution with just the app.components() middleware.

Intergration

Wam can be used as a general solution with the above listed api's but really shines when coupled with Dio.js and the app.components() api, in part inspired by next.js.

Note that the below will also work when used with any other vdom library making use of something like renderToString/renderToStream meaning you can do the exact following with React, Preact and Inferno minus the renderToCache, and stylesheet.

// views/index.js
module.exports = function ({Component, renderToStream, h, renderToCache}) {
	class Head extends Component {
		render () {
			return h('head',
				h('title', 'Hello World'),
				h('link', {rel: 'stylesheet', href: 'style.css'})
			)
		}
	}

	class Body extends Component {
		render () {
			return h('body', Hello, Button, Button);
		}
	}

	class Button extends Component {
		stylesheet () {
			return `
				{
					color: black;
					border: 1px solid red;
					padding: 10px;
				}
			`
		}
		render () {
			return h('button', 'Click Me');
		}
	}

	class Page extends Component {
		render () {
			return h('html', 
				Head,
				Body
			)
		}
	}
	
	renderToCache([Head]);

	return (ctx) => ctx.body = renderToStream(Page);
}

// index.js
const app = new require('wamjs')();

app.use(app.static('public/'));
app.use(app.components('views/', require('dio.js')));