From cdc46845c50cd4dd0bbbecb3ef5d24d832aea3b0 Mon Sep 17 00:00:00 2001
From: Ulf Seltmann <seltmann@ub.uni-leipzig.de>
Date: Thu, 24 Aug 2017 17:07:35 +0200
Subject: [PATCH] * added executable * added test-structure * added
 documentation

---
 .gitignore        |   7 +-
 .npmignore        |   4 +-
 Readme.md         |  48 ++++++-
 bin/dacap         |  23 ++++
 docs/index.md     |  64 +++++++--
 package-lock.json | 334 ++++++++++++++++++++++++++++++++++++++++++++++
 package.json      |  18 ++-
 src/index.ts      | 105 ---------------
 src/server.ts     | 108 +++++++++++++++
 test/cache.ts     |  16 +++
 10 files changed, 596 insertions(+), 131 deletions(-)
 create mode 100755 bin/dacap
 delete mode 100644 src/index.ts
 create mode 100644 src/server.ts

diff --git a/.gitignore b/.gitignore
index e09321a..39e650f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 /node_modules
-/dist/*
-/public/*
-/data/*
+/dist
+/public
+/data
+/site
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
index e1b5565..54fbaab 100644
--- a/.npmignore
+++ b/.npmignore
@@ -6,4 +6,6 @@
 /mkdocs.yml
 /tsconfig.json
 /webpack.*.js
-/src
\ No newline at end of file
+/src
+/site
+/.tmp
\ No newline at end of file
diff --git a/Readme.md b/Readme.md
index 52900b7..1da33b5 100644
--- a/Readme.md
+++ b/Readme.md
@@ -2,18 +2,56 @@
 
 *Dynamic And Compressing Api Proxy* is a service which lets you proxy arbitrary API-Endpoints which are by themself to slow to be used in production environments. All cached Responses are held in memory, so be sure to provide enough.
 
-## requirements
+## Requirements
 
 * node >= v8.2.1
 * npm >= v5.3.0
 
-## file a bug
+## Install the service
 
+```bash
+$# npm install -g dacap
+```
 
+## Start the service
+``` bash
+$# dacap
+```
 
-# register new endpoint
+The Admin Interface is now available under http://localhost:3000/admin
 
-New Endpoints are registered using the admin interface
+You can customize multiple values, see [#Advanced Configuration]
 
+## Usage
+
+First of all one has to define a *Cache* where the requests to an API-Endpoint are stored. This is done from within the Admin Interface.
+
+One has to define the URL to the desired *API-Endpoint* (e.g. `http://foo.example.com/`) and a *Cache-Name* for this API-Endpoint (e.g. `foo`). All API-Requests which normally would go to
+
+    http://foo.example.com/?query=foo&offset=0&limit=10
+
+now you can request by accessing
+
+    http://localhost:3000/ep/foo/?query=foo&offset=0&limit=10
+
+* *Be sure to append the Slash after the Cache-Name, otherwise the Endpoint cannot be resolved.*
+* *Everything after the Cache-Name and Slash will be passed to the API-Endpoint.*
+
+## Advanced Configuration
+
+The service can be configured by environment variables. The following are available:
+
+* `data_dir`: specifies the folder where the cache is stored in intervals. This is only used to
+ make it more easy to restart the service without adding all Endpoints again. After restart the
+ service reads the saved cache-file and keeps it in memory.
+ By default this points to the `data`-folder where the process was invoked. Make sure it can be created if not existing and written if already existing.
+* `proxy_path`: this is the path where the Endpoints will be accessable after registering. By default this is `/ep/`
+* `proxy_port`: this is the port number where the service is listening. Defaults to `3000`
+* `proxy_url`: this is the absolute url of the service with protocol and port if differing from the defaults (e.g. `http://api.example.com:8080`). Set this if you use a different domain name than `localhost` to connect to this service.
+* `cache_ttl`: the time-to-live in seconds of a cached request. after this time the cache will be refreshed. Defaults to `600`
+* `default_check_period`: specifies the interval in seconds the cache is checked for expiry. Defaults to `60`
+* `array_value_size`: unkown configuration. Defaults to `40`
+* `object_value_size`: unkown configuration. Defaults to `80`
+* `autosave_interval`: specifies the interval the cache is stored to harddisk. defaults to `60`;
+* `register_name`: specifies the name of the register where the cache is stored. Defaults to `api-cache`;
 
-/admin/
\ No newline at end of file
diff --git a/bin/dacap b/bin/dacap
new file mode 100755
index 0000000..3d31f4d
--- /dev/null
+++ b/bin/dacap
@@ -0,0 +1,23 @@
+#!/usr/bin/env node
+
+const debug = require('debug')('dacap');
+const pjson = require('../package.json');
+const path = require('path');
+const Server = require('../dist/server').Server;
+
+const storagePath = process.env.data_dir || path.resolve(process.cwd(), 'data');
+const proxyPath = process.env.proxy_path || '/ep/';
+const proxyPort = parseInt(process.env.proxy_port) || 3000;
+const proxyUrl = process.env.proxy_url || `http://localhost:${proxyPort}`;
+const defaultTtl = parseInt(process.env.cache_ttl) || 600;
+const defaultCheckPeriod = parseInt(process.env.default_check_period) || 60;
+const defaultArrayValueSize = parseInt(process.env.array_value_size) || 40;
+const defaultObjectValueSize = parseInt(process.env.object_value_size) || 80;
+const autosaveInterval = parseInt(process.env.autosave_interval) || 60;
+const registerName = process.env.register_name || 'api-cache';
+
+const server = new Server(storagePath, proxyPath, proxyUrl, defaultTtl, defaultCheckPeriod, defaultArrayValueSize, defaultObjectValueSize, autosaveInterval, registerName);
+
+server.listen(proxyPort, () => {
+	debug('server up and running');
+});
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index da37213..1da33b5 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,17 +1,57 @@
-# Welcome to MkDocs
+# dacap
 
-For full documentation visit [mkdocs.org](http://mkdocs.org).
+*Dynamic And Compressing Api Proxy* is a service which lets you proxy arbitrary API-Endpoints which are by themself to slow to be used in production environments. All cached Responses are held in memory, so be sure to provide enough.
 
-## Commands
+## Requirements
 
-* `mkdocs new [dir-name]` - Create a new project.
-* `mkdocs serve` - Start the live-reloading docs server.
-* `mkdocs build` - Build the documentation site.
-* `mkdocs help` - Print this help message.
+* node >= v8.2.1
+* npm >= v5.3.0
 
-## Project layout
+## Install the service
+
+```bash
+$# npm install -g dacap
+```
+
+## Start the service
+``` bash
+$# dacap
+```
+
+The Admin Interface is now available under http://localhost:3000/admin
+
+You can customize multiple values, see [#Advanced Configuration]
+
+## Usage
+
+First of all one has to define a *Cache* where the requests to an API-Endpoint are stored. This is done from within the Admin Interface.
+
+One has to define the URL to the desired *API-Endpoint* (e.g. `http://foo.example.com/`) and a *Cache-Name* for this API-Endpoint (e.g. `foo`). All API-Requests which normally would go to
+
+    http://foo.example.com/?query=foo&offset=0&limit=10
+
+now you can request by accessing
+
+    http://localhost:3000/ep/foo/?query=foo&offset=0&limit=10
+
+* *Be sure to append the Slash after the Cache-Name, otherwise the Endpoint cannot be resolved.*
+* *Everything after the Cache-Name and Slash will be passed to the API-Endpoint.*
+
+## Advanced Configuration
+
+The service can be configured by environment variables. The following are available:
+
+* `data_dir`: specifies the folder where the cache is stored in intervals. This is only used to
+ make it more easy to restart the service without adding all Endpoints again. After restart the
+ service reads the saved cache-file and keeps it in memory.
+ By default this points to the `data`-folder where the process was invoked. Make sure it can be created if not existing and written if already existing.
+* `proxy_path`: this is the path where the Endpoints will be accessable after registering. By default this is `/ep/`
+* `proxy_port`: this is the port number where the service is listening. Defaults to `3000`
+* `proxy_url`: this is the absolute url of the service with protocol and port if differing from the defaults (e.g. `http://api.example.com:8080`). Set this if you use a different domain name than `localhost` to connect to this service.
+* `cache_ttl`: the time-to-live in seconds of a cached request. after this time the cache will be refreshed. Defaults to `600`
+* `default_check_period`: specifies the interval in seconds the cache is checked for expiry. Defaults to `60`
+* `array_value_size`: unkown configuration. Defaults to `40`
+* `object_value_size`: unkown configuration. Defaults to `80`
+* `autosave_interval`: specifies the interval the cache is stored to harddisk. defaults to `60`;
+* `register_name`: specifies the name of the register where the cache is stored. Defaults to `api-cache`;
 
-    mkdocs.yml    # The configuration file.
-    docs/
-        index.md  # The documentation homepage.
-        ...       # Other markdown pages, images and other files.
diff --git a/package-lock.json b/package-lock.json
index 161dda4..61e2dda 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -127,6 +127,12 @@
       "integrity": "sha512-rek8twk9C58gHYqIrUlJsx8NQMhlxqHzln9Z9ODqiNgv3/s+ZwIrfr+djqzsnVM12xe9hL98iJ20lj2RvCBv6A==",
       "dev": true
     },
+    "@types/mocha": {
+      "version": "2.2.42",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.42.tgz",
+      "integrity": "sha512-b6gVDoxEbAQGwbV7gSzeFw/hy3/eEAokztktdzl4bHvGgb9K5zW4mVQDlVYch2w31m8t/J7L2iqhQvz3r5edCQ==",
+      "dev": true
+    },
     "@types/node": {
       "version": "8.0.20",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.20.tgz",
@@ -177,6 +183,12 @@
         "@types/mime": "1.3.1"
       }
     },
+    "@types/should": {
+      "version": "11.2.0",
+      "resolved": "https://registry.npmjs.org/@types/should/-/should-11.2.0.tgz",
+      "integrity": "sha512-+J77XoXmKIXcLK5fWS5B3j31F4wfdclzk+lRxFcKfXTHzZfd153u8w96W30dQBIT4kwKobjvYa0kIb0BWJX21Q==",
+      "dev": true
+    },
     "accepts": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
@@ -308,6 +320,12 @@
       "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
       "dev": true
     },
+    "any-promise": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+      "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=",
+      "dev": true
+    },
     "anymatch": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
@@ -898,6 +916,12 @@
       "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
       "dev": true
     },
+    "browser-stdout": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
+      "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
+      "dev": true
+    },
     "browserify-aes": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.6.tgz",
@@ -1791,6 +1815,12 @@
       "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
       "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
     },
+    "diff": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz",
+      "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=",
+      "dev": true
+    },
     "diffie-hellman": {
       "version": "5.0.2",
       "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz",
@@ -2664,6 +2694,18 @@
       "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
       "dev": true
     },
+    "graceful-readlink": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+      "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
+      "dev": true
+    },
+    "growl": {
+      "version": "1.9.2",
+      "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
+      "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
+      "dev": true
+    },
     "har-schema": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
@@ -3366,6 +3408,12 @@
       "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
       "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
     },
+    "is-utf8": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+      "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+      "dev": true
+    },
     "isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -3476,6 +3524,12 @@
       "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
       "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
     },
+    "json3": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
+      "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
+      "dev": true
+    },
     "json5": {
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
@@ -3581,12 +3635,80 @@
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
       "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
     },
+    "lodash._baseassign": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
+      "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
+      "dev": true,
+      "requires": {
+        "lodash._basecopy": "3.0.1",
+        "lodash.keys": "3.1.2"
+      }
+    },
+    "lodash._basecopy": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
+      "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
+      "dev": true
+    },
+    "lodash._basecreate": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz",
+      "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=",
+      "dev": true
+    },
+    "lodash._getnative": {
+      "version": "3.9.1",
+      "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
+      "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
+      "dev": true
+    },
+    "lodash._isiterateecall": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
+      "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
+      "dev": true
+    },
     "lodash.camelcase": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
       "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
       "dev": true
     },
+    "lodash.create": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz",
+      "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=",
+      "dev": true,
+      "requires": {
+        "lodash._baseassign": "3.2.0",
+        "lodash._basecreate": "3.0.3",
+        "lodash._isiterateecall": "3.0.9"
+      }
+    },
+    "lodash.isarguments": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+      "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
+      "dev": true
+    },
+    "lodash.isarray": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
+      "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
+      "dev": true
+    },
+    "lodash.keys": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
+      "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
+      "dev": true,
+      "requires": {
+        "lodash._getnative": "3.9.1",
+        "lodash.isarguments": "3.1.0",
+        "lodash.isarray": "3.0.4"
+      }
+    },
     "lodash.memoize": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -3635,6 +3757,12 @@
       "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=",
       "dev": true
     },
+    "make-error": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.0.tgz",
+      "integrity": "sha1-Uq06M5zPEM5itAQLcI/nByRLi5Y=",
+      "dev": true
+    },
     "map-cache": {
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
@@ -3809,6 +3937,74 @@
         "minimist": "0.0.8"
       }
     },
+    "mocha": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.0.tgz",
+      "integrity": "sha512-pIU2PJjrPYvYRqVpjXzj76qltO9uBYI7woYAMoxbSefsa+vqAfptjoeevd6bUgwD0mPIO+hv9f7ltvsNreL2PA==",
+      "dev": true,
+      "requires": {
+        "browser-stdout": "1.3.0",
+        "commander": "2.9.0",
+        "debug": "2.6.8",
+        "diff": "3.2.0",
+        "escape-string-regexp": "1.0.5",
+        "glob": "7.1.1",
+        "growl": "1.9.2",
+        "json3": "3.3.2",
+        "lodash.create": "3.1.1",
+        "mkdirp": "0.5.1",
+        "supports-color": "3.1.2"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.9.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+          "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
+          "dev": true,
+          "requires": {
+            "graceful-readlink": "1.0.1"
+          }
+        },
+        "debug": {
+          "version": "2.6.8",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+          "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.1",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
+          "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "1.0.0",
+            "inflight": "1.0.6",
+            "inherits": "2.0.3",
+            "minimatch": "3.0.4",
+            "once": "1.4.0",
+            "path-is-absolute": "1.0.1"
+          }
+        },
+        "has-flag": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+          "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "3.1.2",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
+          "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=",
+          "dev": true,
+          "requires": {
+            "has-flag": "1.0.0"
+          }
+        }
+      }
+    },
     "ms": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -5615,6 +5811,60 @@
       "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
       "dev": true
     },
+    "should": {
+      "version": "11.2.1",
+      "resolved": "https://registry.npmjs.org/should/-/should-11.2.1.tgz",
+      "integrity": "sha1-kPVRRVUtAc/CAGZuToGKHJZw7aI=",
+      "dev": true,
+      "requires": {
+        "should-equal": "1.0.1",
+        "should-format": "3.0.3",
+        "should-type": "1.4.0",
+        "should-type-adaptors": "1.0.1",
+        "should-util": "1.0.0"
+      }
+    },
+    "should-equal": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-1.0.1.tgz",
+      "integrity": "sha1-C26VFvJgGp+wuy3MNpr6HH4gCvc=",
+      "dev": true,
+      "requires": {
+        "should-type": "1.4.0"
+      }
+    },
+    "should-format": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
+      "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
+      "dev": true,
+      "requires": {
+        "should-type": "1.4.0",
+        "should-type-adaptors": "1.0.1"
+      }
+    },
+    "should-type": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
+      "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=",
+      "dev": true
+    },
+    "should-type-adaptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.0.1.tgz",
+      "integrity": "sha1-7+VVPN9oz/ZuXF9RtxLcNRx3vqo=",
+      "dev": true,
+      "requires": {
+        "should-type": "1.4.0",
+        "should-util": "1.0.0"
+      }
+    },
+    "should-util": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz",
+      "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=",
+      "dev": true
+    },
     "signal-exit": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@@ -6231,6 +6481,43 @@
       "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
       "dev": true
     },
+    "ts-mocha": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-1.0.3.tgz",
+      "integrity": "sha1-KSlZ7MVtVrFVgA1TCrLcsxYKN+o=",
+      "dev": true,
+      "requires": {
+        "ts-node": "1.7.3"
+      }
+    },
+    "ts-node": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-1.7.3.tgz",
+      "integrity": "sha1-3uf4qEdRcy08Lkl8rFoC+xF9/uc=",
+      "dev": true,
+      "requires": {
+        "arrify": "1.0.1",
+        "chalk": "1.1.3",
+        "diff": "3.2.0",
+        "make-error": "1.3.0",
+        "minimist": "1.2.0",
+        "mkdirp": "0.5.1",
+        "pinkie": "2.0.4",
+        "source-map-support": "0.4.16",
+        "tsconfig": "5.0.3",
+        "v8flags": "2.1.1",
+        "xtend": "4.0.1",
+        "yn": "1.3.0"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
     "tsc-watch": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-1.0.7.tgz",
@@ -6243,6 +6530,29 @@
         "typescript": "2.4.2"
       }
     },
+    "tsconfig": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-5.0.3.tgz",
+      "integrity": "sha1-X0J45wGACWeo/Dg/0ZZIh48qbjo=",
+      "dev": true,
+      "requires": {
+        "any-promise": "1.3.0",
+        "parse-json": "2.2.0",
+        "strip-bom": "2.0.0",
+        "strip-json-comments": "2.0.1"
+      },
+      "dependencies": {
+        "strip-bom": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+          "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+          "dev": true,
+          "requires": {
+            "is-utf8": "0.2.1"
+          }
+        }
+      }
+    },
     "tslib": {
       "version": "1.7.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz",
@@ -6486,6 +6796,12 @@
         }
       }
     },
+    "user-home": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz",
+      "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=",
+      "dev": true
+    },
     "util": {
       "version": "0.10.3",
       "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
@@ -6525,6 +6841,15 @@
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
       "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
     },
+    "v8flags": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz",
+      "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=",
+      "dev": true,
+      "requires": {
+        "user-home": "1.1.1"
+      }
+    },
     "validate-npm-package-license": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
@@ -6837,6 +7162,15 @@
         }
       }
     },
+    "yn": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-1.3.0.tgz",
+      "integrity": "sha1-GwgSq7jYBdSJZvjfOF3J2syaGdg=",
+      "dev": true,
+      "requires": {
+        "object-assign": "4.1.1"
+      }
+    },
     "zone.js": {
       "version": "0.8.16",
       "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.16.tgz",
diff --git a/package.json b/package.json
index 29d89ee..4109abd 100644
--- a/package.json
+++ b/package.json
@@ -2,17 +2,20 @@
   "name": "dacap",
   "version": "1.0.0",
   "description": "proxies,compresses and caches api-requests",
-  "main": "dist/index.js",
+  "main": "bin/dacap",
+  "bin": {
+    "dacap": "./bin/dacap"
+  },
   "directories": {
     "test": "test"
   },
   "scripts": {
     "webpack-watch": "webpack --config webpack.dev.js --watch",
-    "start": "node dist/index.js",
+    "start": "node bin/dacap",
     "tsc": "tsc",
-    "inspect-watch": "tsc-watch --sourceMap --onSuccess 'node --inspect=0.0.0.0:9229 dist/index.js'",
-    "inspect-brk-watch": "tsc-watch --onSuccess 'node --inspect-brk=0.0.0.0:9229 dist/index.js'",
-    "test": "mocha test",
+    "inspect-watch": "tsc-watch --sourceMap --onSuccess 'node --inspect=0.0.0.0:9229 bin/dacap'",
+    "inspect-brk-watch": "tsc-watch --onSuccess 'node --inspect-brk=0.0.0.0:9229 bin/dacap'",
+    "test": "ts-mocha --recursive test/*",
     "build": "rm -rf dist public && tsc --declaration && webpack --config webpack.prod.js"
   },
   "author": "Ulf Seltmann <ulf.seltmann@uni-leipzig.de>",
@@ -45,11 +48,13 @@
     "@types/compression": "0.0.33",
     "@types/debug": "0.0.30",
     "@types/express": "^4.0.36",
+    "@types/mocha": "^2.2.42",
     "@types/node": "^8.0.20",
     "@types/node-cache": "^4.1.0",
     "@types/react": "^16.0.2",
     "@types/react-dom": "^15.5.3",
     "@types/request": "^2.0.1",
+    "@types/should": "^11.2.0",
     "angular2-template-loader": "^0.6.2",
     "awesome-typescript-loader": "^3.2.3",
     "bootstrap": "^3.3.7",
@@ -60,8 +65,11 @@
     "html-loader": "^0.5.1",
     "html-webpack-plugin": "^2.30.1",
     "jquery": "^3.2.1",
+    "mocha": "^3.5.0",
+    "should": "^11.2.1",
     "source-map-loader": "^0.2.1",
     "style-loader": "^0.18.2",
+    "ts-mocha": "^1.0.3",
     "tsc-watch": "^1.0.7",
     "typescript": "^2.4.2",
     "webpack": "^3.5.5",
diff --git a/src/index.ts b/src/index.ts
deleted file mode 100644
index 1c4a21d..0000000
--- a/src/index.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import * as express from 'express';
-import * as compression from 'compression';
-import * as debugFactory from 'debug';
-import * as path from 'path';
-import * as cache from './cache';
-import * as bodyparser from 'body-parser';
-
-const debug = debugFactory('dacap:index');
-const app = express();
-const storagePath = process.env.data_dir || path.resolve(process.cwd(), 'data');
-const proxyPath = process.env.proxy_path || '/ep/';
-const proxyPort = process.env.proxy_port || 3000;
-const proxyUrl = process.env.proxy_url || `http://localhost:${proxyPort}${proxyPath}`;
-const defaultTtl = process.env.cache_ttl || 600;
-const defaultCheckPeriod = process.env.default_check_period || 60;
-const defaultArrayValueSize = process.env.array_value_size || 40;
-const defaultObjectValueSize = process.env.object_value_size || 80;
-const autosaveInterval = process.env.autosave_interval || 60;
-
-const register = new cache.Register(storagePath, 'apis');
-
-register.restore();
-
-app.get(/favicon.ico/, (req, res, next) => {
-	res.send();
-});
-
-app.use(compression());
-app.use(bodyparser.json());
-
-app.post('/api/add/cache/:name', (req, res, next) => {
-	if (!req.body || !req.body.apiEndPoint) return res.status(400).send(`you need to specify at least an url`);
-	if (register.has(req.body.name)) return res.send(`already registered endpoint "${req.params.name}"`);
-	register.add(req.params.name, req.body.apiEndPoint, {
-		stdTTL: req.body.cacheOptions.ttl || defaultTtl,
-		checkperiod: req.body.cacheOptions.checkPeriod || defaultCheckPeriod,
-		objectValueSize: req.body.cacheOptions.objectValueSize || defaultObjectValueSize,
-		arrayValueSize: req.body.cacheOptions.arrayValueSize || defaultArrayValueSize
-	});
-	res.json(register.getInfo(proxyUrl, req.params.name));
-});
-
-app.get('/api/list/cache', async (req, res, next) => {
-	try {
-		res.json(register.getInfo(proxyUrl));
-	} catch(err) {
-		next(err);
-	}
-});
-
-app.get('/api/delete/cache/:name', (req, res, next) => {
-	try {
-		register.delete(req.params.name);
-		res.json({});
-	} catch (err) {
-		next(err);
-	}
-})
-
-app.get('/api/flush/cache/:name', (req, res, next) => {
-	try {
-		const cache = register.get(req.params.name)
-		cache.flush();
-		res.json(register.getInfo(proxyUrl, req.params.name));
-	} catch(err) {
-		next(err);
-	}
-});
-
-app.get('/api/delete/cache/:name/key/:hash', (req, res, next) => {
-	try {
-		const cache = register.get(req.params.name)
-		cache.del(req.params.hash);
-		res.json({});
-	} catch(err) {
-		next(err);
-	}
-})
-
-app.get('/api/refresh/cache/:name/key/:hash', async (req, res, next) => {
-	try {
-		const cache = register.get(req.params.name);
-		const value = cache.getCache().get(req.params.hash);
-		await cache.refresh(req.params.hash, value);
-		res.json(cache.getDetails(req.params.hash));
-	} catch(err) {
-		next(err);
-	}
-});
-
-app.use('/admin/', express.static(path.resolve(__dirname,'..', 'public')));
-
-app.use(proxyPath, cache.Middleware(register));
-
-const server = app.listen(proxyPort, () => {
-	debug('server up and running');
-});
-process.stdin.resume();
-
-setTimeout(register.save, autosaveInterval);
-
-process.on('SIGTERM', () => {
-	register.save();
-	process.exit();
-});
\ No newline at end of file
diff --git a/src/server.ts b/src/server.ts
new file mode 100644
index 0000000..3717e5d
--- /dev/null
+++ b/src/server.ts
@@ -0,0 +1,108 @@
+import * as express from 'express';
+import * as compression from 'compression';
+import * as debugFactory from 'debug';
+import * as path from 'path';
+import * as cache from './cache';
+import * as bodyparser from 'body-parser';
+
+const debug = debugFactory('dacap:server');
+
+export class Server {
+	private register: cache.Register;
+	private expressApp: express.Application;
+
+	constructor(private storagePath: string,
+		private proxyPath: string,
+		private proxyUrl: string,
+		private defaultTtl: number,
+		private defaultCheckPeriod: number,
+		private defaultArrayValueSize: number,
+		private defaultObjectValueSize: number,
+		private autosaveInterval: number,
+		private registerName: string) {
+
+		this._init();
+	}
+
+	private _init() {
+		this.register = new cache.Register(this.storagePath, this.registerName);
+		this.register.restore();
+		setTimeout(this.register.save, this.autosaveInterval * 1000);
+
+		this.expressApp = express();
+		this.expressApp.get(/favicon.ico/, (req, res, next) => {
+			res.send();
+		});
+
+		this.expressApp.use(compression());
+		this.expressApp.use(bodyparser.json());
+
+		this.expressApp.post('/api/add/cache/:name', (req, res, next) => {
+			if (!req.body || !req.body.apiEndPoint) return res.status(400).send(`you need to specify at least an url`);
+			if (this.register.has(req.body.name)) return res.send(`already registered endpoint "${req.params.name}"`);
+			this.register.add(req.params.name, req.body.apiEndPoint, {
+				stdTTL: req.body.cacheOptions.ttl || this.defaultTtl,
+				checkperiod: req.body.cacheOptions.checkPeriod || this.defaultCheckPeriod,
+				objectValueSize: req.body.cacheOptions.objectValueSize || this.defaultObjectValueSize,
+				arrayValueSize: req.body.cacheOptions.arrayValueSize || this.defaultArrayValueSize
+			});
+			res.json(this.register.getInfo(this.proxyUrl + this.proxyPath, req.params.name));
+		});
+
+		this.expressApp.get('/api/list/cache', async (req, res, next) => {
+			try {
+				res.json(this.register.getInfo(this.proxyUrl + this.proxyPath));
+			} catch (err) {
+				next(err);
+			}
+		});
+
+		this.expressApp.get('/api/delete/cache/:name', (req, res, next) => {
+			try {
+				this.register.delete(req.params.name);
+				res.json({});
+			} catch (err) {
+				next(err);
+			}
+		})
+
+		this.expressApp.get('/api/flush/cache/:name', (req, res, next) => {
+			try {
+				const cache = this.register.get(req.params.name)
+				cache.flush();
+				res.json(this.register.getInfo(this.proxyUrl + this.proxyPath, req.params.name));
+			} catch (err) {
+				next(err);
+			}
+		});
+
+		this.expressApp.get('/api/delete/cache/:name/key/:hash', (req, res, next) => {
+			try {
+				const cache = this.register.get(req.params.name)
+				cache.del(req.params.hash);
+				res.json({});
+			} catch (err) {
+				next(err);
+			}
+		})
+
+		this.expressApp.get('/api/refresh/cache/:name/key/:hash', async (req, res, next) => {
+			try {
+				const cache = this.register.get(req.params.name);
+				const value = cache.getCache().get(req.params.hash);
+				await cache.refresh(req.params.hash, value);
+				res.json(cache.getDetails(req.params.hash));
+			} catch (err) {
+				next(err);
+			}
+		});
+
+		this.expressApp.use('/admin/', express.static(path.resolve(__dirname, '..', 'public')));
+
+		this.expressApp.use(this.proxyPath, cache.Middleware(this.register));
+	}
+
+	listen(port: number, cb) {
+		return this.expressApp.listen(port, cb);
+	}
+}
\ No newline at end of file
diff --git a/test/cache.ts b/test/cache.ts
index e69de29..fa1dd33 100644
--- a/test/cache.ts
+++ b/test/cache.ts
@@ -0,0 +1,16 @@
+import {Register, Cache, Middleware} from '../src/cache';
+import * as path from 'path';
+import 'should';
+
+describe('register', () => {
+	it('should create an instance', (done) => {
+		new Register(path.resolve(__dirname, '..', '.tmp'), 'foobar');
+		done();
+	})
+});
+describe('cache', () => {
+	it('should create an instance', (done) => {
+		new Cache();
+		done();
+	});
+})
\ No newline at end of file
-- 
GitLab