{"id":16416639,"url":"https://github.com/angeal185/sicarii","last_synced_at":"2025-10-26T19:32:15.762Z","repository":{"id":57359142,"uuid":"269754274","full_name":"angeal185/sicarii","owner":"angeal185","description":"The zero dependency http2 nodejs multithreading framework","archived":false,"fork":false,"pushed_at":"2020-07-23T17:23:53.000Z","size":1045,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-31T23:16:18.936Z","etag":null,"topics":["body-parser","cookie-parser","etags","framework","http2","javascript","jwt","logger","multithreading","multithreading-framework","nodejs","nodejs-framework","nodejs-server","rest-api","router","server","session-store","sessions","static-file-server","template-engines"],"latest_commit_sha":null,"homepage":"https://angeal185.github.io/sicarii","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/angeal185.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-05T19:32:45.000Z","updated_at":"2024-11-21T10:36:02.000Z","dependencies_parsed_at":"2022-09-06T21:41:23.531Z","dependency_job_id":null,"html_url":"https://github.com/angeal185/sicarii","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angeal185%2Fsicarii","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angeal185%2Fsicarii/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angeal185%2Fsicarii/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angeal185%2Fsicarii/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/angeal185","download_url":"https://codeload.github.com/angeal185/sicarii/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238394323,"owners_count":19464583,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["body-parser","cookie-parser","etags","framework","http2","javascript","jwt","logger","multithreading","multithreading-framework","nodejs","nodejs-framework","nodejs-server","rest-api","router","server","session-store","sessions","static-file-server","template-engines"],"created_at":"2024-10-11T07:09:44.747Z","updated_at":"2025-10-26T19:32:10.314Z","avatar_url":"https://github.com/angeal185.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](https://i.ibb.co/GcZZgw0/SICARII-1280.png)\n\nThe zero dependency http2 nodejs multithreading framework\n\n![cd-img] ![dep-img] ![sz-img]\n\n[![NPM Version][npm-img]][npm-url] ![lic-img]\n\n# [SICARII Website](https://angeal185.github.io/sicarii/)\n\n# [SICARII Dev Wiki](https://github.com/angeal185/sicarii/wiki)\n\n# Documentation\n\n- [Installation](#installation)\n- [About](#about)\n- [Initialization](#initialization)\n- [build](#build)\n- [server](#server)\n- [sync](#sync)\n- [router](#router)\n- [configuration](#configuration)\n- [stream](#stream)\n- [push handler](#push-handler)\n- [headers](#headers)\n- [app](#app)\n- [body parser](#body-parser)\n- [etags](#etags)\n- [cookie parser](#cookie-parser)\n- [template engines](#template-engines)\n- [botnet](#botnet)\n- [ip blacklist](#ip-blacklist)\n- [ip whitelist](#ip-whitelist)\n- [auth-token](#auth-token)\n- [cache](#cache)\n- [store](#store)\n- [sessions](#sessions)\n- [compression](#compression)\n- [static file server](#static-file-server)\n- [MIME types](#mime-types)\n- [logs](#logs)\n- [crypt](#crypt)\n- [backwards-compatibility](#backwards-compatibility)\n\n\n# Installation\n- [Back to index](#documentation)\n\nnpm\n\nstable release\n\n```sh\n$ npm install sicarii --save\n```\n\ndev release\n\ngit\n```sh\n$ git clone https://github.com/angeal185/sicarii.git\n```\n\n# About\n- [Back to index](#documentation)\n\nSicarii is a nodejs http2 framework for projects of all sizes.\n\n* zero dependencies forever\n* multithreading by default\n* does not need or use the nodejs http2 compatibility layer api but can be extended to do so\n* non restrictive design in that you can use the sicarii api or vanilla nodejs most of the time\n* easily extendable\n\n# Initialization\n- [Back to index](#documentation)\n\nAs sicarii is built for http2, SSL certificates are required.\nThe default path for the ssl certificates is as follows:\n\n* `./cert/localhost.cert`\n* `./cert/localhost.key`\n\nThese options be edited in the default `./config/config.json` file at `config.ssl`.\n* for using the `key/cert/pfx/ca` options, a path to the file should be provided as the arg.\n* `config.server` accepts all of the same default arguments as nodejs http2 server config.\n* sicarii will automatically combine `config.ssl` with `config.server`\n\nself signed certificates can be used for development and created as follows:\n\nECDSA\n\n```bash\n\n$ openssl ecparam -name secp384r1 -genkey -out localhost.key\n$ openssl req -new -x509 -key localhost.key -out localhost.cert -days 365\n\n```\n\nRSA\n\n```bash\n\n$ openssl req -x509 -new -x509 -sha256 -newkey rsa:4096 -nodes -keyout localhost.key -days 365 -out localhost.cert\n\n```\n\n\n# Build\n- [Back to index](#documentation)\n\nrun the following line of code in any file inside your cwd to build sicarii.\n\n```js\n\nrequire('sicarii/build')();\n\n```\n\nUpon first run and if no config file is found, sicarii will attempt to generate the following.\n\n* `./config` ~ default config directory.\n* `./config/config.json` ~ default config file.\n* `./config/ip_config.json` ~ default ip whitelist/blacklist file.\n* `./render` ~ default render/document directory.\n* `./render/index.html` ~ starter html file.\n* `./static` ~ default static file directory.\n* `./static/css/main.css` ~ starter css file.\n* `./static/modules/main.mjs` ~ starter mjs file.\n* `./uploads` ~ default upload directory.\n* `./logs` ~ default logs directory.\n* `./store/cache` ~ default cache write dir.\n* `./store/session` ~ default session write dir.\n* `./store/store` ~ default store write dir.\n\nthis action is sandboxed for security reasons. should you wish to, you can delete the associated build files:\n\n* `/sicarii/build.js`\n* `/sicarii/lib/utils/init.js`\n\n```js\n\nconst { app } = require('sicarii');\n\napp.del_build()\n\n```\n\n# Server\n- [Back to index](#documentation)\n\nSicarii is built to incorporate  multi-threading by default. you can edit your thread count at `config.cluster.workers`\n\nAlthough many frameworks wrap the server object within their app, limiting your server actions to those they\nwish you to have access to, sicarii does not.\nsicarii extends the existing nodejs modules in place, leaving you full access to the nodejs server object.\nMost of these extensions can be either disabled, replaced, configured or extended.\n\nsicarii uses the modern nodejs http2 api and does not need or contain any code related to the\nhttp2 Compatibility API.\n\nBelow is a 30 second minimal `rest-api`/`static server` example.\n\n```js\nconst { app, cluster } = require('sicarii');\n// app is always called first\n\nif (cluster.isMaster) {\n\n  const { sync, Cache, server, logs } = require('sicarii/master');\n\n\n  // Cache extentions here ~ if any\n\n  // server extentions here ~ if any\n\n  // logs extentions here ~ if any\n\n  // * spawn workers\n  // * synchronize master/worker communication\n  // * respawn dead workers\n  // * start cache on config.cache.port cache port\n  // * automatically handle cache requests\n  // * automatically handle log requests\n\n  sync.init().respawn().listen();\n\n} else {\n\n  const { server, router, crypt } = require('sicarii/main');\n\n  // serve static\n  router.get('/', function(stream, headers, flags){\n\n    stream.addHeaders({\n      header2: 'ok2',\n      header3: 'ok3'\n    });\n\n\n    //stream headers and and send static document\n    stream.doc('index.html', 'text/html; charset=utf-8');\n\n  });\n\n  // json rest\n  router.post('/', function(stream, headers, flags){\n    let body = stream.body.json;\n\n    stream.addHeader('x-Static', 'ok');\n\n    // send headers \u0026 response\n    stream.json({key: 'val'});\n  });\n\n  //start worker server at config.port\n  server.listen(app.config.port);\n}\n```\n\n#### server.log_ip()\nrefer to logs for a detailed explanation\n\nlog ip address\n\n```js\n\n/**\n *  @server.log_ip(ip, path)\n *\n *  @param {string} ip ~ ip address to log\n *  @param {string} path ~ path to log\n **/\n\nrouter.get('/login', function(stream, headers, flags){\n\n  server.log_ip(stream.ip, '/login')\n\n});\n\n```\n\n#### server.log_history()\nrefer to logs for a detailed explanation\n\nlog history\n\n```js\n\n/**\n *  @server.log_history(data)\n *\n *  @param {string} data ~ history data to log\n **/\n\n router.get('/login', function(stream, headers, flags){\n\n   let his_log = [Date.now(), 'GET', '/login'].join('::::')\n   server.log_history(his_log)\n\n });\n\n```\n\n#### server.log_error()\nrefer to logs for a detailed explanation\n\nlog error\n\n```js\n\n/**\n *  @server.log_error(data)\n *\n *  @param {string} data ~ error data to log\n **/\n\n router.get('/someerror', function(stream, headers, flags){\n\n   let his_log = [Date.now(), 'GET', '/someerror', '400', 'some message'].join('::::')\n   server.log_error(his_log)\n\n });\n\n```\n\n#### server.pre_cache()\nserver.pre_cache() will enable you to pre-cache your static files/docs.\n\n* this method can only be called once and upon doing so, it will remove itself\n* this method is apart of `sync` although you may have many workers, it will only be called once.\n* this method is for static files/docs only, it is not intended for rendered docs\n* `config.pre_cache` is the path to your pre_cache config file\n* `config.verbose` enabled will log to console the cache status of a streamed file\n\nthe configuration file can be configured like so:\n\n```js\n/* ./config/pre_cache.json */\n\n{\n  \"render\": [{\n    \"ctype\": \"text/html\", // file content-type 'only'\n    \"url\": \"/index.html\"  // file path relative to render path\n  }],\n  \"static\": [{\n    \"ctype\": \"text/css\",\n    \"url\": \"/css/main.css\" // file path relative to static path\n  },{\n    \"ctype\": \"application/javascript\",\n    \"url\": \"/modules/main.mjs\"\n  }]\n}\n\n```\n\nthe method can be called like so:\n\n```js\nconst { app, cluster } = require('sicarii');\n\n\nif(cluster.isMaster) {\n\n  const { sync } = require('sicarii/master');\n\n  sync.init().respawn().listen();\n\n\n} else {\n\n  const { server, router } = require('sicarii/main');\n\n  router.get('/', function(stream, headers, flags){\n\n    // stream.doc() static files are located in the render folder\n    // this file has been cached\n    // [sicarii:GET] /index.html 200 [cache]\n    stream.status(200).doc('index.html', 'text/html')\n\n    // stream.render() files are located in the render folder but are not static\n    // this has not been rendered/cached properly\n    // do not pre-cache rendered files\n    stream.status(200).render('index.html', {not: 'cached'})\n\n  });\n\n  // can be optionally called in chain\n  // sync will ensure the method is only called by first worker\n  server.pre_cache().listen(app.config.port);\n\n}\n```\n\n#### server.push_handler()\nrefer to push handler\n\nserver.push_handler() will enable/disable automatic stream push of static files.\n* this method takes priority over `config.push_handler.enabled`\n\n```js\nconst { app, cluster } = require('sicarii');\n\nif(cluster.isMaster) {\n\n  const { sync } = require('sicarii/master');\n\n  sync.init().respawn().listen();\n\n} else {\n\n  const { server, router } = require('sicarii/main');\n\n  router.get('/', function(stream, headers, flags){\n    stream.status(200).doc('index.html', 'text/html')\n  });\n\n  //enable push_handler manually\n  server.pre_cache().push_handler(true).listen(app.config.port);\n}\n\n```\n\n# Sync\n- [Back to index](#documentation)\n\nthe sync object is is used to control and synchronize events between master/worker\n\n* sync is optionally responsible for all tasks related to the cluster module\n* sync will automatically handle spawning of new worker threads\n* sync will automatically handle respawning of crashed worker threads\n* sync will automatically handle inter-process messaging across all processes\n* sync will automatically initialize Cache and start the cache server\n* sync will handle inter-process ip/history/error logging\n* sync is a part of the master scope\n\n\n#### sync.init()\n\n\n```js\n\n/**\n *  sync.listen(settings)\n *  @param {object} settings // optional worker settings overrides to config.cluster.settings\n **/\n\nif (cluster.isMaster) {\n  const { sync } = require('sicarii/master');\n\n  // * spawn workers\n  // * synchronize master/worker communication\n  sync.init();\n}\n```\n\n#### sync.respawn()\n\n```js\nif (cluster.isMaster) {\n  const { sync } = require('sicarii/master');\n\n  // * respawn dead workers\n  sync.respawn()\n\n  // or\n  sync.init().respawn()\n}\n```\n\n#### sync.listen()\n\n```js\n/**\n *  sync.listen(callback)\n *  @param {function} callback // optional callback\n **/\n\nif (cluster.isMaster) {\n  const { sync } = require('sicarii/master');\n\n  // * start cache on config.cache.port cache port\n  sync.listen()\n\n  // or\n  sync.init().respawn().listen()\n}\n```\n\n#### sync.kill()\n\n```js\n/**\n *  sync.kill(id)\n *  @param {number} id // id of the worker to kill\n **/\n\nif (cluster.isMaster) {\n  const { sync } = require('sicarii/master');\n\n  sync.init().respawn().listen()\n\n  // kill worker with id 1 then worker with id 2\n  // with sync.respawn() active these workers will be respawned\n  setTimeout(function(){\n    sync.kill(1).kill(2)\n  },5000)\n}\n```\n\n#### sync.kill_all()\n\n```js\nif (cluster.isMaster) {\n  const { sync } = require('sicarii/master');\n\n  sync.init().respawn().listen()\n\n  // kill all workers\n  // with sync.respawn() active these workers will be respawned\n  setTimeout(function(){\n    sync.kill_all(1)\n  },5000)\n}\n```\n\n# Router\n- [Back to index](#documentation)\n\n#### methods\n\nThe default allowed router methods can and should be configured at `config.stream.methods`.\n* `config.stream.methods` accepts all compatible http methods. you should only add the ones you use.\n* `config.stream.method_body` contains all of the router methods that accept a body.\n* `config.stream.method_query` contains all of the router methods that accept a query string.\n\n* If you are not using a method in your app, you should remove it to improve both the security and performance of your app.\n\nthe router also accepts all of the default nodejs stream methods.\n\nbelow listed are some basic router method examples:\n```js\n\n/**\n *  router[event](path, callback)\n *  @param {string} path\n *  @param {function} callback ~ function(stream, headers, flags)\n **/\n\nrouter.get('/test', function(stream, headers, flags){\n  let query = stream.query; //json object\n\n  // add header\n  stream.addHeader('Content-Type', 'application/json');\n\n  // add cookie\n  stream.cookie('name', 'value',{\n    Domain: 'localhost',\n    Path: '/',\n    Expires: Date.now(),\n    MaxAge: 9999,\n    HttpOnly: true,\n    SameSite: 'Strict',\n    Secure: true,\n    Priority: 'High'\n  })\n\n  // send headers \u0026 response\n  stream.json({test: 'get'});\n\n  /* or using default nodejs methods */\n\n  stream.respond(stream.headers);\n  stream.end(JSON.stringify({test: 'get'}))\n\n});\n\n\n// head stream\nrouter.head('/test', function(stream, headers, flags){\n  let query = stream.query;\n\n});\n\n// trace stream\nrouter.trace('/test', function(stream, headers, flags){\n  let query = stream.query;\n\n});\n\n\n\n// post stream\nrouter.post('/', function(stream, headers, flags){\n  let body = stream.body.text; // stream.body.buffer / stream.body.json\n\n  console.log(body)\n\n});\n\n// delete stream\nrouter.delete('/', function(stream, headers, flags){\n  let body = stream.body.text; // stream.body.buffer / stream.body.json\n\n  console.log(body)\n\n});\n\n// patch stream\nrouter.patch('/', function(stream, headers, flags){\n  let body = stream.body.text; // stream.body.buffer / stream.body.json\n\n  console.log(body)\n});\n\n// put stream\nrouter.put('/', function(stream, headers, flags){\n  let body = stream.body.text; // stream.body.buffer / stream.body.json\n\n  console.log(body)\n});\n\n\nrouter.get('/', function(stream, headers, flags){\n\n  //serve headers and serve static document\n  stream.doc('/index.html', 'text/html; charset=utf-8');\n\n});\n\n\n\n// send response headers and render static document\nrouter.get('/', function(stream, headers, flags){\n\n  stream.doc('index.html', 'text/html; charset=utf-8');\n\n});\n\nrouter.get('/', function(stream, headers, flags){\n\n  stream.addHeader('key', 'val');\n\n  stream.doc('index.html', 'text/html; charset=utf-8');\n\n});\n\n\n// send response headers and render with optional template engine installed\nrouter.get('/', function(stream, headers, flags){\n\n  // basic ~ default\n  stream.render('index.html', {title: 'basic'})\n\n  // nunjucks\n  stream.render('index.njk', {title: 'nunjucks'})\n\n  // pug\n  stream.render('index.pug', {title: 'pug'})\n\n});\n\n\n```\n\n\n# Configuration\n- [Back to index](#documentation)\n\nsicarii has a tiny but powerful list of configurations\n\nthe configuration file at `./config/config.json` is an essential part of sicarii.\nyou MUST tweak it to your own requirements in order to maximize performance and security.\n\n```js\n//defaults\n\n{\n  \"port\": 8080, // server port\n  \"origin\": \"https://localhost\", // server origin\n  \"verbose\": true, // show log to console\n  \"dev\": true, // log errors to console\n  \"proxy\": false, //  x-forwarded-for as ip address\n  \"ip_config\": \"/config/ip_config\", // path to ip_config.json\n  \"pre_cache\": \"/config/pre_cache\", // path to pre_cache.json\n  \"push_handler\": { // automatic push handler\n    \"enabled\": true,\n    \"accept\": [\"text/html\"], // accept header document types to accept\n    \"path\": \"/config/push\" // path to push config file\n  },\n  \"cluster\": {\n    \"workers\": 2 // worker count\n    \"settings\": { //worker settings\n      \"serialization\": \"json\"\n    }\n  },\n  \"sync\": {\n    \"respawn\": true // auto-respawn dead workers\n  },\n  \"session\": {\n    \"path\": \"/store/session/db.json\", //read/write dir relative to cwd\n    \"maxage\": 1000000, //maxage of sessions in ms\n    \"secret\": \"\" //optional session secret\n  },\n  \"cache\": {\n    \"url\":\"https://localhost:5000\", // cache server url\n    \"timeout\": 5000, //cache response timeout ms\n    \"proxy\": false, // x-forwarded-for as ip address\n    \"authtoken\": { //cache auth-token header\n      \"enabled\": false,\n      \"header\": \"X-Authtoken\",\n      \"token\": \"12345\"\n    },\n    \"whitelist\": { //cache server ip whitelist\n      \"enabled\": true,\n      \"ip\": [\"::ffff:127.0.0.1\"] //cache whitelisted ip addersses\n    },\n    \"server\": {\n      //cache server config ~ accepts all nodejs http2 server settings\n      \"rejectUnauthorized\": false\n    },\n    \"headers\": {\n      //cache server outbound headers\n    }\n  },\n  \"cookie_parser\": {\n    \"enabled\": true, //enable cookie parser\n    \"auto_parse\": true, //enable auto cookie parse\n    \"sig\": {\n      \"hmac\": \"secret\", // cookie sign hmac\n      \"prefix\": \"sig\" // cookie sig prefix\n    }\n  },\n  \"stream\": {\n    \"path_limit\": 100, // stream path size limit ~ false to disable check\n    \"case_sensitive\": true, // converts url pathnames to  lowercase if false\n    \"param_limit\": 1000, // stream url search size limit ~ false to disable check\n    \"body_limit\": 5000, // stream body size limit ~ false to disable check\n    \"methods\": [ // add all allowed http  methods ~ remove if unused\n      \"get\",\n      \"post\",\n      \"connect\",\n      \"put\",\n      \"delete\",\n      \"head\"\n    ],\n    \"querystring\": true, // enable stream.qs\n    \"method_body\": [\"post\", \"delete\", \"patch\", \"put\"], // methods return body\n    \"method_query\": [\"get\",\"connect\", \"head\", \"options\", \"trace\"],// methods return query params\n    \"content_types\": [ // accepted body content-types ~ remove if unused\n      \"application/json\",\n      \"text/plain\",\n      \"multipart/form-data\",\n      \"application/x-www-form-urlencoded\"\n    ]\n  },\n  \"blacklist\": { //enable server ip blacklist\n    \"enabled\": false,\n    \"msg\": \"your ip has been blacklisted, have a nice day\" // unauth msg\n  },\n  \"whitelist\": { //enable server ip whitelist\n    \"enabled\": false,\n    \"msg\": \"Unauthorized\" // unauth msg\n  },\n  \"authtoken\": {  //enable auth token header\n    \"enabled\": false,\n    \"header\": \"X-Authtoken\",\n    \"token\": \"xxxxxx\",\n    \"msg\": \"server offline\" // unauth msg\n  },\n  \"server\": {\n    // accepts all http2 nodejs server options\n  },\n  \"ssl\": {\n    \"cert\": \"/cert/localhost.cert\", // key/cert/pfx/ca as string path to file\n    \"key\": \"/cert/localhost.key\"\n  },\n  \"store\": { // sicarri store\n    \"path\": \"/store/store/db.json\" // read/write path relative to cwd\n  },\n  \"uploads\": {\n    \"enabled\": true,\n    \"path\": \"/uploads\", // uploads dir, relative to cwd()\n    \"recursive\": true, //enable recursive folder creation\n    \"gzip\": true, // compress file using gzip\n    \"brotli\": false, // compress file using brotli\n    \"deflate\": false, // compress file using deflate\n    \"mimetypes\": {\n      // accepted mimetypes\n    },\n    \"max_filename\": 30, // max filename length\n    \"max_filesize\": 50000 // max upload content length\n  },\n  \"static\": {\n    \"path\": \"/static\", // default static file path\n    \"blocked\": [],\n    \"etag\": { // etag header\n      \"enabled\": true, // use etags on rendered files\n      \"digest\": \"sha3-256\", //etag digest hash ~ crypto.getHashes();\n      \"encode\": \"base64\" //etag digest encoding hex/base64\n    },\n    \"cache\": { // static file server cache\n      \"enabled\": true, // enable cache on static file server\n      \"maxage\": 1000000 // cached items maxAge\n    },\n    \"headers\": {} // default headers for static file server\n  },\n  \"render\": { // render/tempate engine defaults\n    \"path\": \"/render\",\n    \"blocked\": [],\n    \"etag\": { // etag header\n      \"enabled\": true, // use etags on rendered files\n      \"digest\": \"sha3-256\", //etag digest hash ~ crypto.getHashes();\n      \"encode\": \"base64\" //etag digest encoding hex/base64\n    },\n    \"cache\": { // rendered files cache\n      \"enabled\": true, // enable cache on rendered files\n      \"maxage\": 1000000 // cached items maxAge\n    },\n    \"headers\": { // default headers for rendered files\n      \"X-Frame-Options\": \"DENY\",\n      \"Referrer-Policy\": \"no-referrer\",\n      \"Server\": \"Nodejs\",\n      \"Access-Control-Allow-Origin\": \"*\",\n      \"Access-Control-Allow-Methods\": \"GET\",\n      \"X-DNS-Prefetch-Control\": \"on\",\n      \"Strict-Transport-Security\": \"max-age=31536000; includeSubDomains\",\n      \"X-Content-Type-Options\": \"nosniff\",\n      \"X-XSS-Protection\": \"1\",\n      \"TK\": \"N\"\n    }\n  },\n  \"compression\": {\n    \"gzip\": { // gzip compression\n      \"enabled\": true,\n      \"prezipped\": false, // use pre-compressed files\n      \"ext\": \".gz\", // compressed file extention\n      \"setting\": {} // accepts all nodejs gzip compression settings\n    },\n    \"brotli\": { // brotli compression\n      \"enabled\": false,\n      \"prezipped\": false, // use pre-compressed files\n      \"ext\": \".br\", // compressed file extention\n      \"setting\": {} // accepts all nodejs brotli compression settings\n    },\n    \"deflate\": { // deflate compression\n      \"enabled\": false,\n      \"prezipped\": false, // use pre-compressed files\n      \"ext\": \".dfl\", // compressed file extention\n      \"setting\": {} // accepts all nodejs deflate compression settings\n    }\n  },\n  \"cors\": { // default stream.cors fallback\n    \"origin\": '',        // string  | Access-Control-Allow-Origin\n    \"methods\": '',       // string  | Access-Control-Allow-Methods\n    \"allow_headers\": '', // string  | Access-Control-Allow-Headers\n    \"expose_headers\": '',// string  | Access-Control-Expose-Headers\n    \"credentials\": true, // boolean | Access-Control-Allow-Credentials\n    \"maxage\": 9999       // number  | Access-Control-Max-Age\n  },\n  \"csp\": { // content security policy object\n    \"default\": \"default-src 'self'\"\n  },\n  \"feature_policy\": { // feature policy object\n    \"default\": \"microphone 'none'; geolocation 'none'\"\n  },\n  \"logs\": {\n    \"path\": \"/logs\", //path to log dir\n    \"separator\": \"|\", // log separator\n    \"logs\":[\"error\", \"history\",\"ip\"],\n    \"cron\": 86400000, // logs cronjob interval\n    \"console_error\": false, //log to console log-related errors\n    \"compression\": \"gzip\", // backup compression ~ gzip/deflate/brotli\n    \"encodeURIComponent\": false, // encode log entries\n    \"error\": {\n      \"enabled\": true, // enable auto error logs\n      \"max_size\": 5000, // log max file size\n      \"base_name\": \"error\", //log file base name\n      \"ext\": \".txt\" //log file base extension\n    },\n    \"history\": {\n      \"enabled\": true, // enable auto history logs\n      \"max_size\": 5000, // log max file size\n      \"base_name\": \"history\", //log file base name\n      \"ext\": \".txt\" //log file base extension\n    },\n    \"ip\": {\n      \"enabled\": true, // enable ip logging\n      \"max_size\": 5000, // log max file size\n      \"base_name\": \"ip\", //log file base name\n      \"ext\": \".txt\" //log file base extension\n      \"log_time\": true, // add timestamp to log\n      \"log_path\": true // add path to log\n    }\n  },\n  \"template_engine\": { // template engine config\n    \"engines\": [\n      \"basic\", \"poorboy\", \"nunjucks\", \"ejs\", \"pug\",\n      \"mustache\", \"twig\", \"squirrelly\", \"ect\", \"eta\",\n      \"liquidjs\"\n    ],\n    \"basic\": {\n      \"enabled\": true,\n      \"settings\": {\n        \"pretty\": false,\n        \"filters\": {},\n        \"cache\": false\n      }\n    },\n    \"squirrelly\": {\n      \"enabled\": false,\n      \"settings\": {}\n    },\n    \"eta\": {\n      \"enabled\": false,\n      \"settings\": {}\n    },\n    \"liquidjs\": {\n      \"enabled\": false,\n      \"settings\": {\n        \"extname\": \".liquid\"\n      }\n    },\n    \"ect\": {\n      \"enabled\": false,\n      \"settings\": {\n        \"cache\": false,\n        \"open\": \"\u003c%\",\n        \"close\": \"%\u003e\"\n      }\n    },\n    \"poorboy\": {\n      \"enabled\": false,\n      \"settings\": {\n        \"use_globals\": false,\n        \"globals\": {}\n      }\n    },\n    \"nunjucks\": {\n      \"enabled\": false,\n      \"jinjacompat\": true,\n      \"filters\": \"\",\n      \"globals\": {\n        \"enabled\": false,\n        \"vars\": {}\n      },\n      \"settings\": {\n        \"autoescape\": true,\n        \"noCache\": true,\n        \"throwOnUndefined\": false,\n        \"trimBlocks\": false,\n        \"lstripBlocks\": false,\n        \"tags\": {}\n      }\n    },\n    \"ejs\": {\n      \"enabled\": false,\n      \"settings\": {}\n    },\n    \"pug\": {\n      \"enabled\": false,\n      \"settings\": {\n        \"pretty\": false,\n        \"filters\": {},\n        \"cache\": false\n      }\n    },\n    \"mustache\": {\n      \"enabled\": false,\n      \"tags\": [\"{{\", \"}}\"],\n      \"settings\": {}\n    },\n    \"twig\": {\n      \"enabled\": false,\n      \"settings\": {}\n    }\n  },\n  \"mimetypes\": {\n    // a list of all your allowed mimetypes\n  },\n  \"crypt\": {\n    \"jwt\":{\n      \"secret\": \"secret\", // jwt secret for hmac\n      \"digest\": \"sha256\", // jwt digest for hmac\n      \"encode\": \"base64\", // jwt encoding\n      \"separator\": \":\", // jwt token separator\n      \"header\": { // jwt header\n        \"typ\": \"JWT\",\n        \"alg\": \"HS256\"\n      },\n      \"claims\": {\n        \"iss\": \"token issuer\", // optional jwt issuer\n        \"sub\": \"token subject\", // optional jwt subject\n        \"aud\": \"token audience\", // optional jwt audience\n        \"exp\": 5000000, // mandatory ms till expires\n        \"nbf\": 0 // optional ms till valid\n      }\n    },\n    \"hmac\": {\n      \"secret\": \"secret\",  // hmac secret\n      \"digest\": \"sha3-512\", // hmac hash function\n      \"encode\": \"hex\" // output encode\n    },\n    \"pbkdf2\": {\n      \"digest\": \"sha3-512\", // hash function\n      \"encode\": \"hex\", // output encode\n      \"iterations\": 50000 // kdf iterations\n    },\n    \"scrypt\": {\n      \"encode\": \"hex\", // output encode\n      \"cost\": 16384, // scrypt cost\n      \"blockSize\":8, // scrypt cost\n      \"parallelization\": 1 // scrypt parallelization\n    },\n    \"encryption\": {\n      \"secret\": \"\", // encrypt/decrypt ~ app secret\n      \"secret_len\": 32, // correct key length\n      \"iterations\": 60000, // iterations to be used in keygen\n      \"digest\": \"sha3-512\", // digest to be used in keygen\n      \"settings\": { // THESE SETTINGS MUST BE VALID\n        \"cipher\": \"aes\", // encrypt/decrypt cipher\n        \"bit_len\": \"256\", // encrypt/decrypt bit\n        \"iv_len\": 32, // encrypt/decrypt iv length\n        \"tag_len\": 16, // encrypt/decrypt auth-tag length\n        \"encode\": \"hex\", // encrypt/decrypt/keygen encoding\n        \"mode\": \"gcm\" // encrypt/decrypt mode\n      }\n    },\n    \"ecdsa\": {\n      \"curve\": \"secp521r1\", // ecdsa curve\n      \"encode\": \"hex\", // ecdsa encoding\n      \"hash\": \"sha3-512\", // ecdsa hash used\n      \"privateKey\": {  // accepts all nodejs ec privateKey settings\n        \"type\": \"sec1\",\n        \"format\": \"der\"\n      },\n      \"publicKey\": { // accepts all nodejs ec publicKey settings\n        \"type\": \"spki\",\n        \"format\": \"der\"\n      }\n    },\n    \"ecdh\": { // ecdh key exchange\n      \"curve\": \"secp521r1\",  // ecdh curve\n      \"encode\": \"hex\"  // ecdh encoding\n    },\n    \"rsa\": { // rsa encryption\n      \"length\": 4096, // rsa modulusLength\n      \"publicExponent\": 65537,\n      \"encode\": \"hex\",\n      \"oaepHash\": \"sha512\", // rsa oeap hash used\n      \"publicKey\": { // accepts all nodejs rsa publicKey settings\n        \"type\": \"pkcs1\",\n        \"format\": \"pem\"\n      },\n      \"privateKey\": { // accepts all nodejs rsa privateKey settings\n        \"type\": \"pkcs8\",\n        \"format\": \"pem\"\n      }\n    },\n    \"otp\": { // contains the one time pad defaults\n      \"rounds\": 1, // otp encrypt/decrypt rounds count\n      \"iterations\": 10000, // iteration count for generating a secure pad\n      \"digest\": \"sha512\", // digest used for generating a secure pad\n      \"encode\": \"hex\" // encoding used for otp\n    }\n  },\n  \"bot\": {\n    \"detect\": {\n      \"items\": [\"Googlebot\"] // manual detect bots via user-agent sub-string\n    },\n    \"block\": {  // automatically block bots via user-agent sub-string\n      \"enabled\": false,\n      \"msg\": \"Unauthorized\", // bot block msg\n      \"items\": [] // blocked bots array\n    }\n  }\n}\n\n```\n\n# Stream\n- [Back to index](#documentation)\n\naccepts all nodejs methods and the following:\n\n#### stream.doc(src, content-type)\n\nstream doc will serve a document from the render folder\n* this method will use cache if available\n* this method will use compression if available\n* this method will stream respond headers\n* this method will send default headers from `config.render.headers`\n* this method will use etag settings from `config.render.etag`\n* this method will use cache settings from `config.render.cache`\n* this method will use gzip/brotli/deflate settings from `config.compression`\n\n```js\n\n/**\n *  stream.doc(path, contentType, callback)\n *  @param {string} path // file path relative to render dir\n *  @param {string} contentType // file content-type\n *  @param {function} callback ~ optional\n **/\n\n  router.get('/', function(stream, headers, flags){\n\n    // send response headers and render static document from the render dir\n    stream.doc('index.html', 'text/html; charset=utf-8');\n\n  });\n```\n\n#### stream.render(src, data)\n\nstream render will serve a rendered document from the render folder.\nrefer to template engines.\n\n* this method will use cache if available\n* this method will use compression if available\n* this method will stream respond headers\n* this method will send default headers from `config.render.headers`\n* this method will use etag settings from `config.render.etag`\n* this method will use cache settings from `config.render.cache`\n* this method will use gzip/brotli/deflate settings from `config.compression`\n\n```js\n\n/**\n *  stream.render(path, obj, callback)\n *  @param {string} path // file path relative to render dir\n *  @param {object} obj // data for rendered file\n *  @param {function} callback ~ optional\n **/\n\n  router.get('/', function(stream, headers, flags){\n\n    // basic ~ default: uses template literals in html documents\n    stream.render('index.html', {title: 'basic'})\n\n    // nunjucks ~ requires manual installation of nunjucks\n    stream.render('index.njk', {title: 'nunjucks'})\n\n    // pug ~ requires manual installation of pug\n    stream.render('index.pug', {title: 'pug'})\n\n  });\n\n```\n\n#### stream.pushStatic(path, ctype)\n\nstream pushStatic will push a file or files from the static folder before requested.\n\n* this method can be chained to push multiple files\n* this method will use cache if available\n* this method will use compression if available\n* this method will send default headers from `config.static.headers`\n* this method will use etag settings from `config.static.etag`\n* this method will use cache settings from `config.static.cache`\n* this method will use gzip/brotli/deflate settings from `config.compression`\n* `config.verbose` will log the push state of a file\n* this method is asynchronous so the `stream` object is immediately returned\n* any errors are handled by sicarii in the same manner as the static file server\n\n```js\n\n/**\n *  stream.pushStatic(path, ctype) // single file\n *  @param {string} path // file path relative to static dir as it would be requested as\n *  @param {string} ctype // file content type as it would be requested as\n *\n *  stream.pushStatic(obj) // multiple files\n *  @param {object} obj // obj.path: file path, obj.ctype: content-type\n **/\n\n  router.get('/', function(stream, headers, flags){\n    /* push files to the browser before the browser requests them */\n\n    // push a file before it has been requested\n    stream\n    .pushStatic('/css/main.css', 'text/css')\n    .status(200)\n    .doc('index.html', 'text/html')\n\n    // or push multiple files before they have been requested\n\n    stream\n    .pushStatic([{\n      path: '/css/main.css', // file path\n      ctype: 'text/css' // file content type\n    },{\n      path: '/favicon.ico',\n      ctype: 'image/x-icon'\n    }])\n    .status(200)\n    .render('index.html', {test: 'push'}, function(err){\n      if(err){return console.error(err)}\n    })\n\n  });\n\n```\n\n#### stream.download(file, content-type)\nstream.download will initiate a file download upon browser navigation.\n\n* stream.download uses `config.static` settings\n* this method will use cache if available\n* this method will use compression if available\n* this method will stream respond headers\n* this method will send default headers from `config.static.headers`\n* this method will use etag settings from `config.static.etag`\n* this method will use cache settings from `config.static.cache`\n* this method will use gzip/brotli/deflate settings from `config.compression`\n* this method will Content-Disposition 'attachment; filename=\"the files name\"' to the headers;\n\n```js\n\n/**\n *  stream.download(path, contentType, callback)\n *  @param {string} path // file path relative to static dir\n *  @param {string} contentType // file content-type\n *  @param {function} callback ~ optional\n **/\n\nrouter.get('/downloadpath', function(stream, headers, flags){\n  // main.mjs will download when /downloadpath is navigated to in the browser\n  stream.download('modules/main.mjs', 'application/javascript');\n\n});\n```\n\n\n#### stream.upload(object, callbabk)\n\nstream.upload will upload a file to your uploads dir if enabled at `config.uploads.enable`\n\n\n* `config.uploads.gzip` will enable/disable gzip compression for uploads\n* `config.uploads.brotli` will enable/disable brotli compression for uploads\n* `config.uploads.deflate` will enable/disable deflate compression for uploads\n* `config.uploads.path` is your upload path relative to cwd()\n* `config.uploads.recursive` will enable recursive dir creation within `config.uploads.path`,\n* `config.uploads.mimetypes` should list all accepted upload mimetypes in the same format as `config.mimetypes`\n* `config.uploads.max_filename` max filename length\n* `config.uploads.max_filesize` max content length\n\nsimple upload example:\n\n```js\n\n/**\n *  stream.upload(settings, callback)\n *  @param {object} settings // upload settings\n *  @param {function} callback ~  function(err,res) | optional\n **/\n\nrouter.post('/upload', function(stream, headers, flags){\n    let ctype = headers.get('content-type');\n\n    if(ctype !== 'application/json'){\n      // do something\n    }\n\n    try {\n      let body = JSON.stringify(stream.body.json);\n\n      let upload_data = {\n        path: '/test/index.json', // path relative to uploads dir\n        ctype: ctype, // content type\n        data: body, // data as string\n        brotli: true, //override default brotli setting config.uploads.brotli ~ optional\n        gzip: false //override default gzip setting config.uploads.gzip ~ optional\n        //deflate: false //override default deflate setting config.uploads.deflate ~ optional\n      }\n\n      stream.upload(upload_data, function(err,res){\n        if(err){\n          // do something\n          return;  \n        }\n\n        stream.json({upload: 'success'})\n\n      })\n    } catch (err) {\n      // do something\n    }\n\n});\n```\n\n#### stream.json(data)\n\nstream.json() performs the following actions:\n\n* add content-type 'application/json' to the headers;\n* stream the headers object.\n* send stringified json\n\n```js\n\n/**\n *  stream.json(obj, contentType, callback)\n *  @param {array/object} obj // data to be stringified\n *  @param {function} callback ~ optional\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  stream.json({send: 'json'})\n\n});\n\n```\n\n\n#### stream.redirect(dest)\n\nstream.redirect() performs the following actions:\n\n* add location destination to the headers;\n* stream the headers object.\n* send redirect\n\n```js\n\n/**\n *  stream.redirect(path)\n *  @param {string} path // redirect anywhere path\n **/\n\nrouter.get('/', function(stream, headers, flags){\n  //redirect to url\n  stream.redirect('/test')\n\n});\n\n```\n\n#### stream.ip\n\nstream.ip returns the client ip address\n\n* enable config.proxy to return ['x-forwarded-for'] ip\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  console.log(stream.ip)\n  // xxx.xxx.x.x.x\n\n});\n\n```\n\n#### stream.headers\n\nstream.headers will return an object containing all current and default outbound headers;\nthis is not to be mistaken with the received `headers` object;\n```js\nrouter.get('/', function(stream, headers, flags){\n\n  //log default received headers to console\n  console.log(headers.all())\n\n  //log default outbound headers to console\n  console.log(stream.headers)\n\n  // add outbound header\n  stream.addHeader('Content-Type', 'text/plain');\n\n  stream.respond(stream.headers);\n  stream.end('headers sent')\n\n})\n\n```\n\n#### stream.addHeader()\n\nstream.addHeader(key,val) will add a header to `stream.headers`\n\n```js\n\n/**\n *  stream.addHeader(key, val)\n *  @param {string} key // header type\n *  @param {string} val // header value\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // add outbound header\n  stream.addHeader('Content-Type','text/plain');\n  //stream.headers['Content-Type'] = 'text/plain';\n\n  stream.respond(stream.headers);\n  stream.end('headers sent')\n\n})\n\n```\n\n#### stream.addHeaders()\n\nstream.addHeaders(obj) will assign an object of headers to `stream.headers`\n\n```js\n\n/**\n *  stream.addHeaders(key, val)\n *  @param {object} obj // headers object\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // add outbound header\n  stream.addHeaders({\n    'content-type':'text/plain',\n    'content-encoding': 'gzip'\n  });\n\n  stream.respond(stream.headers);\n  stream.end('headers sent')\n\n})\n\n```\n\n#### stream.cors()\n\nstream.cors() will add the included cors options to `stream.headers`\n* this method will override any default cors headers in `config.render.headers` || `config.static.headers`\n* this method will fallback to `config.cors` if no object is provided\n\n```js\n\n/**\n *  stream.cors(obj)\n *  @param {object} obj // optional | cors entries || fallback to config.cors\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // add all outbound cors headers\n  stream.cors({\n    origin: '',        // string  | Access-Control-Allow-Origin\n    methods: '',       // string  | Access-Control-Allow-Methods\n    allow_headers: '', // string  | Access-Control-Allow-Headers\n    expose_headers: '',// string  | Access-Control-Expose-Headers\n    credentials: true, // boolean | Access-Control-Allow-Credentials\n    maxage: 9999       // number  | Access-Control-Max-Age\n  });\n\n  // or only some\n  stream.cors({\n    origin: '',\n    methods: '',\n    allow_headers: ''\n  });\n\n  // or use config.cors\n  stream.cors();\n\n  stream.respond(stream.headers)\n\n  stream.end('text')\n})\n\n```\n\n#### stream.ctype()\n\nstream.ctype(val) will add the Content-Type header to `stream.headers`\n\n```js\n\n/**\n *  stream.ctype(val)\n *  @param {string} val // header value\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // add outbound Content-Type header\n  stream.ctype('text/plain');\n\n  stream.respond(stream.headers)\n\n  stream.end('text')\n})\n\n```\n\n#### stream.lang()\n\nstream.lang(val) will add the Content-Language header to `stream.headers`\n\n```js\n\n/**\n *  stream.lang(val)\n *  @param {string} val // header value\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // add outbound Content-Language header\n  stream.lang('en-US');\n\n  stream.respond(stream.headers)\n\n  stream.end('text')\n})\n\n```\n\n#### stream.tk()\n\nstream.tk(val) will add the TK header to `stream.headers`\n\n```js\n\n/**\n *  stream.tk(val)\n *  @param {string} val // header value\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  /*\n  Tk: !  (under construction)\n  Tk: ?  (dynamic)\n  Tk: G  (gateway or multiple parties)\n  Tk: N  (not tracking)\n  Tk: T  (tracking)\n  Tk: C  (tracking with consent)\n  Tk: P  (potential consent)\n  Tk: D  (disregarding DNT)\n  Tk: U  (updated)\n  */\n\n  // add outbound TK header\n  stream.tk('N');\n\n  stream.respond(stream.headers)\n\n  stream.end('text')\n})\n\n```\n\n#### stream.csp()\n\nstream.csp(val) will add the Content-Security-Policy header to `stream.headers`\n\n* stream.csp will load the csp from `config.csp`;\n* this method enables you to store and use multiple pre-defined content security policies\n\n```js\n\n/**\n *  stream.csp(val)\n *  @param {string} val // header value\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // add outbound Content-Security-Policy header from `config.csp.default`\n  stream.csp('default');\n\n  stream.respond(stream.headers)\n\n  stream.end('text')\n})\n\n```\n\n#### stream.feature()\n\nstream.feature(val) will add the Feature-Policy header to `stream.headers`\n\n* stream.feature will load the Feature-Policy from `config.feature_policy`;\n* this method enables you to store and use multiple pre-defined feature policies\n\n```js\n\n/**\n *  stream.feature(val)\n *  @param {string} val // header value\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // add outbound Feature-Policy header from `config.feature_policy.default`\n  stream.feature('default');\n\n  stream.respond(stream.headers)\n\n  stream.end('text')\n})\n\n```\n\n#### stream.status()\n\nstream.status(val) will set the :status header to `stream.headers`\n\n```js\n\n/**\n *  stream.status(val)\n *  @param {number} val // header value\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // add outbound :status header\n  stream.status(200).ctype('text/plain').respond(stream.headers);\n\n  stream.end('text')\n})\n\n```\n\n\n#### stream.query\n\nstream.query is part of `body parser`. if enabled, it will parse the given query to json.\nrefer to body parses section.\n* `config.stream.method_query` controls the accepted router methods.\n* `config.stream.content_types` controls the accepted content types.\n\n```js\nrouter.get('/test', function(stream, headers, flags){\n  let query = stream.query;\n  console.log(query)\n\n});\n\n```\n#### stream.qs\n\nstream.qs is similar to `stream.query` but returns the unparsed querystring.\nThis method is intended for use with custom or complex querystrings;\n\n* `config.stream.querystring` enable/disable\n* the returned querystring is decoded with decodeURIComponent()\n\n```js\nrouter.get('/test', function(stream, headers, flags){\n  let customquery = stream.qs;\n  console.log(customquery)\n\n});\n```\n\n#### stream.body.text\nstream.body.text is the default body parse format\n* returns `string`\n\n```js\nrouter.post('/', function(stream, headers, flags){\n  let body = stream.body.text;\n\n  console.log(body)\n\n});\n```\n\n#### stream.body.buffer\n\nstream.body.buffer is part of `body parser`.\n* returns `buffer`\n\n```js\nrouter.post('/', function(stream, headers, flags){\n  let buff = stream.body.buffer;\n\n  console.log(buff)\n\n});\n```\n\n#### stream.body.json\n\nstream.body.buffer is part of `body parser`.\nrefer to body parses section.\n* returns `json` for supported content-types\n\n```js\nrouter.post('/', function(stream, headers, flags){\n  let obj = stream.body.json;\n\n  console.log(obj)\n\n});\n```\n\n#### stream.cookies\n\nthis method is a part of cookie parser\nrefer to cookie parser\n\nstream.cookie will enable you to easily access all cookies in `headers`\n* this method automatically deserializes all cookies.\n* this method requires `config.cookie_parser.enabled` to be enabled\n* this method can be enabled/disabled at `config.cookie_parser.auto_parse`\n\n```js\nrouter.get('/', function(stream, headers, flags){\n\n  // return cookies object ~ config.cookie_parser.auto_parse\n  console.log(stream.cookies)\n\n})\n```\n\n#### stream.cookie()\n\nthis method is a part of cookie parser\nrefer to cookie parser\n\nstream.cookie(name,val,obj) will enable you to easily add cookies to the `stream.response`\n* this method automatically adds the created cookie to `stream.headers`\n* this method can be enabled/disabled at `config.cookie_parser.enabled`\n* this method can create a separate signed cookie for tamper detection\n* `config.cookie_parser.sig.secret` is used to hmac the cookie\n* `config.cookie_parser.sig.suffix` is the signed cookies suffix\n* a signed cookie will be will use digest/encode settings from `config.crypt.hmac`\n\n\n```js\n\n/**\n *  stream.cookie(key, val, settings)\n *  @param {string} key // cookie name\n *  @param {string} val // cookie value\n *  @param {object} settings // cookie settings\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  //create cookie and add to outbouheaders ~ config.cookie_parser.enabled\n  stream.cookie('name', 'value',{\n    Domain: 'localhost',\n    Path: '/',\n    Expires: Date.now(),\n    MaxAge: 9999,\n    HttpOnly: true,\n    SameSite: 'Lax',\n    Secure: true,\n    Priority: 'High',\n    Signed: true // creates a separate suffixed signed cookie for validation\n  })\n\n  stream.respond(stream.headers);\n  stream.end()\n\n})\n```\n\n# push handler\n- [Back to index](#documentation)\n\nthe push handler will enable/disable automatic stream push of static files.\n\nupon stream, the server will search the accepted header for a match in `config.push_handler.accepted`\nand will push your selected files with the document\n\n* `config.push_handler.enabled` enables this method\n* this method is for static `files` only e.g js/css/png/jpg\n* this method is not for rendered/static `documents` e.g html/xhtml/xml\n* this method is for `GET` requests only.\n* `config.push_handler.accepted` should contain the `requested paths` content-type e.g text/html\n* `config.push_handler.accepted` should not contain the pushed items content-type e.g text/css\n* `config.push_handler.accepted` should only contain document content-types that you use\n* `config.push_handler.accepted` should be as small as possible\n* automatic stream push of static files is recommended only for push intensive sites\n\nthe push configuration file can be configured like so:\n\n```js\n/* ./config/push.json */\n\n\n[{\n  \"url\": \"/single_push\", // the url path that the file is to be pushed for\n  \"ctype\": \"text/css\",   // file content-type 'only'\n  \"path\": \"/css/main.css\" // file path relative to static path\n},{\n  \"url\": \"/multi_push\",\n  \"items\": [{ // push multiple items at same url\n    \"ctype\": \"text/css\",\n    \"path\": \"/css/main.css\"\n  },{\n    \"ctype\": \"image/x-icon\",\n    \"path\": \"/favicon.ico\"\n  }]\n}]\n\n\n```\n\n\n```js\n\nrouter.get('/single_push', function(stream, headers, flags){\n  // will automatically push a static file and send headers/doc\n\n  stream.status(200).doc('index.html', 'text/html')\n});\n\nrouter.get('/multi_push', function(stream, headers, flags){\n  // will automatically push multiple static files and send headers/doc\n\n  stream.status(200).doc('index.html', 'text/html')\n});\n\nrouter.get('/manual_push', function(stream, headers, flags){\n  // will not automatically push multiple static files\n\n\n  stream // manually push multiple static files and send headers/doc\n  .pushStatic([{\n    path: '/css/main.css', // file path\n    ctype: 'text/css' // file content type\n  },{\n    path: '/favicon.ico',\n    ctype: 'image/x-icon'\n  }])\n  .status(200)\n  .doc('index.html', 'text/html')\n});\n\n```\n\n# Headers\n- [Back to index](#documentation)\n\nthe headers object includes the following methods:\n\n#### headers.all()\n\nheaders.all() will return a valid json object containing all received headers\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  // log all received headers\n  console.log(headers.all())\n\n})\n```\n\n#### headers.get()\n\nheaders.get() will return a header from the headers object in nodejs http2 format\n\n```js\n\n/**\n *  headers.get(key)\n *  @param {string} key // header name\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // return content-type header\n  console.log(headers.get('content-type'))\n\n})\n```\n\n#### headers.is()\n\nheaders.is() will return a boolean if the header is equal to the comparison\n\n```js\n\n/**\n *  headers.is(key, val)\n *  @param {string} key // header name\n *  @param {string} val // value to compare\n **/\n\nrouter.get('/admin', function(stream, headers, flags){\n\n  // check content-type\n  if(!headers.is('x-token', 'secret')){\n    app.blacklist(stream.ip)\n  }\n\n})\n\n```\n\n#### headers.has()\n\nheaders.has() will return a boolean if the header exists\n* will also return true for header that exists and has a value of false or 0\n\n```js\n\n/**\n *  headers.has(key)\n *  @param {string} key // header name\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // check if cookie header exists\n  if(headers.has('cookie')){\n    console.log('cookie header exists')\n  }\n\n})\n\n```\n\n#### headers.cookies()\n\nheaders.cookies() will return a deserialized cookies json object\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  // return cookies object\n  console.log(headers.cookies())\n\n})\n\n```\n\n#### headers.ctype()\n\nheaders.ctype() will return the Content-type header if exists\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  console.log(headers.ctype())\n  // application/json ...\n\n})\n\n```\n\n#### headers.agent()\n\nheaders.agent() will return the User-agent header if exists\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  console.log(headers.agent())\n  // some browser user-agent ...\n\n})\n\n```\n\n#### headers.bot()\n\nheaders.bot() will return true if the user-agent detected is a bot\n\n* `config.bot.items` should contain an array of bots to check for\n* this feature could be used to perform targeted seo optimization\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n\n  if(headers.bot()){\n    // render template containing seo data only\n    let useragent = headers.ua();\n\n    if(useragent.includes('google')){\n      stream.render('index_seo_google.html', {\n        data: {\n          some: 'google data',\n          specifically: 'relating',\n          to: 'google seo'\n        }\n      })\n    } else if(useragent.includes('facebook')) {\n      stream.render('index_seo_facebook.html', {\n        data: {\n          some: 'facebook data',\n          specifically: 'relating',\n          to: 'facebook seo'\n        }\n      })\n    } else {\n      stream.render('index_seo_default.html', {\n        data: {\n          some: 'default data',\n          specifically: 'relating',\n          to: 'default seo'\n        }\n      })\n    }\n  } else {\n    // render normal template not polluted with seo\n    stream.render('index.html', {title: 'basic'})\n  }\n\n\n})\n\n```\n\n#### headers.encoding()\n\nheaders.encoding() will return the accept-encoding header if exists\n* the returned value/s will be within a trimmed array\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  console.log(headers.encoding())\n  // ['accepted', 'encoding']\n\n})\n\n```\n\n#### headers.lang()\n\nheaders.lang() will return the accept-language header if exists\n* the returned value/s will be within a trimmed array\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  console.log(headers.lang())\n  // ['accepted', 'language']\n\n})\n\n```\n\n#### headers.accept()\n\nheaders.accept() will return the accept header if exists\n* the returned value/s will be within a trimmed array\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  console.log(headers.accept())\n  // ['accepted', 'content', 'types']\n\n})\n\n```\n\n#### headers.size()\n\nheaders.size() length of the headers object\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  let len = headers.size(); // headers length\n  if(len \u003e 1000){\n    app.blacklist(stream.ip)\n  }\n\n})\n\n```\n\n#### headers.count()\n\nheaders.count() will return a count of your total headers\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  let len = headers.count(); // headers count\n  if(len \u003e 50){\n    app.blacklist(stream.ip)\n  }\n\n})\n\n```\n\n# App\n- [Back to index](#documentation)\n\nthe app object exists as a bridge between worker/master.\n\n\n* app must be accessible outside of the worker/master scope\n* all methods within app are available to the master and worker threads\n* app contains a list of helper functions that might otherwise require dependencies\n\n\n#### app.config\n\napp.config gives you access to your current configuration vars throughout your app.\n\n```js\n\nconsole.log(app.config.port)\n\n```\n\n#### app.set()\n\napp.set() will set environmental variables available to the scope in which they are called\n\n```js\n/**\n *  app.setEnv(key, val)\n *  @param {string} key //  \n *  @param {string|object|number|buffer} val\n **/\n\n app.set('key', 'val');\n\nconsole.log(app.env('key'));\n// val\n\nconsole.log(process.env.key);\n//val\n```\n\n#### app.env()\n\napp.env() will get environmental variables available from the scope in which they are called\n\n```js\n/**\n *  app.env(key)\n *  @param {string} key\n **/\n\n app.set('key', 'val');\n\nconsole.log(app.env('key'));\n// val\n\n```\n\n#### app.uuid\n\napp.config generates a random uuidv4\n\n```js\n\nconsole.log(app.uuid())\n// 4370139d-653c-49eb-933e-a714eec14f69\n\n```\n\n#### app.bot()\n\napp.bot() will return true if the user-agent detected is a bot\n\n* `config.bot.items` should contain an array of bots to check for\n* this feature could be used to perform targeted seo optimization\n* refer to `headers.bot()`\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  let useragent = headers.ua();\n  if(app.bot(useragent)){\n    console.log('bot detected')\n  }\n\n})\n\n```\n\n\n#### app.fetch()\nthe app.fetch method will perform a secure http2 client request to any local or external address.\n\n* app.fetch uses your apps ssl certificate/s to create a secue connection\n\napp.fetch uses body parser to automatically parse responses for the following content-types:\n\n* `application/json` ~ response.json | data as parsed json object/array\n* `multipart/form-data` ~ response.text | data as string\n* `application/x-www-form-urlencoded`  ~ response.text | data as string\n\nall content-types are available as:\n\n* `*` ~ response.text | data as string\n* `*` ~ response.buffer | data as buffer\n\n\n```js\n\n/**\n *  app.fetch(obj, callback, timeout)\n *  @param {object} obj // cookie name\n *  @param {object} callback // function(err,response)\n    @param {number} timeout // milliseconds ~ optionally overrides config.fetch.tomeout\n **/\n\n/* simple json get example */\n\nlet head = {\n  'url': 'https://example_get_url.com', //dest url\n  ':method': 'GET', // fetch method\n  ':path': '/example/path', // fetch path\n  'Content-Type': 'application/json'\n  // your other headers ...\n}\n\nlet timeout = 5000 // optional\n\napp.fetch(head, function(err,response){\n  if(err){return console.error(err)}\n\n  console.log(response.headers) // response headers object\n  console.log(response.json) // response as json ~ if available\n  console.log(response.buffer) // response as buffer\n  console.log(response.text) // response as text\n  console.log(response.statusText) // ok/not ok\n\n},timeout)\n\n/* simple post example */\n\nlet data = JSON.stringify({test: 'body'});\n\nlet head = {\n  'url': 'https://example_post_url.com', //dest url\n  ':method': 'POST', // fetch method\n  ':path': '/example/path' // fetch path\n  'body':  data// fetch body for accepted methods\n  \"Content-Type\": \"application/json\"\n  // ...\n}\n\napp.fetch(head, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.headers) // response headers object\n  console.log(res.json) // response as json ~ if available\n  console.log(res.buffer) // response as buffer\n  console.log(res.text) // response as text\n  console.log(res.statusText) // ok/not ok\n})\n\n```\n\n#### app.etag()\n\nrefer to `stream` for a more detailed explanation.\n\napp.etag can be used to manually create a hashed etag from data that you may use in a stream.\n\nthe following digests are supported:\n\ninsecure\n* `md5`, `md5-sha1`, `ripemd160`, `rmd160`, `sha1`\n\nsecure\n* `sha224`, `sha256`, `sha384`, `sha512`, `sha512-224`, `sha512-256`, `whirlpool`\n\nexcessive\n* `sha3-224`, `sha3-256`, `sha3-384`,`sha3-512`, `blake2b512`, `blake2s256`, `shake128`,`shake256`\n\n\n```js\n/**\n *  app.etag(digest, data, encode)\n *  @param {string} digest // hash digest\n *  @param {string} data // data to be hashed\n *  @param {string} encode // base64/hex\n **/\n\nrouter.get('/etagdemo', function(stream, headers, flags){\n\n  // manual app.etag\n  let etag = app.etag('sha3-512', 'test string', 'base64');\n  stream.addHeader('Etag', etag)\n\n});\n\n```\n\n#### app.digest()\n\napp.digest can be used to manually create a digest from data for the Digest header.\n\nthe following prefixes are recommended:\n\n* `sha-224`, `sha-256`, `sha-384`, `sha-512`\n\nthe following digests are supported recommended:\n\n* `sha224`, `sha256`, `sha384`, `sha512`\n\nthose are the current recommended standards.\nyou can use any equivalents from the above mentioned in `app.etag` if you want to implement your own standard.\n\n```js\n/**\n *  app.digest(prefix, encode, data, digest)\n *  @param {string} prefix // valid http digest header prefix e.g. sha-256/sha-512\n *  @param {string} digest // valid nodejs digest hash digest\n *  @param {string} data // data to be hashed\n *  @param {string} encode // base64/hex\n **/\n\nrouter.get('/digestdemo', function(stream, headers, flags){\n\n  stream.addHeader('Digest', app.digest('sha-512', 'sha512', 'test digest', 'base64'));\n\n});\n\n```\n\n\n#### app.cookie_encode()\n\nrefer to `stream` for a more detailed explanation.\n\napp.cookie_encode can be used to manually create cookies\n\n```js\n\n/**\n *  app.cookie_encode(key, val, settings)\n *  @param {string} key // cookie name\n *  @param {string} val // cookie value\n *  @param {object} settings // cookie settings\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // manual create cookie and add to outbouheaders\n  let new_cookie = app.cookie_encode('name', 'value',{\n    Domain: 'localhost',\n    Path: '/',\n    Expires: Date.now(),\n    MaxAge: 9999,\n    HttpOnly: true,\n    SameSite: 'Lax',\n    Secure: true,\n    Priority: 'High'\n  })\n  // add cookie to headers\n  stream.addHeader('Set-Cookie', [new_cookie]);\n\n  // send headers \u0026 send json response\n  stream.json({msg: 'cookie created'});\n\n})\n```\n\n#### app.cookie_sign()\n\nrefer to `cookie_parser` for a more detailed explanation.\n\napp.cookie_sign can be used to manually create signed cookies\n\n```js\n\n/**\n *  app.cookie_sign(key, val, settings)\n *  @param {string} key // cookie name\n *  @param {string} val // cookie to sign value\n *  @param {object} settings // cookie settings\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  // manual create cookie and add to outbouheaders\n  let cookie_specs = {\n    Domain: 'localhost',\n    Path: '/',\n    Expires: Date.now(),\n    MaxAge: 9999,\n    HttpOnly: true,\n    SameSite: 'Lax',\n    Secure: true,\n    Priority: 'High'\n  }\n  let new_cookie = app.cookie_encode('name', 'value', cookie_specs),\n  // manual create cookie sig and add to outbouheaders\n  signed_cookie = app.cookie_sign('name', 'value', cookie_specs);\n  // only required for manual add\n  stream.addHeader('Set-Cookie',  [new_cookie, signed_cookie]);\n\n  // send headers \u0026 send json response\n  stream.json({msg: 'cookies created'});\n\n})\n```\n\n#### app.cookie_decode()\n\nrefer to `stream` for a more detailed explanation.\n\napp.cookie_decode can be used to create a deserialized cookies object\n\n```js\n\n/**\n *  app.cookie_decode(key, val, settings)\n *  @param {string} settings // cookie header\n **/\n\n\nrouter.get('/', function(stream, headers, flags){\n\n  // manual return cookies object\n  console.log(app.cookie_decode(headers.get('cookie')))\n\n});\n\n```\n\n#### app.cookie_verify()\n\napp.cookie_verify can be used to verify signed cookies\n\n* app.cookie_verify is for signed cookies only\n* app.cookie_verify will return true if the cookie is valid\n\n```js\n\n/**\n *  app.cookie_verify(name, obj)\n *  @param {string} name // cookie name to verify\n *  @param {object} settings // cookies object\n **/\n\nrouter.get('/', function(stream, headers, flags){\n  // verify cookie with name=name\n  console.log(\n    app.cookie_verify('name', headers.get('cookie'))\n  )\n  // true/false\n\n})\n```\n\n#### app.blacklist()\n\nrefer to blacklist\n\napp.blacklist can be used add ip addresses to your blacklist\n\n* this action controlled by `sync`\n* this action will trigger an update of the blacklist cache for all worker threads\n* no server restart is required.\n\n\n```js\n\n/**\n *  app.blicklist(ip)\n *  @param {string|array} ip // ip address/addresses to add to blacklist\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  app.blacklist(stream.ip)\n\n  // or add multiple in Array\n\n  app.blacklist([stream.ip])\n\n});\n\n```\n\n#### app.whitelist()\n\nrefer to whitelist\n\napp.whitelist can be used add ip address to your whitelist\n\n* this action controlled by `sync`\n* this action will trigger an update of the whitelist cache for all worker threads\n* no server restart is required.\n\n\n```js\n\n/**\n *  app.whitelist(ip)\n *  @param {string|array} ip // ip address or array of address to add to whitelist\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  app.whitelist(stream.ip);\n\n  //or multiple in array\n\n  app.whitelist([stream.ip]);\n\n});\n\n```\n\n#### app.gzip\n\nrefer to `compression` for a more detailed explanation.\n\ngzip compression can be used anywhere via the app.gzip method:\n\n```js\n/**\n *  @app.gzip(data, method, options, callback)\n *\n *  @param {Buffer/TypedArray/DataView/ArrayBuffer/string} data\n *  @param {boolean} method ~ true = compress | false = decompress\n *  @param {object} options ~ optional | fallback to config.compression.gzip.settings\n *  @param {function} callback ~ function(err,res) | optional | no callback for sync\n **/\n\nlet str = 'test'\n//gzipSync\nstr = app.gzip(str, true);\n\n//gunzipSync\nstr = app.gzip(str, false);\n\nconsole.log(str.toString())\n// test\n\n//gzip\napp.gzip(str, true, function(err,res){\n\n  //gunzip\n  app.gzip(res, false, function(err,str){\n    console.log(str.toString())\n    // test\n  })\n})\n```\n\n#### app.brotli\n\nrefer to `compression` for a more detailed explanation.\n\nbrotli compression can be used anywhere via the app.brotli method:\n\n```js\n\n/**\n *  @app.brotli(data, method, options, callback)\n *\n *  @param {Buffer/TypedArray/DataView/ArrayBuffer/string} data\n *  @param {boolean} method ~ true = compress | false = decompress\n *  @param {object} options ~ optional | fallback to config.compression.brotli.settings\n *  @param {function} callback ~ function(err,res) | optional | no callback for sync\n **/\n\nlet str = 'test'\n//brotliCompressSync\nstr = app.brotli(str, true);\n\n//brotliDecompressSync\nstr = app.brotli(str, false);\n\nconsole.log(str.toString())\n// test\n\n//brotliCompress\napp.brotli(str, true, function(err,res){\n\n  //brotliDecompress\n  app.brotli(res, false, function(err,str){\n    console.log(str.toString())\n    // test\n  })\n})\n```\n\n#### app.deflate\n\nrefer to `compression` for a more detailed explanation.\n\ndeflate compression can be used anywhere via the app.deflate method:\n\n```js\n\n/**\n *  @app.deflate(data, method, options, callback)\n *\n *  @param {Buffer/TypedArray/DataView/ArrayBuffer/string} data\n *  @param {boolean} method ~ true = compress | false = decompress\n *  @param {object} options ~ optional | fallback to config.compression.deflate.settings\n *  @param {function} callback ~ function(err,res) | optional | no callback for sync\n **/\n\nlet str = 'test'\n//deflateSync\nstr = app.deflate(str, true);\n\n//inflateSync\nstr = app.deflate(str, false);\n\nconsole.log(str.toString())\n// test\n\n//deflate\napp.deflate(str, true, function(err,res){\n\n  //inflate\n  app.deflate(res, false, function(err,str){\n    console.log(str.toString())\n    // test\n  })\n})\n\n```\n\n#### app.engine.add()\nrefer to template engines\n\nextra template engines can be added using `app.engine.add`:\n* `sicarii/lib/adapters` will contain your new engine template.\n* `config.template_engine` will automatically be updated with your settings\n\n\n```js\n\n/**\n *  app.engine.add(title, obj, callback)\n *  @param {string} title // template engine title in snake_case\n *  @param {object} obj // data new engine\n *  @param {function} callback function(err)\n **/\n\napp.engine.add('test', {\n  \"enabled\": false, // must have enabled\n  \"settings\": {\n    \"use_globals\": false,\n    \"globals\":{}\n  }\n}, function(err){\n  if(err){return console.error(err)}\n})\n\n```\n\n#### app.engine.del()\nrefer to template engines\n\nextra engines can be deleted using `app.engine.del`:\n* `sicarii/lib/adapters` will have the adapter removed\n* `config.template_engine` will automatically remove the engine/s\n* this action should be called for production to minimize sicarii's size\n* this action cannot be undone.\n\n\n```js\n\n/**\n *  app.engine.del(items, callback)\n *  @param {array} items // template engine items to remove\n *  @param {function} callback function(err)\n **/\n\napp.engine.del(['pug','twig', 'nunjucks', 'ejs'], function(err){\n  if(err){return console.error(err)}\n})\n\n```\n\n#### app.qs()\n\napp.qs will return a serialized query string from valid json object\n\n```js\n\n/**\n *  app.qs(items, sep, eq)\n *  @param {object} items // query string object\n *  @param {string} sep // query string separetor ~ defalts to \u0026\n *  @param {string} eq // query string equals ~ defalts to =\n **/\n\n var data = {\n   test: '%^\u0026*$#hsdacsddf',\n   test2: 2345\n }\n\n console.log(app.qs(data))\n // test=%25%5E%26*%24%23hsdacsddf\u0026test2=2345\n\n```\n\n#### app.path()\n\napp.path returns a json object containing the parsed path data\n\n```js\n\n/**\n *  app.path(path)\n *  @param {string} path // path string\n **/\n\n let somepath = app.path('sicarii/lib/utils.js');\n\n console.log(somepath)\n // {root: '',dir: 'sicarii/lib',base: 'utils.js',ext: '.js', name: 'utils'}\n\n console.log(somepath.base)\n // utils.js\n\n```\n\n#### app.url()\n\napp.url returns a json object containing the parsed url data\n\n```js\n\n/**\n *  app.url(path)\n *  @param {string} path // path string\n **/\n\n let someurl = app.url('https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash');\n\n console.log(someurl)\n\n /*\n   {\n     protocol: 'https:',\n     slashes: true,\n     auth: 'user:pass',\n     host: 'sub.example.com:8080',\n     port: '8080',\n     hostname: 'sub.example.com',\n     hash: '#hash',\n     search: '?query=string',\n     query: { query: 'string' },\n     pathname: '/p/a/t/h',\n     path: '/p/a/t/h?query=string',\n     href: 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash'\n   }\n\n */\n\n```\n\n#### app.dns.get()\n\n* perform dns lookup\n* returns json object containing results\n\n```js\n/**\n *  app.dns.reverse(path, cnf, callback)\n *  @param {string} path // path string\n *  @param {object} cnf // optional nodejs dns.lookup.options\n *  @param {function} callback // function(err,res)\n **/\n\n app.dns.get('example.org', function(err, data){\n   if(err){return console.log(err)}\n   //{address: 'someaddress', family: 'somefamily'}\n });\n\n```\n\n#### app.dns.getService()\n\n* perform dns lookupService\n* returns json object containing results\n\n```js\n/**\n *  app.dns.getService(path,port,callback)\n *  @param {string} path // path string\n *  @param {number} port // port\n *  @param {function} callback // function(err,res)\n **/\n\n app.dns.getService('127.0.0.1', 80, function(err, data){\n   if(err){return console.log(err)}\n   console.log(data)\n   //{ hostname: 'localhost', service: 'http' }\n });\n\n```\n\n#### app.dns.reverse()\n* perform dns reverse\n* returns json array containing results\n\n```js\n/**\n *  app.dns.reverse(path, callback)\n *  @param {string} path // path string\n *  @param {function} callback // function(err,res)\n **/\n\n app.dns.reverse('208.67.222.222',function(err, hostnames){\n   if(err){return cb(err)}\n   console.log(hostnames)\n   // [ 'resolver1.opendns.com' ]\n })\n\n```\n\n#### app.encode()\n* convert between data encoding\n\nsupported conversions\n* buffer|utf8|hex|base64|Uint8Array|Uint16Array|Uint32Array|Int8Array|Int16Array|Int32Array\n\n```js\n\n/**\n *  app.encode(data, from, to)\n *  @param {string} data // path string\n *  @param {string} from // data current encoding\n *  @param {string} to // ~ optional data to be encoded to\n **/\n\nlet str = 'test string';\n\n// basic encode to buffer\napp.encode(str, 'utf8') // utf8 string to buffer\n\n// convert between\nstr = app.encode(str, 'utf8', 'base64') // from utf8 string to base64\n\nstr = app.encode(str, 'base64', 'hex') // base64 string to hex\n\nstr = app.encode(str, 'hex', 'Uint8Array') // hex string to Uint8Array\n\n// and so on ...\n\n```\n\n# Body parser\n- [Back to index](#documentation)\n\nsicarii has its own built in body parser for the following content types:\n\n* `application/json`\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n\nThese content types can be enabled/disabled at `config.stream.content_types`.\n\nif you are not using it, remove it from `config.stream.content_types` to improve both security and performance.\n\nThe correct content type headers must be sent with the request.\n\n`multipart/form-data` and `application/x-www-form-urlencoded` will automatically be parsed to valid json.\n\nfor example:\n```js\n// query\nrouter.get('/content', function(stream, headers, flags){\n  let query = stream.query; //json object\n\n})\n\n// body\nrouter.post('/content', function(stream, headers, flags){\n  let body = stream.body.json; //json object\n  body = stream.body.buffer; //nodejs buffer\n  body = stream.body.text; //string\n})\n```\n\nAll other content types are available as `text` or `buffer`\n\n# Etags\n- [Back to index](#documentation)\n\nsicarii has its own built in configurable in Etag generator.\n\nit provides separate options for `render/document`  to `static` files and can also be manually overridden\nor manually added on a per case basis.\n\n* automatic `render/document` Etags can be configured at `config.render.etag`\n* automatic `static` file Etags can be configured at `config.static.etag`\n* automatic etags will use cache settings from `config.render.cache` or `config.static.cache` if available\n* etags support either `base64` or `hex` encoding.\n\n\nthe following digests are supported:\n\ninsecure\n* `md5`, `md5-sha1`, `ripemd160`, `rmd160`, `sha1`\n\nsecure\n* `sha224`, `sha256`, `sha384`, `sha512`, `sha512-224`, `sha512-256`, `whirlpool`\n\nexcessive\n* `sha3-224`, `sha3-256`, `sha3-384`,`sha3-512`, `blake2b512`, `blake2s256`, `shake128`,`shake256`\n\nEtags can be manually added using either an `app.etag` or `stram.etag` function like so:\n\n```js\n\n/**\n *  stream.etag(digest, data, encode)\n *  @param {string} digest // hash digest\n *  @param {string} data // data to be hashed\n *  @param {string} encode // base64/hex\n **/\n\nrouter.get('/etagdemo', function(stream, headers, flags){\n\n  // manual app.etag\n  stream.addHeader('Etag', app.etag('sha3-512', 'test string', 'base64'));\n\n  // manual stream.etag ~ will automatically add to stream.headers\n  stream.etag('sha3-512', 'test string', 'base64');\n\n  stream.respond(stream.headers)\n\n  stream.end('test etag')\n\n});\n\n```\n\nAs etags are hashed from the data being sent, they can also easily double as the Digest header:\n\n```js\nrouter.get('/etagdemo', function(stream, headers, flags){\n\n\n  // manual stream ~ will automatically add to stream.headers\n  stream.etag('sha3-512', 'test string', 'base64');\n\n  // set Digest header using hash from Etag\n  stream.addHeader('Digest', 'sha-256=' + stream.headers['Etag']);\n\n\n  stream.respond(stream.headers)\n\n  stream.end('test etag')\n\n});\n\n```\n\n# Cookie parser\n- [Back to index](#documentation)\n\nsicarii has its own built in cookie parser.\n* the cookie parser can be enabled/disabled at `config.cookie_parser.enabled`\n* with `config.cookie_parser.auto_parse` enabled, inbound cookies will automatically be parsed to json.\n* if the cookie parser is disabled, cookies can still be created/parsed through `app.cookie_encode()`/`app.cookie_decode()`.\n\n#### encode cookie\nsicarii has two methods for creating serialized cookies.\n\n* this method has support for multiple cookies\n* this method can create a separate signed cookie for tamper detection\n* `config.cookie_parser.sig.secret` is used to hmac the cookie\n* `config.cookie_parser.sig.suffix` is the signed cookies suffix\n* a signed cookie will be will use digest/encode settings from `config.crypt.hmac`\n* a signed cookie will be will use digest/encode settings from `config.crypt.hmac`\n\n```js\n\n/**\n *  stream.cookie(key, val, settings)\n *  app.cookie(key, val, settings)\n *  @param {string} key // cookie name\n *  @param {string} val // cookie value\n *  @param {object} settings // cookie settings\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  //create cookie and add to outbouheaders ~ config.cookie_parser.enabled\n  stream.cookie('name', 'value',{\n    Domain: 'localhost',\n    Path: '/',\n    Expires: Date.now(),\n    MaxAge: 9999,\n    HttpOnly: true,\n    SameSite: 'Lax',\n    Secure: true,\n    Priority: 'High',\n    Signed: true\n  })\n\n\n  // manual create cookie and add to outbouheaders\n  let cookie_specs = {\n    Domain: 'localhost',\n    Path: '/',\n    Expires: Date.now(),\n    MaxAge: 9999,\n    HttpOnly: true,\n    SameSite: 'Lax',\n    Secure: true,\n    Priority: 'High'\n  }\n  let new_cookie = app.cookie_encode('name', 'value', cookie_specs),\n  // manual create cookie sig and add to outbouheaders\n  signed_cookie = app.cookie_sign('name', 'value', cookie_specs);\n  // only required for manual add\n  stream.addHeader('Set-Cookie',  [new_cookie, signed_cookie]);\n\n  // send headers \u0026 send json response\n  stream.json({msg: 'cookies created'});\n\n})\n```\n\n\n#### decode cookie\nsicarii has three methods for returning a deserialized cookies object\n```js\n\n/**\n *  app.cookie_decode(key, val, settings)\n *  @param {string} settings // cookie header\n **/\n\n\nrouter.get('/', function(stream, headers, flags){\n\n  // return cookies object\n  console.log(headers.cookies())\n\n   // return automatically parsed cookies object ~ config.cookie_parser.auto_parse\n   console.log(stream.cookies)\n\n  // manual return cookies object\n  console.log(app.cookie_decode(headers.get('cookie')))\n\n});\n\n```\n\n\n# Template engines\n- [Back to index](#documentation)\n\nsicarii has the ability to render, cache and compress templates engine templates.\nrefer to `stream.render` for further details\n\n* template engines can be configured at `config.template_engine`\n* templates are rendered with `stream.render`\n* templates use settings from `config.render`\n* templates are compresses if compression is enabled\n* templates are cached if `config.render.cache` is enabled\n* all sicarii template engine adapters are asynchronous.\n* automatic error handling is provided for consistency across all engines\n* each engines individual error messages are still provided in the callback\n\n\n```js\n/**\n *  stream.render(path, obj, callback)\n *  @param {string} path // file path relative to render dir\n *  @param {object} obj // data for rendered file\n *  @param {function} callback ~ optional\n **/\n\n //send headers and rendered doc\n stream.render('index.html', {title: 'basic'})\n\n //or\n\n // send headers and rendered doc\n stream.render('index.html', {title: 'basic'}, function(err){\n   if(err){\n     // the stream has ended and automatic error handling has been provided.\n     return console.log(err)\n   }\n\n   // do something...\n })\n```\n\nsicarii currently supports the following engines:\n\n#### default\n\n* default engine, renders html files with javascript template literals included\n* the default engine is ideal for single page apps or when you do not require extra features\n* the default engine does not require any additional installation\n\n ```js\n\n router.get('/', function(stream, headers, flags){\n\n   // send default headers and render index.html\n   stream.render('index.html', {title: 'basic'})\n\n });\n\n ```\n\n index.html\n ```html\n \u003ctitle\u003e${title}\u003c/title\u003e\n\n ```\n\n#### poorboy\n\n* renders html from javascript\n* write your templates in plain javascript\n* poorboy engine does not require any additional installation\n* poorboy can be extended to use html parsing modules\n* poorboy can be used to render pre-compiled templates from any other engine\n* poorboy is fast\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  // send default headers and render index.js\n  stream.render('index.js', {\n    title: 'poorboy',\n    people: ['bob', 'alice']\n  })\n\n});\n\n```\n\nbasic example\n\n```js\n// /render/index.js\n\nmodule.exports = (data) =\u003e `\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003e${data.title}\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\u003c/body\u003e\n\u003c/html\u003e\n`\n\n```\n\nadvanced example\n\n```js\n// /render/index.js\n\nconst includes = require('./includes'); //cached\nmodule.exports = (data) =\u003e `\n\u003chtml\u003e\n  \u003chead\u003e\n    ${includes.title(data.title)}\n  \u003c/head\u003e\n  \u003cbody\u003e\n   ${includes.group(data.people)}\n  \u003c/body\u003e\n\u003c/html\u003e\n`\n\n```\n\n```js\n// /render/includes.js\n\nconst includes = module.exports = {\n  title: (i) =\u003e '\u003ctitle\u003e'+ i +'\u003c/title\u003e',\n  li: (i) =\u003e '\u003cli\u003e'+ i +'\u003c/li\u003e',\n  group: (i) =\u003e {\n    let ul = '\u003cul\u003e';\n    for (let x = 0; x \u003c i.length; x++) {\n      ul += includes.li(i[x])\n    }\n    ul += '\u003c/ul\u003e';\n    return ul;\n  }\n}\n\n```\n\nextended example\n\n```js\n// /render/index.js\nconst cheerio = require('cheerio'); // cached\nconst includes = require('./includes'); //cached\n\nmodule.exports = (data) =\u003e {\n\n  const $ = cheerio.load(includes.body(data.title))\n\n  let test = $('\u003cul /\u003e').attr({id: 'test'})\n  $(data.people).each(function(i){\n    test.append(includes.li(data.people[i]))\n  })\n\n  $('body').append(\n    $('\u003ch1 /\u003e').text('Hello world'),\n    test\n  )\n\n  return $.html()\n\n}\n\n```\n\n```js\n// /render/includes.js\n\nconst includes = module.exports = {\n  body: (i) =\u003e '\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e'+ i +'\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e',\n  li: (i) =\u003e '\u003cli\u003e'+ i +'\u003c/li\u003e'\n}\n\n```\n\n#### nunjucks\n\n* usage of nunjucks requires nunjucks to be pre installed\n* do not set nunjucks to cache templates as this will be done by sicarii\n* `config.template_engine.nunjucks.filters` is a path to filters file relative to cwd\n* `config.template_engine.nunjucks.globals.vars` are added to all renders\n* refer to nunjucks documentation for further details\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n  // send default headers and render index.njk\n  stream.render('index.njk', {title: 'nunjucks'})\n});\n\n```\n\nindex.njk\n```html\n\u003ctitle\u003e{{title}}\u003c/title\u003e\n\n```\n\ncustom filters can be added like so:\n\n```js\n/*\n\"nunjucks\": {\n  \"enabled\": true,\n  \"filters\": \"/path/to/filters.js\"\n}\n*/\n\n\n// filters.js\nmodule.exports = {\n  shorten: function(str, count) {\n    return str.slice(0, count || 1);\n  }\n}\n\n```\n\n\n#### liquidjs\n\n* usage of liquidjs requires liquidjs to be pre installed\n* do not set liquidjs to cache templates as this will be done by sicarii\n* refer to liquidjs documentation for further details\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  // send default headers and render index.liquid\n  stream.render('index.liquid', {title: 'liquidjs'})\n\n});\n\n```\n\nindex.liquid\n```html\n\u003ctitle\u003e{{title}}\u003c/title\u003e\n\n```\n\n#### squirrelly\n\n* usage of squirrelly requires nunjucks to be pre installed\n* do not set squirrelly to cache templates as this will be done by sicarii\n* refer to squirrelly documentation for further details\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  // send default headers and render index.html\n  stream.render('index.html', {title: 'squirrelly'})\n\n});\n\n```\n\nindex.html\n```html\n\u003ctitle\u003e{{it.title}}\u003c/title\u003e\n\n```\n\n#### twig\n\n* usage of twig requires twig to be pre installed\n* refer to twig documentation for further details\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n  // send default headers and render index.twig\n  stream.render('index.twig', {title: 'twig'})\n\n});\n\n```\n\nindex.twig\n```html\n\u003ctitle\u003e{{title}}\u003c/title\u003e\n\n```\n\n#### ejs\n\n * usage of ejs requires ejs to be pre installed\n * do not set ejs to cache templates as this will be done by sicarii\n * refer to ejs documentation for further details\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n // send default headers and render index.ejs\n stream.render('index.ejs', {title: 'ejs'})\n\n});\n\n```\n\nindex.ejs\n```html\n\u003ctitle\u003e\u003c%= title %\u003e\u003c/title\u003e\n\n```\n\n#### ect\n\n * usage of ect requires ect to be pre installed\n * do not set ect to cache templates as this will be done by sicarii\n * refer to ect documentation for further details\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n // send default headers and render index.ect\n stream.render('index.ect', {title: 'ect'})\n\n});\n\n```\n\nindex.ect\n```html\n\u003ctitle\u003e\u003c%= @title %\u003e\u003c/title\u003e\n\n```\n\n#### eta\n\n * usage of eta requires eta to be pre installed\n * do not set eta to cache templates as this will be done by sicarii\n * refer to eta documentation for further details\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n\n // send default headers and render index.eta\n stream.render('index.eta', {title: 'eta'})\n\n});\n\n```\n\nindex.eta\n```html\n\u003ctitle\u003e\u003c%= it.title %\u003e\u003c/title\u003e\n\n```\n\n\n#### pug\n\n * usage of pug requires pug to be pre installed\n * do not set pug to cache templates as this will be done by sicarii\n * refer to pug documentation for further details\n\n ```js\n\n router.get('/', function(stream, headers, flags){\n\n    // send default headers and render index.pug\n   stream.render('index.pug', {title: 'pug'})\n\n });\n\n ```\n\n index.pug\n ```pug\n html\n      head\n          title #{title}\n\n ```\n\n#### mustache\n\n* usage of mustache requires mustache to be pre installed\n* this async adapter is specific to sicarii\n* mustache has 0 dependencies\n\n* refer to mustache documentation for further details\n\n```js\n\nrouter.get('/', function(stream, headers, flags){\n   // send default headers and render index.html with included partial\n  stream.status(200).render('index.html', {\n    partials: { // include mustache partials in external docs here\n      user: '/partial_user.html', // path to partial relative to render dir\n      years: '/partial_age.html' // path to partial relative to render dir\n    },\n    title: 'test title', // template vars\n    name: 'jack',\n    age: '999'\n  })\n\n});\n\n```\n\nindex.html\n```html\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003e{{title}}\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    my name is {{#name}}{{\u003e user}}{{/name}}\u003cbr\u003e\n    i am {{#age}}{{\u003e years}}{{/age}} years old\n  \u003c/body\u003e\n\u003c/html\u003e\n\n```\n\npartial_user.html\n```html\n\u003cstrong\u003e{{name}}\u003c/strong\u003e\n```\n\npartial_age.html\n```html\n\u003cstrong\u003e{{age}}\u003c/strong\u003e\n```\n\n#### extend\nsicarii template engines is easily extendable\n\n* note ~ extra template engines are currently being added to sicarii\n* template engines can be added or removed\n\n#### add engine\nextra engines can be added using `app.engine.add`:\n* `sicarii/lib/adapters` will contain your new engine template.\n* `config.template_engine` will automatically be updated with your settings\n\n\n```js\n\n/**\n *  app.engine.add(title, obj, callback)\n *  @param {string} title // template engine title in snake_case\n *  @param {object} obj // data new engine\n *  @param {function} callback function(err)\n **/\n\napp.engine.add('test', {\n  \"enabled\": false, // must have enabled\n  \"settings\": {\n    \"use_globals\": false,\n    \"globals\":{}\n  }\n}, function(err){\n  if(err){return console.error(err)}\n})\n\n```\n\nextra engines can be manually added the following way:\n\n* `sicarii/lib/adapters` contains templates that you can use as a base to adapt any template engine.\n* clone one of the template files and rename it\n* edit the cloned file to accept your template engine\n* add the template engine to `config.template_engine.engines` using the same cloned files name\n* duplicate `config.template_engine.default`, rename it, add your settings and enable it.\n* the file name must be the same ass `config.template_engine[your_file_name]`\n* the adapters you are not using can be deleted and removed from config for production\n\nan example of how easy it is to add a template engine to sicarii:\n\n```js\n\n// /sicarii/lib/adapters/ejs.js\n\nconst ejs = require(\"ejs\"),\nconfig = require(process.env.config_file),\nutils = require('../utils'),\nsettings = config.template_engine.ejs.settings;\n\nmodule.exports = function(stream, file, src, url, data, cb){\n  ejs.renderFile(file, data, settings, function(err, data){\n    if(err){\n      utils.err(stream, 'GET', url, 500, 'ejs template render error')\n      if(cb){cb(err)}\n      return;\n    }\n    utils.render_sort(stream, data, url, cb);\n  });\n}\n\n```\n\nyou are simply passing your template engines data through\nto `utils.render_sort` in an async way.\n\n\n#### delete engine\nextra engines can be deleted using `app.engine.del`:\n* `sicarii/lib/adapters` will have the adapter removed\n* `config.template_engine` will automatically remove the engine/s\n* this action should be called for production to minimize sicarii's size\n* this action cannot be undone.\n\n\n```js\n\n/**\n *  app.engine.del(items, callback)\n *  @param {array} items // template engine items to remove\n *  @param {function} callback function(err)\n **/\n\napp.engine.del(['pug','twig', 'nunjucks', 'ejs'], function(err){\n  if(err){return console.error(err)}\n})\n\n```\n\n# Botnet\n- [Back to index](#documentation)\n\nsicarii has its own built in bot detect and block features\n\n* block unwanted bots from accessing data.\n* create targeted seo responses for search engines.\n\n#### bot block\n\n* bot blocking can be configured at `config.bot.block`\n* bot user-agent sub-strings can be manually added to `config.bot.block.items`\n* `config.bot.block.enabled` will enable blocking of all bot user-agent\n  sub-string matches within `config.bot.block.items`\n\n#### bot detect\n\n* bot detection can be configured at `config.bot.detect`\n* bot user-agent sub-strings can be manually added to `config.bot.detect.items`\n\n\n```js\n\n router.get('/', function(stream, headers, flags){\n\n\n   if(headers.bot()){\n     // render template containing seo data only\n     let useragent = headers.ua();\n\n     if(useragent.includes('google')){\n       stream.render('index_seo_google.html', {\n         data: {\n           some: 'google data',\n           specifically: 'relating',\n           to: 'google seo'\n         }\n       })\n     } else if(useragent.includes('facebook')) {\n       stream.render('index_seo_facebook.html', {\n         data: {\n           some: 'facebook data',\n           specifically: 'relating',\n           to: 'facebook seo'\n         }\n       })\n     } else {\n       stream.render('index_seo_default.html', {\n         data: {\n           some: 'default data',\n           specifically: 'relating',\n           to: 'default seo'\n         }\n       })\n     }\n   } else {\n     // render normal template not polluted with seo\n     stream.render('index.html', {title: 'basic'})\n   }\n\n\n })\n\n```\n\n# Ip blacklist\n- [Back to index](#documentation)\n\nsicarii has its own built in ip blacklist\n\n* the ip blacklist can be configured at `config.blacklist`\n* the ip blacklist is controlled by `sync`\n* ip addresses can be manually added to `./config/ip_config.json`\n* dynamically adding a blacklist via `app.blacklist` will sync across all worker threads\n* ip addresses that have been blacklisted will be denied access globally to all worker servers\n\n```js\n\n/**\n *  app.blicklist(ip)\n *  @param {string} ip // ip address to add to blacklist\n **/\n\nrouter.get('/', function(stream, headers, flags){\n\n  app.blacklist(stream.ip)\n\n});\n\n```\n\n# Ip whitelist\n- [Back to index](#documentation)\n\nsicarii has its own built in ip whitelist for both master and worker servers\n\n* the ip whitelist can be configured at `config.whitelist` for workers\n* the ip whitelist can be configured at `config.cache.whitelist` for the master server\n* ip addresses can be manually added to `./config/ip_config.json`\n* ip addresses that have not been whitelisted will be denied access to the master/worker servers\n* this feature should be enabled for production on the master server\n\n```js\n\nconst { app } = require('sicarii');\n\napp.whitelist('some.ip.address')\n\n\n```\n\n# Auth-token\n- [Back to index](#documentation)\n\nsicarii has its own built in header auth-token authentication for both master and worker servers\n\n* the auth-token can be configured at `config.authtoken` for workers\n* the auth-token can be configured at `config.cache.authtoken` for the master server\n* streams that do not have the correct auth-token header will be denied access to the master/worker servers\n* this feature should be enabled for production on the master server\n\n# Cache\n- [Back to index](#documentation)\n\nsicarii has its own built in easily extendable and multi-thread compatible in-memory cache.\n\n* the same cache is shared over all worker-threads to prevent cache duplication.\n* the cache can act as a standalone app for remote usage.\n* the cache supports auth-token and ip authentication for local or remote access.\n* the cache can be hosted locally or remotely.\n* the cache will store compressed streams if either of `gzip/brotli/deflate` are enabled.\n* `render/document` cache can be configured at `config.render.cache`\n* the `render/static` cache will store headers as well as the document.\n* the `render/static` cache will automatically remove items dated past their maxage settings.\n* `static` file cache can be configured at `config.render.static`.\n* if `config.verbose` is enabled, the cache status of a render/doc/file... will be logged to console.\n* the cache module MUST be initiated outside of the worker scope.\n* not doing so would would pointlessly spawn multiple instances of the cache.\n* one instance of cache shares data with all instances of workers.\n* cache has its own `server` object that has been named the same as your apps `server` help to prevent spawning both on the same thread.\n* the cache server can be configured at `config.cache`.\n* the cache port can be set at `config.cache.port`\n* `config.cache.server` accepts all nodejs http2 configuration\n\n\n#### authentication\n\nthe cache server can be authenticated by way of auth-token and/or ip whitelist\n\n* the ip whitelist `config.cache.whitelist` will limit access to the ip addresses in `config.cache.whitelist.ip`\n* the ip authtoken `config.cache.authtoken` will require the specified token header and secret upon connection.\n\n\n#### usage\n\nbelow is `one` example of a `correct` way and an `incorrect` way to setup cache.\n```js\n\nconst { app, cluster } = require('sicarii');\n\nif(cluster.isMaster) {\n\n  /* CORRECT! */\n  const { sync, Cache, server } = require('sicarii/master');\n\n\n  // cache extensions here\n\n  // start cache server manually\n  server.listen()\n\n  // or\n\n  // start cache server with sync\n  sync.init().respawn().listen();\n\n\n} else {\n\n  const { server, router } = require('sicarii/main');\n\n  /* INCORRECT! */\n  const { server } = require('sicarii/master');\n\n\n  //\n  server.listen()\n  server.listen(app.config.port)\n\n}\n\n```\n\n#### cache object\n\nthe cache has the following collections which are `reserved` for sicarii internal usage.\n\n```js\n\n{\n  \"render\": [], // render/document cache\n  \"static\": [], // static cache\n  \"session\": [], // session cache\n  \"store\": []\n}\n\n```\n\n#### cache internal methods\n\nthe cache has the following Methods which are `reserved` for sicarii internal usage.\nyou may use these but should not change them:\n\n```js\n\n/**\n *  @param {string} collection ~ cache collection\n *  @param {object} obj ~ request settings\n **/\n\n// used to add an object within to a collection\nCache.add_cache(collection, obj);\n// Cache.add_cache('store', {key: 'val'});\n\n// used to find an object within a collection\nCache.get_cache(collection, obj);\n\n//used to delete an object by index from a collection\nCache.del_cache_index(collection, obj)\n\n//used to reset a collection\nCache.reset_cache(collection)\n\n//used to import a collection\nCache.import_cache(collection, obj)\n\n//used to export a collection to file\nCache.export_cache(collection, obj)\n\n```\n\n#### cache extend\n\n* the cache server does not share the same nodejs method extensions as your app server.\n\n\nthe Cache and server objects can be easily extended to add your own methods like so:\n\n```js\n\nif(cluster.isMaster) {\n\n  const { sync, Cache, server } = require('sicarii/master');\n\n  /* add to the Cache object */\n\n  //return a collection\n  Cache.prototype.return_example = function(collection){\n    return this[collection];\n  }\n\n  //add a new collection\n  Cache.prototype.new_collection_example = function(collection, obj){\n    this[collection] = obj.new_name;\n    return this;\n  }\n\n  //add a new object to a collection\n  Cache.prototype.new_entry_example = function(collection, obj){\n    this[collection].push(obj)\n    return this\n  }\n\n\n  /* add to or extend the caches server object */\n\n  //add custom error handler to cache server.\n  server.on('error', function(err){\n    console.log(err)\n  })\n\n  //extend on listening to include extra data.\n  server.on('listening', function(err,res){\n    console.log('i am the caches server')\n  })\n\n  // all extensions should be added prior to starting server\n  // server.listen / sync.listen will create the new Cache() object\n  //server.listen()\n\n  sync.init().respawn().listen(/* optional callback*/);\n\n}\n\n\n```\n\n#### cache api\nthe cache can be accessed via either or both of the server/browser depending on your settings.\n\n```js\n\n/* api object */\n\nlet cache_obj = {\n  method: 'val', //the cache method to use\n  src: 'static', // the collection name\n  data: {\n    //the data object with settings/data specific to the method if any.\n  }\n}\n```\n\n#### cache http2 client request using app.fetch\n\n```js\n/* app.fetch example */\n\n\nlet head = {\n  'url': app.config.cache.url, //dest url\n  ':method': 'POST', // fetch method\n  ':path': '/', // fetch path\n  'Content-Type': 'application/json',\n  'X-Auth-Token': 'secret',\n  'body':  JSON.stringify(cache_obj)// fetch body for accepted methods\n}\n\napp.fetch(head, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.json)\n})\n\n```\n\n#### cache http2 client request\n\n```js\n\n/* server example */\n\nconst http2 = require('http2');\n\nlet options = app.set_cert();\n\noptions = Object.assign(options, app.config.server);\n\nclient = http2.connect(app.config.cache.url, options),\nhead = {\n  ':method': 'POST',\n  ':path': '/',\n  'Content-Type': 'application/json',\n  'X-Auth-Token': 'secret'\n},\nstream = client.request(head),\nbody = '';\n\nstream.setEncoding('utf8');\n\nstream.on('response', function(headers){\n  console.log(headers)\n});\n\nstream.on('data', function(chunk){\n  body += chunk;\n});\n\nstream.on('end', function(data){\n  // parse and log result\n  console.log(JSON.parse(body));\n});\n\n// send api object\nstream.end(JSON.stringify(cache_obj), 'utf8');\n\n```\n\n#### cache Browser fetch request\n\n```js\n/* browser example */\n\nfetch('https://localhost:5000/',{\n  method: 'POST',\n  headers: {\n    'content-type': 'application/json',\n    'Sec-Fetch-mode': 'cors',\n    'Sec-Fetch-Site': 'cross-site',\n    'X-Auth-Token': 'secret'\n  },\n  body: JSON.stringify(cache_obj)\n})\n.then(function(res){\n  res.json().then(function(data){\n    console.log(data)\n  })\n})\n.catch(function(err){\n  console.log(err)\n})\n```\n\n# Store\n- [Back to index](#documentation)\n\nsicarii has its own built in easily extendable and multi-thread compatible in-memory/flat-file json store.\n\n* the same store is shared over all worker-threads.\n* store is built into the Cache object\n* store supports auth-token and ip authentication for local or remote access.\n* store can be hosted locally or remotely.\n* store is initiated with Cache.\n* a current timestamp is added to every new store object automatically\n* store is accessed via the `app` object\n* store is available to the master and worker scopes\n* store items must have a unique id\n\n\n#### store api\n\n* the store api is similar to the session api, with a few extra methods\n\n```js\n\n/**\n *  app.store(method, data, callback)\n *  @param {string} method ~ store data\n *  @param {object|array} data ~ store data compatible with method\n *  @param {function} callback ~ function(err,res)\n **/\n\n\nlet obj = {\n  id: app.uuid(),\n  user: 'test',\n  token: 'secret',\n  age 5\n}\n\n// add or update a store object with the same id\n// a date timestamp is automatically added\n// adds object to end of collection\napp.store('add', obj, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// unshift or update a store object with the same id\n// a date timestamp is automatically added\n// adds object to beginning of collection\napp.store('unshift', obj, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n\n// find a store object\napp.store('find', {user: 'test'}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// find the index of a store object\n// returns -1 for not found\napp.store('findIndex', {user: 'test'}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// replace or add additional values to a store object\napp.store('assign', [{user: 'test'}, {age: 23, token: 'newtoken'}], function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.data)\n});\n\n// assign values to each included key of each object in store\napp.store('each', {add: 'this', and: 'this', to: 'each'}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.data)\n});\n\n// delete keys from each object in store\napp.store('omit', ['add', 'and', 'to'], function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// return chunked section of store\n// app.store('chunk', ['chunk size', 'chunk count'])\napp.store('chunk', [2,3], function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.data.data)\n});\n\n// sort store by key and return count amount\n// count is optional\napp.store('sort', {key: 'id', count: 2}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.data)\n});\n\n// delete a store object\napp.store('delete', {user: 'test'}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// get the first x amount of store objects\napp.store('first',  5, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// get the last x amount of store objects\napp.store('last',  5, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// return filtered store objects by values greater than\napp.store('gt', {age: 4}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.data)\n});\n\n// return filtered store objects by values less than\napp.store('lt', {age: 4}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.data)\n});\n\n// return filtered store objects by values greater than or equal to\napp.store('gte', {age: 4}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.data)\n});\n\n// return filtered store objects by values less than or equal to\napp.store('lte', {age: 4}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.data)\n});\n\n// return store collection\napp.store('val', function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n//add multiple objects at the same time\napp.store('concat', [{id: 1}, {id: 2}, {id:3}], function(err,res){\n  if(err){return console.error(err)}\n  console.log(res.data)\n});\n\n\n// save store to file at config.store.path\n// file write is non blocking\napp.store('write', function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// load store into cache from config.store.path\n// this action should only be called once\napp.store('read', function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n```\n\n#### store extend\n\nthe store object can be easily extended via the Cache object to add your own methods like so:\n\n```js\n\nif(cluster.isMaster) {\n\n  const { sync, Cache } = require('sicarii/master');\n\n\n  // Cache.store_[YOUR METHOD NAME]\n\n  // create function to reset store\n  Cache.prototype.store_reset = function(src, obj){\n    //src = 'store'\n    //obj = data\n    this[src] = [];\n    return { success: true, msg: 'store reset' }\n  }\n\n\n  // start app\n  sync.init().respawn().listen(/* optional callback*/);\n\n  // reset sessions\n  app.store('reset', function(err,res){\n    if(err){return console.error(err)}\n    console.log(res)\n    // { success: true, msg: 'store reset' }\n  })\n\n}\n```\n\n# Sessions\n- [Back to index](#documentation)\n\nsicarii has its own built in easily extendable and multi-thread compatible in-memory session store.\n\n* the same sessions are shared over all worker-threads.\n* sessions is built into the Cache object\n* sessions supports auth-token and ip authentication for local or remote access.\n* sessions can be hosted locally or remotely.\n* sessions is initiated with Cache.\n* a current timestamp is added to every new session object automatically\n* sessions are accessed via the `app` object\n* sessions are available to the master and worker scopes\n* session items must have an unique id\n\n\n#### session api\n\n* the session api is similar to the store api, with a few exclusions\n\n```js\n\n/**\n *  app.session(method, data, callback)\n *  @param {string} method ~ session data\n *  @param {object} data ~ session data\n *  @param {function} callback ~ function(err,res)\n **/\n\n\nlet obj = {\n  id: app.uuid(),\n  user: 'test',\n  token: 'secret',\n  age 5\n}\n\n// add or update a session object with the same id\n// a date timestamp is automatically added\n// adds object to end of collection\napp.session('add', obj, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// unshift or update a session object with the same id\n// a date timestamp is automatically added\n// adds object to beginning of collection\napp.session('unshift', obj, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n\n// find a session object or automatically delete expired session\napp.session('find', {user: 'test'}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// find the index of a session object\n// returns -1 for not found\napp.session('findIndex', {user: 'test'}, function(err,res){\n  if(err){return console.error(err)}\n  console.log(res)\n});\n\n// replace or add additional values to a session object\napp.session('ass","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangeal185%2Fsicarii","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fangeal185%2Fsicarii","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangeal185%2Fsicarii/lists"}