diff --git a/js/.eslintrc.js b/js/.eslintrc.js
deleted file mode 100644
index 081185d3425f460805327671a5b4a6321258c89e..0000000000000000000000000000000000000000
--- a/js/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = {
-  root: true,
-  extends: [
-    'plugin:vue/essential',
-    '@vue/airbnb',
-  ],
-};
diff --git a/js/get_union_json.js b/js/get_union_json.ts
similarity index 100%
rename from js/get_union_json.js
rename to js/get_union_json.ts
diff --git a/js/package-lock.json b/js/package-lock.json
index 19317591d8b8e7f7cd78adc4e2a6cd5dfaf91fce..7135bd603884537ba3813806d323739bc3d38f82 100644
--- a/js/package-lock.json
+++ b/js/package-lock.json
@@ -867,6 +867,46 @@
         "to-fast-properties": "^2.0.0"
       }
     },
+    "@fimbul/bifrost": {
+      "version": "0.15.0",
+      "resolved": "https://registry.npmjs.org/@fimbul/bifrost/-/bifrost-0.15.0.tgz",
+      "integrity": "sha512-sHTwnwA9YhxcVEJkBlfKH1KLmGQGnNYPxk+09w5NnkXelYiiP8a5f351weYfxG0CUPLt1Fgkha20Y/9+jhjn/Q==",
+      "dev": true,
+      "requires": {
+        "@fimbul/ymir": "^0.15.0",
+        "get-caller-file": "^2.0.0",
+        "tslib": "^1.8.1",
+        "tsutils": "^3.1.0"
+      },
+      "dependencies": {
+        "get-caller-file": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.1.tgz",
+          "integrity": "sha512-SpOZHfz845AH0wJYVuZk2jWDqFmu7Xubsx+ldIpwzy5pDUpu7OJHK7QYNSA2NPlDSKQwM1GFaAkciOWjjW92Sg==",
+          "dev": true
+        },
+        "tsutils": {
+          "version": "3.5.2",
+          "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.5.2.tgz",
+          "integrity": "sha512-qIlklNuI/1Dzfm+G+kJV5gg3gimZIX5haYtIVQe7qGyKd7eu8T1t1DY6pz4Sc2CGXAj9s1izycctm9Zfl9sRuQ==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.8.1"
+          }
+        }
+      }
+    },
+    "@fimbul/ymir": {
+      "version": "0.15.0",
+      "resolved": "https://registry.npmjs.org/@fimbul/ymir/-/ymir-0.15.0.tgz",
+      "integrity": "sha512-Ow0TfxxQ65vIktHcZyXHeDsGKuzJ9Vt6y77R/aOrXQXLMdYHG+XdbiUWzQbtaGOmNzYVkQfINiFnIdvn5Bn24g==",
+      "dev": true,
+      "requires": {
+        "inversify": "^5.0.0",
+        "reflect-metadata": "^0.1.12",
+        "tslib": "^1.8.1"
+      }
+    },
     "@intervolga/optimize-cssnano-plugin": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/@intervolga/optimize-cssnano-plugin/-/optimize-cssnano-plugin-1.0.6.tgz",
@@ -960,7 +1000,7 @@
     },
     "@types/accepts": {
       "version": "1.3.5",
-      "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
+      "resolved": "http://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
       "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==",
       "dev": true,
       "requires": {
@@ -996,6 +1036,12 @@
         "@types/node": "*"
       }
     },
+    "@types/chai": {
+      "version": "4.1.7",
+      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz",
+      "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==",
+      "dev": true
+    },
     "@types/connect": {
       "version": "3.4.32",
       "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
@@ -1016,7 +1062,7 @@
     },
     "@types/events": {
       "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
       "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==",
       "dev": true
     },
@@ -1054,6 +1100,12 @@
       "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==",
       "dev": true
     },
+    "@types/mocha": {
+      "version": "5.2.5",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz",
+      "integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==",
+      "dev": true
+    },
     "@types/node": {
       "version": "10.11.6",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-10.11.6.tgz",
@@ -1075,6 +1127,12 @@
         "@types/mime": "*"
       }
     },
+    "@types/webpack-env": {
+      "version": "1.13.6",
+      "resolved": "http://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.13.6.tgz",
+      "integrity": "sha512-5Th3OsZ4gTRdr9Mho83BQ23cex4sRhOR4XTG+m+cJc0FhtUBK9Vn62hBJ+pnQYnSxoPOsKoAPOx6FcphxBC8ng==",
+      "dev": true
+    },
     "@types/ws": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.1.tgz",
@@ -1181,6 +1239,57 @@
         "workbox-webpack-plugin": "^3.6.3"
       }
     },
+    "@vue/cli-plugin-typescript": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-typescript/-/cli-plugin-typescript-3.2.0.tgz",
+      "integrity": "sha512-zv9N92mMyidK3+0lyJhIimeMjqIv0SZg8Nkie4xDBEKyliF3KmGLwDstLft7rBm44yqppEROjLMVOxJkuwUVNw==",
+      "dev": true,
+      "requires": {
+        "@types/webpack-env": "^1.13.6",
+        "@vue/cli-shared-utils": "^3.2.0",
+        "fork-ts-checker-webpack-plugin": "^0.5.0",
+        "globby": "^8.0.1",
+        "ts-loader": "^5.3.1",
+        "tslint": "^5.11.0"
+      },
+      "dependencies": {
+        "@vue/cli-shared-utils": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-3.2.0.tgz",
+          "integrity": "sha512-FCX5ABFg5pWhomyXLpCaogJktMvjsS5d4Mn5BfvqcJxCvzOX6ze8ihFK3u//XMeM78dOFpHSjxnRSvHtkEwgsg==",
+          "dev": true,
+          "requires": {
+            "chalk": "^2.4.1",
+            "execa": "^1.0.0",
+            "joi": "^13.0.0",
+            "launch-editor": "^2.2.1",
+            "lru-cache": "^4.1.3",
+            "node-ipc": "^9.1.1",
+            "opn": "^5.3.0",
+            "ora": "^2.1.0",
+            "request": "^2.87.0",
+            "request-promise-native": "^1.0.5",
+            "semver": "^5.5.0",
+            "string.prototype.padstart": "^3.0.0"
+          }
+        },
+        "globby": {
+          "version": "8.0.1",
+          "resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
+          "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==",
+          "dev": true,
+          "requires": {
+            "array-union": "^1.0.1",
+            "dir-glob": "^2.0.0",
+            "fast-glob": "^2.0.2",
+            "glob": "^7.1.2",
+            "ignore": "^3.3.5",
+            "pify": "^3.0.0",
+            "slash": "^1.0.0"
+          }
+        }
+      }
+    },
     "@vue/cli-plugin-unit-mocha": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/@vue/cli-plugin-unit-mocha/-/cli-plugin-unit-mocha-3.1.1.tgz",
@@ -1271,7 +1380,7 @@
         },
         "globby": {
           "version": "8.0.1",
-          "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
           "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==",
           "dev": true,
           "requires": {
@@ -1406,6 +1515,16 @@
         "eslint-plugin-import": "^2.11.0"
       }
     },
+    "@vue/eslint-config-typescript": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-3.2.0.tgz",
+      "integrity": "sha512-rDcOpHpxVOyKVe5kaxV63UOQulYvIAwpX0HZO4+rEnDoxkKtS9iAw2VeaamvrYp+TCFMsguo3CsGg0leu1xWAg==",
+      "dev": true,
+      "requires": {
+        "eslint-plugin-typescript": "^0.14.0",
+        "typescript-eslint-parser": "^21.0.1"
+      }
+    },
     "@vue/preload-webpack-plugin": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.0.tgz",
@@ -1699,7 +1818,7 @@
     },
     "acorn-jsx": {
       "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
+      "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
       "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
       "dev": true,
       "requires": {
@@ -1708,7 +1827,7 @@
       "dependencies": {
         "acorn": {
           "version": "3.3.0",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+          "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
           "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
           "dev": true
         }
@@ -1778,7 +1897,7 @@
       "dependencies": {
         "semver": {
           "version": "5.0.3",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz",
+          "resolved": "http://registry.npmjs.org/semver/-/semver-5.0.3.tgz",
           "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=",
           "dev": true
         }
@@ -1847,7 +1966,7 @@
     },
     "ansi-escapes": {
       "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
       "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
       "dev": true
     },
@@ -2273,7 +2392,7 @@
     },
     "array-equal": {
       "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
+      "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
       "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
       "dev": true
     },
@@ -2417,7 +2536,7 @@
     },
     "async": {
       "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+      "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
       "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
       "dev": true
     },
@@ -2540,7 +2659,7 @@
         },
         "chalk": {
           "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
           "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
           "dev": true,
           "requires": {
@@ -2559,7 +2678,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -3121,7 +3240,7 @@
     },
     "cacache": {
       "version": "10.0.4",
-      "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
+      "resolved": "http://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
       "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==",
       "dev": true,
       "requires": {
@@ -3220,7 +3339,7 @@
     },
     "callsites": {
       "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
       "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
       "dev": true
     },
@@ -3242,7 +3361,7 @@
     },
     "camelcase-keys": {
       "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
       "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
       "dev": true,
       "requires": {
@@ -3342,7 +3461,7 @@
     },
     "chai-nightwatch": {
       "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.1.1.tgz",
+      "resolved": "http://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.1.1.tgz",
       "integrity": "sha1-HKVt52jTwIaP5/wvTTLC/olOa+k=",
       "dev": true,
       "requires": {
@@ -3757,7 +3876,7 @@
     },
     "colors": {
       "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+      "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
       "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
       "dev": true
     },
@@ -3772,7 +3891,7 @@
     },
     "commander": {
       "version": "2.9.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+      "resolved": "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
       "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
       "dev": true,
       "requires": {
@@ -4150,7 +4269,7 @@
     },
     "css-color-names": {
       "version": "0.0.4",
-      "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
+      "resolved": "http://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
       "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=",
       "dev": true
     },
@@ -4263,7 +4382,7 @@
         },
         "jsesc": {
           "version": "0.5.0",
-          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
           "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
           "dev": true
         },
@@ -4280,13 +4399,13 @@
         },
         "regjsgen": {
           "version": "0.2.0",
-          "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
           "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
           "dev": true
         },
         "regjsparser": {
           "version": "0.1.5",
-          "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+          "resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
           "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
           "dev": true,
           "requires": {
@@ -4515,7 +4634,7 @@
     },
     "deep-eql": {
       "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz",
+      "resolved": "http://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz",
       "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=",
       "dev": true,
       "requires": {
@@ -4920,7 +5039,7 @@
     },
     "duplexer": {
       "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+      "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
       "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
       "dev": true
     },
@@ -5179,7 +5298,7 @@
     },
     "eslint": {
       "version": "4.19.1",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz",
+      "resolved": "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz",
       "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==",
       "dev": true,
       "requires": {
@@ -5519,6 +5638,15 @@
         }
       }
     },
+    "eslint-plugin-typescript": {
+      "version": "0.14.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-typescript/-/eslint-plugin-typescript-0.14.0.tgz",
+      "integrity": "sha512-2u1WnnDF2mkWWgU1lFQ2RjypUlmRoBEvQN02y9u+IL12mjWlkKFGEBnVsjs9Y8190bfPQCvWly1c2rYYUSOxWw==",
+      "dev": true,
+      "requires": {
+        "requireindex": "~1.1.0"
+      }
+    },
     "eslint-plugin-vue": {
       "version": "4.7.1",
       "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-4.7.1.tgz",
@@ -5558,7 +5686,7 @@
     },
     "espree": {
       "version": "3.5.4",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
+      "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
       "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==",
       "dev": true,
       "requires": {
@@ -5799,7 +5927,7 @@
     },
     "external-editor": {
       "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
       "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
       "dev": true,
       "requires": {
@@ -6066,7 +6194,7 @@
     },
     "finalhandler": {
       "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+      "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
       "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
       "dev": true,
       "requires": {
@@ -6188,6 +6316,20 @@
       "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
       "dev": true
     },
+    "fork-ts-checker-webpack-plugin": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-0.5.2.tgz",
+      "integrity": "sha512-a5IG+xXyKnpruI0CP/anyRLAoxWtp3lzdG6flxicANnoSzz64b12dJ7ASAVRrI2OaWwZR2JyBaMHFQqInhWhIw==",
+      "dev": true,
+      "requires": {
+        "babel-code-frame": "^6.22.0",
+        "chalk": "^2.4.1",
+        "chokidar": "^2.0.4",
+        "micromatch": "^3.1.10",
+        "minimatch": "^3.0.4",
+        "tapable": "^1.0.0"
+      }
+    },
     "form-data": {
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
@@ -6245,7 +6387,7 @@
         },
         "chalk": {
           "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
           "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
           "dev": true,
           "requires": {
@@ -6258,7 +6400,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -6871,7 +7013,7 @@
         },
         "readable-stream": {
           "version": "1.1.14",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
           "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
           "dev": true,
           "requires": {
@@ -6944,7 +7086,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -7099,7 +7241,7 @@
     },
     "globby": {
       "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
       "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
       "dev": true,
       "requires": {
@@ -7112,7 +7254,7 @@
       "dependencies": {
         "pify": {
           "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
           "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
           "dev": true
         }
@@ -7240,7 +7382,7 @@
     },
     "handle-thing": {
       "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
+      "resolved": "http://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
       "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=",
       "dev": true
     },
@@ -7480,7 +7622,7 @@
     },
     "html-webpack-plugin": {
       "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
       "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
       "dev": true,
       "requires": {
@@ -7509,7 +7651,7 @@
     },
     "htmlparser2": {
       "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz",
+      "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz",
       "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=",
       "dev": true,
       "requires": {
@@ -7536,7 +7678,7 @@
         },
         "readable-stream": {
           "version": "1.0.34",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
           "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
           "dev": true,
           "requires": {
@@ -7562,7 +7704,7 @@
     },
     "http-errors": {
       "version": "1.6.3",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+      "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
       "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
       "dev": true,
       "requires": {
@@ -7619,7 +7761,7 @@
     },
     "http-proxy-middleware": {
       "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz",
+      "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz",
       "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==",
       "dev": true,
       "requires": {
@@ -7969,6 +8111,12 @@
         "loose-envify": "^1.0.0"
       }
     },
+    "inversify": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz",
+      "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==",
+      "dev": true
+    },
     "invert-kv": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
@@ -9066,6 +9214,12 @@
       "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=",
       "dev": true
     },
+    "lodash.unescape": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
+      "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
+      "dev": true
+    },
     "lodash.uniq": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@@ -9308,7 +9462,7 @@
     },
     "media-typer": {
       "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
       "dev": true
     },
@@ -9333,7 +9487,7 @@
     },
     "meow": {
       "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+      "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
       "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
       "dev": true,
       "requires": {
@@ -9576,7 +9730,7 @@
     },
     "mkdirp": {
       "version": "0.5.1",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
       "dev": true,
       "requires": {
@@ -9624,7 +9778,7 @@
         },
         "commander": {
           "version": "2.15.1",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
+          "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
           "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
           "dev": true
         },
@@ -9701,7 +9855,7 @@
       "dependencies": {
         "debug": {
           "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
           "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
           "dev": true,
           "requires": {
@@ -9730,7 +9884,7 @@
         },
         "ms": {
           "version": "0.7.1",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+          "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
           "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
           "dev": true
         },
@@ -9973,7 +10127,7 @@
       "dependencies": {
         "semver": {
           "version": "5.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+          "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
           "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
           "dev": true
         }
@@ -10071,7 +10225,7 @@
         },
         "chalk": {
           "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
           "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
           "dev": true,
           "requires": {
@@ -10094,7 +10248,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -10469,7 +10623,7 @@
     },
     "os-homedir": {
       "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
       "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
       "dev": true
     },
@@ -10520,7 +10674,7 @@
     },
     "os-tmpdir": {
       "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
       "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
       "dev": true
     },
@@ -10727,7 +10881,7 @@
     },
     "path-is-absolute": {
       "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
       "dev": true
     },
@@ -11438,7 +11592,7 @@
     },
     "pretty-bytes": {
       "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz",
+      "resolved": "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz",
       "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=",
       "dev": true
     },
@@ -11523,7 +11677,7 @@
     },
     "proxy-agent": {
       "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.0.0.tgz",
+      "resolved": "http://registry.npmjs.org/proxy-agent/-/proxy-agent-2.0.0.tgz",
       "integrity": "sha1-V+tTR6qAXXTsaByyVknbo5yTNJk=",
       "dev": true,
       "requires": {
@@ -11548,7 +11702,7 @@
         },
         "lru-cache": {
           "version": "2.6.5",
-          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz",
+          "resolved": "http://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz",
           "integrity": "sha1-5W1jVBSO3o13B7WNFDIg/QjfD9U=",
           "dev": true
         },
@@ -11917,7 +12071,7 @@
         },
         "load-json-file": {
           "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+          "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
           "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
           "dev": true,
           "requires": {
@@ -11950,7 +12104,7 @@
         },
         "pify": {
           "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
           "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
           "dev": true
         },
@@ -12023,6 +12177,12 @@
         }
       }
     },
+    "reflect-metadata": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz",
+      "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==",
+      "dev": true
+    },
     "regenerate": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
@@ -12064,7 +12224,7 @@
     },
     "regexpp": {
       "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz",
       "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==",
       "dev": true
     },
@@ -12123,7 +12283,7 @@
       "dependencies": {
         "jsesc": {
           "version": "0.5.0",
-          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
           "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
           "dev": true
         }
@@ -12162,7 +12322,7 @@
         },
         "css-select": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
           "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
           "dev": true,
           "requires": {
@@ -12184,7 +12344,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -12281,7 +12441,7 @@
     },
     "require-uncached": {
       "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
+      "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
       "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
       "dev": true,
       "requires": {
@@ -12289,6 +12449,12 @@
         "resolve-from": "^1.0.0"
       }
     },
+    "requireindex": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.1.0.tgz",
+      "integrity": "sha1-5UBLgVV+91225JxacgBIk/4D4WI=",
+      "dev": true
+    },
     "requires-port": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -12441,7 +12607,7 @@
     },
     "safe-regex": {
       "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
       "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
       "dev": true,
       "requires": {
@@ -12500,7 +12666,7 @@
         },
         "os-locale": {
           "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+          "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
           "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
           "dev": true,
           "requires": {
@@ -12520,7 +12686,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -12624,7 +12790,7 @@
       "dependencies": {
         "source-map": {
           "version": "0.4.4",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+          "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
           "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
           "dev": true,
           "requires": {
@@ -13460,7 +13626,7 @@
     },
     "strip-eof": {
       "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
       "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
       "dev": true
     },
@@ -13619,7 +13785,7 @@
     },
     "tar": {
       "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+      "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
       "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
       "dev": true,
       "requires": {
@@ -13891,7 +14057,7 @@
     },
     "through": {
       "version": "2.3.8",
-      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
       "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
       "dev": true
     },
@@ -14092,6 +14258,19 @@
       "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==",
       "dev": true
     },
+    "ts-loader": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.2.tgz",
+      "integrity": "sha512-TPeXFkdPjOrVEawY4xUgRnzlHEmKQF1DclJghPGq67jKnroVvs6mEGHWYtbUczgeWTvTaqUjSSaMmp1k5do4vw==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.3.0",
+        "enhanced-resolve": "^4.0.0",
+        "loader-utils": "^1.0.2",
+        "micromatch": "^3.1.4",
+        "semver": "^5.0.1"
+      }
+    },
     "ts-node": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
@@ -14121,6 +14300,141 @@
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
       "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
     },
+    "tslint": {
+      "version": "5.12.0",
+      "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.0.tgz",
+      "integrity": "sha512-CKEcH1MHUBhoV43SA/Jmy1l24HJJgI0eyLbBNSRyFlsQvb9v6Zdq+Nz2vEOH00nC5SUx4SneJ59PZUS/ARcokQ==",
+      "dev": true,
+      "requires": {
+        "babel-code-frame": "^6.22.0",
+        "builtin-modules": "^1.1.1",
+        "chalk": "^2.3.0",
+        "commander": "^2.12.1",
+        "diff": "^3.2.0",
+        "glob": "^7.1.1",
+        "js-yaml": "^3.7.0",
+        "minimatch": "^3.0.4",
+        "resolve": "^1.3.2",
+        "semver": "^5.3.0",
+        "tslib": "^1.8.0",
+        "tsutils": "^2.27.2"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.19.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
+          "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
+          "dev": true
+        },
+        "diff": {
+          "version": "3.5.0",
+          "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+          "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+          "dev": true
+        }
+      }
+    },
+    "tslint-config-airbnb": {
+      "version": "5.11.1",
+      "resolved": "https://registry.npmjs.org/tslint-config-airbnb/-/tslint-config-airbnb-5.11.1.tgz",
+      "integrity": "sha512-hkaittm2607vVMe8eotANGN1CimD5tor7uoY3ypg2VTtEcDB/KGWYbJOz58t8LI4cWSyWtgqYQ5F0HwKxxhlkQ==",
+      "dev": true,
+      "requires": {
+        "tslint-consistent-codestyle": "^1.14.1",
+        "tslint-eslint-rules": "^5.4.0",
+        "tslint-microsoft-contrib": "~5.2.1"
+      }
+    },
+    "tslint-consistent-codestyle": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslint-consistent-codestyle/-/tslint-consistent-codestyle-1.14.1.tgz",
+      "integrity": "sha512-UxGRX2fF5LpZtqYpuPFaIva+2D7ASX3pTVw41yis6Hmw7PPA3cBnFEX1jqRsnyxGrca6mHxz7xDnwCHtOjWJMQ==",
+      "dev": true,
+      "requires": {
+        "@fimbul/bifrost": "^0.15.0",
+        "tslib": "^1.7.1",
+        "tsutils": "^2.29.0"
+      }
+    },
+    "tslint-eslint-rules": {
+      "version": "5.4.0",
+      "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz",
+      "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==",
+      "dev": true,
+      "requires": {
+        "doctrine": "0.7.2",
+        "tslib": "1.9.0",
+        "tsutils": "^3.0.0"
+      },
+      "dependencies": {
+        "doctrine": {
+          "version": "0.7.2",
+          "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz",
+          "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=",
+          "dev": true,
+          "requires": {
+            "esutils": "^1.1.6",
+            "isarray": "0.0.1"
+          }
+        },
+        "esutils": {
+          "version": "1.1.6",
+          "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz",
+          "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=",
+          "dev": true
+        },
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        },
+        "tslib": {
+          "version": "1.9.0",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz",
+          "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==",
+          "dev": true
+        },
+        "tsutils": {
+          "version": "3.5.2",
+          "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.5.2.tgz",
+          "integrity": "sha512-qIlklNuI/1Dzfm+G+kJV5gg3gimZIX5haYtIVQe7qGyKd7eu8T1t1DY6pz4Sc2CGXAj9s1izycctm9Zfl9sRuQ==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.8.1"
+          }
+        }
+      }
+    },
+    "tslint-microsoft-contrib": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz",
+      "integrity": "sha512-PDYjvpo0gN9IfMULwKk0KpVOPMhU6cNoT9VwCOLeDl/QS8v8W2yspRpFFuUS7/c5EIH/n8ApMi8TxJAz1tfFUA==",
+      "dev": true,
+      "requires": {
+        "tsutils": "^2.27.2 <2.29.0"
+      },
+      "dependencies": {
+        "tsutils": {
+          "version": "2.28.0",
+          "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz",
+          "integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.8.1"
+          }
+        }
+      }
+    },
+    "tsutils": {
+      "version": "2.29.0",
+      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+      "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.8.1"
+      }
+    },
     "tty-browserify": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
@@ -14173,6 +14487,45 @@
       "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
       "dev": true
     },
+    "typescript": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz",
+      "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==",
+      "dev": true
+    },
+    "typescript-eslint-parser": {
+      "version": "21.0.2",
+      "resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-21.0.2.tgz",
+      "integrity": "sha512-u+pj4RVJBr4eTzj0n5npoXD/oRthvfUCjSKndhNI714MG0mQq2DJw5WP7qmonRNIFgmZuvdDOH3BHm9iOjIAfg==",
+      "dev": true,
+      "requires": {
+        "eslint-scope": "^4.0.0",
+        "eslint-visitor-keys": "^1.0.0",
+        "typescript-estree": "5.3.0"
+      },
+      "dependencies": {
+        "eslint-scope": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz",
+          "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==",
+          "dev": true,
+          "requires": {
+            "esrecurse": "^4.1.0",
+            "estraverse": "^4.1.1"
+          }
+        }
+      }
+    },
+    "typescript-estree": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/typescript-estree/-/typescript-estree-5.3.0.tgz",
+      "integrity": "sha512-Vu0KmYdSCkpae+J48wsFC1ti19Hq3Wi/lODUaE+uesc3gzqhWbZ5itWbsjylLVbjNW4K41RqDzSfnaYNbmEiMQ==",
+      "dev": true,
+      "requires": {
+        "lodash.unescape": "4.0.1",
+        "semver": "5.5.0"
+      }
+    },
     "uc.micro": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz",
@@ -14647,6 +15000,11 @@
         "throttle-debounce": "^2.0.0"
       }
     },
+    "vue-class-component": {
+      "version": "6.3.2",
+      "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-6.3.2.tgz",
+      "integrity": "sha512-cH208IoM+jgZyEf/g7mnFyofwPDJTM/QvBNhYMjqGB8fCsRyTf68rH2ISw/G20tJv+5mIThQ3upKwoL4jLTr1A=="
+    },
     "vue-cli-plugin-apollo": {
       "version": "0.17.4",
       "resolved": "https://registry.npmjs.org/vue-cli-plugin-apollo/-/vue-cli-plugin-apollo-0.17.4.tgz",
@@ -14702,7 +15060,7 @@
     },
     "vue-eslint-parser": {
       "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz",
+      "resolved": "http://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz",
       "integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==",
       "dev": true,
       "requires": {
@@ -14777,6 +15135,14 @@
         "markdown-it-toc-and-anchor": "^4.1.2"
       }
     },
+    "vue-property-decorator": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-7.2.0.tgz",
+      "integrity": "sha512-sCI6NVM3tEDg+mpZrQlgkddtxd9LbFWetue8D+nqO3agfSLz0KoC/UIi2P1l5E0TDhcUeIXS9rasuP2HWg+L4w==",
+      "requires": {
+        "vue-class-component": "^6.2.0"
+      }
+    },
     "vue-router": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.0.2.tgz",
@@ -15176,7 +15542,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -15435,7 +15801,7 @@
       "dependencies": {
         "hoek": {
           "version": "4.2.1",
-          "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
+          "resolved": "http://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
           "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==",
           "dev": true
         },
@@ -15452,7 +15818,7 @@
         },
         "topo": {
           "version": "2.0.2",
-          "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz",
+          "resolved": "http://registry.npmjs.org/topo/-/topo-2.0.2.tgz",
           "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=",
           "dev": true,
           "requires": {
@@ -15579,7 +15945,7 @@
     },
     "wrap-ansi": {
       "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
       "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
       "dev": true,
       "requires": {
@@ -15615,7 +15981,7 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
           "requires": {
@@ -15702,7 +16068,7 @@
     },
     "yargs": {
       "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
       "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==",
       "dev": true,
       "requires": {
diff --git a/js/package.json b/js/package.json
index e51c93b838d5746f1bc085322c869bed957f404c..36c53616e4601eacfa31329da72495000cc750e9 100644
--- a/js/package.json
+++ b/js/package.json
@@ -2,16 +2,13 @@
   "name": "mobilizon",
   "version": "0.1.0",
   "private": true,
-  "engines": {
-    "node": ">=10.0.0"
-  },
   "scripts": {
-    "dev": "vue-cli-service serve",
     "build": "vue-cli-service build --modern",
     "lint": "vue-cli-service lint",
+    "analyze-bundle": "npm run build -- --report-json && webpack-bundle-analyzer ../priv/static/report.json",
+    "dev": "vue-cli-service serve",
     "test:e2e": "vue-cli-service test:e2e",
-    "test:unit": "vue-cli-service test:unit",
-    "analyze-bundle": "npm run build -- --report-json && webpack-bundle-analyzer ../priv/static/report.json"
+    "test:unit": "vue-cli-service test:unit"
   },
   "dependencies": {
     "apollo-absinthe-upload-link": "^1.4.0",
@@ -26,27 +23,35 @@
     "register-service-worker": "^1.4.1",
     "vue": "^2.5.17",
     "vue-apollo": "^3.0.0-beta.26",
+    "vue-class-component": "^6.3.2",
     "vue-gettext": "^2.1.1",
     "vue-gravatar": "^1.3.0",
     "vue-markdown": "^2.2.4",
+    "vue-property-decorator": "^7.2.0",
     "vue-router": "^3.0.2",
     "vuetify": "^1.3.9",
     "vuetify-google-autocomplete": "^2.0.0-beta.5",
     "vuex": "^3.0.1"
   },
   "devDependencies": {
+    "@types/chai": "^4.1.0",
+    "@types/mocha": "^5.2.4",
     "@vue/cli-plugin-babel": "^3.1.1",
     "@vue/cli-plugin-e2e-nightwatch": "^3.1.1",
     "@vue/cli-plugin-eslint": "^3.1.5",
     "@vue/cli-plugin-pwa": "^3.1.2",
+    "@vue/cli-plugin-typescript": "^3.2.0",
     "@vue/cli-plugin-unit-mocha": "^3.1.1",
     "@vue/cli-service": "^3.1.4",
     "@vue/eslint-config-airbnb": "^3.0.5",
+    "@vue/eslint-config-typescript": "^3.1.0",
     "@vue/test-utils": "^1.0.0-beta.26",
     "chai": "^4.2.0",
     "dotenv-webpack": "^1.5.7",
     "node-sass": "^4.10.0",
     "sass-loader": "^7.1.0",
+    "tslint-config-airbnb": "^5.11.1",
+    "typescript": "^3.0.0",
     "vue-cli-plugin-apollo": "^0.17.4",
     "vue-template-compiler": "^2.5.17",
     "webpack-bundle-analyzer": "^3.0.3"
@@ -55,5 +60,8 @@
     "> 1%",
     "last 2 versions",
     "not ie <= 8"
-  ]
+  ],
+  "engines": {
+    "node": ">=10.0.0"
+  }
 }
diff --git a/js/src/App.vue b/js/src/App.vue
index 7c235cb08224d8e0ae13ad0ffbe8603811a6990a..38879d4c370ac97ac7dcbc74681eec9f20c31608 100644
--- a/js/src/App.vue
+++ b/js/src/App.vue
@@ -14,15 +14,15 @@
         >
           <v-list-tile avatar v-if="actor" slot="activator">
             <v-list-tile-avatar>
-                <img v-if="!actor.avatar"
-                  class="img-circle elevation-7 mb-1"
-                  src="https://picsum.photos/125/125/"
-                >
-                <img v-else
-                      class="img-circle elevation-7 mb-1"
-                      :src="actor.avatar"
-                >
-              </v-list-tile-avatar>
+              <img v-if="!actor.avatar"
+                   class="img-circle elevation-7 mb-1"
+                   src="https://picsum.photos/125/125/"
+              >
+              <img v-else
+                   class="img-circle elevation-7 mb-1"
+                   :src="actor.avatar"
+              >
+            </v-list-tile-avatar>
 
             <v-list-tile-content @click="$router.push({name: 'Account', params: { name: actor.username }})">
               <v-list-tile-title>{{ this.displayed_name }}</v-list-tile-title>
@@ -31,11 +31,11 @@
 
           <v-list-tile avatar v-if="actor">
             <v-list-tile-avatar>
-                <img
-                  class="img-circle elevation-7 mb-1"
-                  src="https://picsum.photos/125/125/"
-                >
-              </v-list-tile-avatar>
+              <img
+                class="img-circle elevation-7 mb-1"
+                src="https://picsum.photos/125/125/"
+              >
+            </v-list-tile-avatar>
 
             <v-list-tile-content>
               <v-list-tile-title>Autre identité</v-list-tile-title>
@@ -44,8 +44,8 @@
 
           <v-list-tile @click="$router.push({ name: 'Identities' })">
             <v-list-tile-action>
-            <v-icon>group</v-icon>
-          </v-list-tile-action>
+              <v-icon>group</v-icon>
+            </v-list-tile-action>
             <v-list-tile-content>
               <v-list-tile-title>Identities</v-list-tile-title>
             </v-list-tile-content>
@@ -100,7 +100,7 @@
       transition="scale-transition"
       v-if="user"
     >
-    <v-btn
+      <v-btn
         slot="activator"
         v-model="fab"
         color="blue darken-2"
@@ -134,7 +134,8 @@
         class="white--text"
         v-translate="{
           date: new Date().getFullYear(),
-          }">© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix & <a href="https://vuejs.org/">VueJS</a> & <a href="https://www.vuetifyjs.com/">Vuetify</a> with some love and some weeks
+          }">© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix & <a href="https://vuejs.org/">VueJS</a> & <a
+        href="https://www.vuetifyjs.com/">Vuetify</a> with some love and some weeks
       </span>
     </v-footer>
     <v-snackbar
@@ -148,75 +149,78 @@
   </v-app>
 </template>
 
-<script>
-import gql from 'graphql-tag';
-import NavBar from '@/components/NavBar';
-import { AUTH_USER_ACTOR, AUTH_USER_ID } from '@/constants';
+<script lang="ts">
+  import NavBar from '@/components/NavBar.vue';
+  import { Component, Vue } from 'vue-property-decorator';
+  import { AUTH_USER_ACTOR, AUTH_USER_ID } from '@/constants';
 
-export default {
-  name: 'app',
-  components: {
-    NavBar,
-  },
-  data() {
-    return {
-      drawer: false,
-      fab: false,
-      user: localStorage.getItem(AUTH_USER_ID),
-      items: [
-        {
-          icon: 'poll', text: 'Events', route: 'EventList', role: null,
-        },
-        {
-          icon: 'group', text: 'Groups', route: 'GroupList', role: null,
-        },
-        {
-          icon: 'content_copy', text: 'Categories', route: 'CategoryList', role: 'ROLE_ADMIN',
-        },
-        { icon: 'settings', text: 'Settings', role: 'ROLE_USER' },
-        { icon: 'chat_bubble', text: 'Send feedback', role: 'ROLE_USER' },
-        { icon: 'help', text: 'Help', role: null },
-        { icon: 'phonelink', text: 'App downloads', role: null },
-      ],
-      error: {
-        timeout: 3000,
-        show: false,
-        text: '',
+  @Component({
+    components: {
+      NavBar
+    }
+  })
+  export default class App extends Vue {
+    drawer = false
+    fab = false
+    user = localStorage.getItem(AUTH_USER_ID)
+    items = [
+      {
+        icon: 'poll', text: 'Events', route: 'EventList', role: null
+      },
+      {
+        icon: 'group', text: 'Groups', route: 'GroupList', role: null
       },
-      show_new_event_button: false,
-      actor: localStorage.getItem(AUTH_USER_ACTOR),
-    };
-  },
-  methods: {
-    showMenuItem(elem) {
-      return elem !== null && this.user && this.user.roles !== undefined ? this.user.roles.includes(elem) : true;
-    },
-    getUser() {
-      return this.user === undefined ? false : this.user;
-    },
-    toggleDrawer() {
-      this.drawer = !this.drawer;
-    },
-  },
-  computed: {
-    displayed_name() {
-      return this.actor.display_name === null ? this.actor.username : this.actor.display_name;
-    },
-  },
-};
+      {
+        icon: 'content_copy', text: 'Categories', route: 'CategoryList', role: 'ROLE_ADMIN'
+      },
+      { icon: 'settings', text: 'Settings', role: 'ROLE_USER' },
+      { icon: 'chat_bubble', text: 'Send feedback', role: 'ROLE_USER' },
+      { icon: 'help', text: 'Help', role: null },
+      { icon: 'phonelink', text: 'App downloads', role: null }
+    ]
+    error = {
+      timeout: 3000,
+      show: false,
+      text: ''
+    }
+
+    show_new_event_button = false
+    actor = localStorage.getItem(AUTH_USER_ACTOR)
+
+    get displayed_name () {
+      // FIXME: load actor
+      return 'no implemented'
+      // return this.actor.display_name === null ? this.actor.username : this.actor.display_name
+    }
+
+    showMenuItem (elem) {
+      // FIXME: load actor
+      return false
+      // return elem !== null && this.user && this.user.roles !== undefined ? this.user.roles.includes(elem) : true
+    }
+
+    getUser () {
+      return this.user === undefined ? false : this.user
+    }
+
+    toggleDrawer () {
+      this.drawer = !this.drawer
+    }
+  }
+
 </script>
 
 <style>
-.router-enter-active, .router-leave-active {
-  transition-property: opacity;
-  transition-duration: .25s;
-}
+  .router-enter-active, .router-leave-active {
+    transition-property: opacity;
+    transition-duration: .25s;
+  }
 
-.router-enter-active {
-  transition-delay: .25s;
-}
+  .router-enter-active {
+    transition-delay: .25s;
+  }
 
-.router-enter, .router-leave-active {
-  opacity: 0
-}
+  .router-enter, .router-leave-active {
+    opacity: 0
+  }
 </style>
diff --git a/js/src/api/_entrypoint.js b/js/src/api/_entrypoint.ts
similarity index 100%
rename from js/src/api/_entrypoint.js
rename to js/src/api/_entrypoint.ts
diff --git a/js/src/components/NavBar.vue b/js/src/components/NavBar.vue
index 9b2741432bb8b0b8454fc1cd8e68996f1a5db37d..6c0e7e7023c1b86fe39a003027135f9817c405af 100644
--- a/js/src/components/NavBar.vue
+++ b/js/src/components/NavBar.vue
@@ -74,69 +74,63 @@
         </v-list>
         <v-card-actions>
           <v-spacer></v-spacer>
-          <v-btn flat @click="notificationMenu = false"><translate>Close</translate></v-btn>
-          <v-btn color="primary" flat @click="notificationMenu = false"><translate>Save</translate></v-btn>
+          <v-btn flat @click="notificationMenu = false">
+            <translate>Close</translate>
+          </v-btn>
+          <v-btn color="primary" flat @click="notificationMenu = false">
+            <translate>Save</translate>
+          </v-btn>
         </v-card-actions>
       </v-card>
     </v-menu>
-    <v-btn v-if="!user" :to="{ name: 'Login' }"><translate>Login</translate></v-btn>
+    <v-btn v-if="!user" :to="{ name: 'Login' }">
+      <translate>Login</translate>
+    </v-btn>
   </v-toolbar>
 </template>
 
-<script>
-import {AUTH_USER_ACTOR, AUTH_USER_ID} from '@/constants';
-import {SEARCH} from '@/graphql/search';
+<style>
+  nav.v-toolbar .v-input__slot {
+    margin-bottom: 0;
+  }
+</style>
 
-export default {
-  name: 'NavBar',
-  props: {
-    toggleDrawer: {
-      type: Function,
-      required: true,
-    },
-  },
-  data() {
-    return {
-      notificationMenu: false,
-      notifications: [
-        { header: 'Coucou' },
-        { title: "T'as une notification", subtitle: 'Et elle est cool' },
-      ],
-      model: null,
-      search: [],
-      searchText: null,
-      searchSelect: null,
-      actor: localStorage.getItem(AUTH_USER_ACTOR),
-      user: localStorage.getItem(AUTH_USER_ID),
-    };
-  },
-  apollo: {
-    search: {
-      query: SEARCH,
-      variables() {
-        return {
-          searchText: this.searchText,
-        };
-      },
-      skip() {
-        return !this.searchText;
+<script lang="ts">
+  import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
+  import { AUTH_USER_ACTOR, AUTH_USER_ID } from '@/constants';
+  import { SEARCH } from '@/graphql/search';
+
+  @Component({
+    apollo: {
+      search: {
+        query: SEARCH,
+        variables() {
+          return {
+            searchText: this.searchText,
+          };
+        },
+        skip() {
+          return !this.searchText;
+        },
       },
-    },
-  },
-  watch: {
-    model(val) {
-      switch(val.__typename) {
-        case 'Event':
-          this.$router.push({ name: 'Event', params: { uuid: val.uuid } });
-          break;
-        case 'Actor':
-          this.$router.push({ name: 'Account', params: { name: this.username_with_domain(val) } });
-          break;
-      }
-    },
-  },
-  computed: {
-    items() {
+    }
+  })
+  export default class NavBar extends Vue {
+    @Prop({ required: true, type: Function }) toggleDrawer!: Function;
+
+    notificationMenu = false;
+    notifications = [
+      { header: 'Coucou' },
+      { title: 'T\'as une notification', subtitle: 'Et elle est cool' },
+    ];
+    model = null;
+    search: any[] = [];
+    searchText: string | null = null;
+    searchSelect = null;
+    actor: string | null = localStorage.getItem(AUTH_USER_ACTOR);
+    user: string | null = localStorage.getItem(AUTH_USER_ID);
+
+    get items() {
       return this.search.map(searchEntry => {
         switch (searchEntry.__typename) {
           case 'Actor':
@@ -148,22 +142,29 @@ export default {
         }
         return searchEntry;
       });
-    },
-  },
-  methods: {
+    }
+
+    @Watch('model')
+    onModelChanged(val) {
+      switch (val.__typename) {
+        case 'Event':
+          this.$router.push({ name: 'Event', params: { uuid: val.uuid } });
+          break;
+        case 'Actor':
+          this.$router.push({ name: 'Account', params: { name: this.username_with_domain(val) } });
+          break;
+      }
+    }
+
     username_with_domain(actor) {
       return actor.preferredUsername + (actor.domain === null ? '' : `@${actor.domain}`);
-    },
+    }
+
     enter() {
       console.log('enter');
-      this.$apollo.queries.search.refetch();
+      this.$apollo.queries[ 'search' ].refetch();
     }
-  },
-};
-</script>
 
-<style>
-nav.v-toolbar .v-input__slot {
-  margin-bottom: 0;
-}
-</style>
+  }
+
+</script>
diff --git a/js/src/constants.js b/js/src/constants.ts
similarity index 100%
rename from js/src/constants.js
rename to js/src/constants.ts
diff --git a/js/src/graphql/actor.js b/js/src/graphql/actor.ts
similarity index 100%
rename from js/src/graphql/actor.js
rename to js/src/graphql/actor.ts
diff --git a/js/src/graphql/auth.js b/js/src/graphql/auth.ts
similarity index 100%
rename from js/src/graphql/auth.js
rename to js/src/graphql/auth.ts
diff --git a/js/src/graphql/category.js b/js/src/graphql/category.ts
similarity index 100%
rename from js/src/graphql/category.js
rename to js/src/graphql/category.ts
diff --git a/js/src/graphql/event.js b/js/src/graphql/event.ts
similarity index 100%
rename from js/src/graphql/event.js
rename to js/src/graphql/event.ts
diff --git a/js/src/graphql/search.js b/js/src/graphql/search.ts
similarity index 100%
rename from js/src/graphql/search.js
rename to js/src/graphql/search.ts
diff --git a/js/src/graphql/upload.js b/js/src/graphql/upload.ts
similarity index 100%
rename from js/src/graphql/upload.js
rename to js/src/graphql/upload.ts
diff --git a/js/src/graphql/user.js b/js/src/graphql/user.ts
similarity index 100%
rename from js/src/graphql/user.js
rename to js/src/graphql/user.ts
diff --git a/js/src/main.js b/js/src/main.ts
similarity index 88%
rename from js/src/main.js
rename to js/src/main.ts
index 59584c5a67e8d2e9386bcdcacfbc8a549917240e..ebf0703f4856a8bed7958cd6a54aadfc53a98e16 100644
--- a/js/src/main.js
+++ b/js/src/main.ts
@@ -11,14 +11,16 @@ import 'vuetify/dist/vuetify.min.css';
 import App from '@/App.vue';
 import router from '@/router';
 // import store from './store';
-import translations from '@/i18n/translations.json';
 import { createProvider } from './vue-apollo';
 
+const translations = require('@/i18n/translations.json');
+
 Vue.config.productionTip = false;
 
 Vue.use(VueMarkdown);
 Vue.use(Vuetify);
-const language = window.navigator.userLanguage || window.navigator.language;
+
+const language = (window.navigator as any).userLanguage || window.navigator.language;
 moment.locale(language);
 
 Vue.filter('formatDate', value => (value ? moment(String(value)).format('LLLL') : null));
@@ -33,8 +35,8 @@ Vue.config.language = language.replace('-', '_');
 
 /* eslint-disable no-new */
 new Vue({
-  el: '#app',
   router,
+  el: '#app',
   template: '<App/>',
   apolloProvider: createProvider(),
   components: { App },
diff --git a/js/src/registerServiceWorker.js b/js/src/registerServiceWorker.ts
similarity index 100%
rename from js/src/registerServiceWorker.js
rename to js/src/registerServiceWorker.ts
diff --git a/js/src/router/index.js b/js/src/router/index.ts
similarity index 77%
rename from js/src/router/index.js
rename to js/src/router/index.ts
index 7b1f82f92a0e03d41c240cd2c4ec11f2d8a1082b..1bd4a0e36c5a603b446ad1c7921ee108b0703a89 100644
--- a/js/src/router/index.js
+++ b/js/src/router/index.ts
@@ -1,23 +1,23 @@
 import Vue from 'vue';
 import Router from 'vue-router';
-import PageNotFound from '@/components/PageNotFound';
-import Home from '@/components/Home';
-import Event from '@/components/Event/Event';
-import EventList from '@/components/Event/EventList';
-import Location from '@/components/Location';
-import CreateEvent from '@/components/Event/Create';
-import CategoryList from '@/components/Category/List';
-import CreateCategory from '@/components/Category/Create';
-import Register from '@/components/Account/Register';
-import Login from '@/components/Account/Login';
-import Validate from '@/components/Account/Validate';
-import ResendConfirmation from '@/components/Account/ResendConfirmation';
-import SendPasswordReset from '@/components/Account/SendPasswordReset';
-import PasswordReset from '@/components/Account/PasswordReset';
-import Account from '@/components/Account/Account';
-import CreateGroup from '@/components/Group/Create';
-import Group from '@/components/Group/Group';
-import GroupList from '@/components/Group/GroupList';
+import PageNotFound from '@/components/PageNotFound.vue';
+import Home from '@/components/Home.vue';
+import Event from '@/components/Event/Event.vue';
+import EventList from '@/components/Event/EventList.vue';
+import Location from '@/components/Location.vue';
+import CreateEvent from '@/components/Event/Create.vue';
+import CategoryList from '@/components/Category/List.vue';
+import CreateCategory from '@/components/Category/Create.vue';
+import Register from '@/components/Account/Register.vue';
+import Login from '@/components/Account/Login.vue';
+import Validate from '@/components/Account/Validate.vue';
+import ResendConfirmation from '@/components/Account/ResendConfirmation.vue';
+import SendPasswordReset from '@/components/Account/SendPasswordReset.vue';
+import PasswordReset from '@/components/Account/PasswordReset.vue';
+import Account from '@/components/Account/Account.vue';
+import CreateGroup from '@/components/Group/Create.vue';
+import Group from '@/components/Group/Group.vue';
+import GroupList from '@/components/Group/GroupList.vue';
 import Identities from '../components/Account/Identities.vue';
 
 Vue.use(Router);
diff --git a/js/src/shims-tsx.d.ts b/js/src/shims-tsx.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c656c68b87727e55afdd5b58398ba054b36d51e9
--- /dev/null
+++ b/js/src/shims-tsx.d.ts
@@ -0,0 +1,13 @@
+import Vue, { VNode } from 'vue'
+
+declare global {
+  namespace JSX {
+    // tslint:disable no-empty-interface
+    interface Element extends VNode {}
+    // tslint:disable no-empty-interface
+    interface ElementClass extends Vue {}
+    interface IntrinsicElements {
+      [elem: string]: any
+    }
+  }
+}
diff --git a/js/src/shims-vue.d.ts b/js/src/shims-vue.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d9f24faa42e74d0f178759f8f085a0541db6cc7c
--- /dev/null
+++ b/js/src/shims-vue.d.ts
@@ -0,0 +1,4 @@
+declare module '*.vue' {
+  import Vue from 'vue'
+  export default Vue
+}
diff --git a/js/src/vue-apollo.js b/js/src/vue-apollo.ts
similarity index 93%
rename from js/src/vue-apollo.js
rename to js/src/vue-apollo.ts
index 5e8cbf76c4ba7d2f45b558db9d45102387178541..0a613e000b95c84647cc65ef9f4961547d98dfa9 100644
--- a/js/src/vue-apollo.js
+++ b/js/src/vue-apollo.ts
@@ -33,7 +33,6 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({
 
 const cache = new InMemoryCache({ fragmentMatcher });
 
-
 const authMiddleware = new ApolloLink((operation, forward) => {
   // add the authorization to the headers
   const token = localStorage.getItem(AUTH_TOKEN);
@@ -43,7 +42,9 @@ const authMiddleware = new ApolloLink((operation, forward) => {
     },
   });
 
-  return forward(operation);
+  if (forward) forward(operation);
+
+  return null;
 });
 
 const uploadLink = createLink({
@@ -60,6 +61,8 @@ const link = authMiddleware.concat(uploadLink);
 
 // Config
 const defaultOptions = {
+  cache,
+  link,
   // You can use `https` for secure connection (recommended in production)
   httpEndpoint,
   // You can use `wss` for secure connection (recommended in production)
@@ -74,9 +77,8 @@ const defaultOptions = {
   websocketsOnly: false,
   // Is being rendered on the server?
   ssr: false,
-  cache,
-  link,
   defaultHttpLink: false,
+  connectToDevTools: true,
 };
 
 // Call this in the Vue app file
@@ -89,23 +91,18 @@ export function createProvider(options = {}) {
   apolloClient.wsClient = wsClient;
 
   // Create vue apollo provider
-  const apolloProvider = new VueApollo({
+  return new VueApollo({
     defaultClient: apolloClient,
-    link,
-    cache,
-    connectToDevTools: true,
-    defaultOptions: {
-      $query: {
-        // fetchPolicy: 'cache-and-network',
-      },
-    },
+    // defaultOptions: {
+    //   $query: {
+    //     fetchPolicy: 'cache-and-network',
+    //   },
+    // },
     errorHandler(error) {
       // eslint-disable-next-line no-console
       console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message);
     },
   });
-
-  return apolloProvider;
 }
 
 // Manually call this when user log in
diff --git a/js/tests/unit/.eslintrc.js b/js/tests/unit/.eslintrc.js
deleted file mode 100644
index 00c5cf0bdccc6154da9e6f4ea2eb6e927f9dd395..0000000000000000000000000000000000000000
--- a/js/tests/unit/.eslintrc.js
+++ /dev/null
@@ -1,8 +0,0 @@
-module.exports = {
-  env: {
-    mocha: true,
-  },
-  rules: {
-    'import/no-extraneous-dependencies': 'off',
-  },
-};
diff --git a/js/tests/unit/HelloWorld.spec.js b/js/tests/unit/HelloWorld.spec.js
deleted file mode 100644
index 7527955b9407f89b1fbd8705dfcdeba3fd352037..0000000000000000000000000000000000000000
--- a/js/tests/unit/HelloWorld.spec.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { expect } from 'chai';
-import { shallow } from '@vue/test-utils';
-import HelloWorld from '@/components/HelloWorld.vue';
-
-describe('HelloWorld.vue', () => {
-  it('renders props.msg when passed', () => {
-    const msg = 'new message';
-    const wrapper = shallow(HelloWorld, {
-      propsData: { msg },
-    });
-    expect(wrapper.text()).to.include(msg);
-  });
-});
diff --git a/js/tsconfig.json b/js/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..f28e98af763c4355d169f98f052a5d7d0cc59ff2
--- /dev/null
+++ b/js/tsconfig.json
@@ -0,0 +1,42 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "strict": true,
+    "jsx": "preserve",
+    "importHelpers": true,
+    "moduleResolution": "node",
+    "experimentalDecorators": true,
+    "esModuleInterop": true,
+    "noImplicitAny": false,
+    "allowSyntheticDefaultImports": true,
+    "sourceMap": true,
+    "baseUrl": ".",
+    "types": [
+      "webpack-env",
+      "mocha",
+      "chai"
+    ],
+    "paths": {
+      "@/*": [
+        "src/*"
+      ]
+    },
+    "lib": [
+      "esnext",
+      "dom",
+      "dom.iterable",
+      "scripthost"
+    ]
+  },
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue",
+    "tests/**/*.ts",
+    "tests/**/*.tsx"
+  ],
+  "exclude": [
+    "node_modules"
+  ]
+}
diff --git a/js/tslint.json b/js/tslint.json
new file mode 100644
index 0000000000000000000000000000000000000000..6bd52c80b8d69430673a7c5f22c7742a75a9e4ab
--- /dev/null
+++ b/js/tslint.json
@@ -0,0 +1,7 @@
+{
+  "extends": "tslint-config-airbnb",
+  "rules": {
+    "max-line-length": [ true, 140 ],
+    "import-name": false
+  }
+}