about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/bindings/quickjs.nim11
-rw-r--r--src/buffer/buffer.nim37
-rw-r--r--src/buffer/container.nim4
-rw-r--r--src/config/config.nim4
-rw-r--r--src/display/client.nim3
-rw-r--r--src/html/env.nim2
-rw-r--r--src/io/http.nim3
-rw-r--r--src/io/lineedit.nim9
-rw-r--r--src/io/promise.nim16
-rw-r--r--src/io/request.nim16
-rw-r--r--src/ips/serialize.nim41
-rw-r--r--src/js/exception.nim96
-rw-r--r--src/js/javascript.nim342
-rw-r--r--src/types/url.nim2
-rw-r--r--src/utils/opt.nim68
-rw-r--r--src/utils/twtstr.nim4
16 files changed, 480 insertions, 178 deletions
diff --git a/src/bindings/quickjs.nim b/src/bindings/quickjs.nim
index 306a3b3d..9a76a484 100644
--- a/src/bindings/quickjs.nim
+++ b/src/bindings/quickjs.nim
@@ -212,12 +212,20 @@ type
     is_enumerable*: JS_BOOL
     atom*: JSAtom
 
+  JSClassEnum* {.size: sizeof(uint32).} = enum
+    JS_CLASS_OBJECT = 1
+    JS_CLASS_ARRAY
+    JS_CLASS_ERROR
+
 converter toBool*(js: JS_BOOl): bool {.inline.} =
   cast[cint](js) != 0
 
 converter toJSBool*(b: bool): JS_BOOL {.inline.} =
   cast[JS_BOOL](cint(b))
 
+converter toJSClassID*(e: JSClassEnum): JSClassID {.inline.} =
+  JSClassID(e)
+
 const
   JS_NULL* = JS_MKVAL(JS_TAG_NULL, 0)
   JS_UNDEFINED* = JS_MKVAL(JS_TAG_UNDEFINED, 0)
@@ -338,6 +346,7 @@ proc JS_SetClassProto*(ctx: JSContext, class_id: JSClassID, obj: JSValue)
 proc JS_GetClassProto*(ctx: JSContext, class_id: JSClassID): JSValue
 proc JS_SetConstructor*(ctx: JSContext, func_obj: JSValue, proto: JSValue)
 proc JS_SetPrototype*(ctx: JSContext, obj: JSValue, proto_val: JSValue): cint
+proc JS_GetPrototype*(ctx: JSContext, val: JSValue): JSValue
 
 proc JS_NewBool*(ctx: JSContext, val: JS_BOOL): JSValue
 proc JS_NewInt32*(ctx: JSContext, val: int32): JSValue
@@ -374,6 +383,8 @@ proc JS_GetOwnPropertyNames*(ctx: JSContext, ptab: ptr ptr JSPropertyEnum, plen:
 
 proc JS_GetOwnProperty*(ctx: JSContext, desc: ptr JSPropertyDescriptor, obj: JSValue, prop: JSAtom): cint
 proc JS_Call*(ctx: JSContext, func_obj, this_obj: JSValue, argc: cint, argv: ptr JSValue): JSValue
+proc JS_CallConstructor*(ctx: JSContext, func_obj: JSValue, argc: cint,
+  argv: ptr JSValue): JSValue
 
 proc JS_DefineProperty*(ctx: JSContext, this_obj: JSValue, prop: JSAtom, val: JSValue, getter: JSValue, setter: JSValue, flags: cint): cint
 proc JS_DefinePropertyValue*(ctx: JSContext, this_obj: JSValue, prop: JSAtom, val: JSValue, flags: cint): cint
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim
index 6c2a8eda..e0b24cae 100644
--- a/src/buffer/buffer.nim
+++ b/src/buffer/buffer.nim
@@ -20,6 +20,7 @@ import css/sheet
 import css/stylednode
 import css/values
 import data/charset
+import encoding/decoderstream
 import html/dom
 import html/env
 import html/htmlparser
@@ -44,6 +45,7 @@ import types/cookie
 import types/formdata
 import types/referer
 import types/url
+import utils/opt
 import utils/twtstr
 import xhr/formdata as formdata_impl
 
@@ -555,17 +557,26 @@ proc loadResource(buffer: Buffer, elem: HTMLLinkElement): EmptyPromise =
     let url = url.get
     let media = elem.media
     if media != "":
-      let media = parseMediaQueryList(parseListOfComponentValues(newStringStream(media)))
+      let cvals = parseListOfComponentValues(newStringStream(media))
+      let media = parseMediaQueryList(cvals)
       if not media.applies(document.window): return
-    return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) =
+    return buffer.loader.fetch(newRequest(url)).then(proc(res: Response):
+        Opt[Promise[string]] =
       if res.res == 0: #TODO remove res
         #TODO we should use ReadableStreams for this (which would allow us to
         # parse CSS asynchronously)
-        # yet another hack: needed because closing a stream before
-        # unregistering breaks
         if res.contenttype == "text/css":
-          elem.sheet = parseStylesheet(res.body)
-        res.unregisterFun())
+          return ok(res.text())
+        res.unregisterFun()
+      ).then(proc(s: Opt[string]) =
+        if s.isOk:
+          #TODO this is extremely inefficient, and text() should return
+          # utf8 anyways
+          let ss = newStringStream(s.get)
+          #TODO non-utf-8 css
+          let source = newDecoderStream(ss, cs = CHARSET_UTF_8).readAll()
+          let ss2 = newStringStream(source)
+          elem.sheet = parseStylesheet(ss2))
 
 proc loadResource(buffer: Buffer, elem: HTMLImageElement): EmptyPromise =
   let document = buffer.document
@@ -843,22 +854,22 @@ func submitForm(form: HTMLFormElement, submitter: Element): Option[Request] =
 
   template submitAsEntityBody() =
     var mimetype: string
-    var body = none(string)
-    var multipart = none(FormData)
+    var body: Opt[string]
+    var multipart: Opt[FormData]
     case enctype
     of FORM_ENCODING_TYPE_URLENCODED:
       let kvlist = entrylist.toNameValuePairs()
-      body = some(serializeApplicationXWWWFormUrlEncoded(kvlist))
+      body.ok(serializeApplicationXWWWFormUrlEncoded(kvlist))
       mimeType = $enctype
     of FORM_ENCODING_TYPE_MULTIPART:
-      multipart = some(serializeMultipartFormData(entrylist))
+      multipart.ok(serializeMultipartFormData(entrylist))
       mimetype = $enctype
     of FORM_ENCODING_TYPE_TEXT_PLAIN:
       let kvlist = entrylist.toNameValuePairs()
-      body = some(serializePlainTextFormData(kvlist))
+      body.ok(serializePlainTextFormData(kvlist))
       mimetype = $enctype
-    let req = newRequest(parsedaction, httpmethod,
-      @{"Content-Type": mimetype}, body)
+    let req = newRequest(parsedaction, httpmethod, @{"Content-Type": mimetype},
+      body)
     return some(req) #TODO multipart
 
   template getActionUrl() =
diff --git a/src/buffer/container.nim b/src/buffer/container.nim
index e301e05c..57685784 100644
--- a/src/buffer/container.nim
+++ b/src/buffer/container.nim
@@ -638,13 +638,13 @@ proc onMatch(container: Container, res: BufferMatch) =
     container.needslines = true
     container.hlon = false
 
-proc cursorNextMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.} =
+proc cursorNextMatch*(container: Container, regex: Regex, wrap: bool) =
   container.iface
     .findNextMatch(regex, container.cursorx, container.cursory, wrap)
     .then(proc(res: BufferMatch) =
       container.onMatch(res))
 
-proc cursorPrevMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.} =
+proc cursorPrevMatch*(container: Container, regex: Regex, wrap: bool) =
   container.iface
     .findPrevMatch(regex, container.cursorx, container.cursory, wrap)
     .then(proc(res: BufferMatch) =
diff --git a/src/config/config.nim b/src/config/config.nim
index 45bca17d..48c0dde9 100644
--- a/src/config/config.nim
+++ b/src/config/config.nim
@@ -44,7 +44,7 @@ type
   SiteConfig* = object
     url*: Option[Regex]
     host*: Option[Regex]
-    rewrite_url*: (proc(s: URL): Option[URL])
+    rewrite_url*: (proc(s: URL): Opt[URL])
     cookie*: Option[bool]
     third_party_cookie*: seq[Regex]
     share_cookie_jar*: Option[string]
@@ -55,7 +55,7 @@ type
 
   OmniRule* = object
     match*: Regex
-    substitute_url*: (proc(s: string): Option[string])
+    substitute_url*: (proc(s: string): Opt[string])
 
   StartConfig = object
     visual_home*: string
diff --git a/src/display/client.nim b/src/display/client.nim
index 88185fb7..8196df26 100644
--- a/src/display/client.nim
+++ b/src/display/client.nim
@@ -31,6 +31,7 @@ import ips/forkserver
 import ips/serialize
 import ips/serversocket
 import ips/socketstream
+import js/exception
 import js/intl
 import js/javascript
 import js/module
@@ -39,6 +40,7 @@ import types/blob
 import types/cookie
 import types/dispatcher
 import types/url
+import utils/opt
 import xhr/formdata as formdata_impl
 
 type
@@ -544,6 +546,7 @@ proc newClient*(config: Config, dispatcher: Dispatcher): Client =
 
   ctx.registerType(Console)
 
+  ctx.addDOMExceptionModule()
   ctx.addCookieModule()
   ctx.addURLModule()
   ctx.addDOMModule()
diff --git a/src/html/env.nim b/src/html/env.nim
index 5883dabf..a70508be 100644
--- a/src/html/env.nim
+++ b/src/html/env.nim
@@ -8,6 +8,7 @@ import io/promise
 import io/request
 import io/response
 import io/window
+import js/exception
 import js/intl
 import js/javascript
 import js/timeout
@@ -112,6 +113,7 @@ proc addScripting*(window: Window, selector: Selector[int]) =
   ctx.setOpaque(global, window)
   ctx.defineProperty(global, "window", global)
   JS_FreeValue(ctx, global)
+  ctx.addDOMExceptionModule()
   ctx.addconsoleModule()
   ctx.addNavigatorModule()
   ctx.addDOMModule()
diff --git a/src/io/http.nim b/src/io/http.nim
index f9023f5f..cd6234fd 100644
--- a/src/io/http.nim
+++ b/src/io/http.nim
@@ -8,6 +8,7 @@ import ips/serialize
 import types/blob
 import types/formdata
 import types/url
+import utils/opt
 import utils/twtstr
 
 type
@@ -84,7 +85,7 @@ proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t, userdata: pointer)
   return nmemb
 
 proc applyPostBody(curl: CURL, request: Request, handleData: HandleData) =
-  if request.multipart.issome:
+  if request.multipart.isOk:
     handleData.mime = curl_mime_init(curl)
     if handleData.mime == nil:
       # fail (TODO: raise?)
diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim
index ee25d9a3..39ef480c 100644
--- a/src/io/lineedit.nim
+++ b/src/io/lineedit.nim
@@ -8,6 +8,7 @@ import buffer/cell
 import display/term
 import js/javascript
 import types/color
+import utils/opt
 import utils/twtstr
 
 type
@@ -231,7 +232,7 @@ proc forward(edit: LineEdit) {.jsfunc.} =
     else:
       edit.fullRedraw()
 
-proc prevWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
+proc prevWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} =
   let oc = edit.cursor
   while edit.cursor > 0:
     dec edit.cursor
@@ -243,7 +244,7 @@ proc prevWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
     else:
       edit.fullRedraw()
 
-proc nextWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
+proc nextWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} =
   let oc = edit.cursor
   let ow = edit.news.width(edit.shift, edit.cursor)
   while edit.cursor < edit.news.len:
@@ -258,7 +259,7 @@ proc nextWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
     else:
       edit.fullRedraw()
 
-proc clearWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
+proc clearWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} =
   var i = edit.cursor
   if i > 0:
     # point to the previous character
@@ -273,7 +274,7 @@ proc clearWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
     edit.cursor = i
     edit.fullRedraw()
 
-proc killWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
+proc killWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} =
   var i = edit.cursor
   if i < edit.news.len and edit.news[i].breaksWord(check):
     inc i
diff --git a/src/io/promise.nim b/src/io/promise.nim
index d825c4fa..27288be8 100644
--- a/src/io/promise.nim
+++ b/src/io/promise.nim
@@ -1,5 +1,7 @@
 import tables
 
+import utils/opt
+
 type
   PromiseState* = enum
     PROMISE_PENDING, PROMISE_FULFILLED, PROMISE_REJECTED
@@ -127,6 +129,20 @@ proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Promise[U])): Promise[U]
       next.resolve())
   return next
 
+proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Opt[Promise[U]])):
+    Promise[Opt[U]] {.discardable.} =
+  doAssert promise != nil
+  let next = Promise[Opt[U]]()
+  promise.then(proc(x: T) =
+    let p2 = cb(x)
+    if p2.isOk:
+      p2.get.then(proc(y: U) =
+        next.res = opt(y)
+        next.resolve())
+    else:
+      next.resolve())
+  return next
+
 proc all*(promises: seq[EmptyPromise]): EmptyPromise =
   let res = EmptyPromise()
   var i = 0
diff --git a/src/io/request.nim b/src/io/request.nim
index 11e16718..8bc2bfc8 100644
--- a/src/io/request.nim
+++ b/src/io/request.nim
@@ -66,8 +66,8 @@ type
     httpmethod*: HttpMethod
     url*: Url
     headers* {.jsget.}: Headers
-    body*: Option[string]
-    multipart*: Option[FormData]
+    body*: Opt[string]
+    multipart*: Opt[FormData]
     referer*: URL
     mode* {.jsget.}: RequestMode
     destination* {.jsget.}: RequestDestination
@@ -185,7 +185,7 @@ func newHeaders*(table: Table[string, string]): Headers =
       result.table[k] = @[v]
 
 func newRequest*(url: URL, httpmethod = HTTP_GET, headers = newHeaders(),
-    body = none(string), multipart = none(FormData), mode = RequestMode.NO_CORS,
+    body = opt(string), multipart = opt(FormData), mode = RequestMode.NO_CORS,
     credentialsMode = CredentialsMode.SAME_ORIGIN,
     destination = RequestDestination.NO_DESTINATION, proxy: URL = nil): Request =
   return Request(
@@ -201,8 +201,8 @@ func newRequest*(url: URL, httpmethod = HTTP_GET, headers = newHeaders(),
   )
 
 func newRequest*(url: URL, httpmethod = HTTP_GET,
-    headers: seq[(string, string)] = @[], body = none(string),
-    multipart = none(FormData), mode = RequestMode.NO_CORS, proxy: URL = nil):
+    headers: seq[(string, string)] = @[], body = opt(string),
+    multipart = opt(FormData), mode = RequestMode.NO_CORS, proxy: URL = nil):
     Request =
   let hl = newHeaders()
   for pair in headers:
@@ -234,12 +234,12 @@ func newRequest*(ctx: JSContext, resource: string,
   let url = x.get
   let fallbackMode = some(RequestMode.CORS) #TODO none if resource is request
   var httpMethod = HTTP_GET
-  var body = none(string)
+  var body = opt(string)
   var credentials = CredentialsMode.SAME_ORIGIN
   var mode = fallbackMode.get(RequestMode.NO_CORS)
   let hl = newHeaders()
-  var proxyUrl = none(URL)
-  var multipart = none(FormData)
+  var proxyUrl: Opt[URL]
+  var multipart: Opt[FormData]
   #TODO fallback mode, origin, window, request mode, ...
   if init.isSome:
     let init = init.get
diff --git a/src/ips/serialize.nim b/src/ips/serialize.nim
index be5bbe28..f7fb934e 100644
--- a/src/ips/serialize.nim
+++ b/src/ips/serialize.nim
@@ -11,6 +11,7 @@ import types/blob
 import types/buffersource
 import types/formdata
 import types/url
+import utils/opt
 
 proc swrite*(stream: Stream, n: SomeNumber)
 proc sread*(stream: Stream, n: var SomeNumber)
@@ -68,6 +69,10 @@ proc swrite*[T](stream: Stream, o: Option[T])
 proc sread*[T](stream: Stream, o: var Option[T])
 func slen*[T](o: Option[T]): int
 
+proc swrite*[T, E](stream: Stream, o: Result[T, E])
+proc sread*[T, E](stream: Stream, o: var Result[T, E])
+func slen*[T, E](o: Result[T, E]): int
+
 proc swrite*(stream: Stream, regex: Regex)
 proc sread*(stream: Stream, regex: var Regex)
 func slen*(regex: Regex): int
@@ -339,6 +344,42 @@ func slen*[T](o: Option[T]): int =
   if o.isSome:
     result += slen(o.get)
 
+proc swrite*[T, E](stream: Stream, o: Result[T, E]) =
+  stream.swrite(o.isOk)
+  if o.isOk:
+    when not (T is void):
+      stream.swrite(o.get)
+  else:
+    when not (E is void):
+      stream.swrite(o.error)
+
+proc sread*[T, E](stream: Stream, o: var Result[T, E]) =
+  var x: bool
+  stream.sread(x)
+  if x:
+    when not (T is void):
+      var m: T
+      stream.sread(m)
+      o.ok(m)
+    else:
+      o.ok()
+  else:
+    when not (E is void):
+      var e: E
+      stream.sread(e)
+      o.err(e)
+    else:
+      o.err()
+
+func slen*[T, E](o: Result[T, E]): int =
+  result = slen(o.isSome)
+  if o.isSome:
+    when not (T is void):
+      result += slen(o.get)
+  else:
+    when not (E is void):
+      result += slen(o.error)
+
 proc swrite*(stream: Stream, regex: Regex) =
   stream.swrite(regex.plen)
   stream.writeData(regex.bytecode, regex.plen)
diff --git a/src/js/exception.nim b/src/js/exception.nim
new file mode 100644
index 00000000..1550d431
--- /dev/null
+++ b/src/js/exception.nim
@@ -0,0 +1,96 @@
+import tables
+
+import bindings/quickjs
+import js/javascript
+
+const NamesTable = {
+  "IndexSizeError": 1u16,
+  "HierarchyRequestError": 3u16,
+  "WrongDocumentError": 4u16,
+  "InvalidCharacterError": 5u16,
+  "NoModificationAllowedError": 7u16,
+  "NotFoundError": 8u16,
+  "NotSupportedError": 9u16,
+  "InUseAttributeError": 10u16,
+  "InvalidStateError": 11u16,
+  "SyntaxError": 12u16,
+  "InvalidModificationError": 13u16,
+  "NamespaceError": 14u16,
+  "InvalidAccessError": 15u16,
+  "TypeMismatchError": 17u16,
+  "SecurityError": 18u16,
+  "NetworkError": 19u16,
+  "AbortError": 20u16,
+  "URLMismatchError": 21u16,
+  "QuotaExceededError": 22u16,
+  "TimeoutError": 23u16,
+  "InvalidNodeTypeError": 24u16,
+  "DataCloneError": 25u16
+}.toTable()
+
+type DOMException* = ref object of JSError
+  name* {.jsget.}: string
+
+proc newDOMException*(message = "", name = "Error"): DOMException {.jsctor.} =
+  return DOMException(
+    e: JS_DOM_EXCEPTION,
+    name: name,
+    message: message
+  )
+
+func message0(this: DOMException): string {.jsfget: "message".} =
+  return this.message
+
+func code(this: DOMException): uint16 {.jsfget.} =
+  return NamesTable.getOrDefault(this.name, 0u16)
+
+proc newEvalError*(message: string): JSError =
+  return JSError(
+    e: JS_EVAL_ERROR0,
+    message: message
+  )
+
+proc newRangeError*(message: string): JSError =
+  return JSError(
+    e: JS_RANGE_ERROR0,
+    message: message
+  )
+
+proc newReferenceError*(message: string): JSError =
+  return JSError(
+    e: JS_REFERENCE_ERROR0,
+    message: message
+  )
+
+proc newSyntaxError*(message: string): JSError =
+  return JSError(
+    e: JS_SYNTAX_ERROR0,
+    message: message
+  )
+
+proc newTypeError*(message: string): JSError =
+  return JSError(
+    e: JS_TYPE_ERROR0,
+    message: message
+  )
+
+proc newURIError*(message: string): JSError =
+  return JSError(
+    e: JS_URI_ERROR0,
+    message: message
+  )
+
+proc newInternalError*(message: string): JSError =
+  return JSError(
+    e: JS_INTERNAL_ERROR0,
+    message: message
+  )
+
+proc newAggregateError*(message: string): JSError =
+  return JSError(
+    e: JS_AGGREGATE_ERROR0,
+    message: message
+  )
+
+proc addDOMExceptionModule*(ctx: JSContext) =
+  ctx.registerType(DOMException, JS_CLASS_ERROR, errid = opt(JS_DOM_EXCEPTION))
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index deda1ca5..fb020e5c 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -31,9 +31,11 @@ import tables
 import unicode
 
 import io/promise
+import utils/opt
 
 import bindings/quickjs
 
+export opt
 export options
 
 export
@@ -60,6 +62,19 @@ when sizeof(int) < sizeof(int64):
   export quickjs.`==`
 
 type
+  JSErrorEnum* = enum
+    # QuickJS internal errors
+    JS_EVAL_ERROR0 = "EvalError"
+    JS_RANGE_ERROR0 = "RangeError"
+    JS_REFERENCE_ERROR0 = "ReferenceError"
+    JS_SYNTAX_ERROR0 = "SyntaxError"
+    JS_TYPE_ERROR0 = "TypeError"
+    JS_URI_ERROR0 = "URIError"
+    JS_INTERNAL_ERROR0 = "InternalError"
+    JS_AGGREGATE_ERROR0 = "AggregateError"
+    # Chawan errors
+    JS_DOM_EXCEPTION = "DOMException"
+
   JSContextOpaque* = ref object
     creg: Table[string, JSClassID]
     typemap: Table[pointer, JSClassID]
@@ -72,6 +87,7 @@ type
     next: JSAtom
     value: JSAtom
     Array_prototype_values: JSValue
+    err_ctors: array[JSErrorEnum, JSValue]
 
   JSRuntimeOpaque* = ref object
     plist: Table[pointer, pointer] # Nim, JS
@@ -85,6 +101,30 @@ type
 
   JSFunctionList* = openArray[JSCFunctionListEntry]
 
+  JSError* = ref object of RootObj
+    e*: JSErrorEnum
+    message*: string
+
+  LegacyJSError* = object of CatchableError
+
+  #TODO remove these
+  JS_SyntaxError* = object of LegacyJSError
+  JS_TypeError* = object of LegacyJSError
+  JS_ReferenceError* = object of LegacyJSError
+  JS_RangeError* = object of LegacyJSError
+  JS_InternalError* = object of LegacyJSError
+
+const QuickJSErrors = [
+  JS_EVAL_ERROR0,
+  JS_RANGE_ERROR0,
+  JS_REFERENCE_ERROR0,
+  JS_SYNTAX_ERROR0,
+  JS_TYPE_ERROR0,
+  JS_URI_ERROR0,
+  JS_INTERNAL_ERROR0,
+  JS_AGGREGATE_ERROR0
+]
+
 func getOpaque*(ctx: JSContext): JSContextOpaque =
   return cast[JSContextOpaque](JS_GetContextOpaque(ctx))
 
@@ -134,11 +174,15 @@ proc newJSContext*(rt: JSRuntime): JSContext =
         let s = "next"
         opaque.next = JS_NewAtomLen(ctx, cstring(s), csize_t(s.len))
       block:
-        # 2 - JS_CLASS_ARRAY
-        let arrproto = JS_GetClassProto(ctx, 2)
-        opaque.Array_prototype_values = JS_GetPropertyStr(ctx, arrproto, "values")
+        let arrproto = JS_GetClassProto(ctx, JS_CLASS_ARRAY)
+        opaque.Array_prototype_values = JS_GetPropertyStr(ctx, arrproto,
+          "values")
         JS_FreeValue(ctx, arrproto)
       JS_FreeValue(ctx, sym)
+    for e in JSErrorEnum:
+      let s = $e
+      let err = JS_GetPropertyStr(ctx, global, cstring(s))
+      opaque.err_ctors[e] = err
     JS_FreeValue(ctx, global)
 
   JS_SetContextOpaque(ctx, cast[pointer](opaque))
@@ -172,6 +216,8 @@ proc free*(ctx: var JSContext) =
     JS_FreeAtom(ctx, opaque.done)
     JS_FreeAtom(ctx, opaque.next)
     JS_FreeValue(ctx, opaque.Array_prototype_values)
+    for v in opaque.err_ctors:
+      JS_FreeValue(ctx, v)
     GC_unref(opaque)
   JS_FreeContext(ctx)
   ctx = nil
@@ -218,7 +264,7 @@ func getOpaque*(ctx: JSContext, val: JSValue, class: string): pointer =
   # This needs further investigation.
   if ctx.isGlobal(class):
     let global = JS_GetGlobalObject(ctx)
-    let opaque = JS_GetOpaque(global, 1) # JS_CLASS_OBJECT
+    let opaque = JS_GetOpaque(global, JS_CLASS_OBJECT)
     JS_FreeValue(ctx, global)
     return opaque
   return getOpaque0(val)
@@ -229,7 +275,7 @@ func getOpaque*[T](ctx: JSContext, val: JSValue): pointer =
 proc setInterruptHandler*(rt: JSRuntime, cb: JSInterruptHandler, opaque: pointer = nil) =
   JS_SetInterruptHandler(rt, cb, opaque)
 
-func toString*(ctx: JSContext, val: JSValue): Option[string] =
+func toString*(ctx: JSContext, val: JSValue): Opt[string] =
   var plen: csize_t
   let outp = JS_ToCStringLen(ctx, addr plen, val) # cstring
   if outp != nil:
@@ -237,7 +283,7 @@ func toString*(ctx: JSContext, val: JSValue): Option[string] =
     if plen != 0:
       prepareMutation(ret)
       copyMem(addr ret[0], outp, plen)
-    result = some(ret)
+    result = ok(ret)
     JS_FreeCString(ctx, outp)
 
 proc writeException*(ctx: JSContext, s: Stream) =
@@ -297,10 +343,9 @@ proc definePropertyCWE*[T](ctx: JSContext, this: JSValue, name: string,
     definePropertyCWE(ctx, this, name, toJS(ctx, prop))
 
 func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, tname: string,
-                 ctor: JSCFunction, funcs: JSFunctionList, nimt: pointer,
-                 parent: JSClassID, asglobal: bool, nointerface: bool,
-                 finalizer: proc(val: JSValue),
-                 namespace: JSValue): JSClassID {.discardable.} =
+    ctor: JSCFunction, funcs: JSFunctionList, nimt: pointer, parent: JSClassID,
+    asglobal: bool, nointerface: bool, finalizer: proc(val: JSValue),
+    namespace: JSValue, errid: Opt[JSErrorEnum]): JSClassID {.discardable.} =
   let rt = JS_GetRuntime(ctx)
   discard JS_NewClassID(addr result)
   var ctxOpaque = ctx.getOpaque()
@@ -339,6 +384,8 @@ func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, tname: string,
     JS_FreeValue(ctx, global)
   let jctor = ctx.newJSCFunction($cdef.class_name, ctor, 0, JS_CFUNC_constructor)
   JS_SetConstructor(ctx, jctor, proto)
+  if errid.isSome:
+    ctx.getOpaque().err_ctors[errid.get] = JS_DupValue(ctx, jctor)
   ctxOpaque.ctors[result] = JS_DupValue(ctx, jctor)
   if not nointerface:
     if JS_IsNull(namespace):
@@ -362,41 +409,41 @@ func getMinArgs(params: seq[FuncParam]): int =
         return i
   return params.len
 
-func fromJSInt[T: SomeInteger](ctx: JSContext, val: JSValue): Option[T] =
+func fromJSInt[T: SomeInteger](ctx: JSContext, val: JSValue): Opt[T] =
   when T is int:
     # Always int32, so we don't risk 32-bit only breakage.
     # If int64 is needed, specify it explicitly.
     var ret: int32
     if JS_ToInt32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(int(ret))
+      return err()
+    return ok(int(ret))
   elif T is uint:
     var ret: uint32
     if JS_ToUint32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(uint(ret))
+      return err()
+    return ok(uint(ret))
   elif T is int32:
     var ret: int32
     if JS_ToInt32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(ret)
+      return err()
+    return ok(ret)
   elif T is int64:
     var ret: int64
     if JS_ToInt64(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(ret)
+      return err()
+    return ok(ret)
   elif T is uint32:
     var ret: uint32
     if JS_ToUint32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(ret)
+      return err()
+    return ok(ret)
   elif T is uint64:
     var ret: uint32
     if JS_ToUint32(ctx, addr ret, val) < 0:
-      return none(T)
-    return some(cast[uint64](ret))
+      return err()
+    return ok(cast[uint64](ret))
 
-proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T]
+proc fromJS*[T](ctx: JSContext, val: JSValue): Opt[T]
 
 macro len(t: type tuple): int =
   let i = t.getType()[1].len - 1 # - tuple
@@ -406,182 +453,183 @@ macro fromJSTupleBody(a: tuple) =
   let len = a.getType().len - 1
   let done = ident("done")
   result = newStmtList(quote do:
-    var `done`: Option[bool])
+    var `done`: Opt[bool])
   for i in 0..<len:
     result.add(quote do:
       let next = JS_Call(ctx, next_method, it, 0, nil)
       if JS_IsException(next):
-        return none(T)
+        return err()
       defer: JS_FreeValue(ctx, next)
       let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
       if JS_IsException(doneVal):
-        return none(T)
+        return err()
       defer: JS_FreeValue(ctx, doneVal)
       `done` = fromJS[bool](ctx, doneVal)
       if `done`.isnone: # exception
-        return none(T)
+        return err()
       if `done`.get:
         JS_ThrowTypeError(ctx, "Too few arguments in sequence (got %d, expected %d)", `i`, `len`)
-        return none(T)
+        return err()
       let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().value)
       if JS_IsException(valueVal):
-        return none(T)
+        return err()
       defer: JS_FreeValue(ctx, valueVal)
       let genericRes = fromJS[typeof(result.get[`i`])](ctx, valueVal)
-      if genericRes.isnone: # exception
-        return none(T)
+      if genericRes.isErr: # exception
+        return err()
       `a`[`i`] = genericRes.get
     )
     if i == len - 1:
       result.add(quote do:
         let next = JS_Call(ctx, next_method, it, 0, nil)
         if JS_IsException(next):
-          return none(T)
+          return err()
         defer: JS_FreeValue(ctx, next)
         let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
         `done` = fromJS[bool](ctx, doneVal)
         if `done`.isnone: # exception
-          return none(T)
+          return err()
         var i = `i`
         # we're emulating a sequence, so we must query all remaining parameters too:
         while not `done`.get:
           inc i
           let next = JS_Call(ctx, next_method, it, 0, nil)
           if JS_IsException(next):
-            return none(T)
+            return err()
           defer: JS_FreeValue(ctx, next)
           let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
           if JS_IsException(doneVal):
-            return none(T)
+            return err()
           defer: JS_FreeValue(ctx, doneVal)
           `done` = fromJS[bool](ctx, doneVal)
           if `done`.isnone: # exception
-            return none(T)
+            return err()
           if `done`.get:
             JS_ThrowTypeError(ctx, "Too many arguments in sequence (got %d, expected %d)", i, `len`)
-            return none(T)
+            return err()
           JS_FreeValue(ctx, JS_GetProperty(ctx, next, ctx.getOpaque().value))
       )
 
-proc fromJSTuple[T: tuple](ctx: JSContext, val: JSValue): Option[T] =
+proc fromJSTuple[T: tuple](ctx: JSContext, val: JSValue): Opt[T] =
   let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().sym_iterator)
   if JS_IsException(itprop):
-    return none(T)
+    return err()
   defer: JS_FreeValue(ctx, itprop)
   let it = JS_Call(ctx, itprop, val, 0, nil)
   if JS_IsException(it):
-    return none(T)
+    return err()
   defer: JS_FreeValue(ctx, it)
   let next_method = JS_GetProperty(ctx, it, ctx.getOpaque().next)
   if JS_IsException(next_method):
-    return none(T)
+    return err()
   defer: JS_FreeValue(ctx, next_method)
   var x: T
   fromJSTupleBody(x)
-  return some(x)
+  return ok(x)
 
-proc fromJSSeq[T](ctx: JSContext, val: JSValue): Option[seq[T]] =
+proc fromJSSeq[T](ctx: JSContext, val: JSValue): Opt[seq[T]] =
   let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().sym_iterator)
   if JS_IsException(itprop):
-    return none(seq[T])
+    return err()
   defer: JS_FreeValue(ctx, itprop)
   let it = JS_Call(ctx, itprop, val, 0, nil)
   if JS_IsException(it):
-    return none(seq[T])
+    return err()
   defer: JS_FreeValue(ctx, it)
   let next_method = JS_GetProperty(ctx, it, ctx.getOpaque().next)
   if JS_IsException(next_method):
-    return none(seq[T])
+    return err()
   defer: JS_FreeValue(ctx, next_method)
-  result = some(newSeq[T]())
+  result = ok(newSeq[T]())
   while true:
     let next = JS_Call(ctx, next_method, it, 0, nil)
     if JS_IsException(next):
-      return none(seq[T])
+      return err()
     defer: JS_FreeValue(ctx, next)
     let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
     if JS_IsException(doneVal):
-      return none(seq[T])
+      return err()
     defer: JS_FreeValue(ctx, doneVal)
     let done = fromJS[bool](ctx, doneVal)
     if done.isnone: # exception
-      return none(seq[T])
+      return err()
     if done.get:
       break
     let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().value)
     if JS_IsException(valueVal):
-      return none(seq[T])
+      return err()
     defer: JS_FreeValue(ctx, valueVal)
     let genericRes = fromJS[typeof(result.get[0])](ctx, valueVal)
     if genericRes.isnone: # exception
-      return none(seq[T])
+      return err()
     result.get.add(genericRes.get)
 
-proc fromJSSet[T](ctx: JSContext, val: JSValue): Option[set[T]] =
+proc fromJSSet[T](ctx: JSContext, val: JSValue): Opt[set[T]] =
   let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().sym_iterator)
   if JS_IsException(itprop):
-    return none(set[T])
+    return err()
   defer: JS_FreeValue(ctx, itprop)
   let it = JS_Call(ctx, itprop, val, 0, nil)
   if JS_IsException(it):
-    return none(set[T])
+    return err()
   defer: JS_FreeValue(ctx, it)
   let next_method = JS_GetProperty(ctx, it, ctx.getOpaque().next)
   if JS_IsException(next_method):
-    return none(set[T])
+    return err()
   defer: JS_FreeValue(ctx, next_method)
   var s: set[T]
   result = some(s)
   while true:
     let next = JS_Call(ctx, next_method, it, 0, nil)
     if JS_IsException(next):
-      return none(set[T])
+      return err()
     defer: JS_FreeValue(ctx, next)
     let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done)
     if JS_IsException(doneVal):
-      return none(set[T])
+      return err()
     defer: JS_FreeValue(ctx, doneVal)
     let done = fromJS[bool](ctx, doneVal)
     if done.isnone: # exception
-      return none(set[T])
+      return err()
     if done.get:
       break
     let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().value)
     if JS_IsException(valueVal):
-      return none(set[T])
+      return err()
     defer: JS_FreeValue(ctx, valueVal)
     let genericRes = fromJS[typeof(result.get.items)](ctx, valueVal)
     if genericRes.isnone: # exception
-      return none(set[T])
+      return err()
     result.get.incl(genericRes.get)
 
-proc fromJSTable[A, B](ctx: JSContext, val: JSValue): Option[Table[A, B]] =
+proc fromJSTable[A, B](ctx: JSContext, val: JSValue): Opt[Table[A, B]] =
   var ptab: ptr JSPropertyEnum
   var plen: uint32
   let flags = cint(JS_GPN_STRING_MASK)
   if JS_GetOwnPropertyNames(ctx, addr ptab, addr plen, val, flags) == -1:
     # exception
-    return none(Table[A, B])
+    return err()
   defer:
     for i in 0..<plen:
       let prop = cast[ptr JSPropertyEnum](cast[int](ptab) + sizeof(ptab[]) * int(i))
       JS_FreeAtom(ctx, prop.atom)
     js_free(ctx, ptab)
-  result = some(Table[A, B]())
+  var res = Table[A, B]()
   for i in 0..<plen:
     let prop = cast[ptr JSPropertyEnum](cast[int](ptab) + sizeof(ptab[]) * int(i))
     let atom = prop.atom
     let k = JS_AtomToValue(ctx, atom)
     defer: JS_FreeValue(ctx, k)
     let kn = fromJS[A](ctx, k)
-    if kn.isnone: # exception
-      return none(Table[A, B])
+    if kn.isErr: # exception
+      return err()
     let v = JS_GetProperty(ctx, val, atom)
     defer: JS_FreeValue(ctx, v)
     let vn = fromJS[B](ctx, v)
-    if vn.isnone: # exception
-      return none(Table[A, B])
+    if vn.isErr: # exception
+      return err()
     result.get[kn.get] = vn.get
+  return ok(res)
 
 proc toJS*(ctx: JSContext, s: cstring): JSValue
 proc toJS*(ctx: JSContext, s: string): JSValue
@@ -595,17 +643,21 @@ proc toJS*(ctx: JSContext, n: uint64): JSValue
 proc toJS(ctx: JSContext, n: SomeFloat): JSValue
 proc toJS*(ctx: JSContext, b: bool): JSValue
 proc toJS[U, V](ctx: JSContext, t: Table[U, V]): JSValue
-proc toJS(ctx: JSContext, opt: Option): JSValue
+proc toJS*(ctx: JSContext, opt: Option): JSValue
+proc toJS[T, E](ctx: JSContext, opt: Result[T, E]): JSValue
 proc toJS(ctx: JSContext, s: seq): JSValue
 proc toJS(ctx: JSContext, e: enum): JSValue
 proc toJS(ctx: JSContext, j: JSValue): JSValue
 proc toJS[T](ctx: JSContext, promise: Promise[T]): JSValue
+proc toJS[T, E](ctx: JSContext, promise: Promise[Result[T, E]]): JSValue
 proc toJS(ctx: JSContext, promise: EmptyPromise): JSValue
+proc toJSRefObj(ctx: JSContext, obj: ref object): JSValue
 proc toJS*(ctx: JSContext, obj: ref object): JSValue
+proc toJS*(ctx: JSContext, err: JSError): JSValue
 
 # ew....
-proc fromJSFunction1[T, U](ctx: JSContext, val: JSValue): Option[proc(x: U): Option[T]] =
-  return some(proc(x: U): Option[T] =
+proc fromJSFunction1[T, U](ctx: JSContext, val: JSValue): Opt[proc(x: U): Opt[T]] =
+  return ok(proc(x: U): Opt[T] =
     var arg1 = toJS(ctx, x)
     let ret = JS_Call(ctx, val, JS_UNDEFINED, 1, addr arg1)
     return fromJS[T](ctx, ret)
@@ -617,7 +669,7 @@ macro unpackReturnType(f: typed) =
     x = x[1].getTypeImpl()
   let params = x.findChild(it.kind == nnkFormalParams)
   let rv = params[0]
-  assert rv[0].strVal == "Option"
+  doAssert rv[0].strVal == "Opt"
   let rvv = rv[1]
   result = quote do: `rvv`
 
@@ -631,7 +683,7 @@ macro unpackArg0(f: typed) =
   let rvv = rv[1]
   result = quote do: `rvv`
 
-proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] =
+proc fromJS*[T](ctx: JSContext, val: JSValue): Opt[T] =
   when T is string:
     return toString(ctx, val)
   elif T is char:
@@ -653,14 +705,22 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] =
     return some(r)
   elif T is (proc):
     return fromJSFunction1[typeof(unpackReturnType(T)), typeof(unpackArg0(T))](ctx, val)
-  elif typeof(result.unsafeGet) is Option: # unwrap
+  elif T is Option: # convert
     if JS_IsUndefined(val):
       #TODO what about null?
-      return none(T)
+      return err()
     let res = fromJS[typeof(result.get.get)](ctx, val)
     if res.isNone:
-      return none(T)
-    return some(res)
+      return err()
+    return ok(some(res.get))
+  elif typeof(result).valType is Opt: # unwrap
+    if JS_IsUndefined(val):
+      #TODO what about null?
+      return err()
+    let res = fromJS[typeof(result.get.get)](ctx, val)
+    if res.isNone:
+      return err()
+    return ok(res)
   elif T is seq:
     return fromJSSeq[typeof(result.get.items)](ctx, val)
   elif T is set:
@@ -670,11 +730,11 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] =
   elif T is bool:
     let ret = JS_ToBool(ctx, val)
     if ret == -1: # exception
-      return none(T)
+      return err()
     if ret == 0:
-      return some(false)
-    return some(true)
-  elif typeof(result.get) is Table:
+      return ok(false)
+    return ok(true)
+  elif typeof(result).valType is Table:
     return fromJSTable[typeof(result.get.keys), typeof(result.get.values)](ctx, val)
   elif T is SomeInteger:
     if JS_IsNumber(val):
@@ -683,50 +743,50 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] =
     if JS_IsNumber(val):
       var f64: float64
       if JS_ToFloat64(ctx, addr f64, val) < 0:
-        return none(T)
-      return some(cast[T](f64))
+        return err()
+      return ok(cast[T](f64))
   elif T is enum:
     #TODO implement enum handling...
     if JS_IsException(val):
-      return none(T)
+      return err()
     let s = toString(ctx, val)
-    if s.isnone:
-      return none(T)
+    if s.isErr:
+      return err()
     try:
-      return some(parseEnum[T](s.get))
+      return ok(parseEnum[T](s.get))
     except ValueError:
       JS_ThrowTypeError(ctx, "`%s' is not a valid value for enumeration %s", cstring(s.get), $T)
-      return none(T)
+      return err()
   elif T is JSValue:
-    return some(val)
-  elif T is object:
-    doAssert false, "Dictionary case has not been implemented yet!"
-    #TODO TODO TODO implement dictionary case
-    return none(T)
-  else:
+    return ok(val)
+  elif T is ref object:
     if JS_IsException(val):
-      return none(T)
+      return err()
     let op = cast[T](getOpaque(ctx, val, $T))
     if op == nil:
       JS_ThrowTypeError(ctx, "Value is not an instance of %s", $T)
-      return none(T)
-    return some(op)
+      return err()
+    return ok(op)
+  else:
+    static:
+      doAssert false
 
 const JS_ATOM_TAG_INT = cuint(1u32 shl 31)
 
 func JS_IsNumber(v: JSAtom): JS_BOOL =
   return (cast[cuint](v) and JS_ATOM_TAG_INT) != 0
 
-func fromJS[T: string|uint32](ctx: JSContext, atom: JSAtom): Option[T] =
+func fromJS[T: string|uint32](ctx: JSContext, atom: JSAtom): Opt[T] =
   when T is SomeNumber:
     if JS_IsNumber(atom):
-      return some(T(cast[uint32](atom) and (not JS_ATOM_TAG_INT)))
+      return ok(T(cast[uint32](atom) and (not JS_ATOM_TAG_INT)))
   else:
     let val = JS_AtomToValue(ctx, atom)
     return fromJS[T](ctx, val)
 
-proc getJSFunction*[T, U](ctx: JSContext, val: JSValue): Option[(proc(x: T): Option[U])] =
-  return fromJS[(proc(x: T): Option[U])](ctx, val)
+proc getJSFunction*[T, U](ctx: JSContext, val: JSValue):
+    Opt[(proc(x: T): Opt[U])] =
+  return fromJS[(proc(x: T): Opt[U])](ctx, val)
 
 proc toJS*(ctx: JSContext, s: cstring): JSValue =
   return JS_NewString(ctx, s)
@@ -770,11 +830,23 @@ proc toJS[U, V](ctx: JSContext, t: Table[U, V]): JSValue =
       setProperty(ctx, obj, k, toJS(ctx, v))
   return obj
 
-proc toJS(ctx: JSContext, opt: Option): JSValue =
+proc toJS*(ctx: JSContext, opt: Option): JSValue =
   if opt.isSome:
     return toJS(ctx, opt.get)
   return JS_NULL
 
+proc toJS[T, E](ctx: JSContext, opt: Result[T, E]): JSValue =
+  if opt.isSome:
+    when not (T is void):
+      return toJS(ctx, opt.get)
+    return JS_UNDEFINED
+  else:
+    when not (E is void):
+      let res = toJS(ctx, opt.error)
+      if not JS_IsNull(res):
+        return JS_Throw(ctx, res)
+    return JS_NULL
+
 proc toJS(ctx: JSContext, s: seq): JSValue =
   let a = JS_NewArray(ctx)
   if not JS_IsException(a):
@@ -794,7 +866,7 @@ proc getTypePtr[T](x: T): pointer =
   else:
     return getTypeInfo(x)
 
-proc toJS*(ctx: JSContext, obj: ref object): JSValue =
+proc toJSRefObj(ctx: JSContext, obj: ref object): JSValue =
   if obj == nil:
     return JS_NULL
   let op = JS_GetRuntime(ctx).getOpaque()
@@ -807,6 +879,9 @@ proc toJS*(ctx: JSContext, obj: ref object): JSValue =
   setOpaque(ctx, jsObj, obj)
   return jsObj
 
+proc toJS*(ctx: JSContext, obj: ref object): JSValue =
+  return toJSRefObj(ctx, obj)
+
 proc toJS(ctx: JSContext, e: enum): JSValue =
   return toJS(ctx, $e)
 
@@ -840,15 +915,42 @@ proc toJS[T](ctx: JSContext, promise: Promise[T]): JSValue =
     JS_FreeValue(ctx, resolving_funcs[1]))
   return jsPromise
 
-type
-  JS_Error = object of CatchableError
+proc toJS[T, E](ctx: JSContext, promise: Promise[Result[T, E]]): JSValue =
+  var resolving_funcs: array[2, JSValue]
+  let jsPromise = JS_NewPromiseCapability(ctx, addr resolving_funcs[0])
+  if JS_IsException(jsPromise):
+    return JS_EXCEPTION
+  promise.then(proc(x: Result[T, E]) =
+    if x.isOk:
+      let x = when T is void:
+        JS_UNDEFINED
+      else:
+        toJS(ctx, x.get)
+      let res = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, unsafeAddr x)
+      JS_FreeValue(ctx, res)
+      JS_FreeValue(ctx, x)
+    else: # err
+      let x = when E is void:
+        JS_UNDEFINED
+      else:
+        toJS(ctx, x.get)
+      let res = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, unsafeAddr x)
+      JS_FreeValue(ctx, res)
+      JS_FreeValue(ctx, x)
+    JS_FreeValue(ctx, resolving_funcs[0])
+    JS_FreeValue(ctx, resolving_funcs[1]))
+  return jsPromise
 
-  JS_SyntaxError* = object of JS_Error
-  JS_TypeError* = object of JS_Error
-  JS_ReferenceError* = object of JS_Error
-  JS_RangeError* = object of JS_Error
-  JS_InternalError* = object of JS_Error
+proc toJS*(ctx: JSContext, err: JSError): JSValue =
+  if err.e notin QuickJSErrors:
+    return toJSRefObj(ctx, err)
+  var msg = toJS(ctx, err.message)
+  if JS_IsException(msg):
+    return msg
+  let ctor = ctx.getOpaque().err_ctors[err.e]
+  return JS_CallConstructor(ctx, ctor, 1, addr msg)
 
+type
   JSFuncGenerator = object
     t: BoundFunctionType
     original: NimNode
@@ -1020,7 +1122,7 @@ template fromJS_or_die*(t, ctx, val, ev, dl: untyped): untyped =
     if JS_IsException(val):
       return ev
   let x = fromJS[t](ctx, val)
-  if x.isnone:
+  if x.isNone:
     break dl
   x.get
 
@@ -1248,7 +1350,7 @@ proc newJSProcBody(gen: var JSFuncGenerator, isva: bool): NimNode =
   var ma = gen.actualMinArgs
   result = newStmtList()
   if isva:
-    result.add(quote do: 
+    result.add(quote do:
       if argc < `ma`:
         return JS_ThrowTypeError(ctx, "At least %d arguments required, " &
           "but only %d passed", `ma`, argc)
@@ -1288,7 +1390,7 @@ proc newJSProc(gen: var JSFuncGenerator, params: openArray[NimNode], isva = true
 # declared on the parent function.
 # Note: this causes the entire nim function body to be inlined inside the JS
 # interface function.
-#TODO: implement actual inlining (so we can e.g. get rid of JS_Error, use format strings, etc.)
+#TODO: remove this.
 macro JS_ERR*(a: typed, b: string) =
   result = quote do:
     block when_js:
@@ -1379,7 +1481,7 @@ proc rewriteExceptions(gen: var JSFuncGenerator, errors: var seq[string], node:
         errors.add(c[1].strVal)
     elif c.len > 0:
       gen.rewriteExceptions(errors, c)
-  
+
 proc rewriteExceptions(gen: var JSFuncGenerator) =
   let ostmts = gen.original.findChild(it.kind == nnkStmtList)
   var errors: seq[string]
@@ -1700,10 +1802,10 @@ type
     name*: string
     fun*: JSCFunction
 
-macro registerType*(ctx: typed, t: typed, parent: JSClassID = 0, asglobal =
-                   false, nointerface = false, name: static string = "",
-                   extra_getset: static openarray[TabGetSet] = [],
-                   namespace: JSValue = JS_NULL): JSClassID =
+macro registerType*(ctx: typed, t: typed, parent: JSClassID = 0,
+    asglobal = false, nointerface = false, name: static string = "",
+    extra_getset: static openarray[TabGetSet] = [],
+    namespace: JSValue = JS_NULL, errid = opt(JSErrorEnum)): JSClassID =
   result = newStmtList()
   let tname = t.strVal # the nim type's name.
   let name = if name == "": tname else: name # possibly a different name, e.g. Buffer for Container
@@ -1868,7 +1970,7 @@ static JSClassDef """, `cdname`, """ = {
     var x: `t`
     new(x, nim_finalize_for_js)
     `ctx`.newJSClass(`classDef`, `tname`, `sctr`, `tabList`, getTypePtr(x),
-      `parent`, `asglobal`, `nointerface`, `finName`, `namespace`)
+      `parent`, `asglobal`, `nointerface`, `finName`, `namespace`, `errid`)
   )
   result.add(newBlockStmt(endstmts))
 
diff --git a/src/types/url.nim b/src/types/url.nim
index cba83adb..9e786347 100644
--- a/src/types/url.nim
+++ b/src/types/url.nim
@@ -862,7 +862,7 @@ func serialize*(url: Option[Url], excludefragment = false): string =
 func equals*(a, b: Url, excludefragment = false): bool =
   return a.serialize(excludefragment) == b.serialize(excludefragment)
 
-func `$`*(url: Url): string {.jsfunc, inline.} = url.serialize()
+func `$`*(url: URL): string {.jsfunc.} = url.serialize()
 
 func `$`*(path: UrlPath): string {.inline.} = path.serialize()
 
diff --git a/src/utils/opt.nim b/src/utils/opt.nim
index caba236e..3766516b 100644
--- a/src/utils/opt.nim
+++ b/src/utils/opt.nim
@@ -2,18 +2,26 @@
 
 type
   Result*[T, E] = object
-    when (T is void) and (E is void):
-      has: bool
-    else:
-      case has: bool
+    when E is void and T is void: # weirdness
+      has*: bool
+    elif E is void and not (T is void): # opt
+      case has*: bool
+      of true:
+        val*: T
+      else:
+        discard
+    elif not (E is void) and T is void: # err
+      case has*: bool
+      of true:
+        discard
+      else:
+        ex*: E
+    else: # result
+      case has*: bool
       of true:
-        when not (T is void):
-          val: T
+        val*: T
       else:
-        when not (E is void):
-          ex: E
-        else:
-          discard
+        ex*: E
 
   Opt*[T] = Result[T, void]
 
@@ -31,9 +39,12 @@ template ok*[T](x: T): auto =
 template ok*(): auto =
   ok(typeof(result))
 
-template ok*[T, E](res: var Result[T, E], x: T): Result[T, E] =
+template ok*[T, E](res: var Result[T, E], x: T) =
   res = Result[T, E](has: true, val: x)
 
+template ok*[E](res: var Result[void, E]) =
+  res = Result[void, E](has: true)
+
 template err*[T, E](t: type Result[T, E], e: E): Result[T, E] =
   Result[T, E](has: false, ex: e)
 
@@ -44,22 +55,39 @@ template err*(): auto =
   err(typeof(result))
 
 template err*[T, E](res: var Result[T, E], e: E) =
-  res.ex = e
+  res = Result[T, E](has: false, ex: e)
+
+template err*[T, E](res: var Result[T, E]) =
+  res = Result[T, E](has: false)
 
 template err*[E](e: E): auto =
   err(typeof(result), e)
 
+template opt*[T](v: T): auto =
+  ok(Opt[T], v)
+
+template opt*(t: typedesc): auto =
+  err(Result[t, void])
+
 template isOk*(res: Result): bool = res.has
 template isErr*(res: Result): bool = not res.has
 template isSome*(res: Result): bool = res.isOk
 template isNone*(res: Result): bool = res.isErr
-template get*[T, E](res: Result[T, E]): T = res.val
-template error*[T, E](res: Result[T, E]): E = res.ex
+func get*[T, E](res: Result[T, E]): T {.inline.} = res.val
+func get*[T, E](res: var Result[T, E]): var T = res.val
+func get*[T, E](res: Result[T, E], v: T): T =
+  if res.has:
+    res.val
+  else:
+    v
+func error*[T, E](res: Result[T, E]): E {.inline.} = res.ex
+template valType*[T, E](res: type Result[T, E]): auto = T
+template errType*[T, E](res: type Result[T, E]): auto = E
 
 func isSameErr[T, E, F](a: type Result[T, E], b: type F): bool =
   return E is F
 
-template `?`*[T, E](res: Result[T, E]): T =
+template `?`*[T, E](res: Result[T, E]): auto =
   let x = res # for when res is a funcall
   if x.has:
     when not (T is void):
@@ -73,13 +101,3 @@ template `?`*[T, E](res: Result[T, E]): T =
       return err(x.error)
     else:
       return err()
-
-template `?`*[E](res: Err[E]) =
-  let x = res # for when res is a funcall
-  if not x.has:
-    when typeof(result) is Err[E]:
-      return x
-    elif isSameErr(typeof(result), E):
-      return err(x.error)
-    else:
-      return err()
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index a988c976..6d3c6f08 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -1074,9 +1074,9 @@ func twidth*(s: string, w: int): int =
 func breaksWord*(r: Rune): bool =
   return not (r.isDigitAscii() or r.width() == 0 or r.isAlpha())
 
-type BoundaryFunction* = proc(x: Rune): Option[bool]
+type BoundaryFunction* = proc(x: Rune): Opt[bool]
 
-proc breaksWord*(r: Rune, check: Option[BoundaryFunction]): bool =
+proc breaksWord*(r: Rune, check: Opt[BoundaryFunction]): bool =
   if check.isSome:
     let f = check.get()
     let v = f(r)