changeset 686:a059100b73dc

Irccd: wrap all possible insecure functions in Javascript API, closes #794 @2h To avoid interfering with the Duktape stack, do not throw any exception in all Javascript API, wrap all function and convert them into Duktape errors.
author David Demelier <markand@malikania.fr>
date Fri, 13 Apr 2018 23:05:56 +0200
parents e81c6badede3
children 84537e43b352
files libirccd-js/irccd/js/directory_jsapi.cpp libirccd-js/irccd/js/duktape.hpp libirccd-js/irccd/js/elapsed_timer_jsapi.cpp libirccd-js/irccd/js/file_jsapi.cpp libirccd-js/irccd/js/irccd_jsapi.cpp libirccd-js/irccd/js/irccd_jsapi.hpp libirccd-js/irccd/js/logger_jsapi.cpp libirccd-js/irccd/js/plugin_jsapi.cpp libirccd-js/irccd/js/plugin_jsapi.hpp libirccd-js/irccd/js/server_jsapi.cpp libirccd-js/irccd/js/server_jsapi.hpp libirccd-js/irccd/js/system_jsapi.cpp libirccd-js/irccd/js/timer_jsapi.cpp libirccd-js/irccd/js/unicode_jsapi.cpp libirccd-js/irccd/js/util_jsapi.cpp libirccd/irccd/daemon/server.cpp tests/src/libirccd-js/jsapi-irccd/main.cpp
diffstat 17 files changed, 1797 insertions(+), 865 deletions(-) [+]
line wrap: on
line diff
--- a/libirccd-js/irccd/js/directory_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/directory_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -38,6 +38,22 @@
 
 namespace {
 
+template <typename Handler>
+duk_ret_t wrap(duk_context* ctx, Handler handler)
+{
+    try {
+        return handler();
+    } catch (const boost::system::system_error& ex) {
+        dukx_throw(ctx, ex);
+    } catch (const std::system_error& ex) {
+        dukx_throw(ctx, ex);
+    } catch (const std::exception& ex) {
+        dukx_throw(ctx, ex);
+    }
+
+    return 0;
+}
+
 std::string path(duk_context *ctx)
 {
     duk_push_this(ctx);
@@ -59,73 +75,78 @@
 /*
  * Generic find function for:
  *
- * - Directory.find
- * - Directory.prototype.find
+ * - Irccd.Directory.find
+ * - Irccd.Directory.prototype.find
  *
- * The patternIndex is the argument where to test if the argument is a regex or
+ * The pattern_index is the argument where to test if the argument is a regex or
  * a string.
  */
 duk_ret_t find(duk_context* ctx, std::string base, bool recursive, int pattern_index)
 {
-    try {
-        std::string path;
+    /*
+     * Helper for checking if it's a valid RegExp object.
+     */
+    const auto is_regex = [&] {
+        duk_get_global_string(ctx, "RegExp");
+        const auto result = duk_instanceof(ctx, pattern_index, -1);
+        duk_pop(ctx);
 
-        if (duk_is_string(ctx, pattern_index))
-            path = fs_util::find(base, dukx_get<std::string>(ctx, pattern_index), recursive);
-        else {
-            // Check if it's a valid RegExp object.
-            duk_get_global_string(ctx, "RegExp");
-            const auto is_regex = duk_instanceof(ctx, pattern_index, -1);
-            duk_pop(ctx);
+        return result;
+    };
 
-            if (is_regex) {
-                duk_get_prop_string(ctx, pattern_index, "source");
-                const auto pattern = duk_to_string(ctx, -1);
-                duk_pop(ctx);
+    /**
+     * Helper for getting regex source.
+     */
+    const auto pattern = [&] {
+        duk_get_prop_string(ctx, pattern_index, "source");
+        const auto pattern = duk_to_string(ctx, -1);
+        duk_pop(ctx);
 
-                path = fs_util::find(base, std::regex(pattern), recursive);
-            } else
-                duk_error(ctx, DUK_ERR_TYPE_ERROR, "pattern must be a string or a regex expression");
-        }
+        return pattern;
+    };
 
-        if (path.empty())
-            return 0;
+    std::string path;
 
-        dukx_push(ctx, path);
-    } catch (const std::exception& ex) {
-        duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-    }
+    if (duk_is_string(ctx, pattern_index))
+        path = fs_util::find(base, dukx_get<std::string>(ctx, pattern_index), recursive);
+    else if (is_regex())
+        path = fs_util::find(base, pattern(), recursive);
+    else
+        throw dukx_type_error("pattern must be a string or a regex expression");
 
-    return 1;
+    if (path.empty())
+        return 0;
+
+    return dukx_push(ctx, path);
 }
 
 /*
  * Generic remove function for:
  *
- * - Directory.remove
- * - Directory.prototype.remove
+ * - Irccd.Directory.remove
+ * - Irccd.Directory.prototype.remove
  */
-duk_ret_t remove(duk_context* ctx, const std::string& path, bool recursive)
+duk_ret_t remove(const std::string& path, bool recursive)
 {
-    boost::system::error_code ec;
-
-    if (!boost::filesystem::is_directory(path, ec) || ec)
-        dukx_throw(ctx, system_error(EINVAL, "not a directory"));
+    if (!boost::filesystem::is_directory(path))
+        throw std::system_error(make_error_code(std::errc::invalid_argument));
 
     if (!recursive)
-        boost::filesystem::remove(path, ec);
+        boost::filesystem::remove(path);
     else
-        boost::filesystem::remove_all(path, ec);
+        boost::filesystem::remove_all(path);
 
     return 0;
 }
 
+// {{{ Irccd.Directory.prototype.find
+
 /*
- * Method: Directory.find(pattern, recursive)
+ * Method: Irccd.Directory.prototype.find(pattern, recursive)
  * --------------------------------------------------------
  *
- * Synonym of Directory.find(path, pattern, recursive) but the path is taken
- * from the directory object.
+ * Synonym of Irccd.Directory.find(path, pattern, recursive) but the path is
+ * taken from the directory object.
  *
  * Arguments:
  *   - pattern, the regular expression or file name,
@@ -133,15 +154,21 @@
  * Returns:
  *   The path to the file or undefined if not found.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t method_find(duk_context* ctx)
+duk_ret_t Directory_prototype_find(duk_context* ctx)
 {
-    return find(ctx, path(ctx), duk_get_boolean(ctx, 1), 0);
+    return wrap(ctx, [&] {
+        return find(ctx, path(ctx), dukx_get<bool>(ctx, 1), 0);
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Directory.prototype.remove
+
 /*
- * Method: Directory.remove(recursive)
+ * Method: Irccd.Directory.prototype.remove(recursive)
  * --------------------------------------------------------
  *
  * Synonym of Directory.remove(recursive) but the path is taken from the
@@ -150,23 +177,18 @@
  * Arguments:
  *   - recursive, recursively or not (default: false).
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t method_remove(duk_context* ctx)
+duk_ret_t Directory_prototype_remove(duk_context* ctx)
 {
-    return remove(ctx, path(ctx), duk_get_boolean(ctx, 0));
+    return wrap(ctx, [&] {
+        return remove(path(ctx), dukx_get<bool>(ctx, 0));
+    });
 }
 
-const duk_function_list_entry methods[] = {
-    { "find",       method_find,    DUK_VARARGS },
-    { "remove",     method_remove,  1           },
-    { nullptr,      nullptr,        0           }
-};
+// }}}
 
-/*
- * Directory "static" functions
- * ------------------------------------------------------------------
- */
+// {{{ Irccd.Directory [constructor]
 
 /*
  * Function: Irccd.Directory(path) [constructor]
@@ -177,18 +199,18 @@
  * Arguments:
  *   - path, the path to the directory,
  * Throws:
- *   - Any exception on error
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t constructor(duk_context* ctx)
+duk_ret_t Directory_constructor(duk_context* ctx)
 {
-    if (!duk_is_constructor_call(ctx))
-        return 0;
+    return wrap(ctx, [&] {
+        if (!duk_is_constructor_call(ctx))
+            return 0;
 
-    try {
-        auto path = duk_require_string(ctx, 0);
+        const auto path = dukx_require<std::string>(ctx, 0);
 
         if (!boost::filesystem::is_directory(path))
-            dukx_throw(ctx, system_error(EINVAL, "not a directory"));
+            throw std::system_error(make_error_code(std::errc::invalid_argument));
 
         duk_push_this(ctx);
 
@@ -212,15 +234,17 @@
         dukx_push(ctx, "path");
         dukx_push(ctx, path);
         duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, system_error(errno, ex.what()));
-    }
 
-    return 0;
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Directory.find
+
 /*
- * Function: irccd.Directory.find(path, pattern, recursive)
+ * Function: Irccd.Directory.find(path, pattern, recursive)
  * --------------------------------------------------------
  *
  * Find an entry by a pattern or a regular expression.
@@ -231,14 +255,22 @@
  *   - recursive, set to true to search recursively (default: false).
  * Returns:
  *   The path to the file or undefined on errors or not found.
+ * Throws:
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t func_find(duk_context* ctx)
+duk_ret_t Directory_find(duk_context* ctx)
 {
-    return find(ctx, duk_require_string(ctx, 0), duk_get_boolean(ctx, 2), 1);
+    return wrap(ctx, [&] {
+        return find(ctx, dukx_require<std::string>(ctx, 0), dukx_get<bool>(ctx, 2), 1);
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Directory.remove
+
 /*
- * Function: irccd.Directory.remove(path, recursive)
+ * Function: Irccd.Directory.remove(path, recursive)
  * --------------------------------------------------------
  *
  * Remove the directory optionally recursively.
@@ -247,15 +279,21 @@
  *   - path, the path to the directory,
  *   - recursive, recursively or not (default: false).
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t func_remove(duk_context *ctx)
+duk_ret_t Directory_remove(duk_context *ctx)
 {
-    return remove(ctx, duk_require_string(ctx, 0), duk_get_boolean(ctx, 1));
+    return wrap(ctx, [&] {
+        return remove(dukx_require<std::string>(ctx, 0), dukx_get<bool>(ctx, 1));
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Directory.mkdir
+
 /*
- * Function: irccd.Directory.mkdir(path, mode = 0700)
+ * Function: Irccd.Directory.mkdir(path, mode = 0700)
  * --------------------------------------------------------
  *
  * Create a directory specified by path. It will create needed subdirectories
@@ -264,36 +302,42 @@
  * Arguments:
  *   - path, the path to the directory,
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t func_mkdir(duk_context *ctx)
+duk_ret_t Directory_mkdir(duk_context *ctx)
 {
-    try {
-        boost::filesystem::create_directories(duk_require_string(ctx, 0));
-    } catch (const std::exception &ex) {
-        dukx_throw(ctx, system_error(errno, ex.what()));
-    }
+    return wrap(ctx, [&] {
+        boost::filesystem::create_directories(dukx_require<std::string>(ctx, 0));
 
-    return 0;
+        return 0;
+    });
 }
 
+// }}}
+
+const duk_function_list_entry methods[] = {
+    { "find",           Directory_prototype_find,   DUK_VARARGS },
+    { "remove",         Directory_prototype_remove, 1           },
+    { nullptr,          nullptr,                    0           }
+};
+
 const duk_function_list_entry functions[] = {
-    { "find",           func_find,      DUK_VARARGS },
-    { "mkdir",          func_mkdir,     DUK_VARARGS },
-    { "remove",         func_remove,    DUK_VARARGS },
-    { nullptr,          nullptr,        0           }
+    { "find",           Directory_find,             DUK_VARARGS },
+    { "mkdir",          Directory_mkdir,            DUK_VARARGS },
+    { "remove",         Directory_remove,           DUK_VARARGS },
+    { nullptr,          nullptr,                    0           }
 };
 
 const duk_number_list_entry constants[] = {
-    { "TypeFile",       static_cast<int>(fs::regular_file)    },
-    { "TypeDir",        static_cast<int>(fs::directory_file)  },
-    { "TypeLink",       static_cast<int>(fs::symlink_file)    },
-    { "TypeBlock",      static_cast<int>(fs::block_file)      },
-    { "TypeCharacter",  static_cast<int>(fs::character_file)  },
-    { "TypeFifo",       static_cast<int>(fs::fifo_file)       },
-    { "TypeSocket",     static_cast<int>(fs::socket_file)     },
-    { "TypeUnknown",    static_cast<int>(fs::type_unknown)    },
-    { nullptr,          0                                           }
+    { "TypeFile",       static_cast<int>(fs::regular_file)      },
+    { "TypeDir",        static_cast<int>(fs::directory_file)    },
+    { "TypeLink",       static_cast<int>(fs::symlink_file)      },
+    { "TypeBlock",      static_cast<int>(fs::block_file)        },
+    { "TypeCharacter",  static_cast<int>(fs::character_file)    },
+    { "TypeFifo",       static_cast<int>(fs::fifo_file)         },
+    { "TypeSocket",     static_cast<int>(fs::socket_file)       },
+    { "TypeUnknown",    static_cast<int>(fs::type_unknown)      },
+    { nullptr,          0                                       }
 };
 
 } // !namespace
@@ -308,7 +352,7 @@
     dukx_stack_assert sa(plugin->context());
 
     duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 2);
+    duk_push_c_function(plugin->context(), Directory_constructor, 2);
     duk_put_number_list(plugin->context(), -1, constants);
     duk_put_function_list(plugin->context(), -1, functions);
 
--- a/libirccd-js/irccd/js/duktape.hpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/duktape.hpp	Fri Apr 13 23:05:56 2018 +0200
@@ -255,6 +255,162 @@
 };
 
 /**
+ * \brief Base ECMAScript error class.
+ * \warning Override the function create for your own exceptions
+ */
+class dukx_error {
+private:
+    int type_{DUK_ERR_ERROR};
+    std::string message_;
+
+protected:
+    /**
+     * Constructor with a type of error specified, specially designed for
+     * derived errors.
+     *
+     * \param type of error (e.g. DUK_ERR_ERROR)
+     * \param message the message
+     */
+    inline dukx_error(int type, std::string message) noexcept
+        : type_(type)
+        , message_(std::move(message))
+    {
+    }
+
+public:
+    /**
+     * Constructor with a message.
+     *
+     * \param message the message
+     */
+    inline dukx_error(std::string message) noexcept
+        : message_(std::move(message))
+    {
+    }
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~dukx_error() = default;
+
+    /**
+     * Get internal Duktape error (e.g. DUK_ERR_TYPE_ERROR)
+     *
+     * \return the type
+     */
+    inline int get_type() const noexcept
+    {
+        return type_;
+    }
+
+    /**
+     * Get the error message.
+     *
+     * \return the message
+     */
+    inline const std::string& get_message() const noexcept
+    {
+        return message_;
+    }
+};
+
+/**
+ * \brief Error in eval() function.
+ */
+class dukx_eval_error : public dukx_error {
+public:
+    /**
+     * Construct an EvalError.
+     *
+     * \param message the message
+     */
+    inline dukx_eval_error(std::string message) noexcept
+        : dukx_error(DUK_ERR_EVAL_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief Value is out of range.
+ */
+class dukx_range_error : public dukx_error {
+public:
+    /**
+     * Construct an RangeError.
+     *
+     * \param message the message
+     */
+    inline dukx_range_error(std::string message) noexcept
+        : dukx_error(DUK_ERR_RANGE_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief Trying to use a variable that does not exist.
+ */
+class dukx_reference_error : public dukx_error {
+public:
+    /**
+     * Construct an ReferenceError.
+     *
+     * \param message the message
+     */
+    inline dukx_reference_error(std::string message) noexcept
+        : dukx_error(DUK_ERR_REFERENCE_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief Syntax error in the script.
+ */
+class dukx_syntax_error : public dukx_error {
+public:
+    /**
+     * Construct an SyntaxError.
+     *
+     * \param message the message
+     */
+    inline dukx_syntax_error(std::string message) noexcept
+        : dukx_error(DUK_ERR_SYNTAX_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief Invalid type given.
+ */
+class dukx_type_error : public dukx_error {
+public:
+    /**
+     * Construct an TypeError.
+     *
+     * \param message the message
+     */
+    inline dukx_type_error(std::string message) noexcept
+        : dukx_error(DUK_ERR_TYPE_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief URI manipulation failure.
+ */
+class dukx_uri_error : public dukx_error {
+public:
+    /**
+     * Construct an URIError.
+     *
+     * \param message the message
+     */
+    inline dukx_uri_error(std::string message) noexcept
+        : dukx_error(DUK_ERR_URI_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
  * \brief Operations on different types.
  *
  * This class provides some functions for the given type, depending on the
@@ -280,6 +436,20 @@
  *   - `duk_double_t`,
  *   - `const char*`,
  *   - `std::string`
+ *
+ * It is also specialized for all exceptions types:
+ *
+ *   - `dukx_error`,
+ *   - `dukx_eval_error`,
+ *   - `dukx_range_error`,
+ *   - `dukx_reference_error`,
+ *   - `dukx_syntax_error`,
+ *   - `dukx_type_error`,
+ *   - `dukx_uri_error`.
+ *
+ * And more general std::exception:
+ *
+ *   - `std::exception`.
  */
 template <typename T>
 class dukx_type_traits : public std::false_type {
@@ -580,6 +750,83 @@
 };
 
 /**
+ * \brief Specialization for dukx_error.
+ */
+template <>
+class dukx_type_traits<dukx_error> : public std::true_type {
+public:
+    /**
+     * Create the exception on the stack.
+     *
+     * \param ctx the context
+     * \param ex the error
+     */
+    static void raise(duk_context* ctx, const dukx_error& ex)
+    {
+        duk_error(ctx, ex.get_type(), "%s", ex.get_message().c_str());
+    }
+};
+
+/**
+ * \brief Specialization for dukx_eval_error.
+ */
+template <>
+class dukx_type_traits<dukx_eval_error> : public dukx_type_traits<dukx_error> {
+};
+
+/**
+ * \brief Specialization for dukx_range_error.
+ */
+template <>
+class dukx_type_traits<dukx_range_error> : public dukx_type_traits<dukx_error> {
+};
+
+/**
+ * \brief Specialization for dukx_reference_error.
+ */
+template <>
+class dukx_type_traits<dukx_reference_error> : public dukx_type_traits<dukx_error> {
+};
+
+/**
+ * \brief Specialization for dukx_syntax_error.
+ */
+template <>
+class dukx_type_traits<dukx_syntax_error> : public dukx_type_traits<dukx_error> {
+};
+
+/**
+ * \brief Specialization for dukx_type_error.
+ */
+template <>
+class dukx_type_traits<dukx_type_error> : public dukx_type_traits<dukx_error> {
+};
+
+/**
+ * \brief Specialization for dukx_uri_error.
+ */
+template <>
+class dukx_type_traits<dukx_uri_error> : public dukx_type_traits<dukx_error> {
+};
+
+/**
+ * \brief Specialization for std::exception.
+ */
+template <>
+class dukx_type_traits<std::exception> : public std::true_type {
+public:
+    /**
+     * Raise std::exception as general DUK_ERR_ERROR.
+     *
+     * \param ctx the context
+     * \param ex the exception
+     */
+    static void raise(duk_context* ctx, const std::exception& ex) {
+        duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+    }
+};
+
+/**
  * \brief Partial specialization for collections.
  *
  * Derive from this class to implement type traits for collections.
@@ -784,164 +1031,23 @@
 }
 
 /**
- * \brief Base ECMAScript error class.
- * \warning Override the function create for your own exceptions
- */
-class dukx_error {
-private:
-    int type_{DUK_ERR_ERROR};
-    std::string message_;
-
-protected:
-    /**
-     * Constructor with a type of error specified, specially designed for
-     * derived errors.
-     *
-     * \param type of error (e.g. DUK_ERR_ERROR)
-     * \param message the message
-     */
-    inline dukx_error(int type, std::string message) noexcept
-        : type_(type)
-        , message_(std::move(message))
-    {
-    }
-
-public:
-    /**
-     * Constructor with a message.
-     *
-     * \param message the message
-     */
-    inline dukx_error(std::string message) noexcept
-        : message_(std::move(message))
-    {
-    }
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~dukx_error() = default;
-
-    /**
-     * Create the exception on the stack.
-     *
-     * \note the default implementation search for the global variables
-     * \param ctx the context
-     */
-    void create(duk_context* ctx) const
-    {
-        duk_push_error_object(ctx, type_, "%s", message_.c_str());
-    }
-};
-
-/**
- * \brief Error in eval() function.
- */
-class dukx_eval_error : public dukx_error {
-public:
-    /**
-     * Construct an EvalError.
-     *
-     * \param message the message
-     */
-    inline dukx_eval_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_EVAL_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Value is out of range.
- */
-class dukx_range_error : public dukx_error {
-public:
-    /**
-     * Construct an RangeError.
-     *
-     * \param message the message
-     */
-    inline dukx_range_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_RANGE_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Trying to use a variable that does not exist.
- */
-class dukx_reference_error : public dukx_error {
-public:
-    /**
-     * Construct an ReferenceError.
-     *
-     * \param message the message
-     */
-    inline dukx_reference_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_REFERENCE_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Syntax error in the script.
- */
-class dukx_syntax_error : public dukx_error {
-public:
-    /**
-     * Construct an SyntaxError.
-     *
-     * \param message the message
-     */
-    inline dukx_syntax_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_SYNTAX_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Invalid type given.
- */
-class dukx_type_error : public dukx_error {
-public:
-    /**
-     * Construct an TypeError.
-     *
-     * \param message the message
-     */
-    inline dukx_type_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_TYPE_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief URI manipulation failure.
- */
-class dukx_uri_error : public dukx_error {
-public:
-    /**
-     * Construct an URIError.
-     *
-     * \param message the message
-     */
-    inline dukx_uri_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_URI_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
  * Create an exception into the stack and throws it.
  *
+ * The dukx_type_traits<Error> must have the following function:
+ *
+ * ```
+ * static void raise(const Error&);
+ * ```
+ *
  * \param ctx the Duktape context
  * \param error the error object
  */
 template <typename Error>
 void dukx_throw(duk_context* ctx, const Error& error)
 {
-    error.create(ctx);
+    static_assert(dukx_type_traits<Error>::value, "type T not supported");
 
-    (void)duk_throw(ctx);
+    dukx_type_traits<Error>::raise(ctx, error);
 }
 
 /**
--- a/libirccd-js/irccd/js/elapsed_timer_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/elapsed_timer_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -27,6 +27,8 @@
 
 const char* signature("\xff""\xff""irccd-elapsed-timer-ptr");
 
+// {{{ self
+
 boost::timer::cpu_timer* self(duk_context* ctx)
 {
     dukx_stack_assert sa(ctx);
@@ -42,34 +44,46 @@
     return ptr;
 }
 
+// }}}
+
+// {{{ Irccd.ElapsedTimer.prototype.pause
+
 /*
- * Method: ElapsedTimer.pause
+ * Method: ElapsedTimer.prototype.pause
  * ------------------------------------------------------------------
  *
  * Pause the timer, without resetting the current elapsed time stored.
  */
-duk_ret_t pause(duk_context* ctx)
+duk_ret_t ElapsedTimer_prototype_pause(duk_context* ctx)
 {
     self(ctx)->stop();
 
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.ElapsedTimer.prototype.restart
+
 /*
- * Method: ElapsedTimer.restart
+ * Method: Irccd.ElapsedTimer.prototype.restart
  * ------------------------------------------------------------------
  *
  * Restart the timer without resetting the current elapsed time.
  */
-duk_ret_t restart(duk_context* ctx)
+duk_ret_t ElapsedTimer_prototype_restart(duk_context* ctx)
 {
     self(ctx)->resume();
 
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.ElapsedTimer.prototype.elapsed
+
 /*
- * Method: ElapsedTimer.elapsed
+ * Method: ElapsedTimer.prototype.elapsed
  * ------------------------------------------------------------------
  *
  * Get the number of elapsed milliseconds.
@@ -77,20 +91,24 @@
  * Returns:
  *   The time elapsed.
  */
-duk_ret_t elapsed(duk_context* ctx)
+duk_ret_t ElapsedTimer_prototype_elapsed(duk_context* ctx)
 {
     duk_push_uint(ctx, self(ctx)->elapsed().wall / 1000000LL);
 
     return 1;
 }
 
+// }}}
+
+// {{{ Irccd.ElapsedTimer [constructor]
+
 /*
  * Function: Irccd.ElapsedTimer [constructor]
  * ------------------------------------------------------------------
  *
  * Construct a new ElapsedTimer object.
  */
-duk_ret_t constructor(duk_context* ctx)
+duk_ret_t ElapsedTimer_constructor(duk_context* ctx)
 {
     duk_push_this(ctx);
     duk_push_pointer(ctx, new boost::timer::cpu_timer);
@@ -100,13 +118,17 @@
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.ElapsedTimer [destructor]
+
 /*
  * Function: Irccd.ElapsedTimer [destructor]
  * ------------------------------------------------------------------
  *
  * Delete the property.
  */
-duk_ret_t destructor(duk_context* ctx)
+duk_ret_t ElapsedTimer_destructor(duk_context* ctx)
 {
     duk_get_prop_string(ctx, 0, signature);
     delete static_cast<boost::timer::cpu_timer*>(duk_to_pointer(ctx, -1));
@@ -116,11 +138,13 @@
     return 0;
 }
 
+// }}}
+
 const duk_function_list_entry methods[] = {
-    { "elapsed",    elapsed,    0 },
-    { "pause",      pause,      0 },
-    { "restart",    restart,    0 },
-    { nullptr,      nullptr,    0 }
+    { "elapsed",    ElapsedTimer_prototype_elapsed, 0 },
+    { "pause",      ElapsedTimer_prototype_pause,   0 },
+    { "restart",    ElapsedTimer_prototype_restart, 0 },
+    { nullptr,      nullptr,                        0 }
 };
 
 } // !namespace
@@ -135,10 +159,10 @@
     dukx_stack_assert sa(plugin->context());
 
     duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 0);
+    duk_push_c_function(plugin->context(), ElapsedTimer_constructor, 0);
     duk_push_object(plugin->context());
     duk_put_function_list(plugin->context(), -1, methods);
-    duk_push_c_function(plugin->context(), destructor, 1);
+    duk_push_c_function(plugin->context(), ElapsedTimer_destructor, 1);
     duk_set_finalizer(plugin->context(), -2);
     duk_put_prop_string(plugin->context(), -2, "prototype");
     duk_put_prop_string(plugin->context(), -2, "ElapsedTimer");
--- a/libirccd-js/irccd/js/file_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/file_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -38,7 +38,7 @@
 const char *prototype("\xff""\xff""irccd-file-prototype");
 
 // Remove trailing \r for CRLF line style.
-inline std::string clear_crlf(std::string input)
+std::string clear_crlf(std::string input) noexcept
 {
     if (input.length() > 0 && input.back() == '\r')
         input.pop_back();
@@ -46,6 +46,11 @@
     return input;
 }
 
+std::system_error from_errno() noexcept
+{
+    return std::system_error(make_error_code(static_cast<std::errc>(errno)));
+}
+
 std::shared_ptr<file> self(duk_context* ctx)
 {
     dukx_stack_assert sa(ctx);
@@ -61,120 +66,157 @@
     return *ptr;
 }
 
-/*
- * File methods.
- * ------------------------------------------------------------------
- */
-
-/*
- * Method: File.basename()
- * --------------------------------------------------------
- *
- * Synonym of `irccd.File.basename(path)` but with the path from the file.
- *
- * duk_ret_turns:
- *   The base name.
- */
-duk_ret_t method_basename(duk_context* ctx)
+template <typename Handler>
+duk_ret_t wrap(duk_context* ctx, Handler handler)
 {
-    return dukx_push(ctx, fs_util::base_name(self(ctx)->get_path()));
-}
-
-/*
- * Method: File.close()
- * --------------------------------------------------------
- *
- * Force close of the file, automatically called when object is collected.
- */
-duk_ret_t method_close(duk_context* ctx)
-{
-    self(ctx)->close();
+    try {
+        return handler();
+    } catch (const boost::system::system_error& ex) {
+        dukx_throw(ctx, ex);
+    } catch (const std::system_error& ex) {
+        dukx_throw(ctx, ex);
+    } catch (const std::exception& ex) {
+        dukx_throw(ctx, ex);
+    }
 
     return 0;
 }
 
+// {{{ Irccd.File.prototype.basename
+
 /*
- * Method: File.dirname()
+ * Method: Irccd.File.prototype.basename()
+ * --------------------------------------------------------
+ *
+ * Synonym of `Irccd.File.basename(path)` but with the path from the file.
+ *
+ * Returns:
+ *   The base name.
+ */
+duk_ret_t File_prototype_basename(duk_context* ctx)
+{
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, fs_util::base_name(self(ctx)->get_path()));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.close
+
+/*
+ * Method: Irccd.File.prototype.close()
  * --------------------------------------------------------
  *
- * Synonym of `irccd.File.dirname(path)` but with the path from the file.
+ * Force close of the file, automatically called when object is collected.
+ */
+duk_ret_t File_prototype_close(duk_context* ctx)
+{
+    return wrap(ctx, [&] {
+        self(ctx)->close();
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.dirname
+
+/*
+ * Method: Irccd.File.prototype.dirname()
+ * --------------------------------------------------------
  *
- * duk_ret_turns:
+ * Synonym of `Irccd.File.dirname(path)` but with the path from the file.
+ *
+ * Returns:
  *   The directory name.
  */
-duk_ret_t method_dirname(duk_context* ctx)
+duk_ret_t File_prototype_dirname(duk_context* ctx)
 {
-    return dukx_push(ctx, fs_util::dir_name(self(ctx)->get_path()));
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, fs_util::dir_name(self(ctx)->get_path()));
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File.prototype.lines
+
 /*
- * Method: File.lines()
+ * Method: Irccd.File.prototype.lines()
  * --------------------------------------------------------
  *
  * Read all lines and return an array.
  *
- * duk_ret_turns:
+ * Returns:
  *   An array with all lines.
  * Throws
  *   - Any exception on error.
  */
-duk_ret_t method_lines(duk_context* ctx)
+duk_ret_t File_prototype_lines(duk_context* ctx)
 {
-    duk_push_array(ctx);
+    return wrap(ctx, [&] {
+        duk_push_array(ctx);
+
+        std::FILE* fp = self(ctx)->get_handle();
+        std::string buffer;
+        std::array<char, 128> data;
+        std::int32_t i = 0;
+
+        while (std::fgets(&data[0], data.size(), fp) != nullptr) {
+            buffer += data.data();
+
+            const auto pos = buffer.find('\n');
 
-    std::FILE* fp = self(ctx)->get_handle();
-    std::string buffer;
-    std::array<char, 128> data;
-    std::int32_t i = 0;
+            if (pos != std::string::npos) {
+                dukx_push(ctx, clear_crlf(buffer.substr(0, pos)));
+                duk_put_prop_index(ctx, -2, i++);
+
+                buffer.erase(0, pos + 1);
+            }
+        }
 
-    while (std::fgets(&data[0], data.size(), fp) != nullptr) {
-        buffer += data.data();
+        // Maybe an error in the stream.
+        if (std::ferror(fp))
+            throw from_errno();
 
-        const auto pos = buffer.find('\n');
-
-        if (pos != std::string::npos) {
-            dukx_push(ctx, clear_crlf(buffer.substr(0, pos)));
+        // Missing '\n' in end of file.
+        if (!buffer.empty()) {
+            dukx_push(ctx, clear_crlf(buffer));
             duk_put_prop_index(ctx, -2, i++);
-
-            buffer.erase(0, pos + 1);
         }
-    }
 
-    // Maybe an error in the stream.
-    if (std::ferror(fp))
-        dukx_throw(ctx, system_error());
-
-    // Missing '\n' in end of file.
-    if (!buffer.empty()) {
-        dukx_push(ctx, clear_crlf(buffer));
-        duk_put_prop_index(ctx, -2, i++);
-    }
-
-    return 1;
+        return 1;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File.prototype.read
+
 /*
- * Method: File.read(amount)
+ * Method: Irccd.File.prototype.read(amount)
  * --------------------------------------------------------
  *
  * Read the specified amount of characters or the whole file.
  *
  * Arguments:
  *   - amount, the amount of characters or -1 to read all (Optional, default: -1).
- * duk_ret_turns:
+ * Returns:
  *   The string.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t method_read(duk_context* ctx)
+duk_ret_t File_prototype_read(duk_context* ctx)
 {
-    const auto fp = self(ctx)->get_handle();
-    const auto amount = duk_is_number(ctx, 0) ? duk_get_int(ctx, 0) : -1;
+    return wrap(ctx, [&] {
+        const auto fp = self(ctx)->get_handle();
+        const auto amount = duk_is_number(ctx, 0) ? duk_get_int(ctx, 0) : -1;
 
-    if (amount == 0 || !fp)
-        return 0;
+        if (amount == 0 || !fp)
+            return 0;
 
-    try {
         std::string data;
         std::size_t total = 0;
 
@@ -184,7 +226,7 @@
 
             while ((nread = std::fread(&buffer[0], sizeof (buffer[0]), buffer.size(), fp)) > 0) {
                 if (std::ferror(fp))
-                    dukx_throw(ctx, system_error());
+                    throw from_errno();
 
                 std::copy(buffer.begin(), buffer.begin() + nread, std::back_inserter(data));
                 total += nread;
@@ -194,66 +236,77 @@
             total = std::fread(&data[0], sizeof (data[0]), static_cast<std::size_t>(amount), fp);
 
             if (std::ferror(fp))
-                dukx_throw(ctx, system_error());
+                throw from_errno();
 
             data.resize(total);
         }
 
-        dukx_push(ctx, data);
-    } catch (const std::exception&) {
-        dukx_throw(ctx, system_error());
-    }
-
-    return 1;
+        return dukx_push(ctx, data);
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File.prototype.readline
+
 /*
- * Method: File.readline()
+ * Method: Irccd.File.prototype.readline()
  * --------------------------------------------------------
  *
  * Read the next line available.
  *
- * duk_ret_turns:
+ * Returns:
  *   The next line or undefined if eof.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t method_readline(duk_context* ctx)
+duk_ret_t File_prototype_readline(duk_context* ctx)
 {
-    auto fp = self(ctx)->get_handle();
+    return wrap(ctx, [&] {
+        auto fp = self(ctx)->get_handle();
 
-    if (fp == nullptr || std::feof(fp))
-        return 0;
+        if (fp == nullptr || std::feof(fp))
+            return 0;
 
-    std::string result;
+        std::string result;
 
-    for (int ch; (ch = std::fgetc(fp)) != EOF && ch != '\n'; )
-        result += (char)ch;
-    if (std::ferror(fp))
-        dukx_throw(ctx, system_error());
+        for (int ch; (ch = std::fgetc(fp)) != EOF && ch != '\n'; )
+            result += (char)ch;
+        if (std::ferror(fp))
+            throw from_errno();
 
-    return dukx_push(ctx, clear_crlf(result));
+        return dukx_push(ctx, clear_crlf(result));
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File.prototype.remove
+
 /*
- * Method: File.remove()
+ * Method: Irccd.File.prototype.remove()
  * --------------------------------------------------------
  *
- * Synonym of File.remove(path) but with the path from the file.
+ * Synonym of Irccd.File.prototype.remove(path) but with the path from the file.
  *
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t method_remove(duk_context* ctx)
+duk_ret_t File_prototype_remove(duk_context* ctx)
 {
-    if (::remove(self(ctx)->get_path().c_str()) < 0)
-        dukx_throw(ctx, system_error());
+    return wrap(ctx, [&] {
+        boost::filesystem::remove(self(ctx)->get_path());
 
-    return 0;
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File.prototype.seek
+
 /*
- * Method: File.seek(type, amount)
+ * Method: Irccd.File.prototype.seek(type, amount)
  * --------------------------------------------------------
  *
  * Sets the position in the file.
@@ -262,127 +315,129 @@
  *   - type, the type of setting (File.SeekSet, File.SeekCur, File.SeekSet),
  *   - amount, the new offset.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t method_seek(duk_context* ctx)
+duk_ret_t File_prototype_seek(duk_context* ctx)
 {
-    auto fp = self(ctx)->get_handle();
-    auto type = duk_require_int(ctx, 0);
-    auto amount = duk_require_int(ctx, 1);
+    return wrap(ctx, [&] {
+        auto fp = self(ctx)->get_handle();
+        auto type = duk_require_int(ctx, 0);
+        auto amount = duk_require_int(ctx, 1);
+
+        if (fp != nullptr && std::fseek(fp, amount, type) != 0)
+            throw from_errno();
 
-    if (fp != nullptr && std::fseek(fp, amount, type) != 0)
-        dukx_throw(ctx, system_error());
+        return 0;
+    });
+}
 
-    return 0;
-}
+// }}}
+
+// {{{ Irccd.File.prototype.stat
 
 #if defined(HAVE_STAT)
 
 /*
- * Method: File.stat() [optional]
+ * Method: Irccd.File.prototype.stat() [optional]
  * --------------------------------------------------------
  *
  * Synonym of File.stat(path) but with the path from the file.
  *
- * duk_ret_turns:
+ * Returns:
  *   The stat information.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t method_stat(duk_context* ctx)
+duk_ret_t File_prototype_stat(duk_context* ctx)
 {
-    auto file = self(ctx);
-    struct stat st;
+    return wrap(ctx, [&] {
+        auto file = self(ctx);
+        struct stat st;
 
-    if (file->get_handle() == nullptr && ::stat(file->get_path().c_str(), &st) < 0)
-        dukx_throw(ctx, system_error());
-    else
+        if (file->get_handle() == nullptr && ::stat(file->get_path().c_str(), &st) < 0)
+            throw from_errno();
+
         dukx_push(ctx, st);
 
-    return 1;
+        return 1;
+    });
 }
 
 #endif // !HAVE_STAT
 
+// }}}
+
+// {{{ Irccd.File.prototype.tell
+
 /*
- * Method: File.tell()
+ * Method: Irccd.File.prototype.tell()
  * --------------------------------------------------------
  *
  * Get the actual position in the file.
  *
- * duk_ret_turns:
+ * Returns:
  *   The position.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t method_tell(duk_context* ctx)
+duk_ret_t File_prototype_tell(duk_context* ctx)
 {
-    auto fp = self(ctx)->get_handle();
-    long pos;
+    return wrap(ctx, [&] {
+        auto fp = self(ctx)->get_handle();
+        long pos;
 
-    if (fp == nullptr)
-        return 0;
+        if (fp == nullptr)
+            return 0;
 
-    if ((pos = std::ftell(fp)) == -1L)
-        dukx_throw(ctx, system_error());
-    else
+        if ((pos = std::ftell(fp)) == -1L)
+            throw from_errno();
+
         duk_push_int(ctx, pos);
 
-    return 1;
+        return 1;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File.prototype.write
+
 /*
- * Method: File.write(data)
+ * Method: Irccd.File.prototype.write(data)
  * --------------------------------------------------------
  *
  * Write some characters to the file.
  *
  * Arguments:
  *   - data, the character to write.
- * duk_ret_turns:
+ * Returns:
  *   The number of bytes written.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t method_write(duk_context* ctx)
+duk_ret_t File_prototype_write(duk_context* ctx)
 {
-    auto fp = self(ctx)->get_handle();
-    auto data = dukx_require<std::string>(ctx, 0);
+    return wrap(ctx, [&] {
+        auto fp = self(ctx)->get_handle();
+        auto data = dukx_require<std::string>(ctx, 0);
 
-    if (fp == nullptr)
-        return 0;
+        if (fp == nullptr)
+            return 0;
 
-    const auto nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
+        const auto nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
 
-    if (std::ferror(fp))
-        dukx_throw(ctx, system_error());
+        if (std::ferror(fp))
+            throw from_errno();
 
-    duk_push_uint(ctx, nwritten);
+        duk_push_uint(ctx, nwritten);
 
-    return 1;
+        return 1;
+    });
 }
 
-const duk_function_list_entry methods[] = {
-    { "basename",   method_basename,    0 },
-    { "close",      method_close,       0 },
-    { "dirname",    method_dirname,     0 },
-    { "lines",      method_lines,       0 },
-    { "read",       method_read,        1 },
-    { "readline",   method_readline,    0 },
-    { "remove",     method_remove,      0 },
-    { "seek",       method_seek,        2 },
-#if defined(HAVE_STAT)
-    { "stat",       method_stat,        0 },
-#endif
-    { "tell",       method_tell,        0 },
-    { "write",      method_write,       1 },
-    { nullptr,      nullptr,            0 }
-};
+// }}}
 
-/*
- * File "static" functions
- * ------------------------------------------------------------------
- */
+// {{{ Irccd.File [constructor]
 
 /*
  * Function: Irccd.File(path, mode) [constructor]
@@ -394,35 +449,37 @@
  *   - path, the path to the file,
  *   - mode, the mode string.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t constructor(duk_context* ctx)
+duk_ret_t File_constructor(duk_context* ctx)
 {
-    if (!duk_is_constructor_call(ctx))
-        return 0;
+    return wrap(ctx, [&] {
+        if (!duk_is_constructor_call(ctx))
+            return 0;
 
-    try {
-        auto path = dukx_require<std::string>(ctx, 0);
-        auto mode = dukx_require<std::string>(ctx, 1);
+        const auto path = dukx_require<std::string>(ctx, 0);
+        const auto mode = dukx_require<std::string>(ctx, 1);
 
         duk_push_this(ctx);
         duk_push_pointer(ctx, new std::shared_ptr<file>(new file(path, mode)));
         duk_put_prop_string(ctx, -2, signature);
         duk_pop(ctx);
-    } catch (const std::exception&) {
-        dukx_throw(ctx, system_error());
-    }
 
-    return 0;
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File [destructor]
+
 /*
  * Function: Irccd.File() [destructor]
  * ------------------------------------------------------------------
  *
  * Delete the property.
  */
-duk_ret_t destructor(duk_context* ctx)
+duk_ret_t File_destructor(duk_context* ctx)
 {
     duk_get_prop_string(ctx, 0, signature);
     delete static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
@@ -432,6 +489,10 @@
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.File.basename
+
 /*
  * Function: Irccd.File.basename(path)
  * --------------------------------------------------------
@@ -440,14 +501,22 @@
  *
  * Arguments:
  *   - path, the path to the file.
- * duk_ret_turns:
+ * Returns:
  *   The base name.
+ * Throws:
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t function_basename(duk_context* ctx)
+duk_ret_t File_basename(duk_context* ctx)
 {
-    return dukx_push(ctx, fs_util::base_name(duk_require_string(ctx, 0)));
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, fs_util::base_name(duk_require_string(ctx, 0)));
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File.dirname
+
 /*
  * Function: Irccd.File.dirname(path)
  * --------------------------------------------------------
@@ -456,14 +525,20 @@
  *
  * Arguments:
  *   - path, the path to the file.
- * duk_ret_turns:
- *   The directory name.
+ * Throws:
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t function_dirname(duk_context* ctx)
+duk_ret_t File_dirname(duk_context* ctx)
 {
-    return dukx_push(ctx, fs_util::dir_name(duk_require_string(ctx, 0)));
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, fs_util::dir_name(duk_require_string(ctx, 0)));
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File.exists
+
 /*
  * Function: Irccd.File.exists(path)
  * --------------------------------------------------------
@@ -472,21 +547,21 @@
  *
  * Arguments:
  *   - path, the path to the file.
- * duk_ret_turns:
+ * Returns:
  *   True if exists.
  * Throws:
- *   - Any exception if we don't have access.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t function_exists(duk_context* ctx)
+duk_ret_t File_exists(duk_context* ctx)
 {
-    try {
-        duk_push_boolean(ctx, boost::filesystem::exists(duk_require_string(ctx, 0)));
-    } catch (...) {
-        duk_push_boolean(ctx, false);
-    }
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, boost::filesystem::exists(duk_require_string(ctx, 0)));
+    });
+}
 
-    return 1;
-}
+// }}}
+
+// {{{ Irccd.File.remove
 
 /*
  * function Irccd.File.remove(path)
@@ -497,16 +572,21 @@
  * Arguments:
  *   - path, the path to the file.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t function_remove(duk_context* ctx)
+duk_ret_t File_remove(duk_context* ctx)
 {
-    if (::remove(duk_require_string(ctx, 0)) < 0)
-        dukx_throw(ctx, system_error());
+    return wrap(ctx, [&] {
+        boost::filesystem::remove(dukx_require<std::string>(ctx, 0));
 
-    return 0;
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.File.stat
+
 #if defined(HAVE_STAT)
 
 /*
@@ -517,32 +597,53 @@
  *
  * Arguments:
  *   - path, the path to the file.
- * duk_ret_turns:
+ * Returns:
  *   The stat information.
  * Throws:
- *   - Any exception on error.
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t function_stat(duk_context* ctx)
+duk_ret_t File_stat(duk_context* ctx)
 {
-    struct stat st;
+    return wrap(ctx, [&] {
+        struct stat st;
 
-    if (::stat(duk_require_string(ctx, 0), &st) < 0)
-        dukx_throw(ctx, system_error());
+        if (::stat(duk_require_string(ctx, 0), &st) < 0)
+            throw from_errno();
 
-    return dukx_push(ctx, st);
+        return dukx_push(ctx, st);
+    });
 }
 
 #endif // !HAVE_STAT
 
+// }}}
+
+const duk_function_list_entry methods[] = {
+    { "basename",   File_prototype_basename,    0 },
+    { "close",      File_prototype_close,       0 },
+    { "dirname",    File_prototype_dirname,     0 },
+    { "lines",      File_prototype_lines,       0 },
+    { "read",       File_prototype_read,        1 },
+    { "readline",   File_prototype_readline,    0 },
+    { "remove",     File_prototype_remove,      0 },
+    { "seek",       File_prototype_seek,        2 },
+#if defined(HAVE_STAT)
+    { "stat",       File_prototype_stat,        0 },
+#endif
+    { "tell",       File_prototype_tell,        0 },
+    { "write",      File_prototype_write,       1 },
+    { nullptr,      nullptr,                    0 }
+};
+
 const duk_function_list_entry functions[] = {
-    { "basename",   function_basename,  1 },
-    { "dirname",    function_dirname,   1 },
-    { "exists",     function_exists,    1 },
-    { "remove",     function_remove,    1 },
+    { "basename",   File_basename,              1 },
+    { "dirname",    File_dirname,               1 },
+    { "exists",     File_exists,                1 },
+    { "remove",     File_remove,                1 },
 #if defined(HAVE_STAT)
-    { "stat",       function_stat,      1 },
+    { "stat",       File_stat,                  1 },
 #endif
-    { nullptr,      nullptr,            0 }
+    { nullptr,      nullptr,                    0 }
 };
 
 const duk_number_list_entry constants[] = {
@@ -564,12 +665,12 @@
     dukx_stack_assert sa(plugin->context());
 
     duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 2);
+    duk_push_c_function(plugin->context(), File_constructor, 2);
     duk_put_number_list(plugin->context(), -1, constants);
     duk_put_function_list(plugin->context(), -1, functions);
     duk_push_object(plugin->context());
     duk_put_function_list(plugin->context(), -1, methods);
-    duk_push_c_function(plugin->context(), destructor, 1);
+    duk_push_c_function(plugin->context(), File_destructor, 1);
     duk_set_finalizer(plugin->context(), -2);
     duk_dup(plugin->context(), -1);
     duk_put_global_string(plugin->context(), prototype);
--- a/libirccd-js/irccd/js/irccd_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/irccd_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -29,6 +29,21 @@
 
 namespace {
 
+template <typename Error>
+void do_raise(duk_context* ctx, const Error& ex)
+{
+    dukx_stack_assert sa(ctx, 1);
+
+    duk_get_global_string(ctx, "Irccd");
+    duk_get_prop_string(ctx, -1, "SystemError");
+    duk_remove(ctx, -2);
+    dukx_push(ctx, ex.code().value());
+    dukx_push(ctx, ex.code().message());
+    duk_new(ctx, 2);
+
+    (void)duk_throw(ctx);
+}
+
 const std::unordered_map<std::string, int> errors{
     { "E2BIG",              E2BIG           },
     { "EACCES",             EACCES          },
@@ -146,28 +161,14 @@
 
 } // !namespace
 
-system_error::system_error()
-    : errno_(errno)
-    , message_(std::strerror(errno_))
+void dukx_type_traits<std::system_error>::raise(duk_context* ctx, const std::system_error& ex)
 {
+    do_raise(ctx, ex);
 }
 
-system_error::system_error(int e, std::string message)
-    : errno_(e)
-    , message_(std::move(message))
-{
-}
-
-void system_error::create(duk_context *ctx) const
+void dukx_type_traits<boost::system::system_error>::raise(duk_context* ctx, const boost::system::system_error& ex)
 {
-    dukx_stack_assert sa(ctx, 1);
-
-    duk_get_global_string(ctx, "Irccd");
-    duk_get_prop_string(ctx, -1, "SystemError");
-    duk_remove(ctx, -2);
-    dukx_push(ctx, errno_);
-    dukx_push(ctx, message_);
-    duk_new(ctx, 2);
+    do_raise(ctx, ex);
 }
 
 std::string irccd_jsapi::get_name() const
--- a/libirccd-js/irccd/js/irccd_jsapi.hpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/irccd_jsapi.hpp	Fri Apr 13 23:05:56 2018 +0200
@@ -27,42 +27,15 @@
 #include <cerrno>
 #include <cstring>
 #include <string>
+#include <system_error>
+
+#include <boost/system/system_error.hpp>
 
 #include "jsapi.hpp"
 
 namespace irccd {
 
 /**
- * \brief Custom Javascript exception for system error.
- */
-class system_error {
-private:
-    int errno_;
-    std::string message_;
-
-public:
-    /**
-     * Create a system error from the current errno value.
-     */
-    system_error();
-
-    /**
-     * Create a system error with the given errno and message.
-     *
-     * \param e the errno number
-     * \param message the message
-     */
-    system_error(int e, std::string message);
-
-    /**
-     * Create the SystemError Javascript exception.
-     *
-     * \param ctx the context
-     */
-    void create(duk_context* ctx) const;
-};
-
-/**
  * \brief Irccd Javascript API.
  * \ingroup jsapi
  */
@@ -94,6 +67,36 @@
     static irccd& self(duk_context* ctx);
 };
 
+/**
+ * \brief Specialize dukx_type_traits for std::system_error.
+ */
+template <>
+class dukx_type_traits<std::system_error> : public std::true_type {
+public:
+    /**
+     * Raise an Irccd.SystemError.
+     *
+     * \param ctx the context
+     * param ex the exception
+     */
+    static void raise(duk_context* ctx, const std::system_error& ex);
+};
+
+/**
+ * \brief Specialize dukx_type_traits for boost::system::system_error.
+ */
+template <>
+class dukx_type_traits<boost::system::system_error> : public std::true_type {
+public:
+    /**
+     * Raise an Irccd.SystemError.
+     *
+     * \param ctx the context
+     * param ex the exception
+     */
+    static void raise(duk_context* ctx, const boost::system::system_error& ex);
+};
+
 } // !irccd
 
 #endif // !IRCCD_JS_IRCCD_JSAPI_HPP
--- a/libirccd-js/irccd/js/logger_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/logger_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -28,15 +28,24 @@
 
 namespace {
 
+// {{{ print
+
 duk_ret_t print(duk_context* ctx, std::ostream &out)
 {
-    const auto plugin = dukx_type_traits<js_plugin>::self(ctx);
-
-    out << "plugin " << plugin->get_name() << ": " << duk_require_string(ctx, 0) << std::endl;
+    try {
+        out << "plugin " << dukx_type_traits<js_plugin>::self(ctx)->get_name() << ": ";
+        out << duk_require_string(ctx, 0) << std::endl;
+    } catch (const std::exception& ex) {
+        dukx_throw(ctx, ex);
+    }
 
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.Logger.info
+
 /*
  * Function: Irccd.Logger.info(message)
  * --------------------------------------------------------
@@ -45,45 +54,61 @@
  *
  * Arguments:
  *   - message, the message.
+ * Throws:
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t info(duk_context* ctx)
+duk_ret_t Logger_info(duk_context* ctx)
 {
     return print(ctx, dukx_type_traits<irccd>::self(ctx).get_log().info());
 }
 
+// }}}
+
+// {{{ Irccd.Logger.warning
+
 /*
- * Function: irccd.Logger.warning(message)
+ * Function: Irccd.Logger.warning(message)
  * --------------------------------------------------------
  *
  * Write a warning message.
  *
  * Arguments:
  *   - message, the warning.
+ * Throws:
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t warning(duk_context* ctx)
+duk_ret_t Logger_warning(duk_context* ctx)
 {
     return print(ctx, dukx_type_traits<irccd>::self(ctx).get_log().warning());
 }
 
+// }}}
+
+// {{{ Irccd.Logger.debug
+
 /*
- * Function: Logger.debug(message)
+ * Function: Irccd.Logger.debug(message)
  * --------------------------------------------------------
  *
  * Write a debug message, only shown if irccd is compiled in debug.
  *
  * Arguments:
  *   - message, the message.
+ * Throws:
+ *   - Irccd.SystemError on errors
  */
-duk_ret_t debug(duk_context* ctx)
+duk_ret_t Logger_debug(duk_context* ctx)
 {
     return print(ctx, dukx_type_traits<irccd>::self(ctx).get_log().debug());
 }
 
+// }}}
+
 const duk_function_list_entry functions[] = {
-    { "info",       info,       1 },
-    { "warning",    warning,    1 },
-    { "debug",      debug,      1 },
-    { nullptr,      nullptr,    0 }
+    { "info",       Logger_info,    1 },
+    { "warning",    Logger_warning, 1 },
+    { "debug",      Logger_debug,   1 },
+    { nullptr,      nullptr,        0 }
 };
 
 } // !namespace
--- a/libirccd-js/irccd/js/plugin_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/plugin_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -30,30 +30,20 @@
 
 const char plugin_ref[] = "\xff""\xff""irccd-plugin-ptr";
 
-/*
- * wrap
- * ------------------------------------------------------------------
- *
- * Wrap function for these functions because they all takes the same arguments.
- *
- * - load,
- * - reload,
- * - unload.
- */
-template <typename Func>
-duk_idx_t wrap(duk_context* ctx, int nret, Func&& func)
+template <typename Handler>
+duk_idx_t wrap(duk_context* ctx, Handler handler)
 {
-    std::string name = duk_require_string(ctx, 0);
-
     try {
-        func(dukx_type_traits<irccd>::self(ctx), name);
-    } catch (const std::out_of_range& ex) {
-        (void)duk_error(ctx, DUK_ERR_REFERENCE_ERROR, "%s", ex.what());
-    } catch (const std::exception &ex) {
-        (void)duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+        return handler();
+    } catch (const plugin_error& ex) {
+        dukx_throw(ctx, ex);
+    } catch (const std::system_error& ex) {
+        dukx_throw(ctx, ex);
+    } catch (const std::exception& ex) {
+        dukx_throw(ctx, ex);
     }
 
-    return nret;
+    return 0;
 }
 
 /*
@@ -190,6 +180,8 @@
     return get(ctx, js_plugin::paths_property);
 }
 
+// {{{ Irccd.Plugin.info
+
 /*
  * Function: Irccd.Plugin.info([name])
  * ------------------------------------------------------------------
@@ -209,34 +201,42 @@
  *     selected.
  * Returns:
  *   The plugin information or undefined if the plugin was not found.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_idx_t info(duk_context* ctx)
+duk_idx_t Plugin_info(duk_context* ctx)
 {
-    std::shared_ptr<plugin> plugin;
+    return wrap(ctx, [&] {
+        std::shared_ptr<plugin> plugin;
 
-    if (duk_get_top(ctx) >= 1)
-        plugin = dukx_type_traits<irccd>::self(ctx).plugins().get(duk_require_string(ctx, 0));
-    else
-        plugin = dukx_type_traits<js_plugin>::self(ctx);
+        if (duk_get_top(ctx) >= 1)
+            plugin = dukx_type_traits<irccd>::self(ctx).plugins().get(duk_require_string(ctx, 0));
+        else
+            plugin = dukx_type_traits<js_plugin>::self(ctx);
 
-    if (!plugin)
-        return 0;
+        if (!plugin)
+            return 0;
 
-    duk_push_object(ctx);
-    dukx_push(ctx, plugin->get_name());
-    duk_put_prop_string(ctx, -2, "name");
-    dukx_push(ctx, plugin->get_author());
-    duk_put_prop_string(ctx, -2, "author");
-    dukx_push(ctx, plugin->get_license());
-    duk_put_prop_string(ctx, -2, "license");
-    dukx_push(ctx, plugin->get_summary());
-    duk_put_prop_string(ctx, -2, "summary");
-    dukx_push(ctx, plugin->get_version());
-    duk_put_prop_string(ctx, -2, "version");
+        duk_push_object(ctx);
+        dukx_push(ctx, plugin->get_name());
+        duk_put_prop_string(ctx, -2, "name");
+        dukx_push(ctx, plugin->get_author());
+        duk_put_prop_string(ctx, -2, "author");
+        dukx_push(ctx, plugin->get_license());
+        duk_put_prop_string(ctx, -2, "license");
+        dukx_push(ctx, plugin->get_summary());
+        duk_put_prop_string(ctx, -2, "summary");
+        dukx_push(ctx, plugin->get_version());
+        duk_put_prop_string(ctx, -2, "version");
 
-    return 1;
+        return 1;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Plugin.list
+
 /*
  * Function: Irccd.Plugin.list()
  * ------------------------------------------------------------------
@@ -246,7 +246,7 @@
  * Returns:
  *   The list of all plugin names.
  */
-duk_idx_t list(duk_context* ctx)
+duk_idx_t Plugin_list(duk_context* ctx)
 {
     int i = 0;
 
@@ -260,6 +260,10 @@
     return 1;
 }
 
+// }}}
+
+// {{{ Irccd.Plugin.load
+
 /*
  * Function: Irccd.Plugin.load(name)
  * ------------------------------------------------------------------
@@ -270,16 +274,22 @@
  * Arguments:
  *   - name, the plugin identifier.
  * Throws:
- *   - Error on errors,
- *   - ReferenceError if the plugin was not found.
+ *   - Irccd.PluginError on plugin related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_idx_t load(duk_context* ctx)
+duk_idx_t Plugin_load(duk_context* ctx)
 {
-    return wrap(ctx, 0, [&] (irccd& irccd, const std::string& name) {
-        irccd.plugins().load(name);
+    return wrap(ctx, [&] {
+        dukx_type_traits<irccd>::self(ctx).plugins().load(dukx_require<std::string>(ctx, 0));
+
+        return 0;
     });
 }
 
+// }}}
+
+// {{{ Irccd.Plugin.reload
+
 /*
  * Function: Irccd.Plugin.reload(name)
  * ------------------------------------------------------------------
@@ -289,16 +299,22 @@
  * Arguments:
  *   - name, the plugin identifier.
  * Throws:
- *   - Error on errors,
- *   - ReferenceError if the plugin was not found.
+ *   - Irccd.PluginError on plugin related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_idx_t reload(duk_context* ctx)
+duk_idx_t Plugin_reload(duk_context* ctx)
 {
-    return wrap(ctx, 0, [&] (irccd& irccd, const std::string& name) {
-        irccd.plugins().reload(name);
+    return wrap(ctx, [&] {
+        dukx_type_traits<irccd>::self(ctx).plugins().reload(dukx_require<std::string>(ctx, 0));
+
+        return 0;
     });
 }
 
+// }}}
+
+// {{{ Irccd.Plugin.unload
+
 /*
  * Function: Irccd.Plugin.unload(name)
  * ------------------------------------------------------------------
@@ -308,23 +324,55 @@
  * Arguments:
  *   - name, the plugin identifier.
  * Throws:
- *   - Error on errors,
- *   - ReferenceError if the plugin was not found.
+ *   - Irccd.PluginError on plugin related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_idx_t unload(duk_context* ctx)
+duk_idx_t Plugin_unload(duk_context* ctx)
 {
-    return wrap(ctx, 0, [&] (irccd &irccd, const std::string &name) {
-        irccd.plugins().unload(name);
+    return wrap(ctx, [&] {
+        dukx_type_traits<irccd>::self(ctx).plugins().unload(dukx_require<std::string>(ctx, 0));
+
+        return 0;
     });
 }
 
+// }}}
+
+// {{{ Irccd.PluginError [constructor]
+
+/*
+ * Function: Irccd.PluginError(code, message)
+ * ------------------------------------------------------------------
+ *
+ * Create an Irccd.PluginError object.
+ *
+ * Arguments:
+ *   - code, the error code,
+ *   - message, the error message.
+ */
+duk_ret_t PluginError_constructor(duk_context* ctx)
+{
+    duk_push_this(ctx);
+    duk_push_int(ctx, duk_require_int(ctx, 0));
+    duk_put_prop_string(ctx, -2, "code");
+    duk_push_string(ctx, duk_require_string(ctx, 1));
+    duk_put_prop_string(ctx, -2, "message");
+    duk_push_string(ctx, "PluginError");
+    duk_put_prop_string(ctx, -2, "name");
+    duk_pop(ctx);
+
+    return 0;
+}
+
+// }}}
+
 const duk_function_list_entry functions[] = {
-    { "info",   info,       DUK_VARARGS     },
-    { "list",   list,       0               },
-    { "load",   load,       1               },
-    { "reload", reload,     1               },
-    { "unload", unload,     1               },
-    { nullptr,  nullptr,    0               }
+    { "info",   Plugin_info,    DUK_VARARGS },
+    { "list",   Plugin_list,    0           },
+    { "load",   Plugin_load,    1           },
+    { "reload", Plugin_reload,  1           },
+    { "unload", Plugin_unload,  1           },
+    { nullptr,  nullptr,        0           }
 };
 
 } // !namespace
@@ -373,10 +421,23 @@
     duk_push_c_function(plugin->context(), set_paths, 1);
     duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
 
+    // PluginError function.
+    duk_push_c_function(plugin->context(), PluginError_constructor, 2);
+    duk_push_object(plugin->context());
+    duk_get_global_string(plugin->context(), "Error");
+    duk_get_prop_string(plugin->context(), -1, "prototype");
+    duk_remove(plugin->context(), -2);
+    duk_set_prototype(plugin->context(), -2);
+    duk_put_prop_string(plugin->context(), -2, "prototype");
+    duk_put_prop_string(plugin->context(), -2, "PluginError");
+
     duk_put_prop_string(plugin->context(), -2, "Plugin");
     duk_pop(plugin->context());
 }
 
+using plugin_traits = dukx_type_traits<js_plugin>;
+using plugin_error_traits = dukx_type_traits<plugin_error>;
+
 std::shared_ptr<js_plugin> dukx_type_traits<js_plugin>::self(duk_context* ctx)
 {
     dukx_stack_assert sa(ctx);
@@ -388,4 +449,18 @@
     return plugin->lock();
 }
 
+void plugin_error_traits::raise(duk_context* ctx, const plugin_error& ex)
+{
+    dukx_stack_assert sa(ctx, 1);
+
+    duk_get_global_string(ctx, "Irccd");
+    duk_get_prop_string(ctx, -1, "PluginError");
+    duk_remove(ctx, -2);
+    dukx_push(ctx, ex.code().value());
+    dukx_push(ctx, ex.code().message());
+    duk_new(ctx, 2);
+
+    (void)duk_throw(ctx);
+}
+
 } // !irccd
--- a/libirccd-js/irccd/js/plugin_jsapi.hpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/plugin_jsapi.hpp	Fri Apr 13 23:05:56 2018 +0200
@@ -25,6 +25,7 @@
  */
 
 #include "jsapi.hpp"
+#include "js_plugin.hpp"
 
 namespace irccd {
 
@@ -60,6 +61,21 @@
     static std::shared_ptr<js_plugin> self(duk_context* ctx);
 };
 
+/**
+ * \brief Specialization for plugin_error.
+ */
+template <>
+class dukx_type_traits<plugin_error> : public std::true_type {
+public:
+    /**
+     * Raise a plugin_error.
+     *
+     * \param ctx the context
+     * \param error the error
+     */
+    static void raise(duk_context* ctx, const plugin_error& error);
+};
+
 } // !irccd
 
 #endif // !IRCCD_JS_PLUGIN_JSAPI_HPP
--- a/libirccd-js/irccd/js/server_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/server_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -52,8 +52,24 @@
     return *static_cast<std::shared_ptr<server>*>(ptr);
 }
 
+template <typename Handler>
+duk_ret_t wrap(duk_context* ctx, Handler handler)
+{
+    try {
+        return handler(ctx);
+    } catch (const server_error& ex) {
+        dukx_throw(ctx, ex);
+    } catch (const std::exception& ex) {
+        dukx_throw(ctx, ex);
+    }
+
+    return 0;
+}
+
+// {{{ Irccd.Server.prototype.info
+
 /*
- * Method: Server.info()
+ * Method: Irccd.Server.prototype.info()
  * ------------------------------------------------------------------
  *
  * Get the server information as an object containing the following properties:
@@ -65,9 +81,9 @@
  * sslVerify: true if ssl was verified
  * channels: an array of all channels
  */
-duk_ret_t info(duk_context* ctx)
+duk_ret_t Server_prototype_info(duk_context* ctx)
 {
-    auto server = self(ctx);
+    const auto server = self(ctx);
 
     duk_push_object(ctx);
     dukx_push(ctx, server->get_name());
@@ -94,8 +110,12 @@
     return 1;
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.invite
+
 /*
- * Method: Server.invite(target, channel)
+ * Method: Irccd.Server.prototype.invite(target, channel)
  * ------------------------------------------------------------------
  *
  * Invite someone to a channel.
@@ -103,25 +123,55 @@
  * Arguments:
  *   - target, the target to invite,
  *   - channel, the channel.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t invite(duk_context* ctx)
+duk_ret_t Server_prototype_invite(duk_context* ctx)
 {
-    self(ctx)->invite(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+    return wrap(ctx, [] (auto ctx) {
+        auto target = dukx_require<std::string>(ctx, 0);
+        auto channel = dukx_require<std::string>(ctx, 1);
+
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
 
-    return 0;
+        self(ctx)->invite(std::move(target), std::move(channel));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.isSelf
+
 /*
- * Method: Server.isSelf(nickname)
+ * Method: Irccd.Server.prototype.isSelf(nickname)
  * ------------------------------------------------------------------
+ *
+ * Arguments:
+ *   - nickname, the nickname to check.
+ * Returns:
+ *   True if the nickname targets this server.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t isSelf(duk_context* ctx)
+duk_ret_t Server_prototype_isSelf(duk_context* ctx)
 {
-    return dukx_push(ctx, self(ctx)->is_self(duk_require_string(ctx, 0)));
+    return wrap(ctx, [] (auto ctx) {
+        return dukx_push(ctx, self(ctx)->is_self(dukx_require<std::string>(ctx, 0)));
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.join
+
 /*
- * Method: Server.join(channel, password = undefined)
+ * Method: Irccd.Server.prototype.join(channel, password = undefined)
  * ------------------------------------------------------------------
  *
  * Join a channel with an optional password.
@@ -129,16 +179,31 @@
  * Arguments:
  *   - channel, the channel to join,
  *   - password, the password or undefined to not use.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t join(duk_context* ctx)
+duk_ret_t Server_prototype_join(duk_context* ctx)
 {
-    self(ctx)->join(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = dukx_require<std::string>(ctx, 0);
+        auto password = dukx_get<std::string>(ctx, 1);
 
-    return 0;
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->join(std::move(channel), std::move(password));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.kick
+
 /*
- * Method: Server.kick(target, channel, reason = undefined)
+ * Method: Irccd.Server.prototype.kick(target, channel, reason = undefined)
  * ------------------------------------------------------------------
  *
  * Kick someone from a channel.
@@ -147,16 +212,34 @@
  *   - target, the target to kick,
  *   - channel, the channel,
  *   - reason, the optional reason or undefined to not set.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t kick(duk_context* ctx)
+duk_ret_t Server_prototype_kick(duk_context* ctx)
 {
-    self(ctx)->kick(duk_require_string(ctx, 0), duk_require_string(ctx, 1), duk_get_string(ctx, 2));
+    return wrap(ctx, [] (auto ctx) {
+        auto target = dukx_require<std::string>(ctx, 0);
+        auto channel = dukx_require<std::string>(ctx, 1);
+        auto reason = dukx_get<std::string>(ctx, 2);
 
-    return 0;
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->kick(std::move(target), std::move(channel), std::move(reason));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.me
+
 /*
- * Method: Server.me(target, message)
+ * Method: Irccd.Server.prototype.me(target, message)
  * ------------------------------------------------------------------
  *
  * Send a CTCP Action.
@@ -164,16 +247,31 @@
  * Arguments:
  *   - target, the target or a channel,
  *   - message, the message.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t me(duk_context* ctx)
+duk_ret_t Server_prototype_me(duk_context* ctx)
 {
-    self(ctx)->me(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+    return wrap(ctx, [] (auto ctx) {
+        auto target = dukx_require<std::string>(ctx, 0);
+        auto message = dukx_get<std::string>(ctx, 1);
 
-    return 0;
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->me(std::move(target), std::move(message));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.message
+
 /*
- * Method: Server.message(target, message)
+ * Method: Irccd.Server.prototype.message(target, message)
  * ------------------------------------------------------------------
  *
  * Send a message.
@@ -181,70 +279,133 @@
  * Arguments:
  *   - target, the target or a channel,
  *   - message, the message.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t message(duk_context* ctx)
+duk_ret_t Server_prototype_message(duk_context* ctx)
 {
-    self(ctx)->message(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+    return wrap(ctx, [] (auto ctx) {
+        auto target = dukx_require<std::string>(ctx, 0);
+        auto message = dukx_get<std::string>(ctx, 1);
 
-    return 0;
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->message(std::move(target), std::move(message));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.mode
+
 /*
- * Method: Server.mode(channel, mode, limit, user, mask)
+ * Method: Irccd.Server.prototype.mode(channel, mode, limit, user, mask)
  * ------------------------------------------------------------------
  *
  * Change your mode.
  *
  * Arguments:
  *   - mode, the new mode.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t mode(duk_context* ctx)
+duk_ret_t Server_prototype_mode(duk_context* ctx)
 {
-    self(ctx)->mode(
-        duk_require_string(ctx, 0),
-        duk_require_string(ctx, 1),
-        duk_opt_string(ctx, 2, ""),
-        duk_opt_string(ctx, 3, ""),
-        duk_opt_string(ctx, 4, "")
-    );
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = dukx_require<std::string>(ctx, 0);
+        auto mode = dukx_require<std::string>(ctx, 1);
+        auto limit = dukx_get<std::string>(ctx, 2);
+        auto user = dukx_get<std::string>(ctx, 3);
+        auto mask = dukx_get<std::string>(ctx, 4);
 
-    return 0;
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+        if (mode.empty())
+            throw server_error(server_error::invalid_mode);
+
+        self(ctx)->mode(
+            std::move(channel),
+            std::move(mode),
+            std::move(limit),
+            std::move(user),
+            std::move(mask)
+        );
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.names
+
 /*
- * Method: Server.names(channel)
+ * Method: Irccd.Server.prototype.names(channel)
  * ------------------------------------------------------------------
  *
  * Get the list of names from a channel.
  *
  * Arguments:
  *   - channel, the channel.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t names(duk_context* ctx)
+duk_ret_t Server_prototype_names(duk_context* ctx)
 {
-    self(ctx)->names(duk_require_string(ctx, 0));
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = dukx_require<std::string>(ctx, 0);
 
-    return 0;
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->names(std::move(channel));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.nick
+
 /*
- * Method: Server.nick(nickname)
+ * Method: Irccd.Server.prototype.nick(nickname)
  * ------------------------------------------------------------------
  *
  * Change the nickname.
  *
  * Arguments:
  *   - nickname, the nickname.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t nick(duk_context* ctx)
+duk_ret_t Server_prototype_nick(duk_context* ctx)
 {
-    self(ctx)->set_nickname(duk_require_string(ctx, 0));
+    return wrap(ctx, [] (auto ctx) {
+        auto nickname = dukx_require<std::string>(ctx, 0);
 
-    return 0;
+        if (nickname.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->set_nickname(std::move(nickname));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.notice
+
 /*
- * Method: Server.notice(target, message)
+ * Method: Irccd.Server.prototype.notice(target, message)
  * ------------------------------------------------------------------
  *
  * Send a private notice.
@@ -252,16 +413,31 @@
  * Arguments:
  *   - target, the target,
  *   - message, the notice message.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t notice(duk_context* ctx)
+duk_ret_t Server_prototype_notice(duk_context* ctx)
 {
-    self(ctx)->notice(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+    return wrap(ctx, [] (auto ctx) {
+        auto target = dukx_require<std::string>(ctx, 0);
+        auto message = dukx_get<std::string>(ctx, 1);
 
-    return 0;
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->notice(std::move(target), std::move(message));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.part
+
 /*
- * Method: Server.part(channel, reason = undefined)
+ * Method: Irccd.Server.prototype.part(channel, reason = undefined)
  * ------------------------------------------------------------------
  *
  * Leave a channel.
@@ -269,32 +445,61 @@
  * Arguments:
  *   - channel, the channel to leave,
  *   - reason, the optional reason, keep undefined for portability.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t part(duk_context* ctx)
+duk_ret_t Server_prototype_part(duk_context* ctx)
 {
-    self(ctx)->part(duk_require_string(ctx, 0), duk_get_string(ctx, 1));
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = dukx_require<std::string>(ctx, 0);
+        auto reason = dukx_get<std::string>(ctx, 1);
 
-    return 0;
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->part(std::move(channel), std::move(reason));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.send
+
 /*
- * Method: Server.send(raw)
+ * Method: Irccd.Server.prototype.send(raw)
  * ------------------------------------------------------------------
  *
  * Send a raw message to the IRC server.
  *
  * Arguments:
  *   - raw, the raw message (without terminators).
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t send(duk_context* ctx)
+duk_ret_t Server_prototype_send(duk_context* ctx)
 {
-    self(ctx)->send(duk_require_string(ctx, 0));
+    return wrap(ctx, [] (auto ctx) {
+        auto raw = dukx_require<std::string>(ctx, 0);
 
-    return 0;
+        if (raw.empty())
+            throw server_error(server_error::invalid_message);
+
+        self(ctx)->send(std::move(raw));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.topic
+
 /*
- * Method: Server.topic(channel, topic)
+ * Method: Server.prototype.topic(channel, topic)
  * ------------------------------------------------------------------
  *
  * Change a channel topic.
@@ -302,47 +507,84 @@
  * Arguments:
  *   - channel, the channel,
  *   - topic, the new topic.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t topic(duk_context* ctx)
+duk_ret_t Server_prototype_topic(duk_context* ctx)
 {
-    self(ctx)->topic(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = dukx_require<std::string>(ctx, 0);
+        auto topic = dukx_get<std::string>(ctx, 1);
 
-    return 0;
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->topic(std::move(channel), std::move(topic));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.whois
+
 /*
- * Method: Server.whois(target)
+ * Method: Irccd.Server.prototype.whois(target)
  * ------------------------------------------------------------------
  *
  * Get whois information.
  *
  * Arguments:
  *   - target, the target.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t whois(duk_context* ctx)
+duk_ret_t Server_prototype_whois(duk_context* ctx)
 {
-    self(ctx)->whois(duk_require_string(ctx, 0));
+    return wrap(ctx, [] (auto ctx) {
+        auto target = dukx_require<std::string>(ctx, 0);
 
-    return 0;
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->whois(std::move(target));
+
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.prototype.toString
+
 /*
- * Method: Server.toString()
+ * Method: Irccd.Server.prototype.toString()
  * ------------------------------------------------------------------
  *
  * Convert the object to std::string, convenience for adding the object
  * as property key.
  *
- * duk_ret_turns:
+ * Returns:
  *   The server name (unique).
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t toString(duk_context* ctx)
+duk_ret_t Server_prototype_toString(duk_context* ctx)
 {
-    dukx_push(ctx, self(ctx)->get_name());
+    return wrap(ctx, [] (auto ctx) {
+        dukx_push(ctx, self(ctx)->get_name());
 
-    return 1;
+        return 1;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server [constructor]
+
 /*
  * Function: Irccd.Server(params) [constructor]
  * ------------------------------------------------------------------
@@ -363,15 +605,21 @@
  * username: "user name",       (Optional, default: irccd)
  * realname: "real name",       (Optional, default: IRC Client Daemon)
  * commandChar: "!",            (Optional, the command char, default: "!")
+ *
+ * Arguments:
+ *   - params, the server properties
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t constructor(duk_context* ctx)
+duk_ret_t Server_constructor(duk_context* ctx)
 {
-    if (!duk_is_constructor_call(ctx))
-        return 0;
+    return wrap(ctx, [] (auto ctx) {
+        if (!duk_is_constructor_call(ctx))
+            return 0;
 
-    duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
+        duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
 
-    try {
         auto json = nlohmann::json::parse(duk_json_encode(ctx, 0));
         auto s = server_util::from_json(dukx_type_traits<irccd>::self(ctx).get_service(), json);
 
@@ -379,20 +627,22 @@
         duk_push_pointer(ctx, new std::shared_ptr<server>(std::move(s)));
         duk_put_prop_string(ctx, -2, signature);
         duk_pop(ctx);
-    } catch (const std::exception& ex) {
-        duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-    }
 
-    return 0;
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server [destructor]
+
 /*
  * Function: Irccd.Server() [destructor]
  * ------------------------------------------------------------------
  *
  * Delete the property.
  */
-duk_ret_t destructor(duk_context* ctx)
+duk_ret_t Server_destructor(duk_context* ctx)
 {
     duk_get_prop_string(ctx, 0, signature);
     delete static_cast<std::shared_ptr<server>*>(duk_to_pointer(ctx, -1));
@@ -402,6 +652,10 @@
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.Server.add
+
 /*
  * Function: Irccd.Server.add(s)
  * ------------------------------------------------------------------
@@ -410,14 +664,23 @@
  *
  * Arguments:
  *   - s, the server to add.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t add(duk_context* ctx)
+duk_ret_t Server_add(duk_context* ctx)
 {
-    dukx_type_traits<irccd>::self(ctx).servers().add(dukx_require<std::shared_ptr<server>>(ctx, 0));
+    return wrap(ctx, [] (auto ctx) {
+        dukx_type_traits<irccd>::self(ctx).servers().add(
+            dukx_require<std::shared_ptr<server>>(ctx, 0));
 
-    return 0;
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.Server.find
+
 /*
  * Function: Irccd.Server.find(name)
  * ------------------------------------------------------------------
@@ -426,20 +689,29 @@
  *
  * Arguments:
  *   - name, the server name
- * duk_ret_turns:
+ * Returns:
  *   The server object or undefined if not found.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t find(duk_context* ctx)
+duk_ret_t Server_find(duk_context* ctx)
 {
-    auto server = dukx_type_traits<irccd>::self(ctx).servers().get(duk_require_string(ctx, 0));
+    return wrap(ctx, [] (auto ctx) {
+        auto id = dukx_require<std::string>(ctx, 0);
+        auto server = dukx_type_traits<irccd>::self(ctx).servers().get(id);
 
-    if (!server)
-        return 0;
+        if (!server)
+            return 0;
+
+        dukx_push(ctx, server);
 
-    dukx_push(ctx, server);
+        return 1;
+    });
+}
 
-    return 1;
-}
+// }}}
+
+// {{{ Irccd.Server.list
 
 /*
  * Function: Irccd.Server.list()
@@ -447,14 +719,14 @@
  *
  * Get the map of all loaded servers.
  *
- * duk_ret_turns:
+ * Returns:
  *   An object with string-to-servers pairs.
  */
-duk_ret_t list(duk_context* ctx)
+duk_ret_t Server_list(duk_context* ctx)
 {
     duk_push_object(ctx);
 
-    for (const auto &server : dukx_type_traits<irccd>::self(ctx).servers().servers()) {
+    for (const auto& server : dukx_type_traits<irccd>::self(ctx).servers().servers()) {
         dukx_push(ctx, server);
         duk_put_prop_string(ctx, -2, server->get_name().c_str());
     }
@@ -462,8 +734,12 @@
     return 1;
 }
 
+// }}}
+
+// {{{ Irccd.Server.remove
+
 /*
- * Function: irccd.Server.remove(name)
+ * Function: Irccd.Server.remove(name)
  * ------------------------------------------------------------------
  *
  * Remove a server from the irccd instance. You can pass the server object since
@@ -472,39 +748,69 @@
  * Arguments:
  *   - name the server name.
  */
-duk_ret_t remove(duk_context* ctx)
+duk_ret_t Server_remove(duk_context* ctx)
 {
     dukx_type_traits<irccd>::self(ctx).servers().remove(duk_require_string(ctx, 0));
 
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.ServerError
+
+/*
+ * Function: Irccd.ServerError(code, message)
+ * ------------------------------------------------------------------
+ *
+ * Create an Irccd.ServerError object.
+ *
+ * Arguments:
+ *   - code, the error code,
+ *   - message, the error message.
+ */
+duk_ret_t ServerError_constructor(duk_context* ctx)
+{
+    duk_push_this(ctx);
+    duk_push_int(ctx, duk_require_int(ctx, 0));
+    duk_put_prop_string(ctx, -2, "code");
+    duk_push_string(ctx, duk_require_string(ctx, 1));
+    duk_put_prop_string(ctx, -2, "message");
+    duk_push_string(ctx, "ServerError");
+    duk_put_prop_string(ctx, -2, "name");
+    duk_pop(ctx);
+
+    return 0;
+}
+
+// }}}
+
 const duk_function_list_entry methods[] = {
-    { "info",       info,       0           },
-    { "invite",     invite,     2           },
-    { "isSelf",     isSelf,     1           },
-    { "join",       join,       DUK_VARARGS },
-    { "kick",       kick,       DUK_VARARGS },
-    { "me",         me,         2           },
-    { "message",    message,    2           },
-    { "mode",       mode,       1           },
-    { "names",      names,      1           },
-    { "nick",       nick,       1           },
-    { "notice",     notice,     2           },
-    { "part",       part,       DUK_VARARGS },
-    { "send",       send,       1           },
-    { "topic",      topic,      2           },
-    { "whois",      whois,      1           },
-    { "toString",   toString,   0           },
-    { nullptr,      nullptr,    0           }
+    { "info",       Server_prototype_info,      0           },
+    { "invite",     Server_prototype_invite,    2           },
+    { "isSelf",     Server_prototype_isSelf,    1           },
+    { "join",       Server_prototype_join,      DUK_VARARGS },
+    { "kick",       Server_prototype_kick,      DUK_VARARGS },
+    { "me",         Server_prototype_me,        2           },
+    { "message",    Server_prototype_message,   2           },
+    { "mode",       Server_prototype_mode,      1           },
+    { "names",      Server_prototype_names,     1           },
+    { "nick",       Server_prototype_nick,      1           },
+    { "notice",     Server_prototype_notice,    2           },
+    { "part",       Server_prototype_part,      DUK_VARARGS },
+    { "send",       Server_prototype_send,      1           },
+    { "topic",      Server_prototype_topic,     2           },
+    { "toString",   Server_prototype_toString,  0           },
+    { "whois",      Server_prototype_whois,     1           },
+    { nullptr,      nullptr,                    0           }
 };
 
 const duk_function_list_entry functions[] = {
-    { "add",        add,        1           },
-    { "find",       find,       1           },
-    { "list",       list,       0           },
-    { "remove",     remove,     1           },
-    { nullptr,      nullptr,    0           }
+    { "add",        Server_add,                 1           },
+    { "find",       Server_find,                1           },
+    { "list",       Server_list,                0           },
+    { "remove",     Server_remove,              1           },
+    { nullptr,      nullptr,                    0           }
 };
 
 } // !namespace
@@ -519,11 +825,23 @@
     dukx_stack_assert sa(plugin->context());
 
     duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 1);
+
+    // ServerError function.
+    duk_push_c_function(plugin->context(), ServerError_constructor, 2);
+    duk_push_object(plugin->context());
+    duk_get_global_string(plugin->context(), "Error");
+    duk_get_prop_string(plugin->context(), -1, "prototype");
+    duk_remove(plugin->context(), -2);
+    duk_set_prototype(plugin->context(), -2);
+    duk_put_prop_string(plugin->context(), -2, "prototype");
+    duk_put_prop_string(plugin->context(), -2, "ServerError");
+
+    // Server constructor.
+    duk_push_c_function(plugin->context(), Server_constructor, 1);
     duk_put_function_list(plugin->context(), -1, functions);
     duk_push_object(plugin->context());
     duk_put_function_list(plugin->context(), -1, methods);
-    duk_push_c_function(plugin->context(), destructor, 1);
+    duk_push_c_function(plugin->context(), Server_destructor, 1);
     duk_set_finalizer(plugin->context(), -2);
     duk_dup_top(plugin->context());
     duk_put_global_string(plugin->context(), prototype);
@@ -533,6 +851,7 @@
 }
 
 using server_traits = dukx_type_traits<std::shared_ptr<server>>;
+using server_error_traits = dukx_type_traits<server_error>;
 
 void server_traits::push(duk_context* ctx, std::shared_ptr<server> server)
 {
@@ -560,4 +879,18 @@
     return file;
 }
 
+void server_error_traits::raise(duk_context* ctx, const server_error& ex)
+{
+    dukx_stack_assert sa(ctx, 1);
+
+    duk_get_global_string(ctx, "Irccd");
+    duk_get_prop_string(ctx, -1, "ServerError");
+    duk_remove(ctx, -2);
+    dukx_push(ctx, ex.code().value());
+    dukx_push(ctx, ex.code().message());
+    duk_new(ctx, 2);
+
+    (void)duk_throw(ctx);
+}
+
 } // !irccd
--- a/libirccd-js/irccd/js/server_jsapi.hpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/server_jsapi.hpp	Fri Apr 13 23:05:56 2018 +0200
@@ -74,6 +74,21 @@
     static std::shared_ptr<server> require(duk_context* ctx, duk_idx_t index);
 };
 
+/**
+ * \brief Specialization for server_error.
+ */
+template <>
+class dukx_type_traits<server_error> : public std::true_type {
+public:
+    /**
+     * Raise a server_error.
+     *
+     * \param ctx the context
+     * \param error the error
+     */
+    static void raise(duk_context* ctx, const server_error& error);
+};
+
 } // !irccd
 
 #endif // !IRCCD_JS_SERVER_JSAPI_HPP
--- a/libirccd-js/irccd/js/system_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/system_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -37,6 +37,26 @@
 
 namespace {
 
+// {{{ wrap
+
+template <typename Handler>
+duk_ret_t wrap(duk_context* ctx, Handler handler)
+{
+    try {
+        return handler();
+    } catch (const std::system_error& ex) {
+        dukx_throw(ctx, ex);
+    } catch (const std::exception& ex) {
+        dukx_throw(ctx, ex);
+    }
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.System.env
+
 /*
  * Function: Irccd.System.env(key)
  * ------------------------------------------------------------------
@@ -47,12 +67,20 @@
  *   - key, the environment variable.
  * Returns:
  *   The value.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t env(duk_context* ctx)
+duk_ret_t System_env(duk_context* ctx)
 {
-    return dukx_push(ctx, sys::env(duk_get_string(ctx, 0)));
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, sys::env(dukx_get<std::string>(ctx, 0)));
+    });
 }
 
+// }}}
+
+// {{{ Irccd.System.exec
+
 /*
  * Function: Irccd.System.exec(cmd)
  * ------------------------------------------------------------------
@@ -61,14 +89,20 @@
  *
  * Arguments:
  *   - cmd, the command to execute.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t exec(duk_context* ctx)
+duk_ret_t System_exec(duk_context* ctx)
 {
     std::system(duk_require_string(ctx, 0));
 
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.System.home
+
 /*
  * Function: Irccd.System.home()
  * ------------------------------------------------------------------
@@ -77,12 +111,20 @@
  *
  * Returns:
  *   The user home directory.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t home(duk_context* ctx)
+duk_ret_t System_home(duk_context* ctx)
 {
-    return dukx_push(ctx, sys::home());
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, sys::home());
+    });
 }
 
+// }}}
+
+// {{{ Irccd.System.name
+
 /*
  * Function: Irccd.System.name()
  * ------------------------------------------------------------------
@@ -91,12 +133,20 @@
  *
  * Returns:
  *   The system name.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t name(duk_context* ctx)
+duk_ret_t System_name(duk_context* ctx)
 {
-    return dukx_push(ctx, sys::name());
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, sys::name());
+    });
 }
 
+// }}}
+
+// {{{ Irccd.System.popen
+
 #if defined(HAVE_POPEN)
 
 /*
@@ -110,34 +160,51 @@
  *   - mode, the mode (e.g. "r").
  * Returns:
  *   A irccd.File object.
- * Throws
- *   - irccd.system_error on failures.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t popen(duk_context* ctx)
+duk_ret_t System_popen(duk_context* ctx)
 {
-    auto fp = ::popen(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+    return wrap(ctx, [&] {
+        auto fp = ::popen(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
 
-    if (fp == nullptr)
-        dukx_throw(ctx, system_error());
+        if (fp == nullptr)
+            throw std::system_error(make_error_code(static_cast<std::errc>(errno)));
 
-    return dukx_push(ctx, std::make_shared<file>(fp, [] (auto fp) { ::pclose(fp); }));
+        return dukx_push(ctx, std::make_shared<file>(fp, [] (auto fp) { ::pclose(fp); }));
+    });
 }
 
 #endif // !HAVE_POPEN
 
+// }}}
+
+// {{{ Icrcd.System.sleep
+
 /*
  * Function: Irccd.System.sleep(delay)
  * ------------------------------------------------------------------
  *
  * Sleep the main loop for the specific delay in seconds.
+ *
+ * Arguments:
+ *   - delay, the delay in seconds.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t sleep(duk_context* ctx)
+duk_ret_t System_sleep(duk_context* ctx)
 {
-    std::this_thread::sleep_for(std::chrono::seconds(duk_get_int(ctx, 0)));
+    return wrap(ctx, [&] {
+        std::this_thread::sleep_for(std::chrono::seconds(duk_get_int(ctx, 0)));
 
-    return 0;
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.System.ticks
+
 /*
  * Function: Irccd.System.ticks()
  * ------------------------------------------------------------------
@@ -146,73 +213,100 @@
  *
  * Returns:
  *   The number of milliseconds.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t ticks(duk_context* ctx)
+duk_ret_t System_ticks(duk_context* ctx)
 {
-    duk_push_int(ctx, sys::ticks());
-
-    return 1;
+    return wrap(ctx, [&] {
+        return dukx_push<unsigned>(ctx, sys::ticks());
+    });
 }
 
+// }}}
+
+// {{{ Irccd.System.usleep
+
 /*
- * Function: irccd.System.usleep(delay)
+ * Function: Irccd.System.usleep(delay)
  * ------------------------------------------------------------------
  *
  * Sleep the main loop for the specific delay in microseconds.
+ *
+ * Arguments:
+ *   - delay, the delay in microseconds.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t usleep(duk_context* ctx)
+duk_ret_t System_usleep(duk_context* ctx)
 {
-    std::this_thread::sleep_for(std::chrono::microseconds(duk_get_int(ctx, 0)));
+    return wrap(ctx, [&] {
+        std::this_thread::sleep_for(std::chrono::microseconds(duk_get_int(ctx, 0)));
 
-    return 0;
+        return 0;
+    });
 }
 
+// }}}
+
+// {{{ Irccd.System.uptime
+
 /*
- * Function: irccd.System.uptime()
+ * Function: Irccd.System.uptime()
  * ------------------------------------------------------------------
  *
  * Get the system uptime.
  *
  * Returns:
  *   The system uptime.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t uptime(duk_context* ctx)
+duk_ret_t System_uptime(duk_context* ctx)
 {
-    duk_push_int(ctx, sys::uptime());
-
-    return 0;
+    return wrap(ctx, [&] {
+        return dukx_push<unsigned>(ctx, sys::uptime());
+    });
 }
 
+// }}}
+
+// {{{ Irccd.System.version
+
 /*
- * Function: irccd.System.version()
+ * Function: Irccd.System.version()
  * ------------------------------------------------------------------
  *
  * Get the operating system version.
  *
  * Returns:
  *   The system version.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t version(duk_context* ctx)
+duk_ret_t System_version(duk_context* ctx)
 {
-    dukx_push(ctx, sys::version());
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, sys::version());
+    });
+}
 
-    return 1;
-}
+// }}}
 
 const duk_function_list_entry functions[] = {
-    { "env",        env,        1 },
-    { "exec",       exec,       1 },
-    { "home",       home,       0 },
-    { "name",       name,       0 },
+    { "env",        System_env,     1 },
+    { "exec",       System_exec,    1 },
+    { "home",       System_home,    0 },
+    { "name",       System_name,    0 },
 #if defined(HAVE_POPEN)
-    { "popen",      popen,      2 },
+    { "popen",      System_popen,   2 },
 #endif
-    { "sleep",      sleep,      1 },
-    { "ticks",      ticks,      0 },
-    { "uptime",     uptime,     0 },
-    { "usleep",     usleep,     1 },
-    { "version",    version,    0 },
-    { nullptr,      nullptr,    0 }
+    { "sleep",      System_sleep,   1 },
+    { "ticks",      System_ticks,   0 },
+    { "uptime",     System_uptime,  0 },
+    { "usleep",     System_usleep,  1 },
+    { "version",    System_version, 0 },
+    { nullptr,      nullptr,        0 }
 };
 
 } // !namespace
--- a/libirccd-js/irccd/js/timer_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/timer_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -33,6 +33,8 @@
 const char* signature("\xff""\xff""irccd-timer-ptr");
 const char* table("\xff""\xff""irccd-timer-callbacks");
 
+// {{{ timer
+
 class timer {
 public:
     enum class type_t {
@@ -122,6 +124,10 @@
     }
 }
 
+// }}}
+
+// {{{ self
+
 timer* self(duk_context* ctx)
 {
     dukx_stack_assert sa(ctx);
@@ -137,37 +143,43 @@
     return static_cast<timer*>(ptr);
 }
 
+// }}}
+
+// {{{ Irccd.Timer.prototype.start
+
 /*
- * Method: timer.start()
+ * Method: Irccd.Timer.prototype.start()
  * --------------------------------------------------------
  *
  * Start the timer. If the timer is already started the method is a no-op.
  */
-duk_ret_t start(duk_context* ctx)
+duk_ret_t Timer_prototype_start(duk_context* ctx)
 {
     self(ctx)->start();
 
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.Timer.prototype.stop
+
 /*
- * Method: timer.stop()
+ * Method: Irccd.Timer.prototype.stop()
  * --------------------------------------------------------
  *
  * Stop the timer.
  */
-duk_ret_t stop(duk_context* ctx)
+duk_ret_t Timer_prototype_stop(duk_context* ctx)
 {
     self(ctx)->stop();
 
     return 0;
 }
 
-const duk_function_list_entry methods[] = {
-    { "start",  start,      0 },
-    { "stop",   stop,       0 },
-    { nullptr,  nullptr,    0 }
-};
+// }}}
+
+// {{{ Irccd.Timer [destructor]
 
 /*
  * Function: Irccd.Timer() [destructor]
@@ -175,7 +187,7 @@
  *
  * Deleter the timer.
  */
-duk_ret_t destructor(duk_context* ctx)
+duk_ret_t Timer_destructor(duk_context* ctx)
 {
     dukx_stack_assert sa(ctx);
 
@@ -196,8 +208,12 @@
     return 0;
 }
 
+// }}}
+
+// {{{ Irccd.Timer [constructor]
+
 /*
- * Function: Irccd.timer(type, delay, callback) [constructor]
+ * Function: Irccd.Timer(type, delay, callback) [constructor]
  * --------------------------------------------------------
  *
  * Create a new timer object.
@@ -207,41 +223,53 @@
  *   - delay, the interval in milliseconds,
  *   - callback, the function to call.
  */
-duk_ret_t constructor(duk_context* ctx)
+duk_ret_t Timer_constructor(duk_context* ctx)
 {
     if (!duk_is_constructor_call(ctx))
         return 0;
 
-    // Check parameters.
-    auto type = duk_require_int(ctx, 0);
-    auto delay = duk_require_int(ctx, 1);
+    try {
+        // Check parameters.
+        const auto type = duk_require_int(ctx, 0);
+        const auto delay = duk_require_int(ctx, 1);
 
-    if (type < static_cast<int>(timer::type_t::single) || type > static_cast<int>(timer::type_t::repeat))
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid timer type");
-    if (delay < 0)
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "negative delay given");
-    if (!duk_is_callable(ctx, 2))
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "missing callback function");
+        if (type < static_cast<int>(timer::type_t::single) || type > static_cast<int>(timer::type_t::repeat))
+            duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid timer type");
+        if (delay < 0)
+            duk_error(ctx, DUK_ERR_TYPE_ERROR, "negative delay given");
+        if (!duk_is_callable(ctx, 2))
+            duk_error(ctx, DUK_ERR_TYPE_ERROR, "missing callback function");
 
-    auto plugin = dukx_type_traits<js_plugin>::self(ctx);
-    auto& daemon = dukx_type_traits<irccd>::self(ctx);
-    auto object = new timer(daemon.get_service(), plugin, delay, static_cast<timer::type_t>(type));
+        auto plugin = dukx_type_traits<js_plugin>::self(ctx);
+        auto& daemon = dukx_type_traits<irccd>::self(ctx);
+        auto object = new timer(daemon.get_service(), plugin, delay, static_cast<timer::type_t>(type));
 
-    duk_push_this(ctx);
-    duk_push_pointer(ctx, object);
-    duk_put_prop_string(ctx, -2, signature);
-    duk_push_c_function(ctx, destructor, 1);
-    duk_set_finalizer(ctx, -2);
-    duk_pop(ctx);
+        duk_push_this(ctx);
+        duk_push_pointer(ctx, object);
+        duk_put_prop_string(ctx, -2, signature);
+        duk_push_c_function(ctx, Timer_destructor, 1);
+        duk_set_finalizer(ctx, -2);
+        duk_pop(ctx);
 
-    // Store the function in a table to be called later.
-    duk_get_global_string(ctx, table);
-    duk_dup(ctx, 2);
-    duk_put_prop_string(ctx, -2, object->key().c_str());
+        // Store the function in a table to be called later.
+        duk_get_global_string(ctx, table);
+        duk_dup(ctx, 2);
+        duk_put_prop_string(ctx, -2, object->key().c_str());
+    } catch (const std::exception& ex) {
+        dukx_throw(ctx, ex);
+    }
 
     return 0;
 }
 
+// }}}
+
+const duk_function_list_entry methods[] = {
+    { "start",  Timer_prototype_start,  0                   },
+    { "stop",   Timer_prototype_stop,   0                   },
+    { nullptr,  nullptr,                0                   }
+};
+
 const duk_number_list_entry constants[] = {
     { "Single",     static_cast<int>(timer::type_t::single) },
     { "Repeat",     static_cast<int>(timer::type_t::repeat) },
@@ -260,7 +288,7 @@
     dukx_stack_assert sa(plugin->context());
 
     duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 3);
+    duk_push_c_function(plugin->context(), Timer_constructor, 3);
     duk_put_number_list(plugin->context(), -1, constants);
     duk_push_object(plugin->context());
     duk_put_function_list(plugin->context(), -1, methods);
--- a/libirccd-js/irccd/js/unicode_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/unicode_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -24,6 +24,8 @@
 
 namespace {
 
+// {{{ Irccd.Unicode.isDigit
+
 /*
  * Function: Irccd.Unicode.isDigit(code)
  * --------------------------------------------------------
@@ -33,12 +35,14 @@
  * Returns:
  *   True if the code is in the digit category.
  */
-duk_ret_t is_digit(duk_context* ctx)
+duk_ret_t Unicode_isDigit(duk_context* ctx) noexcept
 {
-    duk_push_boolean(ctx, unicode::isdigit(duk_get_int(ctx, 0)));
+    return dukx_push(ctx, unicode::isdigit(duk_get_int(ctx, 0)));
+}
 
-    return 1;
-}
+// }}}
+
+// {{{ Irccd.Unicode.isLetter
 
 /*
  * Function: Irccd.Unicode.isLetter(code)
@@ -49,12 +53,14 @@
  * Returns:
  *   True if the code is in the letter category.
  */
-duk_ret_t is_letter(duk_context* ctx)
+duk_ret_t Unicode_isLetter(duk_context* ctx) noexcept
 {
-    duk_push_boolean(ctx, unicode::isalpha(duk_get_int(ctx, 0)));
+    return dukx_push(ctx, unicode::isalpha(duk_get_int(ctx, 0)));
+}
 
-    return 1;
-}
+// }}}
+
+// {{{ Irccd.Unicode.isLower
 
 /*
  * Function: Irccd.Unicode.isLower(code)
@@ -65,12 +71,14 @@
  * Returns:
  *   True if the code is lower case.
  */
-duk_ret_t is_lower(duk_context* ctx)
+duk_ret_t Unicode_isLower(duk_context* ctx) noexcept
 {
-    duk_push_boolean(ctx, unicode::islower(duk_get_int(ctx, 0)));
+    return dukx_push(ctx, unicode::islower(duk_get_int(ctx, 0)));
+}
 
-    return 1;
-}
+// }}}
+
+// {{{ Irccd.Unicode.isSpace
 
 /*
  * Function: Irccd.Unicode.isSpace(code)
@@ -81,12 +89,14 @@
  * Returns:
  *   True if the code is in the space category.
  */
-duk_ret_t is_space(duk_context* ctx)
+duk_ret_t Unicode_isSpace(duk_context* ctx) noexcept
 {
-    duk_push_boolean(ctx, unicode::isspace(duk_get_int(ctx, 0)));
+    return dukx_push(ctx, unicode::isspace(duk_get_int(ctx, 0)));
+}
 
-    return 1;
-}
+// }}}
+
+// {{{ Irccd.Unicode.isTitle
 
 /*
  * Function: Irccd.Unicode.isTitle(code)
@@ -97,12 +107,14 @@
  * Returns:
  *   True if the code is title case.
  */
-duk_ret_t is_title(duk_context* ctx)
+duk_ret_t Unicode_isTitle(duk_context* ctx) noexcept
 {
-    duk_push_boolean(ctx, unicode::istitle(duk_get_int(ctx, 0)));
+    return dukx_push(ctx, unicode::istitle(duk_get_int(ctx, 0)));
+}
 
-    return 1;
-}
+// }}}
+
+// {{{ Irccd.Unicode.isUpper
 
 /*
  * Function: Irccd.Unicode.isUpper(code)
@@ -113,21 +125,21 @@
  * Returns:
  *   True if the code is upper case.
  */
-duk_ret_t is_upper(duk_context* ctx)
+duk_ret_t Unicode_isUpper(duk_context* ctx) noexcept
 {
-    duk_push_boolean(ctx, unicode::isupper(duk_get_int(ctx, 0)));
+    return dukx_push(ctx, unicode::isupper(duk_get_int(ctx, 0)));
+}
 
-    return 1;
-}
+// }}}
 
 const duk_function_list_entry functions[] = {
-    { "isDigit",        is_digit,   1 },
-    { "isLetter",       is_letter,  1 },
-    { "isLower",        is_lower,   1 },
-    { "isSpace",        is_space,   1 },
-    { "isTitle",        is_title,   1 },
-    { "isUpper",        is_upper,   1 },
-    { nullptr,          nullptr,    0 }
+    { "isDigit",        Unicode_isDigit,    1 },
+    { "isLetter",       Unicode_isLetter,   1 },
+    { "isLower",        Unicode_isLower,    1 },
+    { "isSpace",        Unicode_isSpace,    1 },
+    { "isTitle",        Unicode_isTitle,    1 },
+    { "isUpper",        Unicode_isUpper,    1 },
+    { nullptr,          nullptr,            0 }
 };
 
 } // !namespace
--- a/libirccd-js/irccd/js/util_jsapi.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd-js/irccd/js/util_jsapi.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -21,6 +21,7 @@
 #include <irccd/string_util.hpp>
 
 #include "duktape_vector.hpp"
+#include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
 #include "util_jsapi.hpp"
 
@@ -28,8 +29,10 @@
 
 namespace {
 
+// {{{ subst
+
 /*
- * Read parameters for irccd.Util.format function, the object is defined as
+ * Read parameters for Irccd.Util.format function, the object is defined as
  * following:
  *
  * {
@@ -40,7 +43,7 @@
  *   fieldn: ...
  * }
  */
-string_util::subst get_subst(duk_context* ctx, int index)
+string_util::subst subst(duk_context* ctx, int index)
 {
     string_util::subst params;
 
@@ -64,6 +67,10 @@
     return params;
 }
 
+// }}}
+
+// {{{ split
+
 /*
  * split (for Irccd.Util.cut)
  * ------------------------------------------------------------------
@@ -84,7 +91,7 @@
 
         while (duk_next(ctx, -1, 1)) {
             // Split individual tokens as array if spaces are found.
-            auto tmp = string_util::split(duk_to_string(ctx, -1), pattern);
+            const auto tmp = string_util::split(duk_to_string(ctx, -1), pattern);
 
             result.insert(result.end(), tmp.begin(), tmp.end());
             duk_pop_2(ctx);
@@ -94,6 +101,10 @@
     return result;
 }
 
+// }}}
+
+// {{{ limit
+
 /*
  * limit (for Irccd.Util.cut)
  * ------------------------------------------------------------------
@@ -115,6 +126,10 @@
     return value;
 }
 
+// }}}
+
+// {{{ lines
+
 /*
  * lines (for Irccd.Util.cut)
  * ------------------------------------------------------------------
@@ -159,6 +174,28 @@
     return result;
 }
 
+// }}}
+
+// {{{ wrap
+
+template <typename Handler>
+duk_ret_t wrap(duk_context* ctx, Handler handler)
+{
+    try {
+        return handler();
+    } catch (const std::system_error& ex) {
+        dukx_throw(ctx, ex);
+    } catch (const std::exception& ex) {
+        dukx_throw(ctx, ex);
+    }
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Util.cut
+
 /*
  * Function: Irccd.Util.cut(data, maxc, maxl)
  * --------------------------------------------------------
@@ -189,24 +226,31 @@
  * Throws:
  *   - RangeError if maxl or maxc are negative numbers,
  *   - RangeError if one word length was bigger than maxc,
- *   - TypeError if data is not a string or a list of strings.
+ *   - TypeError if data is not a string or a list of strings,
+ *   - Irccd.SystemError on other errors.
  */
-duk_ret_t cut(duk_context* ctx)
+duk_ret_t Util_cut(duk_context* ctx)
 {
-    auto list = lines(ctx, split(ctx), limit(ctx, 1, "maxc", 72));
-    auto maxl = limit(ctx, 2, "maxl", INT_MAX);
+    return wrap(ctx, [&] {
+        const auto list = lines(ctx, split(ctx), limit(ctx, 1, "maxc", 72));
+        const auto maxl = limit(ctx, 2, "maxl", INT_MAX);
+
+        if (list.size() > static_cast<std::size_t>(maxl))
+            return 0;
 
-    if (list.size() > static_cast<std::size_t>(maxl))
-        return 0;
+        // Empty list but lines() returns at least one.
+        if (list.size() == 1 && list[0].empty()) {
+            duk_push_array(ctx);
+            return 1;
+        }
 
-    // Empty list but lines() returns at least one.
-    if (list.size() == 1 && list[0].empty()) {
-        duk_push_array(ctx);
-        return 1;
-    }
+        return dukx_push(ctx, list);
+    });
+}
 
-    return dukx_push(ctx, list);
-}
+// }}}
+
+// {{{ Irccd.Util.format
 
 /*
  * Function: Irccd.Util.format(text, parameters)
@@ -219,17 +263,19 @@
  *   - params, the parameters.
  * Returns:
  *   The converted text.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t format(duk_context* ctx)
+duk_ret_t Util_format(duk_context* ctx)
 {
-    try {
-        dukx_push(ctx, string_util::format(duk_get_string(ctx, 0), get_subst(ctx, 1)));
-    } catch (const std::exception &ex) {
-        (void)duk_error(ctx, DUK_ERR_SYNTAX_ERROR, "%s", ex.what());
-    }
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, string_util::format(dukx_get<std::string>(ctx, 0), subst(ctx, 1)));
+    });
+}
 
-    return 1;
-}
+// }}}
+
+// {{{ Irccd.Util.splituser
 
 /*
  * Function: Irccd.Util.splituser(ident)
@@ -241,13 +287,19 @@
  *   - ident, the full identity.
  * Returns:
  *   The nickname.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t splituser(duk_context* ctx)
+duk_ret_t Util_splituser(duk_context* ctx)
 {
-    dukx_push(ctx, irc::user::parse(duk_require_string(ctx, 0)).nick());
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, irc::user::parse(dukx_require<std::string>(ctx, 0)).nick());
+    });
+}
 
-    return 1;
-}
+// }}}
+
+// {{{ Irccd.Util.splithost
 
 /*
  * Function: Irccd.Util.splithost(ident)
@@ -259,20 +311,24 @@
  *   - ident, the full identity.
  * Returns:
  *   The hostname.
+ * Throws:
+ *   - Irccd.SystemError on errors.
  */
-duk_ret_t splithost(duk_context* ctx)
+duk_ret_t Util_splithost(duk_context* ctx)
 {
-    dukx_push(ctx, irc::user::parse(duk_require_string(ctx, 0)).host());
-
-    return 1;
+    return wrap(ctx, [&] {
+        return dukx_push(ctx, irc::user::parse(dukx_require<std::string>(ctx, 0)).host());
+    });
 }
 
+// }}}
+
 const duk_function_list_entry functions[] = {
-    { "cut",        cut,        DUK_VARARGS },
-    { "format",     format,     DUK_VARARGS },
-    { "splituser",  splituser,  1           },
-    { "splithost",  splithost,  1           },
-    { nullptr,      nullptr,    0           }
+    { "cut",        Util_cut,       DUK_VARARGS },
+    { "format",     Util_format,    DUK_VARARGS },
+    { "splituser",  Util_splituser, 1           },
+    { "splithost",  Util_splithost, 1           },
+    { nullptr,      nullptr,        0           }
 };
 
 } // !namespace
--- a/libirccd/irccd/daemon/server.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/libirccd/irccd/daemon/server.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -555,6 +555,8 @@
 
 void server::join(std::string channel, std::string password)
 {
+    assert(!channel.empty());
+
     auto it = std::find_if(rchannels_.begin(), rchannels_.end(), [&] (const auto& c) {
         return c.name == channel;
     });
--- a/tests/src/libirccd-js/jsapi-irccd/main.cpp	Fri Apr 13 22:43:10 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-irccd/main.cpp	Fri Apr 13 23:05:56 2018 +0200
@@ -76,7 +76,7 @@
 BOOST_AUTO_TEST_CASE(from_native)
 {
     duk_push_c_function(plugin_->context(), [] (duk_context *ctx) -> duk_ret_t {
-        dukx_throw(ctx, system_error(EINVAL, "hey"));
+        dukx_throw(ctx, std::system_error(make_error_code(std::errc::invalid_argument)));
 
         return 0;
     }, 0);
@@ -89,7 +89,6 @@
         "} catch (e) {"
         "  errno = e.errno;"
         "  name = e.name;"
-        "  message = e.message;"
         "  v1 = (e instanceof Error);"
         "  v2 = (e instanceof Irccd.SystemError);"
         "}"
@@ -102,8 +101,6 @@
     BOOST_TEST(EINVAL == duk_get_int(plugin_->context(), -1));
     BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
     BOOST_TEST("SystemError" == duk_get_string(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
-    BOOST_TEST("hey" == duk_get_string(plugin_->context(), -1));
     BOOST_TEST(duk_get_global_string(plugin_->context(), "v1"));
     BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
     BOOST_TEST(duk_get_global_string(plugin_->context(), "v2"));