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

danmaru

v1.3.1

Published

No dependency, convenience HTTTP/HTTPS server wrapper

Downloads

19

Readme

danmaru

No dependency, convenience HTTTP/HTTPS server wrapper in TypeScript. For complete TypeScript code see https://github.com/sdrsdr/danmaru

How to use


import * as http  from 'http';
import {compose,log_all,codes} from 'danmaru';
const log=log_all();

const server= new http.Server();

compose(
	server,[
		{prefix:"/hello_json?", do: (req,resp)=>{
			let who=req.full_url.searchParams.get('who')??"<who param not found in searchParams>";
			log.mark("Hello "+who+" of JSON!");
			resp.json_response(codes.OK,{say:'Hello '+who+' of JSON',method:req.method});
		}}
	],{log}
);

server.listen(1234,()=>{
    log.mark("danmaru HTTP server started at port 1234");
});

compose

function compose(
	server:http_Server|https_Server, 
	http_actions:http_action_t[], 
	options?:options_t
):boolean ;

Hookup server to handle requests via http_actions various options like log functions, global maximal body size and global method filters goes in options

http_action_t

interface http_action_t {
	prefix:string;
	do:http_action_cb;
	m?:string[]; //allowed methods
	max_body_size?:number; //if not set max_body_size from options or MAX_BODY_SIZE will be enforced
	exact_match?:boolean; //default false; if true prefix must exact-match
	error_catcher?:error_catcher_cb; //catch failing requests
}

Array of this interface goes in compose to describe the urls handled by the server.

  • prefix is the start of the url to match. You can have same prefix in the compose array multiple time with different allowed methods and different do. The match lookup folows array order of compose http_actions param. First match handles the request completely.
  • do is the callback that will genrate the responce
  • m Is optional string array that explicityl allows only mentioned HTTP methods. If this is not set the method filter from compose's optins kick in. It is possible to have multiple http_action with same prefix but different m filters and different do
  • max_body_size is optional limiter for http resuest body size once the request matches. You can set global limit in compose's options. There is some hard coded limit if none is set explicityl.
  • exact_match is assumed false if missing. This changes how the incoming request url is matched against http_action. It is possible to have multiple http_action with same prefix but different exact_match and different do
  • error_catcher a callback that will receive all request about to be rejected by the library.

options_t

interface options_t {
	log?:logger_t; 
	indexer?:http_action_cb; //default handler; if not set a 404 is send back
	auto_headers?:OutgoingHttpHeaders; //this headers are preset for sending for each response 
	auto_handle_OPTIONS?:boolean; //default is false. Just do resp.simple_response(200) and return for all non 404 urls and OPTIONS method (passing the content of auto_headers to the browser)
	max_body_size?:number; //in characters if not set MAX_BODY_SIZE will be enforced
	allowed_methods?:string[]; // default is no-filtering;
	catch_to_500?:boolean; //catch exceptions in http_action_t.do, log in err, respond with error code 500 (if possible)
	error_catcher?:error_catcher_cb; //catch failing requests
}

options for compose:

  • log log4js inspired interface to logging facility to be used in the server. Some helper functions are provided: function log_all():logger_t and function log_none():logger_t to help with faster setup.
  • indexer is a url handler for unmatched requests. It can do custom 404 pages or index direcotories somehow. It's all up to you. The helper function http_action_gone is provided that just returns cacheable 410 GONE responses.
  • auto_headers headers specified here will be automatically send with every response. Primary target is ...auto_headers:{"Access-Control-Allow-Origin":"*"}, ...
  • auto_handle_OPTIONS is of by default. If set to true it allows for unmatched requests with OPTIONS method to be automatically replied with 200 OK plus the auto_headers to allow some browsers to check for CORS headers.
  • max_body_size global request body size limiter. if not set MAX_BODY_SIZE will be enforced.
  • allowed_methods states what HTTP methods your server will handle. If you want to handle only GET and/or POST request you can state so here and keep http_action param of compose cleaner or you can intermix all HTTP method filtering to you like
  • catch_to_500 makes error handling easy in .do callback by puting a try {...} catch ( ... ) { ... } block so an exception thrown in your code will cause internal server error code 500 to be send. To proper catch async errors you need to return the (hidden) promise from from the first async function called. See the example in http_action_cb below
  • error_catcher a callback that will receive all request about to be rejected by the library.

http_action_cb

interface http_action_cb {
	(req:CompleteIncomingMessage, resp:SimpleServerResponse) :void|Promise<any>;
}

The http request handling callback in form

function handle_a_request(req:CompleteIncomingMessage, resp:SimpleServerResponse) {
	...
}

or

async function handle_a_request(req:CompleteIncomingMessage, resp:SimpleServerResponse) {
	...
}

this is the do in http_action_t and indexer in options_t

as usual use the information in req to craft a resp

if you're using catch_to_500 option and mixing sync and async functions make really sure you're returing the tail call functions. For example if you're using a authentication middleware you should do some strict returns:


type chain_cb_t=(req: CompleteIncomingMessage, resp: SimpleServerResponse, auth_artefact:string)=>void|Promise<any>;

//chack auth call chain if auth ok
function auth (req: CompleteIncomingMessage, resp: SimpleServerResponse, chain:chain_cb_t){
	let  auth_artefact=req......
	....
	return chain(req,resp,auth_artefact)
}

async function dothis(req: CompleteIncomingMessage, resp: SimpleServerResponse, auth_artefact:string) {
	await ....
}
...
compose(
	server,
	[
	...
	{prefix:"/api/dothis?", do: (req,resp)=>{return auth(req,resp,dothis)}},
	{prefix:"/api/dothat?", do: (req,resp)=>{return auth(req,resp,dothat)}},
	....
	],
	options
);

CompleteIncomingMessage

The danmaru request object extending from NodeJS IncomingMessage

interface CompleteIncomingMessage extends IncomingMessage {
	//assert this from parent
	url:string;
	method:string;

	//expand a bit 
	action:http_action_t;
	full_url:URL;
	body_string:string;
	is_damaged:boolean;
}
  • url, method these are optional in original IncomingMessage but as we're working with HTTP/HTTPS servers we can assert them in danmaru
  • action this is the http_action_t that is assigned to handle the request. This might come handy in some layered design
  • full_url as the original IncomingMessage have url that is just a string danmaru creates a full_url object from URL class factoring in Host header and allowing for easy search params (GET params) access wia req.full_url.searchParams.get('paramname')
  • body_string in case of request with a body the text is collected here fully before the call to http_action_cb is done. If the body goes above limiting max_body_size of http_action_t or options_t danmaru will return 400 BAD REQUEST and http_action_cb will NOT be called
  • is_damaged is flag set and used internally by danmaru to mark a IncomingMessage as damaged by error, cancellation or max_body_size violation such request should not reach http_action_cb

SimpleServerResponse

The danmaru request object extending from NodeJS ServerResponse

export interface SimpleServerResponse extends ServerResponse {
	action:http_action_t;
	req_url:string;
	logger:logger_t;
	auto_headers:OutgoingHttpHeaders;
	simple_response:(code:number,data?:any, headers?:OutgoingHttpHeaders, reason?:string)=>boolean;
	json_response:(code:number,data:string|object, headers?:OutgoingHttpHeaders, reason?:string)=>boolean;
}
  • action this is the http_action_t that is assigned to handle the request. This might come handy in some layered design
  • req_url a copy of the associated IncomingMessage .url kept for easy logging.
  • logger the logger from associated compose
  • auto_headers from the compose options
  • simple_response method for "one line response": resp.simple_response(200) is all it take to "confirm" a http_action_cb you can specify the response body data in data, set additional headers in headers or customise the response text via reason if you like.
  • json_response method for "one line JSON response": resp.simple_response(200,{ok:true}) is all it take to create return some JSON to the requester. It resolves to simple_response with proper stringification, if needed, plus a Content-Type: application/json; charset=UTF-8 header to make the standard committee happy.

#logger_t

the log4js inspired logging facility

interface logger_t {
	debug:logfunction_t;
	info:logfunction_t;
	warn:logfunction_t;
	error:logfunction_t;
	mark:logfunction_t;
}

this provided utility function explains it all:

function log_all():logger_t {
	return {
		debug:console.log,
		info:console.log,
		warn:console.log,
		error:console.log,
		mark:console.log,
	}
}

For static files serving we recoomend using serve-handler and calling it in indexer function

import {compose} from 'danmaru';
import serveHandler from 'serve-handler'

....

compose(server,[...],{
    indexer:(req,resp)=>{
   	if (req.url.startsWith('/api/')) {
   		log.info("unhandled api call at "+req.url);
   		resp.simple_response(codes.NOT_FOUND);
   		return;
   	}
   	serveHandler(req,resp,{public:"./fe/dist", rewrites:[{source:'/',destination:'/index.html'}]});
   }
});

For more (advanced) examples take a look at our test files :)