summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md4
-rw-r--r--lib/std/jsfetch.nim198
-rw-r--r--lib/std/jsformdata.nim65
-rw-r--r--lib/std/jsheaders.nim83
-rw-r--r--tools/kochdocs.nim2
5 files changed, 351 insertions, 1 deletions
diff --git a/changelog.md b/changelog.md
index 6081c90b1..3e9ed6276 100644
--- a/changelog.md
+++ b/changelog.md
@@ -192,6 +192,10 @@ provided by the operating system.
 
 - `std/options` changed `$some(3)` to `"some(3)"` instead of `"Some(3)"`
   and `$none(int)` to `"none(int)"` instead of `"None[int]"`.
+- Added `std/jsfetch` module [Fetch](https://developer.mozilla.org/docs/Web/API/Fetch_API) wrapper for JavaScript target.
+- Added `std/jsheaders` module [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) wrapper for JavaScript target.
+- Added `std/jsformdata` module [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) wrapper for JavaScript target.
+
 
 - `system.addEscapedChar` now renders `\r` as `\r` instead of `\c`, to be compatible
   with most other languages.
diff --git a/lib/std/jsfetch.nim b/lib/std/jsfetch.nim
new file mode 100644
index 000000000..2ff894985
--- /dev/null
+++ b/lib/std/jsfetch.nim
@@ -0,0 +1,198 @@
+## - Fetch for the JavaScript target: https://developer.mozilla.org/docs/Web/API/Fetch_API
+## .. Note:: jsfetch is Experimental. jsfetch module requires `-d:nimExperimentalJsfetch`
+when not defined(js):
+  {.fatal: "Module jsfetch is designed to be used with the JavaScript backend.".}
+
+when defined(nimExperimentalJsfetch) or defined(nimdoc):
+  import std/[asyncjs, jsheaders, jsformdata]
+  from std/httpcore import HttpMethod
+  from std/jsffi import JsObject
+
+  type
+    FetchOptions* = ref object of JsRoot  ## Options for Fetch API.
+      keepalive*: bool
+      metod* {.importjs: "method".}: cstring
+      body*, integrity*, referrer*, mode*, credentials*, cache*, redirect*, referrerPolicy*: cstring
+
+    FetchModes* = enum  ## Mode options.
+      fmCors = "cors"
+      fmNoCors = "no-cors"
+      fmSameOrigin = "same-origin"
+
+    FetchCredentials* = enum  ## Credential options. See https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
+      fcInclude = "include"
+      fcSameOrigin = "same-origin"
+      fcOmit = "omit"
+
+    FetchCaches* = enum  ## https://developer.mozilla.org/docs/Web/API/Request/cache
+      fchDefault = "default"
+      fchNoStore = "no-store"
+      fchReload = "reload"
+      fchNoCache = "no-cache"
+      fchForceCache = "force-cache"
+
+    FetchRedirects* = enum  ## Redirects options.
+      frFollow = "follow"
+      frError = "error"
+      frManual = "manual"
+
+    FetchReferrerPolicies* = enum  ## Referrer Policy options.
+      frpNoReferrer = "no-referrer"
+      frpNoReferrerWhenDowngrade = "no-referrer-when-downgrade"
+      frpOrigin = "origin"
+      frpOriginWhenCrossOrigin = "origin-when-cross-origin"
+      frpUnsafeUrl = "unsafe-url"
+
+    Body* = ref object of JsRoot  ## https://developer.mozilla.org/en-US/docs/Web/API/Body
+      bodyUsed*: bool
+
+    Response* = ref object of JsRoot  ## https://developer.mozilla.org/en-US/docs/Web/API/Response
+      bodyUsed*, ok*, redirected*: bool
+      typ* {.importjs: "type".}: cstring
+      url*, statusText*: cstring
+      status*: cint
+      headers*: Headers
+      body*: Body
+
+    Request* = ref object of JsRoot  ## https://developer.mozilla.org/en-US/docs/Web/API/Request
+      bodyUsed*, ok*, redirected*: bool
+      typ* {.importjs: "type".}: cstring
+      url*, statusText*: cstring
+      status*: cint
+      headers*: Headers
+      body*: Body
+
+
+  func newResponse*(body: cstring | FormData): Response {.importjs: "(new Response(#))".}
+    ## Constructor for `Response`. This does *not* call `fetch()`. Same as `new Response()`.
+
+  func newRequest*(url: cstring): Request {.importjs: "(new Request(#))".}
+    ## Constructor for `Request`. This does *not* call `fetch()`. Same as `new Request()`.
+
+  func clone*(self: Response | Request): Response {.importjs: "#.$1()".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Response/clone
+
+  proc text*(self: Response): Future[cstring] {.importjs: "#.$1()".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Body/text
+
+  proc json*(self: Response): Future[JsObject] {.importjs: "#.$1()".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Body/json
+
+  proc formData*(self: Body): Future[FormData] {.importjs: "#.$1()".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Body/formData
+
+  proc unsafeNewFetchOptions*(metod, body, mode, credentials, cache, referrerPolicy: cstring;
+      keepalive: bool; redirect = "follow".cstring; referrer = "client".cstring; integrity = "".cstring): FetchOptions {.importjs:
+      "{method: #, body: #, mode: #, credentials: #, cache: #, referrerPolicy: #, keepalive: #, redirect: #, referrer: #, integrity: #}".}
+    ## .. Warning:: Unsafe `newfetchOptions`.
+
+  func newfetchOptions*(metod: HttpMethod; body: cstring;
+      mode: FetchModes; credentials: FetchCredentials; cache: FetchCaches; referrerPolicy: FetchReferrerPolicies;
+      keepalive: bool; redirect = frFollow; referrer = "client".cstring; integrity = "".cstring): FetchOptions =
+    ## Constructor for `FetchOptions`.
+    result = FetchOptions(
+      body: body, mode: $mode, credentials: $credentials, cache: $cache, referrerPolicy: $referrerPolicy,
+      keepalive: keepalive, redirect: $redirect, referrer: referrer, integrity: integrity,
+      metod: (case metod
+        of HttpHead:   "HEAD".cstring
+        of HttpGet:    "GET".cstring
+        of HttpPost:   "POST".cstring
+        of HttpPut:    "PUT".cstring
+        of HttpDelete: "DELETE".cstring
+        of HttpPatch:  "PATCH".cstring
+        else:          "GET".cstring
+      )
+    )
+
+  proc fetch*(url: cstring | Request): Future[Response] {.importjs: "$1(#)".}
+    ## `fetch()` API, simple `GET` only, returns a `Future[Response]`.
+
+  proc fetch*(url: cstring | Request; options: FetchOptions): Future[Response] {.importjs: "$1(#, #)".}
+    ## `fetch()` API that takes a `FetchOptions`, returns a `Future[Response]`.
+
+  func toCstring*(self: Request | Response | Body | FetchOptions): cstring {.importjs: "JSON.stringify(#)".}
+
+  func `$`*(self: Request | Response | Body | FetchOptions): string = $toCstring(self)
+
+
+runnableExamples("-d:nimExperimentalJsfetch -r:off"):
+  import std/[asyncjs, jsconsole, jsheaders, jsformdata]
+  from std/httpcore import HttpMethod
+  from std/jsffi import JsObject
+  from std/sugar import `=>`
+
+  block:
+    let options0: FetchOptions = unsafeNewFetchOptions(
+      metod = "POST".cstring,
+      body = """{"key": "value"}""".cstring,
+      mode = "no-cors".cstring,
+      credentials = "omit".cstring,
+      cache = "no-cache".cstring,
+      referrerPolicy = "no-referrer".cstring,
+      keepalive = false,
+      redirect = "follow".cstring,
+      referrer = "client".cstring,
+      integrity = "".cstring
+    )
+    assert options0.keepalive == false
+    assert options0.metod == "POST".cstring
+    assert options0.body == """{"key": "value"}""".cstring
+    assert options0.mode == "no-cors".cstring
+    assert options0.credentials == "omit".cstring
+    assert options0.cache == "no-cache".cstring
+    assert options0.referrerPolicy == "no-referrer".cstring
+    assert options0.redirect == "follow".cstring
+    assert options0.referrer == "client".cstring
+    assert options0.integrity == "".cstring
+
+  block:
+    let options1: FetchOptions = newFetchOptions(
+      metod =  HttpPost,
+      body = """{"key": "value"}""".cstring,
+      mode = fmNoCors,
+      credentials = fcOmit,
+      cache = fchNoCache,
+      referrerPolicy = frpNoReferrer,
+      keepalive = false,
+      redirect = frFollow,
+      referrer = "client".cstring,
+      integrity = "".cstring
+    )
+    assert options1.keepalive == false
+    assert options1.metod == $HttpPost
+    assert options1.body == """{"key": "value"}""".cstring
+    assert options1.mode == $fmNoCors
+    assert options1.credentials == $fcOmit
+    assert options1.cache == $fchNoCache
+    assert options1.referrerPolicy == $frpNoReferrer
+    assert options1.redirect == $frFollow
+    assert options1.referrer == "client".cstring
+    assert options1.integrity == "".cstring
+
+  block:
+    let response: Response = newResponse(body = "-. .. --".cstring)
+    let request: Request = newRequest(url = "http://nim-lang.org".cstring)
+
+  if not defined(nodejs):
+    block:
+      proc doFetch(): Future[Response] {.async.} =
+        fetch "https://httpbin.org/get".cstring
+
+      proc example() {.async.} =
+        let response: Response = await doFetch()
+        assert response.ok
+        assert response.status == 200.cint
+        assert response.headers is Headers
+        assert response.body is Body
+
+      discard example()
+
+    when defined(nimExperimentalAsyncjsThen):
+      block:
+        proc example2 {.async.} =
+          await fetch("https://api.github.com/users/torvalds".cstring)
+            .then((response: Response) => response.json())
+            .then((json: JsObject) => console.log(json))
+            .catch((err: Error) => console.log("Request Failed", err))
+
+        discard example2()
diff --git a/lib/std/jsformdata.nim b/lib/std/jsformdata.nim
new file mode 100644
index 000000000..2728fbdd0
--- /dev/null
+++ b/lib/std/jsformdata.nim
@@ -0,0 +1,65 @@
+## - `FormData` for the JavaScript target: https://developer.mozilla.org/en-US/docs/Web/API/FormData
+when not defined(js):
+  {.fatal: "Module jsformdata is designed to be used with the JavaScript backend.".}
+
+type FormData* = ref object of JsRoot ## FormData API.
+
+func newFormData*(): FormData {.importjs: "new FormData()".}
+
+func add*(self: FormData; name: cstring; value: SomeNumber | bool | cstring) {.importjs: "#.append(#, #)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
+  ## Duplicate keys are allowed and order is preserved.
+
+func add*(self: FormData; name: cstring; value: SomeNumber | bool | cstring, filename: cstring) {.importjs: "#.append(#, #, #)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
+  ## Duplicate keys are allowed and order is preserved.
+
+func delete*(self: FormData; name: cstring) {.importjs: "#.$1(#)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/delete
+  ##
+  ## .. Warning:: Deletes *all items* with the same key name.
+
+func getAll*(self: FormData; name: cstring): seq[cstring] {.importjs: "#.$1(#)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/getAll
+
+func hasKey*(self: FormData; name: cstring): bool {.importjs: "#.has(#)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/has
+
+func keys*(self: FormData): seq[cstring] {.importjs: "Array.from(#.$1())".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/keys
+
+func values*(self: FormData): seq[cstring] {.importjs: "Array.from(#.$1())".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/values
+
+func pairs*(self: FormData): seq[tuple[key, val: cstring]] {.importjs: "Array.from(#.entries())".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries
+
+func put*(self: FormData; name, value, filename: cstring) {.importjs: "#.set(#, #, #)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/set
+
+func `[]=`*(self: FormData; name, value: cstring) {.importjs: "#.set(#, #)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/set
+
+func `[]`*(self: FormData; name: cstring): cstring {.importjs: "#.get(#)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/FormData/get
+
+func clear*(self: FormData) {.importjs:
+  "(() => { const frmdt = #; Array.from(frmdt.keys()).forEach((key) => frmdt.delete(key)) })()".}
+  ## Convenience func to delete all items from `FormData`.
+
+func toCstring*(self: FormData): cstring {.importjs: "JSON.stringify(#)".}
+
+func `$`*(self: FormData): string = $toCstring(self)
+
+func len*(self: FormData): int {.importjs: "Array.from(#.entries()).length".}
+
+
+runnableExamples("-r:off"):
+  let data: FormData = newFormData()
+  data["key0"] = "value0".cstring
+  data.add("key1".cstring, "value1".cstring)
+  data.delete("key1")
+  assert data.hasKey("key0")
+  assert data["key0"] == "value0".cstring
+  data.clear()
+  assert data.len == 0
diff --git a/lib/std/jsheaders.nim b/lib/std/jsheaders.nim
new file mode 100644
index 000000000..a67e54ef7
--- /dev/null
+++ b/lib/std/jsheaders.nim
@@ -0,0 +1,83 @@
+## - HTTP Headers for the JavaScript target: https://developer.mozilla.org/en-US/docs/Web/API/Headers
+when not defined(js):
+  {.fatal: "Module jsheaders is designed to be used with the JavaScript backend.".}
+
+type Headers* = ref object of JsRoot ## HTTP Headers API.
+
+func newHeaders*(): Headers {.importjs: "new Headers()".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/Headers
+
+func add*(self: Headers; key: cstring; value: cstring) {.importjs: "#.append(#, #)".}
+  ## Allows duplicated keys.
+  ## https://developer.mozilla.org/en-US/docs/Web/API/Headers/append
+
+func delete*(self: Headers; key: cstring) {.importjs: "#.$1(#)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/Headers/delete
+  ##
+  ## .. Warning:: Delete *all* items with `key` from the headers, including duplicated keys.
+
+func hasKey*(self: Headers; key: cstring): bool {.importjs: "#.has(#)".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/Headers/has
+
+func keys*(self: Headers): seq[cstring] {.importjs: "Array.from(#.$1())".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/Headers/keys
+
+func values*(self: Headers): seq[cstring] {.importjs: "Array.from(#.$1())".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/Headers/values
+
+func entries*(self: Headers): seq[tuple[key, value: cstring]] {.importjs: "Array.from(#.$1())".}
+  ## https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries
+
+func `[]`*(self: Headers; key: cstring): cstring {.importjs: "#.get(#)".}
+  ## Get *all* items with `key` from the headers, including duplicated values.
+  ## https://developer.mozilla.org/en-US/docs/Web/API/Headers/get
+
+func `[]=`*(self: Headers; key: cstring; value: cstring) {.importjs: "#.set(#, #)".}
+  ## Do *not* allow duplicated keys, overwrites duplicated keys.
+  ## https://developer.mozilla.org/en-US/docs/Web/API/Headers/set
+
+func clear*(self: Headers) {.importjs:
+  "(() => { const header = #; Array.from(header.keys()).forEach((key) => header.delete(key)) })()".}
+  ## Convenience func to delete all items from `Headers`.
+
+func toCstring*(self: Headers): cstring {.importjs: "JSON.stringify(Array.from(#.entries()))".}
+  ## Returns a `cstring` representation of `Headers`.
+
+func `$`*(self: Headers): string = $toCstring(self)
+
+func len*(self: Headers): int {.importjs: "Array.from(#.entries()).length".}
+
+
+runnableExamples("-r:off"):
+
+  block:
+    let header: Headers = newHeaders()
+    header.add("key", "value")
+    assert header.hasKey("key")
+    assert header.keys() == @["key".cstring]
+    assert header.values() == @["value".cstring]
+    assert header["key"] == "value".cstring
+    header["other"] = "another".cstring
+    assert header["other"] == "another".cstring
+    assert header.entries() == @[("key".cstring, "value".cstring), ("other".cstring, "another".cstring)]
+    assert header.toCstring() == """[["key","value"],["other","another"]]""".cstring
+    header.delete("other")
+    assert header.entries() == @[("key".cstring, "value".cstring)]
+    header.clear()
+    assert header.entries() == @[]
+    assert header.len == 0
+
+  block:
+    let header: Headers = newHeaders()
+    header.add("key", "a")
+    header.add("key", "b")  ## Duplicated.
+    header.add("key", "c")  ## Duplicated.
+    assert header["key"] == "a, b, c".cstring
+    header["key"] = "value".cstring
+    assert header["key"] == "value".cstring
+
+  block:
+    let header: Headers = newHeaders()
+    header["key"] = "a"
+    header["key"] = "b"  ## Overwrites.
+    assert header["key"] == "b".cstring
diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim
index 1f9493c8c..530ce8571 100644
--- a/tools/kochdocs.nim
+++ b/tools/kochdocs.nim
@@ -14,7 +14,7 @@ const
   webUploadOutput = "web/upload"
 
 var nimExe*: string
-const allowList = ["jsbigints.nim"]
+const allowList = ["jsbigints.nim", "jsheaders.nim", "jsformdata.nim", "jsfetch.nim"]
 
 template isJsOnly(file: string): bool =
   file.isRelativeTo("lib/js") or