diff options
-rw-r--r-- | changelog.md | 4 | ||||
-rw-r--r-- | lib/std/jsfetch.nim | 198 | ||||
-rw-r--r-- | lib/std/jsformdata.nim | 65 | ||||
-rw-r--r-- | lib/std/jsheaders.nim | 83 | ||||
-rw-r--r-- | tools/kochdocs.nim | 2 |
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 |