diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 11fc1856..9a53de3f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,21 +8,20 @@ jobs: matrix: os: - ubuntu-latest - - macos-latest - # - windows-latest - # Blocked until we no longer require libev; Dream still works on - # Windows, but testing it is awkward at the moment. ocaml: + - 5.0.x + - 4.14.x + - 4.13.x - 4.12.x + - 4.11.x + - 4.10.x + - 4.09.x + - 4.08.x include: - - os: ubuntu-latest - ocaml: 4.08.x - - os: ubuntu-latest - ocaml: 4.09.x - - os: ubuntu-latest - ocaml: 4.10.x - - os: ubuntu-latest - ocaml: 4.11.x + - os: macos-latest + ocaml: 4.12.x + - os: windows-latest + ocaml: 4.14.x runs-on: ${{matrix.os}} steps: @@ -31,27 +30,66 @@ jobs: submodules: recursive - uses: avsm/setup-ocaml@v2 + if: runner.os != 'Windows' + with: + ocaml-compiler: ${{matrix.ocaml}} + + - uses: avsm/setup-ocaml@v2 + if: runner.os == 'Windows' with: ocaml-compiler: ${{matrix.ocaml}} + opam-repositories: | + opam-repository-mingw: https://github.com/ocaml-opam/opam-repository-mingw.git#sunset + default: https://github.com/ocaml/opam-repository.git + - run: opam depext --yes conf-sqlite3 - run: opam depext --yes conf-postgresql - run: opam depext --yes conf-libev - - run: opam install --yes --deps-only --with-test . - - run: opam exec -- dune runtest - - run: | + if: runner.os != 'Windows' + + # The tests require ppx_expect. The latest versions of it introduced changes + # in the formatting of the output, and also require OCaml >= 4.10, which + # makes testing on < 4.10 awkward. So, we skip tests on < 4.10. + - shell: bash + run: | set -e set -x - EXAMPLES=$(find example -maxdepth 1 -type d -not -name "m-mirage*"| grep -v "^example/0" | grep -v "^example$" | sort) - shopt -s nullglob + WITH_TEST=--with-test + case ${{matrix.ocaml}} in + 4.09.x) WITH_TEST=;; + 4.08.x) WITH_TEST=;; + esac + + # Tests on Windows are disabled because of a difference in ppx_expect + # output. See https://github.com/aantron/dream/pull/282. + case ${{runner.os}} in + Windows) WITH_TEST=;; + esac + + OPAM=$(which opam || true) + if [ -z "$OPAM" ] + then + OPAM=D:\\cygwin\\wrapperbin\\opam.cmd + fi - for EXAMPLE in $EXAMPLES - do - FILE=$(ls $EXAMPLE/*.ml $EXAMPLE/*.re $EXAMPLE/server/*.ml $EXAMPLE/server/*.re) - EXE=$(echo $FILE | sed 's/\..*$/.exe/g') - echo dune build $EXE - opam exec -- dune build $EXE - done + $OPAM install --yes --deps-only $WITH_TEST ./dream-pure.opam ./dream-httpaf.opam ./dream.opam + + if [ ! -z "$WITH_TEST" ] + then + $OPAM exec -- dune runtest + + EXAMPLES=$(find example -maxdepth 1 -type d -not -name "w-mirage*" -not -name "r-tyxml" | grep -v "^example/0" | grep -v "^example$" | sort) + shopt -s nullglob + + for EXAMPLE in $EXAMPLES + do + FILE=$(ls $EXAMPLE/*.ml $EXAMPLE/*.re $EXAMPLE/server/*.ml $EXAMPLE/server/*.re) + EXE=$(echo $FILE | sed 's/\..*$/.exe/g') + echo dune build $EXE + $OPAM exec -- dune build $EXE + done + fi quickstart: strategy: @@ -77,3 +115,29 @@ jobs: else exit 1 fi + + mirage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - run: mkdir ../repo-copy + - run: cp -r * ../repo-copy/ + - uses: avsm/setup-ocaml@v2 + with: + ocaml-compiler: 4.14.x + - run: opam install --yes --deps-only ./dream-pure.opam ./dream-httpaf.opam ./dream.opam ./dream-mirage.opam + - run: opam install --yes mirage mirage-clock-unix + - run: cd example/w-mirage && mv config.ml config.ml.backup + - run: cd example/w-mirage && sed -e 's/package "dream-mirage"//' < config.ml.backup > config.ml + - run: cd example/w-mirage && opam exec -- mirage configure -t unix + - run: cd example/w-mirage && opam exec -- make depends + - run: cd example/w-mirage && ls duniverse + - run: cp -r ../repo-copy example/w-mirage/duniverse/dream + - run: cd example/w-mirage/duniverse && rm -rf ocaml-cstruct logs ke fmt lwt bytes seq mirage-flow sexplib0 ptime tls domain-name ocaml-ipaddr mirage-clock ocplib-endian + - run: cd example/w-mirage && mv config.ml.backup config.ml + - run: cd example/w-mirage && sed -e 's/(libraries/(libraries dream-mirage/' < dune.build > dune.build.2 + - run: cd example/w-mirage && mv dune.build.2 dune.build + - run: cd example/w-mirage && opam exec -- dune build + - run: file example/w-mirage/_build/default/main.exe diff --git a/.gitignore b/.gitignore index a4114480..08738be9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,10 +15,15 @@ _esy/ esy.lock # Release script -dream-* +dream-*.gz +dream-*/ # Bisect_ppx _coverage/ # Humans scratch/ + +# Editors +.vscode/ +*.swp diff --git a/.gitmodules b/.gitmodules index f3b0748b..23c0967f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,5 +12,5 @@ url = https://github.com/aantron/ocaml-h2.git [submodule "src/vendor/paf"] path = src/vendor/paf - url = https://github.com/dinosaure/paf-le-chien.git + url = https://github.com/aantron/paf-le-chien.git branch = dream diff --git a/.ocamlformat b/.ocamlformat index 9059cdef..7bf428e0 100644 --- a/.ocamlformat +++ b/.ocamlformat @@ -1,21 +1,16 @@ -version = 0.19.0 +version = 0.25.1 profile = conventional leading-nested-match-parens = false -align-constructors-decl = true -align-variants-decl = true space-around-variants = false space-around-arrays = false space-around-lists = false space-around-records = false -break-before-in = auto break-infix = fit-or-vertical break-separators = after -space-around-records = true -break-cases = all +break-cases = fit-or-vertical cases-exp-indent = 2 exp-grouping = preserve -nested-match = align if-then-else = fit-or-vertical let-and = sparse type-decl = sparse diff --git a/LICENSE.md b/LICENSE.md index 2f5408b0..2440b3ae 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2021, Anton Bachin +Copyright (c) 2021-2023, Anton Bachin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index fcd759a3..b95a344f 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,40 @@ +PACKAGES := dream-pure,dream-httpaf,dream + .PHONY : build build : - @dune build -p dream --no-print-directory @install + @dune build --only-packages $(PACKAGES) --no-print-directory @install .PHONY : watch watch : - @dune build -p dream --no-print-directory -w + @dune build --only-packages $(PACKAGES) --no-print-directory @install -w + +.PHONY : deps +deps : + opam install --deps-only --with-test ./dream-pure.opam ./dream-httpaf.opam ./dream.opam + +TEST ?= test +ROOT := $(shell [ -f ../dune-workspace ] && echo .. || echo .) .PHONY : test test : - @find . -name '*.coverage' | xargs rm -f + @find $(ROOT) -name '*.coverage' | xargs rm -f @dune build --no-print-directory \ - --instrument-with bisect_ppx --force @test/runtest + --instrument-with bisect_ppx --force @$(TEST)/runtest @bisect-ppx-report html @bisect-ppx-report summary @echo See _coverage/index.html .PHONY : test-watch test-watch : - @dune build --no-print-directory -w --root . @test/runtest + @dune build --no-print-directory -w @$(TEST)/runtest .PHONY : coverage-serve coverage-serve : - cd _coverage && dune exec -- serve -p 8082 + cd _coverage && dune exec -- dream-serve -p 8082 .PHONY : promote promote : - dune promote --root . + dune promote @make --no-print-directory test .PHONY : docs @@ -66,7 +75,7 @@ clean : clean-coverage dune clean dune clean --root . make --no-print-directory -C docs/web clean - rm -rf src/graphiql/node_modules dream-* _release + rm -rf src/graphiql/node_modules .PHONY : test-ocamlformat test-ocamlformat : @@ -92,21 +101,25 @@ todo-all : VERSION := $(shell git describe --abbrev=0) RELEASE := dream-$(VERSION) -FILES := src dream.opam dune-project LICENSE.md README.md +FILES := \ + src dream.opam dream-httpaf.opam dream-pure.opam dream-mirage.opam \ + dune-project LICENSE.md README.md .PHONY : release release : clean rm -rf $(RELEASE) $(RELEASE).tar $(RELEASE).tar.gz _release - mkdir $(RELEASE) + mkdir -p $(RELEASE) cp -r $(FILES) $(RELEASE) rm -rf $(RELEASE)/src/vendor/gluten/.github rm -rf $(RELEASE)/src/vendor/gluten/async + rm -rf $(RELEASE)/src/vendor/gluten/eio rm -rf $(RELEASE)/src/vendor/gluten/mirage rm -rf $(RELEASE)/src/vendor/gluten/nix rm -rf $(RELEASE)/src/vendor/httpaf/.github rm -rf $(RELEASE)/src/vendor/httpaf/async rm -rf $(RELEASE)/src/vendor/httpaf/benchmarks rm -rf $(RELEASE)/src/vendor/httpaf/certificates + rm -rf $(RELEASE)/src/vendor/httpaf/eio rm -rf $(RELEASE)/src/vendor/httpaf/examples rm -rf $(RELEASE)/src/vendor/httpaf/images rm -rf $(RELEASE)/src/vendor/httpaf/lib_test @@ -115,6 +128,7 @@ release : clean rm -rf $(RELEASE)/src/vendor/h2/.github rm -rf $(RELEASE)/src/vendor/h2/async rm -rf $(RELEASE)/src/vendor/h2/certificates + rm -rf $(RELEASE)/src/vendor/h2/eio rm -rf $(RELEASE)/src/vendor/h2/examples rm -rf $(RELEASE)/src/vendor/h2/lib_test rm -rf $(RELEASE)/src/vendor/h2/mirage @@ -123,20 +137,30 @@ release : clean rm -rf $(RELEASE)/src/vendor/h2/vegeta-plot.png rm -rf $(RELEASE)/src/vendor/websocketaf/.github rm -rf $(RELEASE)/src/vendor/websocketaf/async + rm -rf $(RELEASE)/src/vendor/websocketaf/eio rm -rf $(RELEASE)/src/vendor/websocketaf/examples rm -rf $(RELEASE)/src/vendor/websocketaf/lib_test rm -rf $(RELEASE)/src/vendor/websocketaf/mirage rm -rf $(RELEASE)/src/vendor/websocketaf/nix + rm -rf $(RELEASE)/src/vendor/paf tar cf $(RELEASE).tar $(RELEASE) ls -l $(RELEASE).tar gzip -9 $(RELEASE).tar mkdir -p _release cp $(RELEASE).tar.gz _release (cd _release && tar xf $(RELEASE).tar.gz) + opam remove -y dream-pure dream-httpaf dream gluten httpaf h2 websocketaf paf + opam pin remove -y dream-pure dream-httpaf dream + opam pin add -y --no-action dream-pure _release/$(RELEASE) --kind=path + opam pin add -y --no-action dream-httpaf _release/$(RELEASE) --kind=path opam pin add -y --no-action dream _release/$(RELEASE) --kind=path opam reinstall -y --verbose dream + @echo Run make release-finish to complete after killing the server cd example/1-hello && dune exec --root . ./hello.exe || true - opam remove -y dream - opam pin remove -y dream + +.PHONY : release-finish +release-finish : + opam remove -y dream-pure dream-httpaf dream + opam pin remove -y dream-pure dream-httpaf dream md5sum $(RELEASE).tar.gz ls -l $(RELEASE).tar.gz diff --git a/README.md b/README.md index c0d59267..25405410 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ Dream is **one flat module** in **one package**, documented on - [**Cryptography**][crypto] helpers, key rotation, and a chosen cipher. - A neat [**logger**][logging], and attention to configuring the OCaml runtime nicely. -- [**Deployment**][deploy] instructions for **Digital Ocean** and **Heroku**, - with sample CI scripts. +- [**Deployment**][deploy] instructions for **Digital Ocean**, **Heroku**, and + **Fly.io**, with sample CI scripts.
@@ -110,49 +110,37 @@ into. ## Quick start -
+Visit one of the first tutorials in the [online +playground][2-middleware-playground], and read its +[docs](https://github.com/aantron/dream/tree/master/example/2-middleware#files). +You can get and build it locally with:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/aantron/dream/master/example/quickstart.sh)"
-
- -This downloads and runs [`quickstart.sh`][quickstart.sh], which does a -sandboxed build of one of the first [tutorials][tutorial], -[**`2-middleware`**][2-middleware]. It's mostly the same as: +Most of the other [examples][tutorial] are also loaded in the playground. See +the links on its [home page][playground]. -``` -git clone https://github.com/aantron/dream.git --recursive -cd dream/example/2-middleware -npm install esy && npx esy -npx esy start -``` +## esy -Knowing that, you can start from any other [example][tutorial]. All of them -include their own build commands. They don't have to be subdirectories of -`dream` — you can copy them out to start your own project directory. -Especially consider starting with the [full-stack examples][fullstack], which -build both a Dream server and a JavaScript client. +Visit any of the [examples][tutorial], such as +[**`2-middleware`**][2-middleware], and re-create the files locally. The file +[`esy.json`](https://github.com/aantron/dream/blob/master/example/2-middleware/esy.json) +shows how to depend on Dream. All of the examples are installed by running `npx +esy`, and started with `npx esy start`. ### opam ``` -opam install dream.1.0.0~alpha2 +opam install dream ``` -After that, go to one of the examples, such as [**`1-hello`**][1-hello], and -build it: +After that, go to any of the [examples][tutorial], such as +[**`2-middleware`**][2-middleware], re-create the files locally, and run it: ``` -cd example/1-hello -dune exec --root . ./hello.exe +dune exec ./middleware.exe ``` -### Playground - -Most of the examples are loaded into the [playground][playground]. For instance, -[**`2-middleware`**][2-middleware] is at -[http://dream.as/2-middleware][2-middleware-playground]. - [esy-example]: https://github.com/aantron/dream/tree/master/example/w-esy#files [quickstart.sh]: https://github.com/aantron/dream/blob/master/example/quickstart.sh [esy]: https://esy.sh/ @@ -177,7 +165,7 @@ Most of the examples are loaded into the [playground][playground]. For instance, small-to-medium deployments. - [**Examples**][examples] — These cover various HTTP scenarios. - [**API reference**][api-main] -- [Watching][fswatch] and [live reloading][reload]. +- [Watching][watch] and [live reloading][reload]. [tutorial]: https://github.com/aantron/dream/tree/master/example#readme [examples]: https://github.com/aantron/dream/tree/master/example#examples @@ -187,7 +175,7 @@ Most of the examples are loaded into the [playground][playground]. For instance, [deploying]: https://github.com/aantron/dream/tree/master/example#deploying [api-main]: https://aantron.github.io/dream/#types [fullstack]: https://github.com/aantron/dream/tree/master/example#full-stack -[fswatch]: https://github.com/aantron/dream/tree/master/example/w-fswatch#files +[watch]: https://github.com/aantron/dream/tree/master/example/w-watch#files [reload]: https://github.com/aantron/dream/tree/master/example/w-live-reload#files
@@ -224,9 +212,10 @@ Most of the examples are loaded into the [playground][playground]. For instance, Apart from the [issues](https://github.com/aantron/dream/issues), good places to discuss Dream are... -- #dream on the [Reason Discord](https://discord.gg/YCTDuzbg). -- #webdev on the [OCaml Discord](https://discord.gg/DyhPFYGr) +- #dream on the [Reason Discord](https://discord.gg/2JTYRq2rYh). +- #webdev on the [OCaml Discord](https://discord.gg/sx45hPkkWV) - The [OCaml Discuss forum](https://discuss.ocaml.org/). +- The development stream on [Twitch](https://www.twitch.tv/antron_ML). Highlight `@antron` to poke @aantron specifically. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index e06afe31..36b89ca7 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -46,7 +46,7 @@ Later, you'll need to fork the repository on GitHub, and add your fork as a remote: ``` -git remote add fork git@github.com/my-github-name/dream.git +git remote add fork git@github.com:my-github-name/dream.git ``` Install Dream's dependencies: @@ -58,17 +58,23 @@ opam install --deps-only ./dream.opam --with-test If you don't have an opam switch ready, first create one with ``` -opam switch create . 4.12.0 --no-install +opam switch create . 4.14.1 --no-install ``` You can now add some code that will exercise your change, so you can test it as you work. There are two main places for this: 1. The tests in `test/`. They can be run with `make test`. View the generated - coverage report in `_coverage/index.html` to see how much the tests exercies + coverage report in `_coverage/index.html` to see how much the tests exercise your changes. -2. The examples in `example/`. I often test changes by modifying an example that + To run tests from a single directory, for example `test/expect/pure`, run + `make test TEST=test/expect/pure`. + +2. The tests can also be run in watch mode using `make test-watch`. This is not + compatible with coverage reports at the moment. + +3. The examples in `example/`. I often test changes by modifying an example that is almost on topic for the code I'm changing, and then not committing the example. In some cases, though, it's easiest to fork or write a new example for some new code, and commit it. New examples greatly appreciated! To build @@ -101,6 +107,12 @@ If you want to work again later, be sure to use `--recurse-submodules` during git pull --recurse-submodules ``` +**Note:** Please don't force-push into a PR — it makes incremental review +very difficult, and we will squash-merge most PRs anyway! + +**Note:** Please don't resolve conversations in PRs. Reviewers use resolving +conversations to keep track of what has been addressed. +
If you need to link to the local version of Dream from a project that lives in @@ -137,8 +149,8 @@ To build the docs, go to make deps ``` -This will install npm packages and opam packages (some of which are pinned to -git commits). +This will install npm and opam packages. In particular, the site currently +requires odoc 2.0.2, Soupault, and a specific version of Highlight.js. After that, back in the project root, @@ -153,3 +165,11 @@ make docs ``` to build the docs locally. They are output to `docs/web/build/index.html`. + +You can also use + +``` +make docs-watch +``` + +to rebuild the docs automatically as you write them. diff --git a/docs/asset/sample.html b/docs/asset/sample.html index c82db91e..834aebc6 100644 --- a/docs/asset/sample.html +++ b/docs/asset/sample.html @@ -69,7 +69,6 @@ @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html (hello "world")); ] - @@ Dream.not_found diff --git a/docs/asset/sample.png b/docs/asset/sample.png index 36f3d5bb..1bda0dde 100644 Binary files a/docs/asset/sample.png and b/docs/asset/sample.png differ diff --git a/docs/web/Makefile b/docs/web/Makefile index a32f6f86..6f92fa7d 100644 --- a/docs/web/Makefile +++ b/docs/web/Makefile @@ -4,7 +4,7 @@ ODOC := odoc/default/_doc/_html .PHONY : build build : dune build @doc --root $(ROOT) --no-print-directory --build-dir `pwd`/odoc \ - --only-packages dream + --only-packages dream-pure,dream-httpaf,dream rm -f site/index.html dune exec -- postprocess/index.exe \ $(ODOC)/dream/Dream/index.html site/index.html diff --git a/docs/web/postprocess/common.ml b/docs/web/postprocess/common.ml index ae90059f..aa2a4531 100644 --- a/docs/web/postprocess/common.ml +++ b/docs/web/postprocess/common.ml @@ -13,8 +13,35 @@ let if_expected expected test f = f () else begin Soup.write_file "actual" actual; - Printf.ksprintf failwith "Mismatch; wrote %s" - (Filename.concat (Sys.getcwd ()) "actual") + prerr_newline (); + prerr_newline (); + prerr_endline "Mismatch with expected initial HTML content."; + prerr_newline (); + prerr_endline + "The Dream docs build rewrites HTML emitted by odoc to make it neater."; + prerr_endline + "Each rewritten tag has an expected initial content for sanity checking."; + + prerr_endline "The actual found content has been written to"; + prerr_newline (); + prerr_endline (" " ^ (Filename.concat (Sys.getcwd ()) "actual")); + prerr_newline (); + + begin match String.split_on_char '\n' actual with + | [] -> () + | first_line::_ -> + prerr_endline "Hint:"; + prerr_newline (); + prerr_endline (" " ^ first_line); + prerr_newline () + end; + + prerr_endline "Hint: make sure odoc 2.0.2 is installed."; + prerr_endline + "Other versions of odoc generate markup that doesn't match the expected."; + prerr_newline (); + + Printf.ksprintf failwith "Mismatch" end let add_backing_lines soup = diff --git a/docs/web/postprocess/index.ml b/docs/web/postprocess/index.ml index 0ef32806..dee5ae5f 100644 --- a/docs/web/postprocess/index.ml +++ b/docs/web/postprocess/index.ml @@ -592,7 +592,7 @@ let val_redirect_replacement = {| let stream_expected = {|
val stream : ?status:[< status ] -> ?code:int -> ?headers:(string * string) list -> -(response -> unit promise) -> response promise +?close:bool -> (stream -> unit promise) -> response promise
|} @@ -601,7 +601,8 @@ let stream_replacement = {| ?status:[< status ] -> ?code:int -> ?headers:(string * string) list -> - (response -> unit promise) -> response promise + ?close:bool -> + (stream -> unit promise) -> response promise |} @@ -618,30 +619,30 @@ let empty_replacement = {| |} let add_header_expected = {|
- val add_header : string -> string -> 'a message -> 'a message + val add_header : 'a message -> string -> string -> unit
|} let add_header_replacement = {|
val add_header :
-  string -> string -> 'a message -> 'a message
+  'a message -> string -> string -> unit
 |}
 
-let with_header_expected = {|
- val with_header : string -> string -> 'a message -> 'a message +let set_header_expected = {|
+ val set_header : 'a message -> string -> string -> unit
|} -let with_header_replacement = {| -
val with_header :
-  string -> string -> 'a message -> 'a message
+let set_header_replacement = {|
+
val set_header :
+  'a message -> string -> string -> unit
 |}
 
 let add_set_cookie_expected = {|
 |}
 
@@ -656,12 +657,30 @@ let add_set_cookie_replacement = {|
   ?secure:bool ->
   ?http_only:bool ->
   ?same_site:[< `Strict | `Lax | `None ] option ->
-    string -> string -> request -> response -> response
+    response -> request -> string -> string -> unit
+
|} + +let drop_cookie_expected = {| +|} + +let drop_cookie_replacement = {| +
val drop_cookie :
+  ?prefix:[< `Host | `Secure ] option ->
+  ?domain:string ->
+  ?path:string option ->
+  ?secure:bool ->
+  ?http_only:bool ->
+  ?same_site:[< `Strict | `Lax | `None ] option ->
+    response -> request -> string -> unit
 
|} let cookie_expected = {| |} @@ -672,7 +691,7 @@ let cookie_replacement = {| ?domain:string -> ?path:string option -> ?secure:bool -> - string -> request -> string option + request -> string -> string option
|} @@ -688,35 +707,91 @@ let bigstring_replacement = {|
|} -let next_expected = {|
- val next : buffer:(buffer -> int -> int -> unit) -> close:(unit -> unit) -> exn:(exn -> unit) -> -request -> unit +let read_stream_expected = {|
+ val read_stream : stream -> data:(buffer -> int -> int -> bool -> bool -> unit) -> flush:(unit -> unit) -> +ping:(buffer -> int -> int -> unit) -> pong:(buffer -> int -> int -> unit) -> close:(int -> unit) -> +exn:(exn -> unit) -> unit
|} -let next_replacement = {| -
val next :
-  buffer:(buffer -> int -> int -> unit) ->
-  close:(unit -> unit) ->
+let read_stream_replacement = {|
+
val read_stream :
+  stream ->
+  data:(buffer -> int -> int -> bool -> bool -> unit) ->
+  flush:(unit -> unit) ->
+  ping:(buffer -> int -> int -> unit) ->
+  pong:(buffer -> int -> int -> unit) ->
+  close:(int -> unit) ->
   exn:(exn -> unit) ->
-  request ->
     unit
 
|} -let write_bigstring_expected = {|
- val write_buffer : ?offset:int -> ?length:int -> response -> buffer -> unit promise +let write_stream_expected = {|
+ val write_stream : stream -> buffer -> int -> int -> bool -> bool -> close:(int -> unit) -> +exn:(exn -> unit) -> (unit -> unit) -> unit
|} -let write_bigstring_replacement = {| -
val write_buffer :
-  ?offset:int ->
-  ?length:int ->
-    response -> buffer -> unit promise
-
+let write_stream_replacement = {| +
val write_stream :
+  stream ->
+  buffer -> int -> int ->
+  bool -> bool ->
+  close:(int -> unit) ->
+  exn:(exn -> unit) ->
+  (unit -> unit) ->
+    unit
+
|} + +let flush_stream_expected = {|
+ val flush_stream : stream -> close:(int -> unit) -> exn:(exn -> unit) -> (unit -> unit) -> unit +
|} +let flush_stream_replacement = {| +
val flush_stream :
+  stream ->
+  close:(int -> unit) ->
+  exn:(exn -> unit) ->
+  (unit -> unit) ->
+    unit
+
|} + +let ping_stream_expected = {|
+ val ping_stream : stream -> buffer -> int -> int -> close:(int -> unit) -> exn:(exn -> unit) -> +(unit -> unit) -> unit +
+|} + +let ping_stream_replacement = {| +
+val ping_stream :
+  stream ->
+  buffer -> int -> int ->
+  close:(int -> unit) ->
+  exn:(exn -> unit) ->
+  (unit -> unit) ->
+    unit
+
|} + +let pong_stream_expected = {|
+ val pong_stream : stream -> buffer -> int -> int -> close:(int -> unit) -> exn:(exn -> unit) -> +(unit -> unit) -> unit +
+|} + +let pong_stream_replacement = {| +
+val pong_stream :
+  stream ->
+  buffer -> int -> int ->
+  close:(int -> unit) ->
+  exn:(exn -> unit) ->
+  (unit -> unit) ->
+    unit
+
|} + let form_expected = {|
type 'a form_result = [ @@ -775,13 +850,14 @@ let form_replacement = {| |} let form'_expected = {|
- val form : request -> (string * string) list form_result promise + val form : ?csrf:bool -> request -> (string * string) list form_result promise
|} let form'_replacement = {|
val form :
-  request -> (string * string) list form_result promise
+  ?csrf:bool ->
+    request -> (string * string) list form_result promise
 
|} @@ -797,13 +873,14 @@ let multipart_form_replacement = {| |} let multipart_expected = {|
- val multipart : request -> multipart_form form_result promise + val multipart : ?csrf:bool -> request -> multipart_form form_result promise
|} let multipart_replacement = {|
val multipart :
-  request -> multipart_form form_result promise
+  ?csrf:bool ->
+    request -> multipart_form form_result promise
 
|} @@ -908,22 +985,6 @@ let verify_csrf_token_replacement = {| |} -let form_tag_expected = {|
- val form_tag : ?method_:[< method_ ] -> ?target:string -> -?enctype:[< `Multipart_form_data ] -> ?csrf_token:bool -> action:string -> request -> string -
-|} - -let form_tag_replacement = {| -
val form_tag :
-  ?method_:[< method_ ] ->
-  ?target:string ->
-  ?enctype:[< `Multipart_form_data ] ->
-  ?csrf_token:bool ->
-    action:string -> request -> string
-
-|} - let scope_expected = {|
val scope : string -> middleware list -> route list -> route
@@ -1019,42 +1080,103 @@ let static_replacement = {| |} -let set_session_expected = {|
- val put_session : string -> string -> request -> unit promise +let set_session_expected = {|
+ val set_session_field : request -> string -> string -> unit promise
|} let set_session_replacement = {| -
val put_session :
-  string -> string -> request -> unit promise
+
val set_session_field :
+  request -> string -> string -> unit promise
 
|} let websocket_expected = {|
- val websocket : ?headers:(string * string) list -> (websocket -> unit promise) -> response promise + val websocket : ?headers:(string * string) list -> ?close:bool -> (websocket -> unit promise) -> response promise
|} let websocket_replacement = {|
val websocket :
   ?headers:(string * string) list ->
-  (websocket -> unit promise) ->
-    response promise
+  ?close:bool ->
+    (websocket -> unit promise) -> response promise
 
|} +let text_or_binary_expected = {|
+ type text_or_binary = [ +
+ + + + + + + + +
+ | `Text +
+ | `Binary +
+ ] +
+|} + +let text_or_binary_replacement = {| +
type text_or_binary = [ `Text | `Binary ]
+|} + +let end_of_message_expected = {|
+ type end_of_message = [ + + + + + + + + + +
+ | `End_of_message +
+ | `Continues +
+ ] +
+|} + +let end_of_message_replacement = {| +
type end_of_message = [ `End_of_message | `Continues ]
+|} + let send_expected = {|
- val send : ?kind:[< `Text | `Binary ] -> websocket -> string -> unit promise + val send : ?text_or_binary:[< text_or_binary ] -> ?end_of_message:[< end_of_message ] -> websocket -> string -> unit promise
|} let send_replacement = {|
val send :
-  ?kind:[< `Text | `Binary ] ->
+  ?text_or_binary:[< text_or_binary ] ->
+  ?end_of_message:[< end_of_message ] ->
     websocket -> string -> unit promise
 
|} +let receive_fragment_expected = {|
+ val receive_fragment : websocket -> (string * text_or_binary * end_of_message) option promise +
+|} + +let receive_fragment_replacement = {| +
val receive_fragment :
+  websocket ->
+    (string * text_or_binary * end_of_message) option promise
+
+|} + let close_websocket_expected = {|
val close_websocket : ?code:int -> websocket -> unit promise
@@ -1229,13 +1351,13 @@ let initialize_log_replacement = {|
|} let error_template_expected = {|
- val error_template : (error -> string option -> response -> response promise) -> error_handler + val error_template : (error -> string -> response -> response promise) -> error_handler
|} let error_template_replacement = {|
val error_template :
-  (error -> string option -> response -> response promise) ->
+  (error -> string -> response -> response promise) ->
     error_handler
 
|} @@ -1279,11 +1401,6 @@ let error_expected = {|
severity : log_level; - - - debug : bool; - - will_send_response : bool; @@ -1308,21 +1425,20 @@ let error_replacement = {| response : response option; client : string option; severity : log_level; - debug : bool; will_send_response : bool; } |} -let new_local_expected = {|
- val new_local : ?name:string -> ?show_value:('a -> string) -> unit -> 'a local +let new_field_expected = {|
+ val new_field : ?name:string -> ?show_value:('a -> string) -> unit -> 'a field
|} -let new_local_replacement = {| -
val new_local :
+let new_field_replacement = {|
+
val new_field :
   ?name:string ->
   ?show_value:('a -> string) ->
-    unit -> 'a local
+    unit -> 'a field
 
|} @@ -1339,9 +1455,8 @@ let new_global_replacement = {| |} let run_expected = {|
- val run : ?interface:string -> ?port:int -> ?stop:unit promise -> ?debug:bool -> -?error_handler:error_handler -> ?secret:string -> ?old_secrets:string list -> ?prefix:string -> -?https:bool -> ?certificate_file:string -> ?key_file:string -> ?builtins:bool -> + val run : ?interface:string -> ?port:int -> ?stop:unit promise -> ?error_handler:error_handler -> +?tls:bool -> ?certificate_file:string -> ?key_file:string -> ?builtins:bool -> ?greeting:bool -> ?adjust_terminal:bool -> handler -> unit
|} @@ -1351,12 +1466,8 @@ let run_replacement = {| ?interface:string -> ?port:int -> ?stop:unit promise -> - ?debug:bool -> ?error_handler:error_handler -> - ?secret:string -> - ?old_secrets:string list -> - ?prefix:string -> - ?https:true -> + ?tls:bool -> ?certificate_file:string -> ?key_file:string -> ?builtins:bool -> @@ -1366,9 +1477,8 @@ let run_replacement = {|
|} let serve_expected = {|
- val serve : ?interface:string -> ?port:int -> ?stop:unit promise -> ?debug:bool -> -?error_handler:error_handler -> ?secret:string -> ?old_secrets:string list -> ?prefix:string -> -?https:bool -> ?certificate_file:string -> ?key_file:string -> ?builtins:bool -> + val serve : ?interface:string -> ?port:int -> ?stop:unit promise -> ?error_handler:error_handler -> +?tls:bool -> ?certificate_file:string -> ?key_file:string -> ?builtins:bool -> handler -> unit promise
|} @@ -1378,12 +1488,8 @@ let serve_replacement = {| ?interface:string -> ?port:int -> ?stop:unit promise -> - ?debug:bool -> ?error_handler:error_handler -> - ?secret:string -> - ?old_secrets:string list -> - ?prefix:string -> - ?https:bool -> + ?tls:bool -> ?certificate_file:string -> ?key_string:string -> ?builtins:bool -> @@ -1459,17 +1565,15 @@ let decrypt_replacement = {| |} let request_expected = {|
- val request : ?client:string -> ?method_:[< method_ ] -> ?target:string -> -?version:(int * int) -> ?headers:(string * string) list -> string -> request + val request : ?method_:[< method_ ] -> ?target:string -> ?headers:(string * string) list +-> string -> request
|} let request_replacement = {|
val request :
-  ?client:string ->
-  ?method_:[< method_ ] ->
+  ?method_:[< method_ ] ->
   ?target:string ->
-  ?version:int * int ->
   ?headers:(string * string) list ->
     string -> request
 
|} @@ -1484,6 +1588,43 @@ let sort_headers_replacement = {| (string * string) list -> (string * string) list |} +let message_expected = {|
+ and 'a message = 'a Dream_pure.Message.message +
+|} + +let message_replacement = {| +and 'a message +|} + +let client_expected' = {|
+ and client = Dream_pure.Message.client +
+|} + +let client_replacement' = {| +and client +|} + +let server_expected' = {|
+ and server = Dream_pure.Message.server +
+|} + +let server_replacement' = {| +and server +|} + +let set_secret_expected = {|
+ val set_secret : ?old_secrets:string list -> string -> middleware +
+|} + +let set_secret_replacement = {| +
val set_secret :
+  ?old_secrets:string list -> string -> middleware
+
|} + let pretty_print_signatures soup = let method_ = soup $ "#type-method_" in if_expected @@ -1494,7 +1635,7 @@ let pretty_print_signatures soup = Soup.replace (method_ $ "> table") (Soup.parse method_replacement); Soup.add_class "multiline" method_); - let rewrite_status_group id expected replacement = + let rewrite_status_group ?(multiline = true) id expected replacement = let group = soup $ id in if_expected expected @@ -1502,7 +1643,8 @@ let pretty_print_signatures soup = (fun () -> group $$ "> code" |> Soup.iter Soup.delete; Soup.replace (group $ "> table") (Soup.parse replacement); - Soup.add_class "multiline" group) + if multiline then + Soup.add_class "multiline" group) in rewrite_status_group @@ -1600,7 +1742,7 @@ let pretty_print_signatures soup = in replace "#val-add_header" add_header_expected add_header_replacement; - multiline "#val-with_header" with_header_expected with_header_replacement; + multiline "#val-set_header" set_header_expected set_header_replacement; let add_set_cookie = soup $ "#val-set_cookie" in if_expected @@ -1612,6 +1754,16 @@ let pretty_print_signatures soup = (Soup.parse add_set_cookie_replacement); Soup.add_class "multiline" add_set_cookie); + let drop_cookie = soup $ "#val-drop_cookie" in + if_expected + drop_cookie_expected + (fun () -> pretty_print drop_cookie) + (fun () -> + Soup.replace + (drop_cookie $ "> code") + (Soup.parse drop_cookie_replacement); + Soup.add_class "multiline" drop_cookie); + multiline "#val-cookie" cookie_expected cookie_replacement; let bigstring = soup $ "#type-buffer" in @@ -1622,23 +1774,6 @@ let pretty_print_signatures soup = Soup.replace (bigstring $ "> code") (Soup.parse bigstring_replacement); Soup.add_class "multiline" bigstring); - let next = soup $ "#val-next" in - if_expected - next_expected - (fun () -> pretty_print next) - (fun () -> - Soup.replace (next $ "> code") (Soup.parse next_replacement); - Soup.add_class "multiline" next); - - let write_bigstring = soup $ "#val-write_buffer" in - if_expected - write_bigstring_expected - (fun () -> pretty_print write_bigstring) - (fun () -> - Soup.replace - (write_bigstring $ "> code") (Soup.parse write_bigstring_replacement); - Soup.add_class "multiline" write_bigstring); - let form = soup $ "#type-form_result" in if_expected form_expected @@ -1681,7 +1816,6 @@ let pretty_print_signatures soup = verify_csrf_token_expected verify_csrf_token_replacement; - multiline "#val-form_tag" form_tag_expected form_tag_replacement; multiline "#val-scope" scope_expected scope_replacement; replace "#val-get" get_expected get_replacement; replace "#val-post" post_expected post_replacement; @@ -1692,7 +1826,8 @@ let pretty_print_signatures soup = replace "#val-patch" patch_expected patch_replacement; replace "#val-any" any_expected any_replacement; multiline "#val-static" static_expected static_replacement; - multiline "#val-put_session" set_session_expected set_session_replacement; + multiline "#val-set_session_field" + set_session_expected set_session_replacement; multiline "#val-websocket" websocket_expected websocket_replacement; multiline "#val-send" send_expected send_replacement; multiline "#val-close_websocket" @@ -1756,8 +1891,7 @@ let pretty_print_signatures soup = Soup.replace (error $ "> table") (Soup.parse error_replacement); Soup.add_class "multiline" error); - multiline "#val-new_local" new_local_expected new_local_replacement; - multiline "#val-new_global" new_global_expected new_global_replacement; + multiline "#val-new_field" new_field_expected new_field_replacement; let run = soup $ "#val-run" in if_expected @@ -1795,7 +1929,28 @@ let pretty_print_signatures soup = Soup.replace (request $ "> code") (Soup.parse request_replacement); Soup.add_class "multiline" request); - multiline "#val-sort_headers" sort_headers_expected sort_headers_replacement + multiline "#val-sort_headers" sort_headers_expected sort_headers_replacement; + + replace "#type-message" message_expected message_replacement; + replace "#type-client" client_expected' client_replacement'; + replace "#type-server" server_expected' server_replacement'; + + multiline "#val-read_stream" read_stream_expected read_stream_replacement; + multiline "#val-write_stream" write_stream_expected write_stream_replacement; + multiline "#val-flush_stream" flush_stream_expected flush_stream_replacement; + multiline "#val-ping_stream" ping_stream_expected ping_stream_replacement; + multiline "#val-pong_stream" pong_stream_expected pong_stream_replacement; + + rewrite_status_group ~multiline:false + "#type-text_or_binary" text_or_binary_expected text_or_binary_replacement; + rewrite_status_group ~multiline:false + "#type-end_of_message" end_of_message_expected end_of_message_replacement; + + multiline + "#val-receive_fragment" + receive_fragment_expected receive_fragment_replacement; + + multiline "#val-set_secret" set_secret_expected set_secret_replacement let remove_stdlib soup = soup $$ ".xref-unresolved:contains(\"Stdlib\")" |> Soup.iter (fun element -> diff --git a/docs/web/site/docs.css b/docs/web/site/docs.css index bcc19848..275f3a08 100644 --- a/docs/web/site/docs.css +++ b/docs/web/site/docs.css @@ -60,6 +60,47 @@ src: url('tenor-sans-v12-latin-regular.woff2') format('woff2'); } +/* Theme */ + +/* Dark theme (default) */ +:root, body:not([data-theme="light"]) { + --bg-color: #131618; + --text-color: #c9d1d9; + --code-bg-color: #2c333b; + --border-color: #282828; + --link-color: #8dc5ff; + --external-link-color: #5d7fcd; + --anchor-color: #bfcdea; + + --of-color: #bec5cd; + --target-backing-color: #390022; + + --hljs-keyword-color: #ff6c9b; + --hljs-identifier-color: #70df5c; + --hljs-tag-color: #c28eff; + --hljs-string-color: #e3db7a; +} + +/* Light theme */ +:root, body[data-theme="light"] { + --bg-color: #f5f7fa; + --text-color: #1f2937; + --code-bg-color: #eef1f6; + --header-bg-color: #f5f7fa; + --border-color: #e0e0e0; + --link-color: #1c7ed6; + --external-link-color: #1d4ed8; + --anchor-color: #888; + + --of-color: #6b7280; + --target-backing-color: #f7f6f3; + + --hljs-keyword-color: #d94879; + --hljs-identifier-color: #22863a; + --hljs-tag-color: #6f42c1; + --hljs-string-color: #b94e48; +} + body { font-family: Lato, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 16px; @@ -101,15 +142,15 @@ h6 { /* Colors and presentation styles. */ body { - background-color: #131618; - color: #c9d1d9; + background-color: var(--bg-color); + color: var(--text-color); } .odoc-content pre { - background-color: #1a1f26; + background-color: var(--code-bg-color); margin-left: 1em; margin-right: 1em; - border: 1px solid #111; + border: 1px solid var(--border-color); } .odoc-content .spec > pre { background: none; @@ -119,7 +160,7 @@ body { .odoc-content code { /* color: #ddd; */ - background-color: #2c333b; + background-color: var(--code-bg-color); padding: 0 5px; margin: 0 1px; white-space: nowrap; @@ -150,17 +191,40 @@ body { } */ header { - background-color: #131618; - border-bottom: 1px solid #282828; + background-color: var(--bg-color); + border-bottom: 1px solid var(--border-color); } header .topmost { /* background-color: #0f131a; */ - border-bottom: 1px solid #282828; + border-bottom: 1px solid var(--border-color); } -h1 { - text-shadow: -2px 2px black; +.topmost .toolbar { + float: right; +} + +.topmost .toggle-theme-btn { + all: unset; + position: relative; +} + +.topmost .toggle-theme-btn::before { + content: "\F186"; /* moon */ + position: absolute; + left: calc(0% - 16px + -8px); + top: calc(0% + 4px); + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; + font-family: FontAwesome, FontAwesomeBrands; + font-size: 15px; +} + +body:not([data-theme="light"]) .topmost .toggle-theme-btn::before { + content: "\F185"; /* sun */ } header pre { @@ -191,7 +255,7 @@ footer { } :target .backing { - background-color: #390022; + background-color: var(--target-backing-color); } nav ~ * a[href="#builtin"], @@ -214,7 +278,7 @@ a[href^=http]::after { font-family: FontAwesome; font-size: 10px; line-height: 18px; - color: #5d7fcd; + color: var(--external-link-color); position: relative; top: -1px; margin-left: 2px; @@ -225,7 +289,7 @@ a[href^=http]::after { } a, a:visited, a:active { - color: #8dc5ff; + color: var(--link-color); text-decoration: none; } @@ -234,27 +298,27 @@ a:hover { } .odoc-content a > code { - color: #8dc5ff; + color: var(--link-color); } .hljs-module-access, .hljs-keyword, .keyword { - color: #ff6c9b; + color: var(--hljs-keyword-color); } .hljs-identifier, .hljs-literal, .hljs-type { - color: #70df5c; + color: var(--hljs-identifier-color); } .hljs-tag { - color: #c28eff; + color: var(--hljs-tag-color); } .hljs-string { - color: #e3db7a; + color: var(--hljs-string-color); } .of { - color: #bec5cd; + color: var(--of-color); } .topmost ul { @@ -407,7 +471,7 @@ ul ul li { height: 100%; width: 43rem; /* background-color: #262626; */ - border-right: 1px solid #282828; + border-right: 1px solid var(--border-color); } h2, h2 ~ :not(.odoc-spec):not(nav), footer { @@ -462,8 +526,8 @@ ul ul li { margin-bottom: 24px; } - #type-outgoing + .spec-doc { - margin-top: -1.5rem; + #type-server + .spec-doc { + margin-top: -26px; margin-bottom: 48px; } #val-patch + .spec-doc { @@ -582,8 +646,8 @@ h2:first-of-type { overflow-y: scroll; scrollbar-width: none; line-height: 30px; - border-right: 1px solid #262626; - background-color: #131618; + border-right: 1px solid var(--border-color); + background-color: var(--bg-color); /* color: #ddd; */ } .odoc-toc::-webkit-scrollbar { @@ -674,7 +738,7 @@ h2 > .anchor, h3 > .anchor { font-family: FontAwesome; font-size: 10px; font-style: oblique; - color: #bfcdea; + color: var(--anchor-color); position: relative; top: -1.75px; left: -4px; diff --git a/docs/web/site/docs.js b/docs/web/site/docs.js index 8b404bf1..369f6189 100644 --- a/docs/web/site/docs.js +++ b/docs/web/site/docs.js @@ -4,8 +4,7 @@ // Copyright 2021 Anton Bachin *) - -console.log("foo"); +/* Scrolling */ function current_section() { var threshold = window.innerHeight / 2; @@ -49,3 +48,38 @@ function scroll() { }; window.onscroll = scroll; + + +/* Theme mode */ + +var THEME_MODE_KEY = "dream-theme" + +function apply_theme(theme) { + if (theme === "light") { + document.body.setAttribute("data-theme", "light"); + } else { + document.body.removeAttribute("data-theme"); + } +} + +function toggle_theme() { + var current_theme = localStorage.getItem(THEME_MODE_KEY); + var new_theme = current_theme === "dark" ? "light" : "dark"; + localStorage.setItem(THEME_MODE_KEY, new_theme); + apply_theme(new_theme); +} + +function init_theme() { + var default_theme = "dark"; + var stored_theme = localStorage.getItem(THEME_MODE_KEY) || default_theme; + apply_theme(stored_theme); +} + +function prepare_button() { + var theme_toggle_button = document.querySelector(".toggle-theme-btn"); + if (theme_toggle_button) { + theme_toggle_button.addEventListener("click", toggle_theme); + } +} + +document.addEventListener("DOMContentLoaded", prepare_button); diff --git a/docs/web/templates/index.html b/docs/web/templates/index.html index c63b4d53..61784f2d 100644 --- a/docs/web/templates/index.html +++ b/docs/web/templates/index.html @@ -24,6 +24,10 @@ + +
@@ -32,11 +36,15 @@
Tidy Web framework for OCaml and ReasonML
+ +
+ +
let hello who =
@@ -52,8 +60,7 @@ 
Tidy Web framework for OCaml and ReasonML
@@ Dream.router [ Dream.get "/" (fun _ -> Dream.html (hello "world")); - ] - @@ Dream.not_found
+ ] diff --git a/docs/web/web.opam b/docs/web/web.opam index 4d60507d..75555f02 100644 --- a/docs/web/web.opam +++ b/docs/web/web.opam @@ -1,11 +1,9 @@ opam-version: "2.0" +synopsis: "Dream docs" +maintainer: "Anton Bachin " depends: [ "lambdasoup" - "odoc" + "odoc" {= "2.0.2"} "soupault" {>= "2.5.0"} ] - -pin-depends: [ - ["odoc.2.0.0~master" "git+https://github.com/aantron/odoc.git#dbb37e20717985edfd8f734e00b9ab6f705d81a4"] -] diff --git a/docs/web/web.opam.locked b/docs/web/web.opam.locked index 7b304864..6068898b 100644 --- a/docs/web/web.opam.locked +++ b/docs/web/web.opam.locked @@ -1,71 +1,78 @@ opam-version: "2.0" depends: [ - "ISO8601" {= "0.2.6"} "astring" {= "0.8.5"} "base-bigarray" {= "base"} "base-bytes" {= "base"} "base-threads" {= "base"} "base-unix" {= "base"} - "base64" {= "3.5.0"} - "bigarray-compat" {= "1.0.0"} - "cmdliner" {= "1.0.4"} - "conf-libev" {= "4-11"} - "containers" {= "3.3"} - "cppo" {= "1.6.7"} + "base64" {= "3.5.1"} + "bigarray-compat" {= "1.1.0"} + "bos" {= "0.2.1"} + "camlp-streams" {= "5.0.1"} + "camomile" {= "1.0.2"} + "cmdliner" {= "1.1.1"} + "conf-libev" {= "4-12"} + "conf-pkg-config" {= "2"} + "containers" {= "3.11"} + "cppo" {= "1.6.9"} "csexp" {= "1.5.1"} - "cstruct" {= "6.0.0"} - "dune" {= "2.8.5"} - "dune-configurator" {= "2.8.5"} - "ezjsonm" {= "1.2.0"} - "fileutils" {= "0.6.3"} - "fmt" {= "0.8.9"} + "cstruct" {= "6.1.1"} + "ctypes" {= "0.20.1"} + "digestif" {= "1.1.3"} + "dune" {= "3.7.0"} + "dune-configurator" {= "3.7.0"} + "either" {= "1.0.0"} + "eqaf" {= "0.9"} + "ezjsonm" {= "1.3.0"} + "fileutils" {= "0.6.4"} + "fmt" {= "0.9.0"} "fpath" {= "0.7.3"} - "hex" {= "1.4.0"} - "jingoo" {= "1.4.3"} - "jsonm" {= "1.0.1"} - "lambdasoup" {= "0.7.2"} + "hex" {= "1.5.0"} + "integers" {= "0.7.0"} + "jingoo" {= "1.4.4"} + "jsonm" {= "1.0.2"} + "lambdasoup" {= "1.0.0"} "logs" {= "0.7.0"} - "lua-ml" {= "0.9.2"} - "lwt" {= "5.4.0"} - "markup" {= "1.0.0-1"} - "menhir" {= "20210310"} - "menhirLib" {= "20210310"} - "menhirSdk" {= "20210310"} - "mmap" {= "1.1.0"} - "ocaml" {= "4.12.0"} - "ocaml-base-compiler" {= "4.12.0"} - "ocaml-compiler-libs" {= "v0.12.3"} + "lua-ml" {= "0.9.4"} + "lwt" {= "5.6.1"} + "markup" {= "1.0.3"} + "menhir" {= "20211128"} + "menhirLib" {= "20211128"} + "menhirSdk" {= "20211128"} + "ocaml" {= "4.14.1"} + "ocaml-base-compiler" {= "4.14.1"} + "ocaml-compiler-libs" {= "v0.12.4"} "ocaml-config" {= "2"} - "ocaml-migrate-parsetree" {= "2.1.0"} + "ocaml-migrate-parsetree" {= "2.4.0"} "ocaml-options-vanilla" {= "1"} - "ocaml-syntax-shims" {= "1.0.0"} - "ocamlbuild" {= "0.14.0"} - "ocamlfind" {= "1.9.1"} - "ocplib-endian" {= "1.1"} + "ocamlbuild" {= "0.14.2"} + "ocamlfind" {= "1.9.6"} + "ocplib-endian" {= "1.2"} "odate" {= "0.6"} - "odoc" {= "2.0.0~master"} + "odoc" {= "2.0.2"} + "odoc-parser" {= "1.0.1"} + "otoml" {= "1.0.4"} "ppx_derivers" {= "1.2.1"} "ppx_deriving" {= "5.2.1"} - "ppxlib" {= "0.22.0"} - "re" {= "1.9.0"} + "ppxlib" {= "0.25.1"} + "re" {= "1.10.4"} "result" {= "1.5"} + "rresult" {= "0.7.0"} "seq" {= "base"} - "sexplib0" {= "v0.14.0"} - "soupault" {= "2.5.0"} - "spelll" {= "0.3"} + "sexplib0" {= "v0.15.1"} + "soupault" {= "4.4.0"} + "spelll" {= "0.4"} "stdlib-shims" {= "0.3.0"} - "stringext" {= "1.6.0"} - "toml" {= "7.0.0"} - "topkg" {= "1.0.3"} - "tsort" {= "2.0.0"} - "tyxml" {= "4.4.0"} + "topkg" {= "1.0.7"} + "tsort" {= "2.1.0"} + "tyxml" {= "4.5.0"} "uchar" {= "0.0.2"} - "uucp" {= "13.0.0"} - "uutf" {= "1.0.2"} -] -pin-depends: [ - ["odoc.2.0.0~master" "git+https://github.com/aantron/odoc.git#dbb37e20717985edfd8f734e00b9ab6f705d81a4"] + "uucp" {= "15.0.0"} + "uutf" {= "1.0.3"} + "yaml" {= "3.1.0"} ] name: "web" -version: "dev" +version: "~dev" +synopsis: "Dream docs" +maintainer: "Anton Bachin " diff --git a/dream-httpaf.opam b/dream-httpaf.opam new file mode 100644 index 00000000..57afb7f1 --- /dev/null +++ b/dream-httpaf.opam @@ -0,0 +1,47 @@ +opam-version: "2.0" + +synopsis: "Internal: shared http/af stack for Dream (server) and Hyper (client)" +description: "This package does not have a stable API." + +license: "MIT" +homepage: "https://github.com/aantron/dream" +doc: "https://aantron.github.io/dream" +bug-reports: "https://github.com/aantron/dream/issues" +dev-repo: "git+https://github.com/aantron/dream.git" + +author: "Anton Bachin " +maintainer: "Anton Bachin " + +depends: [ + "dream-pure" + "dune" {>= "2.7.0"} # --instrument-with. + "lwt" + "lwt_ppx" {>= "1.2.2"} + "lwt_ssl" + "ocaml" {>= "4.08.0"} + "ssl" {>= "0.5.8"} # Ssl.get_negotiated_alpn_protocol. + + # Currently vendored. + # "gluten" + # "gluten-lwt-unix" + # "httpaf" + # "httpaf-lwt-unix" + # "h2" + # "h2-lwt-unix" + # "hpack" + # "websocketaf" + + # Dependencies of vendored packages. + "angstrom" {>= "0.14.0"} + "base64" {>= "3.0.0"} + "bigstringaf" {>= "0.5.0"} # h2. + "digestif" {>= "0.7.2"} # websocket/af, sha1, default implementation. + "faraday" {>= "0.6.1"} + "faraday-lwt-unix" + "lwt_ssl" {>= "1.2.0"} # Gluten. + "psq" # h2. +] + +build: [ + ["dune" "build" "-p" name "-j" jobs] +] diff --git a/dream-mirage.opam b/dream-mirage.opam index dc846e77..7db4b607 100644 --- a/dream-mirage.opam +++ b/dream-mirage.opam @@ -47,16 +47,24 @@ author: "Anton Bachin " maintainer: "Anton Bachin " depends: [ - "dream" - "dune" {>= "2.7.0"} - "dream" "bigarray-compat" "bigstringaf" "digestif" {>= "1.0.0"} + "dream" + "dream-httpaf" + "dream-pure" + "dune" {>= "2.7.0"} + "duration" + "emile" {>= "1.1"} + "ke" {>= "0.4"} # paf. + "letsencrypt" {>= "0.3.0"} "lwt" - "tls-mirage" "lwt_ppx" {>= "1.2.2"} - "letsencrypt" {>= "0.3.0"} + "mimic" {>= "0.0.5"} + "mirage-time" + "rresult" + "tcpip" + "tls-mirage" ] build: [ diff --git a/dream-pure.opam b/dream-pure.opam new file mode 100644 index 00000000..6f6b7f26 --- /dev/null +++ b/dream-pure.opam @@ -0,0 +1,35 @@ +opam-version: "2.0" + +synopsis: "Internal: shared HTTP types for Dream (server) and Hyper (client)" +description: "This package does not have a stable API." + +license: "MIT" +homepage: "https://github.com/aantron/dream" +doc: "https://aantron.github.io/dream" +bug-reports: "https://github.com/aantron/dream/issues" +dev-repo: "git+https://github.com/aantron/dream.git" + +author: "Anton Bachin " +maintainer: "Anton Bachin " + +depends: [ + "base64" {>= "3.1.0"} # Base64.encode_string. + "bigstringaf" {>= "0.5.0"} # Bigstringaf.to_string. + "dune" {>= "2.7.0"} # --instrument-with. + "hmap" + "lwt" + "lwt_ppx" {>= "1.2.2"} + "ocaml" {>= "4.08.0"} + "ptime" {>= "0.8.1"} # Ptime.weekday. + "uri" {>= "4.2.0"} + + # Testing, development. + "alcotest" {with-test} + "bisect_ppx" {with-test & >= "2.5.0"} # --instrument-with. + "ppx_expect" {with-test} + "ppx_yojson_conv" {with-test} +] + +build: [ + ["dune" "build" "-p" name "-j" jobs] +] diff --git a/dream.opam b/dream.opam index 9dc72d81..d2edf3f1 100644 --- a/dream.opam +++ b/dream.opam @@ -48,54 +48,36 @@ maintainer: "Anton Bachin " depends: [ "base-unix" - "base64" {>= "3.1.0"} # Base64.encode_string. "bigarray-compat" - "caqti" {>= "1.6.0"} # https://github.com/aantron/dream/issues/44. - "caqti-lwt" - "conf-libev" {os != "win32"} + "camlp-streams" + "caqti" {>= "2.0.0"} + "caqti-lwt" {>= "2.0.0"} + ("conf-libev" {os != "win32"} | "ocaml" {os = "win32"}) "cstruct" {>= "6.0.0"} + "dream-httpaf" {>= "1.0.0~alpha2"} + "dream-pure" {>= "1.0.0~alpha2"} "dune" {>= "2.7.0"} # --instrument-with. - "emile" "fmt" {>= "0.8.7"} # `Italic. "graphql_parser" "graphql-lwt" - "hmap" + "lambdasoup" {>= "0.6.1"} "lwt" "lwt_ppx" {>= "1.2.2"} "lwt_ssl" "logs" {>= "0.5.0"} "magic-mime" - "mimic" - "mirage-clock" + "markup" {>= "1.0.2"} + "mirage-clock" {>= "3.0.0"} # now_d_ps : unit -> int * int64. "mirage-crypto" {>= "0.8.1"} # AES-256-GCM. - "mirage-crypto-rng" {>= "0.8.0"} # Signature of initialize. - "mirage-time" - "mirage-stack" - "multipart_form" {>= "0.3.0"} + "mirage-crypto-rng" + "mirage-crypto-rng-lwt" + "multipart_form" {>= "0.4.0"} + "multipart_form-lwt" "ocaml" {>= "4.08.0"} + "ptime" {>= "0.8.1"} # Ptime.v. "ssl" {>= "0.5.8"} # Ssl.get_negotiated_alpn_protocol. "uri" {>= "4.2.0"} "yojson" # ... - "ptime" {>= "0.8.1"} # time and date - - # Currently vendored. - # "gluten" - # "gluten-lwt-unix" - # "httpaf" - # "httpaf-lwt-unix" - # "h2" - # "h2-lwt-unix" - # "hpack" - # "websocketaf" - - # Dependencies of vendored packages. - "angstrom" {>= "0.14.0"} - "bigstringaf" {>= "0.5.0"} # h2. - "digestif" {>= "0.7.2"} # websocket/af, sha1, default implementation. - "faraday" {>= "0.6.1"} - "faraday-lwt-unix" - "psq" # h2. - "result" # http/af, websocket/af. # Testing, development. "alcotest" {with-test} @@ -103,13 +85,16 @@ depends: [ "caqti-driver-postgresql" {with-test} "caqti-driver-sqlite3" {with-test} "crunch" {with-test} - "lambdasoup" {with-test} - "ppx_expect" {with-test} + "js_of_ocaml" {with-test} + "js_of_ocaml-ppx" {with-test} + "ppx_expect" {with-test & >= "v0.15.0"} # Formatting changes. "ppx_yojson_conv" {with-test} "reason" {with-test} "tyxml" {with-test & >= "4.5.0"} - "tyxml-jsx" {with-test & >= "4.5.0"} - "tyxml-ppx" {with-test & >= "4.5.0"} + + # Blocked until https://github.com/ocsigen/tyxml/pull/312. + # "tyxml-jsx" {with-test & >= "4.5.0"} + # "tyxml-ppx" {with-test & >= "4.5.0"} ] build: [ diff --git a/example/1-hello/README.md b/example/1-hello/README.md index 64ef42f6..6d09ca5e 100644 --- a/example/1-hello/README.md +++ b/example/1-hello/README.md @@ -12,9 +12,9 @@ let () =
-It's the absolute minimum Dream server. It responds to all requests with the -same text. At startup, Dream prints a message to the log, telling you where to -point your browser. Your terminal probably makes the link clickable. +It's the minimal Dream server. It responds to all requests with the same text. +At startup, Dream prints a message to the log, telling you where to point your +browser. Your terminal probably makes the link clickable.
$ cd example/1-hello
 $ npm install esy && npx esy
@@ -53,7 +53,7 @@ name of the `.ml` file, but with `.ml` changed to `.exe`.
 - [**`r-hello`**](../r-hello#files) is a Reason syntax version of this example.
 - [**`w-esy`**](../w-esy#files) gives more detail on the [esy](https://esy.sh/)
   packaging.
-- [**`w-fswatch`**](../w-fswatch#files) sets up a primitive development watcher.
+- [**`w-watch`**](../w-watch#files) sets up a development watcher.
 
 
 
diff --git a/example/1-hello/esy.json b/example/1-hello/esy.json index 3e71335d..3dc785b3 100644 --- a/example/1-hello/esy.json +++ b/example/1-hello/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/2-middleware/esy.json b/example/2-middleware/esy.json index c810fd25..6fb945e6 100644 --- a/example/2-middleware/esy.json +++ b/example/2-middleware/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/3-router/README.md b/example/3-router/README.md index 70fd2c80..d290181f 100644 --- a/example/3-router/README.md +++ b/example/3-router/README.md @@ -20,10 +20,9 @@ let () = Dream.get "/echo/:word" (fun request -> - Dream.html (Dream.param "word" request)); + Dream.html (Dream.param request "word")); ] - @@ Dream.not_found ```
$ cd example/3-router
@@ -42,12 +41,7 @@ The syntax `:word` in a route creates a path parameter, which can be read with
 
 
 
-[The whole router is a middleware](https://aantron.github.io/dream/#val-router),
-just like [`Dream.logger`](https://aantron.github.io/dream/#val-logger). When
-none of the routes match, the router passes the request to the next handler,
-which is right beneath it. In this example, we just respond with `404 Not
-Found` when that happens.
-
+When none of the routes match, the router returns a `404 Not Found` response.
 Except for the status code, the `404 Not Found` response is *completely* empty,
 so it might not display well in your browser. In example
 [**`9-error`**](../9-error#files), we will decorate all error responses with
diff --git a/example/3-router/esy.json b/example/3-router/esy.json
index 94efa7b9..93db2682 100644
--- a/example/3-router/esy.json
+++ b/example/3-router/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/3-router/router.ml b/example/3-router/router.ml
index f2d45dae..fb1a9dab 100644
--- a/example/3-router/router.ml
+++ b/example/3-router/router.ml
@@ -9,7 +9,6 @@ let () =
 
     Dream.get "/echo/:word"
       (fun request ->
-        Dream.html (Dream.param "word" request));
+        Dream.html (Dream.param request "word"));
 
   ]
-  @@ Dream.not_found
diff --git a/example/4-counter/README.md b/example/4-counter/README.md
index f65f3f3d..4c495ba6 100644
--- a/example/4-counter/README.md
+++ b/example/4-counter/README.md
@@ -22,7 +22,6 @@ let () =
     Dream.get "/" (fun _ ->
       Dream.html (Printf.sprintf "Saw %i request(s)!" !count));
   ]
-  @@ Dream.not_found
 ```
 
$ cd example/4-counter
 $ npm install esy && npx esy
@@ -42,14 +41,6 @@ promise with [Lwt](https://github.com/ocsigen/lwt#readme), the promise library
 used by Dream. The next example, [**`5-promise`**](../5-promise#files), does
 exactly that!
 
-
 
**Next steps:** diff --git a/example/4-counter/counter.ml b/example/4-counter/counter.ml index 0f4c9d50..2a1d10ac 100644 --- a/example/4-counter/counter.ml +++ b/example/4-counter/counter.ml @@ -12,4 +12,3 @@ let () = Dream.get "/" (fun _ -> Dream.html (Printf.sprintf "Saw %i request(s)!" !count)); ] - @@ Dream.not_found diff --git a/example/4-counter/esy.json b/example/4-counter/esy.json index b783e0a6..afc4c40f 100644 --- a/example/4-counter/esy.json +++ b/example/4-counter/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/5-promise/README.md b/example/5-promise/README.md index 60110392..a8d932e3 100644 --- a/example/5-promise/README.md +++ b/example/5-promise/README.md @@ -38,7 +38,6 @@ let () = !successful !failed)); ] - @@ Dream.not_found ```
$ cd example/5-promise
diff --git a/example/5-promise/esy.json b/example/5-promise/esy.json
index 0df7adec..ca93786e 100644
--- a/example/5-promise/esy.json
+++ b/example/5-promise/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/5-promise/promise.ml b/example/5-promise/promise.ml
index 34bc200c..ffa27902 100644
--- a/example/5-promise/promise.ml
+++ b/example/5-promise/promise.ml
@@ -27,4 +27,3 @@ let () =
         !successful !failed));
 
   ]
-  @@ Dream.not_found
diff --git a/example/6-echo/README.md b/example/6-echo/README.md
index c1f5f58f..f05ad376 100644
--- a/example/6-echo/README.md
+++ b/example/6-echo/README.md
@@ -18,7 +18,6 @@ let () =
         body);
 
   ]
-  @@ Dream.not_found
 ```
 
 
$ cd example/6-echo
diff --git a/example/6-echo/echo.ml b/example/6-echo/echo.ml
index fbdca6fe..4f63612c 100644
--- a/example/6-echo/echo.ml
+++ b/example/6-echo/echo.ml
@@ -10,4 +10,3 @@ let () =
         body);
 
   ]
-  @@ Dream.not_found
diff --git a/example/6-echo/esy.json b/example/6-echo/esy.json
index 6f1ef285..49d5597f 100644
--- a/example/6-echo/esy.json
+++ b/example/6-echo/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/7-template/README.md b/example/7-template/README.md
index 4162f6c0..03ec529a 100644
--- a/example/7-template/README.md
+++ b/example/7-template/README.md
@@ -23,12 +23,11 @@ let () =
 
     Dream.get "/:word"
       (fun request ->
-        Dream.param "word" request
+        Dream.param request "word"
         |> render
         |> Dream.html);
 
   ]
-  @@ Dream.not_found
 ```
 
 
$ cd example/7-template
@@ -141,8 +140,10 @@ and not supported by Dream.
 
 **See also:**
 
-- [**`w-template-files`**](../w-template-files) moves the template into a
+- [**`w-template-files`**](../w-template-files#files) moves the template into a
   separate `.eml.html` to avoid problems with editor support.
+- [**`w-template-logic`**](../w-template-logic#files) shows how to put control
+  flow into templates.
 - [**`w-tyxml`**](../w-tyxml#files) shows how to use
   [TyXML](https://github.com/ocsigen/tyxml), a different templater that uses
   OCaml's type system to prevent emitting many kinds of invalid HTML.
diff --git a/example/7-template/esy.json b/example/7-template/esy.json
index 7fa24238..ef14e89b 100644
--- a/example/7-template/esy.json
+++ b/example/7-template/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/7-template/template.eml.ml b/example/7-template/template.eml.ml
index f70a1129..f6cd751e 100644
--- a/example/7-template/template.eml.ml
+++ b/example/7-template/template.eml.ml
@@ -12,9 +12,8 @@ let () =
 
     Dream.get "/:word"
       (fun request ->
-        Dream.param "word" request
+        Dream.param request "word"
         |> render
         |> Dream.html);
 
   ]
-  @@ Dream.not_found
diff --git a/example/8-debug/README.md b/example/8-debug/README.md
index fc3057af..fa68800e 100644
--- a/example/8-debug/README.md
+++ b/example/8-debug/README.md
@@ -2,12 +2,12 @@
 
 
-Getting Dream to respond with more debug information is as easy as adding -`~debug:true` to [`Dream.run`](https://aantron.github.io/dream/#val-run): +Dream has a built-in error handler for showing debug information. You can enable +it by passing it to `Dream.run`: ```ocaml let () = - Dream.run ~debug:true + Dream.run ~error_handler:Dream.debug_error_handler @@ Dream.logger @@ Dream.router [ @@ -20,7 +20,6 @@ let () = raise (Failure "The Web app failed!")); ] - @@ Dream.not_found ```
$ cd example/8-debug
@@ -38,31 +37,34 @@ response, and [http://localhost:8080/fail](http://localhost:8080/fail)
 debugger will show reports like this:
 
 ```
-(Failure "The Web app failed!")
-Raised at Stdlib__string.index_rec in file "string.ml", line 115, characters 19-34
-Called from Sexplib0__Sexp.Printing.index_of_newline in file "src/sexp.ml", line 113, characters 13-47
+Failure("The Web app failed!")
+Raised at Stdlib__map.Make.find in file "map.ml", line 137, characters 10-25
+Called from Logs.Tag.find in file "src/logs.ml", line 154, characters 14-32
 
 From: Application
 Blame: Server
 Severity: Error
 
-Client: 127.0.0.1:61988
+Client: 127.0.0.1:64687
 
 GET /fail HTTP/1.1
 Host: localhost:8080
 Connection: keep-alive
 Upgrade-Insecure-Requests: 1
-User-Agent: Mozilla/5.0 [...snip...]
-Accept: text/html,application/xhtml+xml, [...snip...]
+User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
 Sec-GPC: 1
 Sec-Fetch-Site: none
 Sec-Fetch-Mode: navigate
 Sec-Fetch-User: ?1
 Sec-Fetch-Dest: document
 Accept-Encoding: gzip, deflate, br
-Accept-Language: en-US;q=0.9,en;q=0.8
+Accept-Language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
 
-dream.request_id.last_id: 2
+dream.client: 127.0.0.1:64687
+dream.tls: false
+dream.request_id: 3
+dream.params:
 ```
 
 
@@ -76,7 +78,7 @@ As you can see, the report includes:
 - `Severity:` a suggested log level for the error,
 - `Client:` the client address,
 - request headers,
-- any request-scoped and application-scoped variables set in the request.
+- any other request variables.
 
 
@@ -91,8 +93,9 @@ work with in development.
 
 
-Both the debugger's output and the non-debug error page are fully customizable -— we will do this in the [very next example](../9-error#files)! +You can have Dream show a custom error page with any information or graphics +that you like — we will do this in the [very next +example](../9-error#files)! diff --git a/example/8-debug/debug.ml b/example/8-debug/debug.ml index 24bd25c3..f7548a38 100644 --- a/example/8-debug/debug.ml +++ b/example/8-debug/debug.ml @@ -1,5 +1,5 @@ let () = - Dream.run ~debug:true + Dream.run ~error_handler:Dream.debug_error_handler @@ Dream.logger @@ Dream.router [ @@ -12,4 +12,3 @@ let () = raise (Failure "The Web app failed!")); ] - @@ Dream.not_found diff --git a/example/8-debug/esy.json b/example/8-debug/esy.json index c42b6512..91383720 100644 --- a/example/8-debug/esy.json +++ b/example/8-debug/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/9-error/README.md b/example/9-error/README.md index beb7aa57..fdd1c21e 100644 --- a/example/9-error/README.md +++ b/example/9-error/README.md @@ -10,28 +10,21 @@ an `error_handler` is to call [`Dream.error_template`](https://aantron.github.io/dream/#val-error_template): ```ocaml -let my_error_template debug_info suggested_response = +let my_error_template _error debug_info suggested_response = let status = Dream.status suggested_response in let code = Dream.status_to_int status and reason = Dream.status_to_string status in - suggested_response - |> Dream.with_header "Content-Type" Dream.text_html - |> Dream.with_body begin + Dream.set_header suggested_response "Content-Type" Dream.text_html; + Dream.set_body suggested_response begin

<%i code %> <%s reason %>

- -% begin match debug_info with -% | None -> () -% | Some debug_info -> -
<%s debug_info %>
-% end; - +
<%s debug_info %>
- end - |> Lwt.return + end; + Lwt.return suggested_response let () = Dream.run ~error_handler:(Dream.error_template my_error_template) @@ -50,12 +43,6 @@ Try it in the [playground](http://dream.as/9-error). We kept the error template simple for the sake of the example, but this is where you'd put in neat graphics to make a beautiful error page! -This app doesn't show debug information by default. However, try adding -`~debug:true` to [`Dream.run`](https://aantron.github.io/dream/#val-run), -rebuilding the app, and accessing it again. You will see the same kind of output -as in example [**`8-debug`**](../8-debug#files), but now you control its -placement and styling. -
Dream will call the error template for every single error response it generates: @@ -77,10 +64,8 @@ including return a completely new response.
-`debug_info` is `None` by default. If you passed `~debug:true` to -[`Dream.run`](https://aantron.github.io/dream/#val-run), it is `Some` of a -string that contains the debug info that we saw in the previous example, -[**`8-debug`**](../8-debug#files). +`debug_info` is a multiline string containing the same information as in the +previous example, [**`8-debug`**](../8-debug#files). @@ -88,8 +73,9 @@ string that contains the debug info that we saw in the previous example, If you don't customize the error handler, Dream defaults to sending only empty responses, so that your application can be fully localization-friendly — -even at the lowest levels. This is also in accordance with the [OWASP Error -Handling Cheat +even at the lowest levels. Rather than leaking hardcoded English strings, Dream +relies on the user's browser to display its own built-in, localized error pages. +This choice is also in accordance with the [OWASP Error Handling Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Error_Handling_Cheat_Sheet.html).
diff --git a/example/9-error/error.eml.ml b/example/9-error/error.eml.ml index f3565c1e..fdd88f81 100644 --- a/example/9-error/error.eml.ml +++ b/example/9-error/error.eml.ml @@ -3,25 +3,18 @@ let my_error_template _error debug_info suggested_response = let code = Dream.status_to_int status and reason = Dream.status_to_string status in - suggested_response - |> Dream.with_header "Content-Type" Dream.text_html - |> Dream.with_body begin + Dream.set_header suggested_response "Content-Type" Dream.text_html; + Dream.set_body suggested_response begin

<%i code %> <%s reason %>

- -% begin match debug_info with -% | None -> () -% | Some debug_info -> -
<%s debug_info %>
-% end; - +
<%s debug_info %>
- end - |> Lwt.return + end; + Lwt.return suggested_response let () = Dream.run ~error_handler:(Dream.error_template my_error_template) @@ Dream.logger - @@ Dream.not_found + @@ fun _ -> Dream.empty `Not_Found diff --git a/example/9-error/esy.json b/example/9-error/esy.json index 62f66784..d582da02 100644 --- a/example/9-error/esy.json +++ b/example/9-error/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/README.md b/example/README.md index 1891dd5e..f38fba43 100644 --- a/example/README.md +++ b/example/README.md @@ -55,6 +55,10 @@ There are several examples showing Dream with Reason syntax. - [**`r-hello`**](r-hello#files)  —  the simplest Dream server. - [**`r-template`**](r-template#files)  —  renders HTML templates and protects against XSS. +- [**`r-template-files`**](r-template-files#files)  —  templates + in separate `.html` files for better editor support. +- [**`r-template-logic`**](r-template-logic#files)  —  control + flow inside templates. - [**`r-template-stream`**](r-template-stream#files)  —  streams templates as response bodies. - [**`r-tyxml`**](r-tyxml#files)  —  type-checked server-side @@ -79,6 +83,7 @@ There are several examples showing Dream with Reason syntax. - [**`z-heroku`**](z-heroku#files)  —  to [Heroku](https://www.heroku.com). +- [**`z-fly`**](z-fly#files)  —  to [Fly.io](https://fly.io/). - [**`z-docker-esy`**](z-docker-esy#files)  —  on a server, using Docker, with package manager esy. - [**`z-docker-opam`**](z-docker-opam#files)  —  on a server, @@ -97,8 +102,10 @@ if something is missing!
-- [**`w-template-files`**](w-template-files#files)  —  - templates in separate `.html` files for better editor support. +- [**`w-template-files`**](w-template-files#files)  —  templates + in separate `.html` files for better editor support. +- [**`w-template-logic`**](w-template-logic#files)  —  control + flow inside templates. - [**`w-graphql-subscription`**](w-graphql-subscription#files)  —  GraphQL subscriptions. - [**`w-postgres`**](w-postgres#files)  —  connects to a @@ -113,11 +120,12 @@ if something is missing! [esy](https://esy.sh/), an npm-like package manager. - [**`w-one-binary`**](w-one-binary#files)  —  bakes static assets into a self-contained server binary. -- [**`w-fswatch`**](w-fswatch#files)  —  sets up a development - watcher using fswatch. +- [**`w-watch`**](w-watch#files)  —  sets up a development + watcher. - [**`w-live-reload`**](w-live-reload#files)  —  a simple live-reloading setup. -- [**`w-nginx`**](w-nginx#files)  —  uses nginx as a reverse proxy. +- [**`w-nginx`**](w-nginx#files)  —  uses nginx as a + reverse proxy. - [**`w-tyxml`**](w-tyxml#files)  —  uses TyXML for type-checked HTML templating. - [**`w-long-polling`**](w-long-polling#files)  —  old form of diff --git a/example/a-log/README.md b/example/a-log/README.md index ec33ce19..8423a93b 100644 --- a/example/a-log/README.md +++ b/example/a-log/README.md @@ -22,7 +22,6 @@ let () = raise (Failure "The Web app failed!")); ] - @@ Dream.not_found ```
$ cd example/a-log
diff --git a/example/a-log/esy.json b/example/a-log/esy.json
index af7e524e..1a22a9c0 100644
--- a/example/a-log/esy.json
+++ b/example/a-log/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/a-log/log.ml b/example/a-log/log.ml
index 7afc7e21..457bf791 100644
--- a/example/a-log/log.ml
+++ b/example/a-log/log.ml
@@ -14,4 +14,3 @@ let () =
         raise (Failure "The Web app failed!"));
 
   ]
-  @@ Dream.not_found
diff --git a/example/b-session/README.md b/example/b-session/README.md
index 8bab6403..2c93fdb3 100644
--- a/example/b-session/README.md
+++ b/example/b-session/README.md
@@ -11,10 +11,10 @@ let () =
   @@ Dream.memory_sessions
   @@ fun request ->
 
-    match Dream.session "user" request with
+    match Dream.session_field request "user" with
     | None ->
       let%lwt () = Dream.invalidate_session request in
-      let%lwt () = Dream.put_session "user" "alice" request in
+      let%lwt () = Dream.set_session_field request "user" "alice" in
       Dream.html "You weren't logged in; but now you are!"
 
     | Some username ->
@@ -62,12 +62,12 @@ There are two other session back ends, which are persistent:
   stores session data in encrypted cookies. That is, session data is stored on
   clients, rather than on the server. You can replace `Dream.memory_sessions`
   with `Dream.cookie_sessions` and it will work right away. However, if you
-  want to be able to decrypt sessions set by previous runs of the server, pass
-  `~secret:"my-secret"` to
-  [`Dream.run`](https://aantron.github.io/dream/#val-run) so that it doesn't
-  generate a random key each time.
+  want to be able to decrypt sessions set by previous runs of the server, use
+  the [`Dream.set_secret`](https://aantron.github.io/dream/#val-set_secret)
+  middleware before `Dream.cookie_sessions`. If you don't, the server will be
+  using a different random encryption key each time it starts.
 - [`Dream.sql_sessions`](https://aantron.github.io/dream/#val-sql_sessions)
-  stores sessions in a database. It's used in example
+  stores sessions in a database. It is shown in example
   [**`h-sql`**](../h-sql#files).
 
 
diff --git a/example/b-session/esy.json b/example/b-session/esy.json index 92d97769..78bf6712 100644 --- a/example/b-session/esy.json +++ b/example/b-session/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/b-session/session.ml b/example/b-session/session.ml index b1d981b5..8a0c0458 100644 --- a/example/b-session/session.ml +++ b/example/b-session/session.ml @@ -4,10 +4,10 @@ let () = @@ Dream.memory_sessions @@ fun request -> - match Dream.session "user" request with + match Dream.session_field request "user" with | None -> let%lwt () = Dream.invalidate_session request in - let%lwt () = Dream.put_session "user" "alice" request in + let%lwt () = Dream.set_session_field request "user" "alice" in Dream.html "You weren't logged in; but now you are!" | Some username -> diff --git a/example/c-cookie/README.md b/example/c-cookie/README.md index b9b0e906..10c67b5d 100644 --- a/example/c-cookie/README.md +++ b/example/c-cookie/README.md @@ -6,20 +6,21 @@ Let's [set our own cookie](https://aantron.github.io/dream/#cookies): ```ocaml let () = - Dream.run ~secret:"foo" + Dream.run + @@ Dream.set_secret "foo" @@ Dream.logger @@ fun request -> - match Dream.cookie "ui.language" request with + match Dream.cookie request "ui.language" with | Some value -> Printf.ksprintf Dream.html "Your preferred language is %s!" (Dream.html_escape value) | None -> - Dream.response "Set language preference; come again!" - |> Dream.add_header "Content-Type" Dream.text_html - |> Dream.set_cookie "ui.language" "ut-OP" request - |> Lwt.return + let response = Dream.response "Set language preference; come again!" in + Dream.add_header response "Content-Type" Dream.text_html; + Dream.set_cookie response request "ui.language" "ut-OP"; + Lwt.return response ```
$ cd example/c-cookie
@@ -42,7 +43,9 @@ That's because it access certain fields of the request to set some fairly
 aggressive security defaults:
 
 - Cookie encryption, for which it accesses the encryption key. This is why we
-  passed `~secret` to [`Dream.run`](https://aantron.github.io/dream/#val-run).
+  used the
+  [`Dream.set_secret`](https://aantron.github.io/dream/#val-set_secret)
+  middleware.
 - Whether the request likely came through an HTTPS connection, to set the
   [`Secure`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
   attribute.
diff --git a/example/c-cookie/cookie.ml b/example/c-cookie/cookie.ml
index 197ef757..e145e5ee 100644
--- a/example/c-cookie/cookie.ml
+++ b/example/c-cookie/cookie.ml
@@ -1,15 +1,16 @@
 let () =
-  Dream.run ~secret:"foo"
+  Dream.run
+  @@ Dream.set_secret "foo"
   @@ Dream.logger
   @@ fun request ->
 
-    match Dream.cookie "ui.language" request with
+    match Dream.cookie request "ui.language" with
     | Some value ->
       Printf.ksprintf
         Dream.html "Your preferred language is %s!" (Dream.html_escape value)
 
     | None ->
-      Dream.response "Set language preference; come again!"
-      |> Dream.add_header "Content-Type" Dream.text_html
-      |> Dream.set_cookie "ui.language" "ut-OP" request
-      |> Lwt.return
+      let response = Dream.response "Set language preference; come again!" in
+      Dream.add_header response "Content-Type" Dream.text_html;
+      Dream.set_cookie response request "ui.language" "ut-OP";
+      Lwt.return response
diff --git a/example/c-cookie/esy.json b/example/c-cookie/esy.json
index c5b5c72a..4ad51afa 100644
--- a/example/c-cookie/esy.json
+++ b/example/c-cookie/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/d-form/README.md b/example/d-form/README.md
index 8a80c227..cac1d4db 100644
--- a/example/d-form/README.md
+++ b/example/d-form/README.md
@@ -16,7 +16,8 @@ let show_form ?message request =
       

You entered: <%s message %>!

% end; - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
@@ -42,7 +43,6 @@ let () = Dream.empty `Bad_Request); ] - @@ Dream.not_found ```
$ cd example/d-form
@@ -53,38 +53,39 @@ Try it in the [playground](http://dream.as/d-form).
 
 
-We didn't write a literal `
` tag in the template. Instead, we used -[`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) to generate -the tag. [`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) also -snuck in a hidden `` field containing a CSRF token: +The template adds a CSRF token to the form using +[`Dream.csrf_tag`](https://aantron.github.io/dream/#val-csrf_tag). Its output +looks something like this: ```html - -
``` -This hidden `dream.csrf` field helps to -[prevent CSRF attacks](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) -attacks against the form. +That generated, hidden `dream.csrf` field helps to [prevent CSRF +attacks](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html). +It should be the [first +field](https://portswigger.net/web-security/csrf/tokens#how-should-csrf-tokens-be-transmitted) +in your form. -[`Dream.form`](https://aantron.github.io/dream/#val-form) expects `dream.csrf` -and checks it. If there is anything wrong with the token, -[`Dream.form`](https://aantron.github.io/dream/#val-form) will return a [value -other than `` `Ok _``](https://aantron.github.io/dream/#type-form_result). +When the form is submitted and parsed using +[`Dream.form`](https://aantron.github.io/dream/#val-form), `Dream.form` expects +to find the `dream.csrf` field, and checks it. If there is anything wrong with +the CSRF token, [`Dream.form`](https://aantron.github.io/dream/#val-form) will +return a [value other than +`` `Ok _``](https://aantron.github.io/dream/#type-form_result).
The form fields carried inside `` `Ok _`` are returned in sorted order, so you can reliably pattern-match on them. -The bad token results, like `` `Expired _``, also carry the form fields. You can -add handling for them to recover. For example, if you receive an expired form, -you may want to resend it with some of the fields pre-filled to received -values, so that the user can try again quickly. +The bad token results, like `` `Expired (_, _)``, also carry the form fields. +You can add handling for them to recover. For example, if you receive a form +with an expired token, you may want to resend it with some of the fields pre- +filled to received values, so that the user can try again quickly. However, do not send back any sensitive data, because *any* result other than `` `Ok _`` *might* indicate an attack in progress. That said, `` `Expired _`` diff --git a/example/d-form/esy.json b/example/d-form/esy.json index 971f5e60..7932c23a 100644 --- a/example/d-form/esy.json +++ b/example/d-form/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/d-form/form.eml.ml b/example/d-form/form.eml.ml index 7ee950b5..40fe08fa 100644 --- a/example/d-form/form.eml.ml +++ b/example/d-form/form.eml.ml @@ -8,7 +8,8 @@ let show_form ?message request =

You entered: <%s message %>!

% end; - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
@@ -34,4 +35,3 @@ let () = Dream.empty `Bad_Request); ] - @@ Dream.not_found diff --git a/example/e-json/README.md b/example/e-json/README.md index fc26a1cf..b0eddddb 100644 --- a/example/e-json/README.md +++ b/example/e-json/README.md @@ -10,6 +10,8 @@ converter between JSON and an OCaml data type. We then create a little server that listens for JSON of the right shape, and echoes back its `message` field: ```ocaml +open Ppx_yojson_conv_lib.Yojson_conv.Primitives + type message_object = { message : string; } [@@deriving yojson] @@ -35,7 +37,6 @@ let () = |> Dream.json); ] - @@ Dream.not_found ``` To get this working, we have to add `ppx_yojson_conv` to our diff --git a/example/e-json/esy.json b/example/e-json/esy.json index bb18141f..d321e957 100644 --- a/example/e-json/esy.json +++ b/example/e-json/esy.json @@ -1,9 +1,10 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", "@opam/ppx_yojson_conv": "*", - "ocaml": "4.12.x" + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/e-json/json.ml b/example/e-json/json.ml index fa8fad05..3d839bf6 100644 --- a/example/e-json/json.ml +++ b/example/e-json/json.ml @@ -1,3 +1,5 @@ +open Ppx_yojson_conv_lib.Yojson_conv.Primitives + type message_object = { message : string; } [@@deriving yojson] @@ -23,4 +25,3 @@ let () = |> Dream.json); ] - @@ Dream.not_found diff --git a/example/f-static/README.md b/example/f-static/README.md index 308296ef..da6bbf17 100644 --- a/example/f-static/README.md +++ b/example/f-static/README.md @@ -20,7 +20,6 @@ let () = @@ Dream.router [ Dream.get "/static/**" (Dream.static ".") ] - @@ Dream.not_found ```
diff --git a/example/f-static/esy.json b/example/f-static/esy.json index 5177eb8d..c7ec6589 100644 --- a/example/f-static/esy.json +++ b/example/f-static/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/f-static/static.ml b/example/f-static/static.ml index 2ede0c2c..daf1c776 100644 --- a/example/f-static/static.ml +++ b/example/f-static/static.ml @@ -4,4 +4,3 @@ let () = @@ Dream.router [ Dream.get "/static/**" (Dream.static ".") ] - @@ Dream.not_found diff --git a/example/g-upload/README.md b/example/g-upload/README.md index b9f94ef9..ede8c0d7 100644 --- a/example/g-upload/README.md +++ b/example/g-upload/README.md @@ -11,7 +11,8 @@ sizes: let home request = - <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %> +
+ <%s! Dream.csrf_tag request %>
@@ -47,7 +48,6 @@ let () = | _ -> Dream.empty `Bad_Request); ] - @@ Dream.not_found ```
$ cd example/g-upload
@@ -85,10 +85,11 @@ streaming file uploads.
 [`Dream.multipart`](https://aantron.github.io/dream/#val-multipart) behaves just
 like [`Dream.form`](https://aantron.github.io/dream/#val-form) when it comes to
 [CSRF protection](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html).
-See example [**`d-form`**](../d-form#files). We still use
-[`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) to generate
-the form in the template. The only difference is that we now pass it
-``~enctype:`Multipart_form_data`` to make its output look like this:
+See example [**`d-form`**](../d-form#files). We use
+[`Dream.csrf_tag`](https://aantron.github.io/dream/#val-csrf_tag) to generate
+the CSRF token in the template, and pass the `enctype="multipart/form-data"`
+attribute as needed for forms to upload files. The template output looks like
+this:
 
 ```html
 
diff --git a/example/g-upload/esy.json b/example/g-upload/esy.json index d2ce772e..95dbe50e 100644 --- a/example/g-upload/esy.json +++ b/example/g-upload/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/g-upload/upload.eml.ml b/example/g-upload/upload.eml.ml index 5c2a1752..2471a4ac 100644 --- a/example/g-upload/upload.eml.ml +++ b/example/g-upload/upload.eml.ml @@ -1,7 +1,8 @@ let home request = - <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %> + + <%s! Dream.csrf_tag request %>
@@ -37,4 +38,3 @@ let () = | _ -> Dream.empty `Bad_Request); ] - @@ Dream.not_found diff --git a/example/h-sql/README.md b/example/h-sql/README.md index bf85abb1..4636c93a 100644 --- a/example/h-sql/README.md +++ b/example/h-sql/README.md @@ -9,21 +9,22 @@ a library for talking to SQL databases: ```ocaml module type DB = Caqti_lwt.CONNECTION -module R = Caqti_request module T = Caqti_type let list_comments = let query = - R.collect T.unit T.(tup2 int string) - "SELECT id, text FROM comment" in + let open Caqti_request.Infix in + (T.unit ->* T.(tup2 int string)) + "SELECT id, text FROM comment" in fun (module Db : DB) -> let%lwt comments_or_error = Db.collect_list query () in Caqti_lwt.or_fail comments_or_error let add_comment = let query = - R.exec T.string - "INSERT INTO comment (text) VALUES ($1)" in + let open Caqti_request.Infix in + (T.string ->. T.unit) + "INSERT INTO comment (text) VALUES ($1)" in fun text (module Db : DB) -> let%lwt unit_or_error = Db.exec query text in Caqti_lwt.or_fail unit_or_error @@ -35,7 +36,8 @@ let render comments request = % comments |> List.iter (fun (_id, comment) ->

<%s comment %>

<% ); %> - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
@@ -62,7 +64,6 @@ let () = Dream.empty `Bad_Request); ] - @@ Dream.not_found ```
$ cd example/h-sql
@@ -120,7 +121,7 @@ We also had to make an addition to our
 [`esy.json`](https://github.com/aantron/dream/blob/master/example/h-sql/esy.json):
 
 
"dependencies": {
-  "@opam/caqti-driver-sqlite3": "*"
+  "@opam/caqti-driver-sqlite3": "^1.7.0"
 }
 
diff --git a/example/h-sql/esy.json b/example/h-sql/esy.json index b0eb1d83..2b29f1e1 100644 --- a/example/h-sql/esy.json +++ b/example/h-sql/esy.json @@ -1,9 +1,10 @@ { "dependencies": { - "@opam/caqti-driver-sqlite3": "*", - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/caqti-driver-sqlite3": "^1.7.0", + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/h-sql/sql.eml.ml b/example/h-sql/sql.eml.ml index fe406b97..c542c4d8 100644 --- a/example/h-sql/sql.eml.ml +++ b/example/h-sql/sql.eml.ml @@ -1,19 +1,20 @@ module type DB = Caqti_lwt.CONNECTION -module R = Caqti_request module T = Caqti_type let list_comments = let query = - R.collect T.unit T.(tup2 int string) - "SELECT id, text FROM comment" in + let open Caqti_request.Infix in + (T.unit ->* T.(t2 int string)) + "SELECT id, text FROM comment" in fun (module Db : DB) -> let%lwt comments_or_error = Db.collect_list query () in Caqti_lwt.or_fail comments_or_error let add_comment = let query = - R.exec T.string - "INSERT INTO comment (text) VALUES ($1)" in + let open Caqti_request.Infix in + (T.string ->. T.unit) + "INSERT INTO comment (text) VALUES ($1)" in fun text (module Db : DB) -> let%lwt unit_or_error = Db.exec query text in Caqti_lwt.or_fail unit_or_error @@ -25,7 +26,8 @@ let render comments request = % comments |> List.iter (fun (_id, comment) ->

<%s comment %>

<% ); %> - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
@@ -52,4 +54,3 @@ let () = Dream.empty `Bad_Request); ] - @@ Dream.not_found diff --git a/example/i-graphql/README.md b/example/i-graphql/README.md index 921cd6e6..190bfeb9 100644 --- a/example/i-graphql/README.md +++ b/example/i-graphql/README.md @@ -19,7 +19,7 @@ let hardcoded_users = [ let user = Graphql_lwt.Schema.(obj "user" - ~fields:(fun _info -> [ + ~fields:[ field "id" ~typ:(non_null int) ~args:Arg.[] @@ -28,7 +28,7 @@ let user = ~typ:(non_null string) ~args:Arg.[] ~resolve:(fun _info user -> user.name); - ])) + ]) let schema = Graphql_lwt.Schema.(schema [ @@ -55,7 +55,6 @@ let () = Dream.any "/graphql" (Dream.graphql Lwt.return schema); Dream.get "/" (Dream.graphiql ~default_query "/graphql"); ] - @@ Dream.not_found ```
$ cd example/i-graphql
diff --git a/example/i-graphql/esy.json b/example/i-graphql/esy.json
index da7d0e49..5ced7825 100644
--- a/example/i-graphql/esy.json
+++ b/example/i-graphql/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/i-graphql/graphql.ml b/example/i-graphql/graphql.ml
index 27a68c7c..45d52d82 100644
--- a/example/i-graphql/graphql.ml
+++ b/example/i-graphql/graphql.ml
@@ -7,7 +7,7 @@ let hardcoded_users = [
 
 let user =
   Graphql_lwt.Schema.(obj "user"
-    ~fields:(fun _info -> [
+    ~fields:[
       field "id"
         ~typ:(non_null int)
         ~args:Arg.[]
@@ -16,7 +16,7 @@ let user =
         ~typ:(non_null string)
         ~args:Arg.[]
         ~resolve:(fun _info user -> user.name);
-    ]))
+    ])
 
 let schema =
   Graphql_lwt.Schema.(schema [
@@ -43,4 +43,3 @@ let () =
     Dream.any "/graphql" (Dream.graphql Lwt.return schema);
     Dream.get "/" (Dream.graphiql ~default_query "/graphql");
   ]
-  @@ Dream.not_found
diff --git a/example/j-stream/README.md b/example/j-stream/README.md
index 7e9a4c64..3b72575c 100644
--- a/example/j-stream/README.md
+++ b/example/j-stream/README.md
@@ -7,30 +7,29 @@ their whole bodies into memory, and then writing them. Here, we echo request
 bodies chunk by chunk:
 
 ```ocaml
-let echo request response =
-  let rec loop () =
-    match%lwt Dream.read request with
-    | None ->
-      Dream.close_stream response
-    | Some chunk ->
-      let%lwt () = Dream.write response chunk in
-      let%lwt () = Dream.flush response in
-      loop ()
-  in
-  loop ()
-
 let () =
   Dream.run
   @@ Dream.logger
   @@ Dream.router [
 
     Dream.post "/echo" (fun request ->
+      let request_stream = Dream.body_stream request in
+
       Dream.stream
         ~headers:["Content-Type", "application/octet-stream"]
-        (echo request));
+        (fun response_stream ->
+          let rec loop () =
+            match%lwt Dream.read request_stream with
+            | None ->
+              Dream.close response_stream
+            | Some chunk ->
+              let%lwt () = Dream.write response_stream chunk in
+              let%lwt () = Dream.flush response_stream in
+              loop ()
+          in
+          loop ()));
 
   ]
-  @@ Dream.not_found
 ```
 
 
$ cd example/j-stream
@@ -49,7 +48,15 @@ curl -X POST http://localhost:8080/echo -T -
 
 You will see the server responding immediately to each line on STDIN.
 
-See [*Streaming*](https://aantron.github.io/dream/#streaming) in the API docs.
+
+ +Note that you don't have to call +[`Dream.close`](https://aantron.github.io/dream/#val-close) on the stream +explicitly. [`Dream.stream`](https://aantron.github.io/dream/#val-stream) +automatically closes the stream when the callback's promise resolves or is +rejected with an exception. + +See [*Streams*](https://aantron.github.io/dream/#streams) in the API docs.
diff --git a/example/j-stream/esy.json b/example/j-stream/esy.json index b493821a..a4091268 100644 --- a/example/j-stream/esy.json +++ b/example/j-stream/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/j-stream/stream.ml b/example/j-stream/stream.ml index e16d30d6..08a222ca 100644 --- a/example/j-stream/stream.ml +++ b/example/j-stream/stream.ml @@ -1,24 +1,23 @@ -let echo request response = - let rec loop () = - match%lwt Dream.read request with - | None -> - Dream.close_stream response - | Some chunk -> - let%lwt () = Dream.write response chunk in - let%lwt () = Dream.flush response in - loop () - in - loop () - let () = Dream.run @@ Dream.logger @@ Dream.router [ Dream.post "/echo" (fun request -> + let request_stream = Dream.body_stream request in + Dream.stream ~headers:["Content-Type", "application/octet-stream"] - (echo request)); + (fun response_stream -> + let rec loop () = + match%lwt Dream.read request_stream with + | None -> + Dream.close response_stream + | Some chunk -> + let%lwt () = Dream.write response_stream chunk in + let%lwt () = Dream.flush response_stream in + loop () + in + loop ())); ] - @@ Dream.not_found diff --git a/example/k-websocket/README.md b/example/k-websocket/README.md index 341f1b40..981ec437 100644 --- a/example/k-websocket/README.md +++ b/example/k-websocket/README.md @@ -41,13 +41,11 @@ let () = Dream.websocket (fun websocket -> match%lwt Dream.receive websocket with | Some "Hello?" -> - let%lwt () = Dream.send websocket "Good-bye!" in - Dream.close_websocket websocket + Dream.send websocket "Good-bye!" | _ -> Dream.close_websocket websocket)); ] - @@ Dream.not_found ```
$ cd example/k-websocket
@@ -63,11 +61,19 @@ Visit [http://localhost:8080](http://localhost:8080)
 
 
-See [*WebSockets*](https://aantron.github.io/dream/#websockets) in the API docs. - If you are running under HTTPS, be sure to use `wss://` for the protocol scheme, rather than `ws://`, on the client. +You don't have to call +[`Dream.close_websocket`](https://aantron.github.io/dream/#val-close_websocket) +when you are done with the WebSocket. +[`Dream.websocket`](https://aantron.github.io/dream/#val-websocket) calls it +automatically when your callback's promise resolves or is rejected with an +exception. This example calls `Dream.close_websocket` in one branch just +because there is nothing else to do. + +See [*WebSockets*](https://aantron.github.io/dream/#websockets) in the API docs. +
**Last step:** @@ -76,4 +82,15 @@ rather than `ws://`, on the client.
+**See also:** + +- [**`w-chat`**](../w-chat#files) is a simple WebSocket-based chat application. +- [**`w-live-reload`**](../w-live-reload#files) uses WebSockets to implement + live reloading. +- [**`w-graphql-subscription`**](../w-graphql-subscription) does not show a + WebSocket directly, but shows GraphQL subscriptions, which are implemented + over WebSockets. + +
+ [Up to the tutorial index](../#readme) diff --git a/example/k-websocket/esy.json b/example/k-websocket/esy.json index e327d40e..16174a11 100644 --- a/example/k-websocket/esy.json +++ b/example/k-websocket/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/k-websocket/websocket.eml.ml b/example/k-websocket/websocket.eml.ml index 3fdff498..d75df5dc 100644 --- a/example/k-websocket/websocket.eml.ml +++ b/example/k-websocket/websocket.eml.ml @@ -31,10 +31,8 @@ let () = Dream.websocket (fun websocket -> match%lwt Dream.receive websocket with | Some "Hello?" -> - let%lwt () = Dream.send websocket "Good-bye!" in - Dream.close_websocket websocket + Dream.send websocket "Good-bye!" | _ -> Dream.close_websocket websocket)); ] - @@ Dream.not_found diff --git a/example/l-https/README.md b/example/l-https/README.md index c9aa1da0..29f04fdd 100644 --- a/example/l-https/README.md +++ b/example/l-https/README.md @@ -2,12 +2,12 @@
-Enabling HTTPS in Dream is very easy: just pass `~https:true` to +Enabling HTTPS in Dream is very easy: just pass `~tls:true` to [`Dream.run`](https://aantron.github.io/dream/#val-run): ```ocaml let () = - Dream.run ~https:true + Dream.run ~tls:true @@ Dream.logger @@ fun _ -> Dream.html "Good morning, world!" ``` diff --git a/example/l-https/esy.json b/example/l-https/esy.json index 3b2721b3..8ca91c4a 100644 --- a/example/l-https/esy.json +++ b/example/l-https/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/l-https/https.ml b/example/l-https/https.ml index d9f4a077..d3a9c565 100644 --- a/example/l-https/https.ml +++ b/example/l-https/https.ml @@ -1,4 +1,4 @@ let () = - Dream.run ~https:true + Dream.run ~tls:true @@ Dream.logger @@ fun _ -> Dream.html "Good morning, world!" diff --git a/example/r-advanced-template/README.md b/example/r-advanced-template/README.md deleted file mode 100644 index b12a78e2..00000000 --- a/example/r-advanced-template/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# `r-advanced-template` - -
- -Dream templates allow for interleaving any control structures with your template code. This example shows how to do this with if statements, list iterations, and pattern matching. Although it may seem intuitive that the code somehow 'returns' the template, in reality the HTML generation happens in an imperative style. This means that any code within the template must evaluate to `unit`, and so the semicolons in this example are not optional. We use `List.iter` instead of `List.map` for a similar reason. - -```reason -let render_home = tasks => { - - -

My TODO

- <% tasks |> List.iter(((name, complete)) => { %> -

Task <%s name %>: - <% if (complete) { %> - complete! - <% } else { %> - not complete - <% }; %> -

- <% }); %> - - -}; - - -// You can begin a line with `%` instead of using `<% ... %>` -let render_task = (tasks, task) => { - - -% (switch (List.find_opt(((task_, _)) => task == task_, tasks)) { -% | Some((name, complete)) => -

TODO task: <%s name %>, complete: <%B complete %>

-% | None => -

Task not found!

-% }); - - -}; - -let tasks = [ - ("write documentation", true), - ("create examples", true), - ("publish website", true), - ("profit", false), -]; - -let () = - Dream.run - @@ Dream.logger - @@ Dream.router([ - Dream.get("/", _ => render_home(tasks) |> Dream.html), - Dream.get("/:task", request => - Dream.param("task", request) |> render_task(tasks) |> Dream.html - ), - ]) - @@ Dream.not_found; -``` - -
$ cd example/r-advanced-template
-$ npm install esy && npx esy
-$ npx esy start
- -Try it in the [playground](http://dream.as/r-advanced-template). - -
- -**See also:** - -- [**`w-template**](../w-template) for more information about templates. -- [**`w-advanced-template**](../w-advanced-template) for the OCaml version of this example. - -
- -[Up to the example index](../#reason) diff --git a/example/r-advanced-template/template.eml.re b/example/r-advanced-template/template.eml.re deleted file mode 100644 index 66715bab..00000000 --- a/example/r-advanced-template/template.eml.re +++ /dev/null @@ -1,50 +0,0 @@ -let render_home = tasks => { - - -

My TODO

- <% tasks |> List.iter(((name, complete)) => { %> -

Task <%s name %>: - <% if (complete) { %> - complete! - <% } else { %> - not complete - <% }; %> -

- <% }); %> - - -}; - - -// You can begin a line with `%` instead of using `<% ... %>` -let render_task = (tasks, task) => { - - -% (switch (List.find_opt(((task_, _)) => task == task_, tasks)) { -% | Some((name, complete)) => -

TODO task: <%s name %>, complete: <%B complete %>

-% | None => -

Task not found!

-% }); - - -}; - -let tasks = [ - ("write documentation", true), - ("create examples", true), - ("publish website", true), - ("profit", false), -]; - -let () = - Dream.run - @@ Dream.logger - @@ Dream.router([ - Dream.get("/", _ => render_home(tasks) |> Dream.html), - Dream.get("/:task", request => - Dream.param("task", request) |> render_task(tasks) |> Dream.html - ), - ]) - @@ Dream.not_found; - diff --git a/example/r-fullstack-melange/README.md b/example/r-fullstack-melange/README.md index 41c455b7..ec2bf251 100644 --- a/example/r-fullstack-melange/README.md +++ b/example/r-fullstack-melange/README.md @@ -37,8 +37,7 @@ let () = Dream.get("/static/**", Dream.static("./static")), - ]) - @@ Dream.not_found; + ]); ``` ...and the client, in @@ -85,7 +84,7 @@ example in OCaml syntax. - [**`w-esy`**](../w-esy#files) details the server's [esy](https://esy.sh/) packaging. -- [**`w-fswatch`**](../w-fswatch#files) sets up a primitive development watcher. +- [**`w-watch`**](../w-fswatch#files) sets up a development watcher. - [**`w-one-binary`**](../w-one-binary#files) bundles assets into a self-contained binary. - [**`7-template`**](../7-template#files) discusses the templater, including diff --git a/example/r-fullstack-melange/esy.json b/example/r-fullstack-melange/esy.json index 0d7c5ada..c2241544 100644 --- a/example/r-fullstack-melange/esy.json +++ b/example/r-fullstack-melange/esy.json @@ -1,11 +1,12 @@ { "name": "fullstack-melange", "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "@opam/reason": "^3.7.0", + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "@opam/reason": "^3.8.0", "melange": "melange-re/melange", - "ocaml": "4.12.x" + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/r-fullstack-melange/server/server.eml.re b/example/r-fullstack-melange/server/server.eml.re index c55429b6..98b2d3bd 100644 --- a/example/r-fullstack-melange/server/server.eml.re +++ b/example/r-fullstack-melange/server/server.eml.re @@ -18,5 +18,4 @@ let () = Dream.get("/static/**", Dream.static("./static")), - ]) - @@ Dream.not_found; + ]); diff --git a/example/r-graphql/README.md b/example/r-graphql/README.md index 8730a973..9f8e41b9 100644 --- a/example/r-graphql/README.md +++ b/example/r-graphql/README.md @@ -22,7 +22,7 @@ let hardcoded_users = [ let user = Graphql_lwt.Schema.( - obj("user", ~fields=_info => + obj("user", ~fields= [ field("id", ~typ=non_null(int), ~args=Arg.[], ~resolve=(_info, user) => user.id @@ -65,8 +65,7 @@ let () = @@ Dream.router([ Dream.any("/graphql", Dream.graphql(Lwt.return, schema)), Dream.get("/", Dream.graphiql(~default_query, "/graphql")), - ]) - @@ Dream.not_found; + ]); ```
$ cd example/r-graphql
diff --git a/example/r-graphql/esy.json b/example/r-graphql/esy.json
index d5b1351d..6482d70e 100644
--- a/example/r-graphql/esy.json
+++ b/example/r-graphql/esy.json
@@ -1,9 +1,10 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "@opam/reason": "^3.7.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "@opam/reason": "^3.8.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/r-graphql/graphql.re b/example/r-graphql/graphql.re
index cd129b1b..d4f14f05 100644
--- a/example/r-graphql/graphql.re
+++ b/example/r-graphql/graphql.re
@@ -10,7 +10,7 @@ let hardcoded_users = [
 
 let user =
   Graphql_lwt.Schema.(
-    obj("user", ~fields=_info =>
+    obj("user", ~fields=
       [
         field("id", ~typ=non_null(int), ~args=Arg.[], ~resolve=(_info, user) =>
           user.id
@@ -53,5 +53,4 @@ let () =
   @@ Dream.router([
     Dream.any("/graphql", Dream.graphql(Lwt.return, schema)),
     Dream.get("/", Dream.graphiql(~default_query, "/graphql")),
-  ])
-  @@ Dream.not_found;
+  ]);
diff --git a/example/r-hello/esy.json b/example/r-hello/esy.json
index d76dae4b..f310f7ec 100644
--- a/example/r-hello/esy.json
+++ b/example/r-hello/esy.json
@@ -1,9 +1,10 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "@opam/reason": "^3.7.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "@opam/reason": "^3.8.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/r-template-files/README.md b/example/r-template-files/README.md
index 8df5140c..0da81c31 100644
--- a/example/r-template-files/README.md
+++ b/example/r-template-files/README.md
@@ -2,20 +2,11 @@
 
 
-While templates can be written with other code in `.re` files, they can also -live in their own source files. This can be useful as templates become larger, -or when you have many templates. - -If your template file is mostly HTML, you can give it a name like -`template.eml.html`, to trigger HTML syntax highlighting by your editor. -Additionally, if you are using `ocamlformat`, the `.html` extension will -prevent errors that come from `ocamlformat` attempting to format the syntax of -the template. - -This example does just that. It splits the code of the basic template example, -[**r-template**](../r-template#files), into two files. The first is the +This example splits the code of the basic template example, +[**`r-template`**](../r-template#files), into two files. The first is the template, in -[`template.eml.html`](https://github.com/aantron/dream/blob/master/example/r-template-files/template.eml.html): +[`template.eml.html`](https://github.com/aantron/dream/blob/master/example/r-template-files/template.eml.html). We use the `.html` extension because it is +mostly HTML, and to prevent `refmt` from trying to format the file: ```html let render = param => { @@ -27,23 +18,37 @@ let render = param => { }; ``` -After preprocessing by the templater, this file becomes `template.ml`, so it +After preprocessing by the templater, this file becomes `template.re`, so it defines a module `Template`, containing a function `Template.render`. We call -this function from the main server in -[`server.ml`](https://github.com/aantron/dream/blob/master/example/w-template-files/server.ml): +this function from the main server module in +[`server.ml`](https://github.com/aantron/dream/blob/master/example/r-template-files/server.re): ```reason let () = - Dream.run @@ - Dream.logger @@ - Dream.router([ + Dream.run + @@ Dream.logger + @@ Dream.router([ + Dream.get("/:word", request => - Dream.param("word", request) |> Template.render |> Dream.html - ), - ]) @@ - Dream.not_found; + Dream.param("word", request) + |> Template.render + |> Dream.html), + + ]); ``` +Because we are using the extension `.eml.html` rather than `.eml.re`, we now +have to specifically tell the templater to emit Reason syntax in our +[`dune`](https://github.com/aantron/dream/blob/master/example/r-template-files/dune) +file: + +
(rule
+ (targets template.re)
+ (deps template.eml.html)
+ (action (run dream_eml %{deps} --workspace %{workspace_root} --emit-reason))
+ +
+
$ cd example/r-template-files
 $ npm install esy && npx esy
 $ npx esy start
@@ -52,11 +57,11 @@ let () = **See also:** -- [**r-template**](../r-template#files) for comments on the -[`dune` file](https://github.com/aantron/dream/blob/master/example/w-template-files/dune). -- [**7-template**](../7-template#files) for comments on [security -information](https://github.com/aantron/dream/tree/master/example/7-template#security). -- [**w-template-files**](../w-template-files) for the OCaml version of this example. +- [**`r-template`**](../r-template#files) for the one-file version. +- [**`7-template`**](../7-template#files) for comments on [security + information](../7-template#security). +- [**`w-template-files`**](../w-template-files) for the OCaml version of this + example.
diff --git a/example/r-template-files/esy.json b/example/r-template-files/esy.json index d78cda78..62d26806 100644 --- a/example/r-template-files/esy.json +++ b/example/r-template-files/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/r-template-files/server.re b/example/r-template-files/server.re index 21bd77f9..303f0902 100644 --- a/example/r-template-files/server.re +++ b/example/r-template-files/server.re @@ -1,9 +1,11 @@ let () = - Dream.run @@ - Dream.logger @@ - Dream.router([ + Dream.run + @@ Dream.logger + @@ Dream.router([ + Dream.get("/:word", request => - Dream.param("word", request) |> Template.render |> Dream.html - ), - ]) @@ - Dream.not_found; + Dream.param(request, "word") + |> Template.render + |> Dream.html), + + ]); diff --git a/example/r-template-logic/README.md b/example/r-template-logic/README.md new file mode 100644 index 00000000..12797069 --- /dev/null +++ b/example/r-template-logic/README.md @@ -0,0 +1,85 @@ +# `r-template-logic` + +
+ +Reason control expressions can be used inside Dream templates. This example +shows a template with a loop written with `List.iter`, an `if`-expression, and +a `switch`-expression. + +It's helpful to know that template fragments are written to a buffer +imperatively. That means that template fragments evaluate to `unit`, and the +surrounding Reason code often needs semicolons. Templates also tend to use +`List.iter` rather than `List.map`. + +```reason +let render_home = tasks => { + + +% tasks |> List.iter(((name, complete)) => { +

Task <%s name %>: +% if (complete) { + complete! +% } else { + not complete +% }; +

+% }); + + +}; + +let render_task = (tasks, task) => { + + +% (switch (List.assoc_opt(task, tasks)) { +% | Some(complete) => +

Task: <%s task %>

+

Complete: <%B complete %>

+% | None => +

Task not found!

+% }); + + +}; + +let tasks = [ + ("Write documentation", true), + ("Create examples", true), + ("Publish website", true), + ("Profit", false), +]; + +let () = + Dream.run + @@ Dream.logger + @@ Dream.router([ + + Dream.get("/", _ => + render_home(tasks) + |> Dream.html), + + Dream.get("/:task", request => + Dream.param(request, "task") + |> render_task(tasks) + |> Dream.html), + + ]); +``` + +
$ cd example/r-template-logic
+$ npm install esy && npx esy
+$ npx esy start
+ +Try it in the [playground](http://dream.as/r-template-logic). + +
+ +**See also:** + +- [**`7-template`**](../7-template#files) for basic information about templates. +- [**`w-template-logic`**](../w-template-logic#files) for the OCaml version + of this example. + +
+ +[Up to the example index](../#reason) diff --git a/example/r-advanced-template/dune b/example/r-template-logic/dune similarity index 100% rename from example/r-advanced-template/dune rename to example/r-template-logic/dune diff --git a/example/r-advanced-template/dune-project b/example/r-template-logic/dune-project similarity index 100% rename from example/r-advanced-template/dune-project rename to example/r-template-logic/dune-project diff --git a/example/w-advanced-template/esy.json b/example/r-template-logic/esy.json similarity index 71% rename from example/w-advanced-template/esy.json rename to example/r-template-logic/esy.json index 7fa24238..e29ae7c3 100644 --- a/example/w-advanced-template/esy.json +++ b/example/r-template-logic/esy.json @@ -1,8 +1,10 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "@opam/reason": "^3.8.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/r-template-logic/template.eml.re b/example/r-template-logic/template.eml.re new file mode 100644 index 00000000..f9f44f95 --- /dev/null +++ b/example/r-template-logic/template.eml.re @@ -0,0 +1,52 @@ +let render_home = tasks => { + + +% tasks |> List.iter(((name, complete)) => { +

Task <%s name %>: +% if (complete) { + complete! +% } else { + not complete +% }; +

+% }); + + +}; + +let render_task = (tasks, task) => { + + +% (switch (List.assoc_opt(task, tasks)) { +% | Some(complete) => +

Task: <%s task %>

+

Complete: <%B complete %>

+% | None => +

Task not found!

+% }); + + +}; + +let tasks = [ + ("Write documentation", true), + ("Create examples", true), + ("Publish website", true), + ("Profit", false), +]; + +let () = + Dream.run + @@ Dream.logger + @@ Dream.router([ + + Dream.get("/", _ => + render_home(tasks) + |> Dream.html), + + Dream.get("/:task", request => + Dream.param(request, "task") + |> render_task(tasks) + |> Dream.html), + + ]); diff --git a/example/r-template-stream/README.md b/example/r-template-stream/README.md index c3c69efe..7fa8fd19 100644 --- a/example/r-template-stream/README.md +++ b/example/r-template-stream/README.md @@ -7,23 +7,20 @@ in a response [stream](https://aantron.github.io/dream/#streaming): ```reason let render = response => { - let%lwt () = { - %% response - - - -% let rec paragraphs = index => { -

<%i index %>

-% let%lwt () = Dream.flush(response); -% let%lwt () = Lwt_unix.sleep(1.); -% paragraphs(index + 1); -% }; -% let%lwt () = paragraphs(0); - - - - }; - Dream.close_stream(response) + %% response + + + +% let rec paragraphs = index => { +

<%i index %>

+% let%lwt () = Dream.flush(response); +% let%lwt () = Lwt_unix.sleep(1.); +% paragraphs(index + 1); +% }; +% let%lwt () = paragraphs(0); + + + }; let () = diff --git a/example/r-template-stream/esy.json b/example/r-template-stream/esy.json index 8c080144..3ef68400 100644 --- a/example/r-template-stream/esy.json +++ b/example/r-template-stream/esy.json @@ -1,9 +1,10 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "@opam/reason": "^3.7.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "@opam/reason": "^3.8.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/r-template-stream/template_stream.eml.re b/example/r-template-stream/template_stream.eml.re index cf86b796..d5afce58 100644 --- a/example/r-template-stream/template_stream.eml.re +++ b/example/r-template-stream/template_stream.eml.re @@ -1,21 +1,18 @@ let render = response => { - let%lwt () = { - %% response - - + %% response + + -% let rec paragraphs = index => { -

<%i index %>

-% let%lwt () = Dream.flush(response); -% let%lwt () = Lwt_unix.sleep(1.); -% paragraphs(index + 1); -% }; -% let%lwt () = paragraphs(0); +% let rec paragraphs = index => { +

<%i index %>

+% let%lwt () = Dream.flush(response); +% let%lwt () = Lwt_unix.sleep(1.); +% paragraphs(index + 1); +% }; +% let%lwt () = paragraphs(0); - - - }; - Dream.close_stream(response) + + }; let () = diff --git a/example/r-template/README.md b/example/r-template/README.md index a91bb556..19208090 100644 --- a/example/r-template/README.md +++ b/example/r-template/README.md @@ -23,8 +23,7 @@ let () = Dream.get("/", (_ => Dream.html(greet("world")))), - ]) - @@ Dream.not_found; + ]); ```
$ cd example/r-template
@@ -62,6 +61,8 @@ same.
 
 **See also:**
 
+- [**`r-template-files`**](../r-template-files#files) puts the template into a
+  separate `.eml.html` file, which can help with editor problems.
 - [**`r-template-stream`**](../r-template-stream#files) streams a template to a
   response.
 - [**`9-error`**](../9-error#files) sets up a central error template. The
diff --git a/example/r-template/esy.json b/example/r-template/esy.json
index 7d418fb4..e29ae7c3 100644
--- a/example/r-template/esy.json
+++ b/example/r-template/esy.json
@@ -1,9 +1,10 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "@opam/reason": "^3.7.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "@opam/reason": "^3.8.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/r-template/template.eml.re b/example/r-template/template.eml.re
index 5fe9eaaa..3ed3b4ae 100644
--- a/example/r-template/template.eml.re
+++ b/example/r-template/template.eml.re
@@ -14,5 +14,4 @@ let () =
     Dream.get("/",
       (_ => Dream.html(greet("world")))),
 
-  ])
-  @@ Dream.not_found;
+  ]);
diff --git a/example/r-tyxml/README.md b/example/r-tyxml/README.md
index 11eb2aa3..26a6d273 100644
--- a/example/r-tyxml/README.md
+++ b/example/r-tyxml/README.md
@@ -28,8 +28,7 @@ let () =
     Dream.get("/",
       (_ => Dream.html(html_to_string(greet("world"))))),
 
-  ])
-  @@ Dream.not_found
+  ]);
 ```
 
 
$ cd example/r-tyxml
@@ -43,8 +42,9 @@ To get this, we depend on package `tyxml-jsx` in
 
 
{
   "dependencies": {
-    "@opam/dream": "aantron/dream:dream.opam",
+    "@opam/dream": "1.0.0~alpha4",
     "@opam/dune": "^2.0",
+    "@opam/reason": "^3.8.0",
     "@opam/tyxml": "*",
     "@opam/tyxml-jsx": "*",
     "ocaml": "4.12.x"
diff --git a/example/r-tyxml/esy.json b/example/r-tyxml/esy.json
index 5ba1f5e3..3010d31e 100644
--- a/example/r-tyxml/esy.json
+++ b/example/r-tyxml/esy.json
@@ -1,11 +1,12 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "@opam/reason": "^3.7.0",
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "@opam/reason": "^3.8.0",
     "@opam/tyxml": "*",
     "@opam/tyxml-jsx": "*",
-    "ocaml": "4.12.x"
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/r-tyxml/tyxml.re b/example/r-tyxml/tyxml.re
index e89a44a9..6324f7aa 100644
--- a/example/r-tyxml/tyxml.re
+++ b/example/r-tyxml/tyxml.re
@@ -19,5 +19,4 @@ let () =
     Dream.get("/",
       (_ => Dream.html(html_to_string(greet("world"))))),
 
-  ])
-  @@ Dream.not_found
+  ]);
diff --git a/example/w-advanced-template/README.md b/example/w-advanced-template/README.md
deleted file mode 100644
index 4df84768..00000000
--- a/example/w-advanced-template/README.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# `w-advanced-template`
-
-
- -Dream templates allow for interleaving any control structures with your template code. This example shows how to do this with if statements, list iterations, and pattern matching. Although it may seem intuitive that the code somehow 'returns' the template, in reality the HTML generation happens in an imperative style. This means that any code within the template must evaluate to `unit`, and so the semicolons in this example are not optional. We use `List.iter` instead of `List.map` for a similar reason. - -```ocaml -(* In OCaml, `begin ... end` is the same as `( ... )` *) -let render_home tasks = - - -

My TODO

- <% tasks |> List.iter begin fun (name, complete) -> %> -

Task <%s name %>: - <% if complete then ( %> - complete! - <% ) else ( %> - not complete - <% ); %> -

- <% end; %> - - - - -(* You can also begin a line with `%` instead of using `<% ... %>` *) -let render_task tasks task = - - -% (match List.find_opt (fun (task_, _) -> task = task_) tasks with -% | Some (name, complete) -> -

TODO task: <%s name %>, complete: <%B complete %>

-% | None -> begin -

Task not found!

-% end); - - - -let tasks = [ - ("write documentation", true); - ("create examples", true); - ("publish website", true); - ("profit", false); -] - -let () = - Dream.run - @@ Dream.logger - @@ Dream.router [ - Dream.get "/" - (fun _ -> - render_home tasks - |> Dream.html); - - - Dream.get "/:task" - (fun request -> - Dream.param "task" request - |> render_task tasks - |> Dream.html); - - ] - @@ Dream.not_found -``` - -
$ cd example/w-advanced-template
-$ npm install esy && npx esy
-$ npx esy start
- -Try it in the [playground](http://dream.as/w-advanced-template). - -
- -**See also:** - -- [**`w-template**](../w-template) for more information about templates. -- [**`r-advanced-template**](../r-advanced-template) for the Reason syntax version of this example. - -
- -[Up to the example index](../#examples) diff --git a/example/w-advanced-template/template.eml.ml b/example/w-advanced-template/template.eml.ml deleted file mode 100644 index 45713efa..00000000 --- a/example/w-advanced-template/template.eml.ml +++ /dev/null @@ -1,56 +0,0 @@ -(* In OCaml, `begin ... end` is the same as `( ... )` *) -let render_home tasks = - - -

My TODO

- <% tasks |> List.iter begin fun (name, complete) -> %> -

Task <%s name %>: - <% if complete then ( %> - complete! - <% ) else ( %> - not complete - <% ); %> -

- <% end; %> - - - - -(* You can also begin a line with `%` instead of using `<% ... %>` *) -let render_task tasks task = - - -% (match List.find_opt (fun (task_, _) -> task = task_) tasks with -% | Some (name, complete) -> -

TODO task: <%s name %>, complete: <%B complete %>

-% | None -> begin -

Task not found!

-% end); - - - -let tasks = [ - ("write documentation", true); - ("create examples", true); - ("publish website", true); - ("profit", false); -] - -let () = - Dream.run - @@ Dream.logger - @@ Dream.router [ - Dream.get "/" - (fun _ -> - render_home tasks - |> Dream.html); - - - Dream.get "/:task" - (fun request -> - Dream.param "task" request - |> render_task tasks - |> Dream.html); - - ] - @@ Dream.not_found diff --git a/example/w-chat/README.md b/example/w-chat/README.md index 1540efa9..ec0b86b8 100644 --- a/example/w-chat/README.md +++ b/example/w-chat/README.md @@ -11,16 +11,16 @@ hash table, listens for messages, and forwards them to all the other WebSockets in the hash table: ```ocaml -let handle_client websocket = - let client_id = connect websocket in +let handle_client client = + let client_id = track client in let rec loop () = - match%lwt Dream.receive websocket with + match%lwt Dream.receive client with | Some message -> let%lwt () = send message in loop () | None -> - disconnect client_id; - Dream.close_websocket websocket + forget client_id; + Dream.close_websocket client in loop () ``` diff --git a/example/w-chat/chat.eml.ml b/example/w-chat/chat.eml.ml index 3a462b2a..5bec1d71 100644 --- a/example/w-chat/chat.eml.ml +++ b/example/w-chat/chat.eml.ml @@ -30,17 +30,17 @@ let home = -let clients = +let clients : (int, Dream.websocket) Hashtbl.t = Hashtbl.create 5 -let connect = +let track = let last_client_id = ref 0 in fun websocket -> last_client_id := !last_client_id + 1; Hashtbl.replace clients !last_client_id websocket; !last_client_id -let disconnect client_id = +let forget client_id = Hashtbl.remove clients client_id let send message = @@ -48,16 +48,16 @@ let send message = |> List.of_seq |> Lwt_list.iter_p (fun client -> Dream.send client message) -let handle_client websocket = - let client_id = connect websocket in +let handle_client client = + let client_id = track client in let rec loop () = - match%lwt Dream.receive websocket with + match%lwt Dream.receive client with | Some message -> let%lwt () = send message in loop () | None -> - disconnect client_id; - Dream.close_websocket websocket + forget client_id; + Dream.close_websocket client in loop () @@ -73,4 +73,3 @@ let () = (fun _ -> Dream.websocket handle_client); ] - @@ Dream.not_found diff --git a/example/w-chat/esy.json b/example/w-chat/esy.json index 03c0de5e..23269b23 100644 --- a/example/w-chat/esy.json +++ b/example/w-chat/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-content-security-policy/README.md b/example/w-content-security-policy/README.md index 5bfee80d..b04e3447 100644 --- a/example/w-content-security-policy/README.md +++ b/example/w-content-security-policy/README.md @@ -41,7 +41,6 @@ let () = Dream.empty `OK); ] - @@ Dream.not_found ```
$ cd example/w-content-security-policy
diff --git a/example/w-content-security-policy/content_security_policy.eml.ml b/example/w-content-security-policy/content_security_policy.eml.ml
index 257442b8..ad9da9cb 100644
--- a/example/w-content-security-policy/content_security_policy.eml.ml
+++ b/example/w-content-security-policy/content_security_policy.eml.ml
@@ -26,4 +26,3 @@ let () =
       Dream.empty `OK);
 
   ]
-  @@ Dream.not_found
diff --git a/example/w-content-security-policy/esy.json b/example/w-content-security-policy/esy.json
index d22ba09b..65b82749 100644
--- a/example/w-content-security-policy/esy.json
+++ b/example/w-content-security-policy/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/w-esy/README.md b/example/w-esy/README.md
index f9207013..ceaefa5d 100644
--- a/example/w-esy/README.md
+++ b/example/w-esy/README.md
@@ -10,7 +10,7 @@ and looks like this:
 ```json
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
+    "@opam/dream": "1.0.0~alpha4",
     "@opam/dune": "^2.0",
     "ocaml": "4.12.x"
   },
diff --git a/example/w-esy/esy.json b/example/w-esy/esy.json
index 3e71335d..3dc785b3 100644
--- a/example/w-esy/esy.json
+++ b/example/w-esy/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/w-flash/README.md b/example/w-flash/README.md
index ffc98d7f..290de182 100644
--- a/example/w-flash/README.md
+++ b/example/w-flash/README.md
@@ -13,7 +13,8 @@ absolutely primitive form with just one field:
 let form request =
   
   
-    <%s! Dream.form_tag ~action:"/" request %>
+    
+ <%s! Dream.csrf_tag request %>
@@ -28,7 +29,7 @@ let result request = -% Dream.flash request |> List.iter (fun (category, text) -> +% Dream.flash_messages request |> List.iter (fun (category, text) ->

<%s category %>: <%s text %>

<% ); %> @@ -37,21 +38,23 @@ let result request = The app just displays the form. The text that is entered into the form by the user is placed into a flash message using -[`Dream.put_flash`](https://aantron.github.io/dream/#val-put_flash). The app +[`Dream.add_flash_message`](https://aantron.github.io/dream/#val-add_flash_message). +The app then tells the client to redirect to `/result`. The handler for `/result` calls the `result` template, which retrieves the flash message using -[`Dream.flash`](https://aantron.github.io/dream/#val-put_flash) and displays it. -We need to include -[`Dream.flash_messages`](https://aantron.github.io/dream/#val-flash_messages) in -our middleware stack, because that is the piece that actually puts the messages -into cookies and reads them back out: +[`Dream.flash_messages`](https://aantron.github.io/dream/#val-flash_messages) +and displays it. We need to include +[`Dream.flash`](https://aantron.github.io/dream/#val-flash) in our middleware +stack, because that is the piece that actually puts the messages into cookies +and reads them back out: ```ocaml let () = + Dream.set_log_level "dream.flash" `Debug; Dream.run @@ Dream.logger @@ Dream.memory_sessions - @@ Dream.flash_messages + @@ Dream.flash @@ Dream.router [ Dream.get "/" @@ -62,7 +65,7 @@ let () = (fun request -> match%lwt Dream.form request with | `Ok ["text", text] -> - let () = Dream.put_flash "Info" text request in + let () = Dream.add_flash_message request "Info" text in Dream.redirect request "/result" | _ -> Dream.redirect request "/"); @@ -72,7 +75,16 @@ let () = Dream.html (result request)); ] - @@ Dream.not_found +``` + +The example configures a custom log level for flash messages using +`Dream.set_log_level`. Setting this to `` `Debug`` means the server logs +will display a log point summarizing the flash messages on every +request, like this: + +``` +10.11.21 01:48:21.629 dream.log INFO REQ 3 GET /result ::1:39808 Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 +10.11.21 01:48:21.629 dream.flash DEBUG REQ 3 Flash messages: Info: Some Message ```
$ cd example/w-flash
diff --git a/example/w-flash/esy.json b/example/w-flash/esy.json
index 1981835d..fbbb748b 100644
--- a/example/w-flash/esy.json
+++ b/example/w-flash/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/w-flash/flash.eml.ml b/example/w-flash/flash.eml.ml
index e07ffab8..3db146cc 100644
--- a/example/w-flash/flash.eml.ml
+++ b/example/w-flash/flash.eml.ml
@@ -1,7 +1,8 @@
 let form request =
   
   
-    <%s! Dream.form_tag ~action:"/" request %>
+    
+ <%s! Dream.csrf_tag request %>
@@ -11,17 +12,18 @@ let result request = -% Dream.flash request |> List.iter (fun (category, text) -> +% Dream.flash_messages request |> List.iter (fun (category, text) ->

<%s category %>: <%s text %>

<% ); %> let () = + Dream.set_log_level "dream.flash" `Debug; Dream.run @@ Dream.logger @@ Dream.memory_sessions - @@ Dream.flash_messages + @@ Dream.flash @@ Dream.router [ Dream.get "/" @@ -32,7 +34,7 @@ let () = (fun request -> match%lwt Dream.form request with | `Ok ["text", text] -> - let () = Dream.put_flash "Info" text request in + let () = Dream.add_flash_message request "Info" text in Dream.redirect request "/result" | _ -> Dream.redirect request "/"); @@ -42,4 +44,3 @@ let () = Dream.html (result request)); ] - @@ Dream.not_found diff --git a/example/w-fswatch/README.md b/example/w-fswatch/README.md deleted file mode 100644 index 7b7d0dc9..00000000 --- a/example/w-fswatch/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# `w-fswatch` - -
- -This example sets up a simple development watcher using -[fswatch](https://github.com/emcrisostomo/fswatch), which is available in system -package managers such as APT and Homebrew: - -```sh -#!/bin/bash - -npx esy start & -fswatch -o hello.ml -l 2 | xargs -L1 bash -c \ - "killall hello.exe || true; (npx esy start || true) &" -``` - -
$ cd example/w-fswatch
-$ bash watch.sh
- -
- -This watcher rebuilds `hello.exe` every time `hello.ml` changes. It's a bit -verbose and clunky, but it gets the job done. We may be able to offer a better -solution in the future. - -The reason we are not suggesting `dune watch -w` is because it does not kill the -running server, which we are doing manually here, with the `killall` command. - -As your project grows, replace `hello.ml` with the list of your source -directories. For example, - -``` -fswatch -o client server -l 2 -``` - -
- -**See also:** - -- [**`w-live-reload`**](../w-live-reload#files) adds live reloading, so that - browsers reload when the server is restarted. -- [**`w-esy`**](../w-esy#files) discusses [esy](https://esy.sh/) packaging. - - -
- -[Up to the example index](../#examples) diff --git a/example/w-fswatch/watch.sh b/example/w-fswatch/watch.sh deleted file mode 100644 index d1f1dd10..00000000 --- a/example/w-fswatch/watch.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -npx esy start & -fswatch -o hello.ml -l 2 | xargs -L1 bash -c \ - "killall hello.exe || true; (npx esy start || true) &" diff --git a/example/w-fullstack-jsoo/README.md b/example/w-fullstack-jsoo/README.md index d5bf7b8a..57e2c597 100644 --- a/example/w-fullstack-jsoo/README.md +++ b/example/w-fullstack-jsoo/README.md @@ -37,7 +37,6 @@ let () = (Dream.static "./static"); ] - @@ Dream.not_found ``` The rest is printed by the client, in diff --git a/example/w-fullstack-jsoo/esy.json b/example/w-fullstack-jsoo/esy.json index 37d63a63..b0d73d70 100644 --- a/example/w-fullstack-jsoo/esy.json +++ b/example/w-fullstack-jsoo/esy.json @@ -1,10 +1,11 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", "@opam/js_of_ocaml": "*", "@opam/js_of_ocaml-ppx": "*", - "ocaml": "4.12.x" + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-fullstack-jsoo/server/server.eml.ml b/example/w-fullstack-jsoo/server/server.eml.ml index a9be889e..9f97307b 100644 --- a/example/w-fullstack-jsoo/server/server.eml.ml +++ b/example/w-fullstack-jsoo/server/server.eml.ml @@ -18,4 +18,3 @@ let () = (Dream.static "./static"); ] - @@ Dream.not_found diff --git a/example/w-fullstack-rescript/README.md b/example/w-fullstack-rescript/README.md index 059045a9..a7dd9183 100644 --- a/example/w-fullstack-rescript/README.md +++ b/example/w-fullstack-rescript/README.md @@ -39,7 +39,6 @@ let () = (Dream.static "./static"); ] - @@ Dream.not_found ``` ...and the rest of the message in the client, @@ -59,7 +58,7 @@ let () = { let p = document |> Document.createElement("p") p->Element.setInnerText(text) - body |> Element.appendChild(p) + body |> Element.appendChild(~child=p) } } ``` diff --git a/example/w-fullstack-rescript/bsconfig.json b/example/w-fullstack-rescript/bsconfig.json index 4fbdcdc7..77f0cb38 100644 --- a/example/w-fullstack-rescript/bsconfig.json +++ b/example/w-fullstack-rescript/bsconfig.json @@ -1,7 +1,7 @@ { "name": "fullstack-rescript", "bs-dependencies": [ - "bs-webapi" + "rescript-webapi" ], "sources": [ "common", diff --git a/example/w-fullstack-rescript/client/client.res b/example/w-fullstack-rescript/client/client.res index 3ff56fef..0cfceda0 100644 --- a/example/w-fullstack-rescript/client/client.res +++ b/example/w-fullstack-rescript/client/client.res @@ -1,16 +1,15 @@ open Webapi.Dom let () = { - let body = document |> Document.querySelector("body") + let body = document->Document.querySelector("body") - switch (body) { + switch body { | None => () | Some(body) => - let text = Common.greet(#Client) - let p = document |> Document.createElement("p") + let p = document->Document.createElement("p") p->Element.setInnerText(text) - body |> Element.appendChild(p) + body->Element.appendChild(~child=p) } } diff --git a/example/w-fullstack-rescript/esy.json b/example/w-fullstack-rescript/esy.json index bee5005b..8d952651 100644 --- a/example/w-fullstack-rescript/esy.json +++ b/example/w-fullstack-rescript/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-fullstack-rescript/package.json b/example/w-fullstack-rescript/package.json index 4443fbcc..68df9a3e 100644 --- a/example/w-fullstack-rescript/package.json +++ b/example/w-fullstack-rescript/package.json @@ -1,13 +1,14 @@ { "name": "fullstack-rescript", "dependencies": { - "bs-platform": "*", - "bs-webapi": "*", "esbuild": "*", - "esy": "*" + "esy": "*", + "rescript": "*", + "rescript-webapi": "^0.5.0" }, "scripts": { - "build": "bsb -make-world", + "postinstall": "npx esy install", + "build": "rescript", "pack": "esbuild lib/js/client/client.js --bundle --outfile=static/client.js", "start": "npm run build && npm run pack && npx esy start" } diff --git a/example/w-fullstack-rescript/server/server.eml.ml b/example/w-fullstack-rescript/server/server.eml.ml index 0827f76b..203cb7ad 100644 --- a/example/w-fullstack-rescript/server/server.eml.ml +++ b/example/w-fullstack-rescript/server/server.eml.ml @@ -18,4 +18,3 @@ let () = (Dream.static "./static"); ] - @@ Dream.not_found diff --git a/example/w-graphql-subscription/README.md b/example/w-graphql-subscription/README.md index b4f7af28..1f56eb6f 100644 --- a/example/w-graphql-subscription/README.md +++ b/example/w-graphql-subscription/README.md @@ -48,7 +48,6 @@ let () = Dream.any "/graphql" (Dream.graphql Lwt.return schema); Dream.get "/" (Dream.graphiql ~default_query "/graphql"); ] - @@ Dream.not_found ```
$ cd example/w-graphql-subscriptions
diff --git a/example/w-graphql-subscription/esy.json b/example/w-graphql-subscription/esy.json
index 640af0e9..c26fb1f5 100644
--- a/example/w-graphql-subscription/esy.json
+++ b/example/w-graphql-subscription/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/w-graphql-subscription/graphql_subscription.ml b/example/w-graphql-subscription/graphql_subscription.ml
index 7f7d304f..b30457ff 100644
--- a/example/w-graphql-subscription/graphql_subscription.ml
+++ b/example/w-graphql-subscription/graphql_subscription.ml
@@ -36,4 +36,3 @@ let () =
     Dream.any "/graphql" (Dream.graphql Lwt.return schema);
     Dream.get "/" (Dream.graphiql ~default_query "/graphql");
   ]
-  @@ Dream.not_found
diff --git a/example/w-live-reload/README.md b/example/w-live-reload/README.md
index 6d59d305..d9d9600d 100644
--- a/example/w-live-reload/README.md
+++ b/example/w-live-reload/README.md
@@ -2,83 +2,20 @@
 
 
-This example shows a simple live reloading setup. It works by injecting a script -into the `` of HTML documents. The script opens a WebSocket back to the -server. If the WebSocket gets closed, the script tries to reconnect. When the -server comes back up, the client is able to reconnect, and reloads itself. +This example shows a simple live reloading setup using the `Dream.livereload` +middleware. It works by injecting a script into the `` of HTML documents. +The script opens a WebSocket back to the server. If the WebSocket gets closed, +the script tries to reconnect. When the server comes back up, the client is able +to reconnect and reloads itself. -```js -var socketUrl = "ws://" + location.host + "/_live-reload" -var socket = new WebSocket(socketUrl); - -socket.onclose = function(event) { - const intervalMs = 100; - const attempts = 100; - let attempt = 0; - - function reload() { - ++attempt; - - if(attempt > attempts) { - console.error("Could not reconnect to server"); - return; - } - - reconnectSocket = new WebSocket(socketUrl); - - reconnectSocket.onerror = function(event) { - setTimeout(reload, intervalMs); - }; - - reconnectSocket.onopen = function(event) { - location.reload(); - }; - }; - - reload(); -}; -``` - -The injection is done by a small middleware: - -```ocaml - -let inject_live_reload_script inner_handler request = - let%lwt response = inner_handler request in - - match Dream.header "Content-Type" response with - | Some "text/html; charset=utf-8" -> - let%lwt body = Dream.body response in - let soup = - Markup.string body - |> Markup.parse_html ~context:`Document - |> Markup.signals - |> Soup.from_signals - in - - begin match Soup.Infix.(soup $? "head") with - | None -> - Lwt.return response - | Some head -> - Soup.create_element "script" ~inner_text:live_reload_script - |> Soup.append_child head; - response - |> Dream.with_body (Soup.to_string soup) - |> Lwt.return - end - - | _ -> - Lwt.return response -``` - -The example server just wraps a single page at `/` with the middleware. The page +The example server just wraps a single page at `/` with the `Dream.livereload` middleware. The page displays a tag that changes each time it is loaded: ```ocaml let () = Dream.run @@ Dream.logger - @@ inject_live_reload_script + @@ Dream.livereload @@ Dream.router [ Dream.get "/" (fun _ -> @@ -87,13 +24,7 @@ let () = |> Printf.sprintf "Good morning, world! Random tag: %s" |> Dream.html); - Dream.get "/_live-reload" (fun _ -> - Dream.websocket (fun socket -> - let%lwt _ = Dream.receive socket in - Dream.close_websocket socket)); - ] - @@ Dream.not_found ```
$ cd example/w-live-reload
@@ -119,18 +50,18 @@ Good morning, world! Random tag: jRak
 
 
-This example plays very well with [**`w-fswatch`**](../w-fswatch#files), which -shows how to rebuild and restart a development server every time sources are -modified in the file system. Combining the two examples, it is possible to -propagate reloading all the way to the client, whenever any of the server's -source code changes. +This example plays very well with [**`w-watch`**](../w-watch#files), which shows +how to rebuild and restart a development server every time sources are modified +in the file system. Combining the two examples, it is possible to propagate +reloading all the way to the client, whenever any of the server's source code +changes.
**See also:** - [**`k-websocket`**](../k-websocket#files) introduces WebSockets. -- [**`w-fswatch`**](../w-fswatch#files) rebuilds and restarts a server each +- [**`w-watch`**](../w-watch#files) rebuilds and restarts a server each time its source code changes.
diff --git a/example/w-live-reload/esy.json b/example/w-live-reload/esy.json index d9b6cdfb..9a08ed7f 100644 --- a/example/w-live-reload/esy.json +++ b/example/w-live-reload/esy.json @@ -1,9 +1,10 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", "@opam/lambdasoup": "*", - "ocaml": "4.12.x" + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-live-reload/live_reload.ml b/example/w-live-reload/live_reload.ml index 9bfd450b..5b5d7098 100644 --- a/example/w-live-reload/live_reload.ml +++ b/example/w-live-reload/live_reload.ml @@ -1,68 +1,7 @@ -let live_reload_script = {js| - -var socketUrl = "ws://" + location.host + "/_live-reload" -var socket = new WebSocket(socketUrl); - -socket.onclose = function(event) { - const intervalMs = 100; - const attempts = 100; - let attempt = 0; - - function reload() { - ++attempt; - - if(attempt > attempts) { - console.error("Could not reconnect to server"); - return; - } - - reconnectSocket = new WebSocket(socketUrl); - - reconnectSocket.onerror = function(event) { - setTimeout(reload, intervalMs); - }; - - reconnectSocket.onopen = function(event) { - location.reload(); - }; - }; - - reload(); -}; - -|js} - -let inject_live_reload_script inner_handler request = - let%lwt response = inner_handler request in - - match Dream.header "Content-Type" response with - | Some "text/html; charset=utf-8" -> - let%lwt body = Dream.body response in - let soup = - Markup.string body - |> Markup.parse_html ~context:`Document - |> Markup.signals - |> Soup.from_signals - in - - begin match Soup.Infix.(soup $? "head") with - | None -> - Lwt.return response - | Some head -> - Soup.create_element "script" ~inner_text:live_reload_script - |> Soup.append_child head; - response - |> Dream.with_body (Soup.to_string soup) - |> Lwt.return - end - - | _ -> - Lwt.return response - let () = Dream.run @@ Dream.logger - @@ inject_live_reload_script + @@ Dream.livereload @@ Dream.router [ Dream.get "/" (fun _ -> @@ -71,10 +10,4 @@ let () = |> Printf.sprintf "Good morning, world! Random tag: %s" |> Dream.html); - Dream.get "/_live-reload" (fun _ -> - Dream.websocket (fun socket -> - let%lwt _ = Dream.receive socket in - Dream.close_websocket socket)); - ] - @@ Dream.not_found diff --git a/example/w-long-polling/esy.json b/example/w-long-polling/esy.json index 25e65606..671c94c4 100644 --- a/example/w-long-polling/esy.json +++ b/example/w-long-polling/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-long-polling/long_polling.eml.ml b/example/w-long-polling/long_polling.eml.ml index 6562c838..2aefa528 100644 --- a/example/w-long-polling/long_polling.eml.ml +++ b/example/w-long-polling/long_polling.eml.ml @@ -74,4 +74,3 @@ let () = Dream.html (String.concat "\n" (List.rev messages))); ] - @@ Dream.not_found diff --git a/example/m-mirage/README.md b/example/w-mirage-letsencrypt/README.md similarity index 82% rename from example/m-mirage/README.md rename to example/w-mirage-letsencrypt/README.md index 34f8f41f..426aff06 100644 --- a/example/m-mirage/README.md +++ b/example/w-mirage-letsencrypt/README.md @@ -1,4 +1,6 @@ -### DreamOS +# `w-mirage` + +
This example is a simple unikernel which has a front-page and an echo route to repeat what the user said into the URL. This is a simple example of how to make @@ -15,11 +17,14 @@ encrypt challenge. ```sh $ gcloud init +# Your name below must be __globally__ unique, dream-os will be taken by now $ gcloud projects create dream-os --name="dream-os" -# Enable billing on the dream-os project +# Enable billing for project +# Go to https://cloud.google.com, log into Console, select project from +# dropdown, then click billing $ gcloud config set project dream-os -$ gcloud compute address --region europe-west1 -# Set your zone file with the given IP address +$ gcloud compute addresses create --region europe-west1 +# Set your in your DNS zone file the IP address yielded above $ gsutil mb gs://dream-os ``` @@ -37,10 +42,10 @@ address. ```sh $ opam install mirage $ mirage configure -t virtio --dhcp true --hostname --tls true \ - --letsencrypt true --productive false + --letsencrypt true --production false $ make depends $ mirage build -$ solo5-virtio-mkimage gs://dream-os +$ solo5-virtio-mkimage -f tar -- dream.tar.gz dream.virtio ``` #### Deployement diff --git a/example/m-mirage/_tags b/example/w-mirage-letsencrypt/_tags similarity index 100% rename from example/m-mirage/_tags rename to example/w-mirage-letsencrypt/_tags diff --git a/example/m-mirage/config.ml b/example/w-mirage-letsencrypt/config.ml similarity index 91% rename from example/m-mirage/config.ml rename to example/w-mirage-letsencrypt/config.ml index 7945bef7..65545229 100644 --- a/example/m-mirage/config.ml +++ b/example/w-mirage-letsencrypt/config.ml @@ -35,8 +35,9 @@ let letsencrypt = let dream = foreign "Unikernel.Make" ~packages:[ package "ca-certs-nss" - ; package "dns-client.mirage" - ; package "dream-mirage.paf.le" + ; package "dns-client" ~sublibs:[ "mirage" ] + ; package "dream-mirage" ~sublibs:[ "paf.le" ] + ; package "checkseum" ~sublibs:[ "c" ] ; package "dream-mirage" ] ~keys:Key.([ abstract port ; abstract hostname diff --git a/example/m-mirage/unikernel.ml b/example/w-mirage-letsencrypt/unikernel.ml similarity index 82% rename from example/m-mirage/unikernel.ml rename to example/w-mirage-letsencrypt/unikernel.ml index 87b67658..61851644 100644 --- a/example/m-mirage/unikernel.ml +++ b/example/w-mirage-letsencrypt/unikernel.ml @@ -20,9 +20,8 @@ module Make @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html "Good morning, world! (from MirageOS)") ; Dream.get "/echo/:word" echo ] - @@ Dream.not_found - module DNS = Dns_client_mirage.Make (Random) (Time) (Mclock) (Stack) + module DNS = Dns_client_mirage.Make (Random) (Time) (Mclock) (Pclock) (Stack) module Let = LE.Make (Time) (Stack) module Nss = Ca_certs_nss.Make (Pclock) module Paf = Paf_mirage.Make (Time) (Stack) @@ -35,7 +34,7 @@ module Make let error_handler _ ?request:_ _ _ = () let get_certificates ?(production= false) cfg stackv4v6 = - Paf.init ~port:80 stackv4v6 >>= fun t -> + Paf.init ~port:80 (Stack.tcp stackv4v6) >>= fun t -> let service = Paf.http_service ~error_handler Let.request_handler in Lwt_switch.with_switch @@ fun stop -> let `Initialized th = Paf.serve ~stop service t in @@ -50,18 +49,22 @@ module Make let https_with_letsencrypt stackv4v6 = let cfg = { LE.certificate_seed= Key_gen.cert_seed () + ; LE.certificate_key_type= `ED25519 + ; LE.certificate_key_bits= None ; LE.email= Option.bind (Key_gen.email ()) (R.to_option <.> Emile.of_string) - ; LE.seed= Key_gen.account_seed () + ; LE.account_seed= Key_gen.account_seed () + ; LE.account_key_type= `ED25519 + ; LE.account_key_bits= None ; LE.hostname= Domain_name.(host_exn <.> of_string_exn) (Key_gen.hostname ()) } in get_certificates ~production:(Key_gen.production ()) cfg stackv4v6 >>= fun certificates -> let tls = Tls.Config.server ~certificates () in - Dream.https ~port:(Key_gen.port ()) stackv4v6 ~cfg:tls dream + Dream.https ~port:(Key_gen.port ()) (Stack.tcp stackv4v6) ~cfg:tls dream let https stackv4v6 = - Dream.https ~port:(Key_gen.port ()) stackv4v6 dream + Dream.https ~port:(Key_gen.port ()) (Stack.tcp stackv4v6) dream let http stackv4v6 = - Dream.http ~port:(Key_gen.port ()) stackv4v6 dream + Dream.http ~port:(Key_gen.port ()) (Stack.tcp stackv4v6) dream let start _console () () () () stackv4v6 = match Key_gen.tls (), Key_gen.letsencrypt () with diff --git a/example/w-mirage/README.md b/example/w-mirage/README.md new file mode 100644 index 00000000..1c2c3746 --- /dev/null +++ b/example/w-mirage/README.md @@ -0,0 +1,61 @@ +# `w-mirage` + +
+ +This example shows how to build and use Dream with Mirage. Like any Mirage +starter project, it consists of two files. + +`config.ml`: + +```ocaml +open Mirage + +let main = + main + ~packages:[package "dream-mirage"] + "Unikernel.Hello_world" + (pclock @-> time @-> stackv4v6 @-> job) + +let () = + register "hello" [ + main + $ default_posix_clock + $ default_time + $ generic_stackv4v6 default_network + ] +``` + +`unikernel.ml`: + +```ocaml +module Hello_world + (Pclock : Mirage_clock.PCLOCK) + (Time : Mirage_time.S) + (Stack : Tcpip.Stack.V4V6) = +struct + module Dream = + Dream__mirage.Mirage.Make (Pclock) (Time) (Stack) + + let start _pclock _time stack = + Dream.http ~port:8080 (Stack.tcp stack) + @@ Dream.logger + @@ fun _ -> Dream.html "Good morning, world!" +end +``` + +
+ +It's the basic example [**`2-middleware`**](../2-middleware#files) adapted to +Mirage. To build and run, do + +
$ cd example/w-mirage
+$ opam install mirage
+$ mirage configure -t unix
+$ make depends
+$ dune build
+$ _build/default/main.exe
+
+ +
+ +[Up to the example index](../#examples) diff --git a/example/w-mirage/config.ml b/example/w-mirage/config.ml new file mode 100644 index 00000000..20cc6d28 --- /dev/null +++ b/example/w-mirage/config.ml @@ -0,0 +1,15 @@ +open Mirage + +let main = + main + ~packages:[package "dream-mirage"] + "Unikernel.Hello_world" + (pclock @-> time @-> stackv4v6 @-> job) + +let () = + register "hello" [ + main + $ default_posix_clock + $ default_time + $ generic_stackv4v6 default_network + ] diff --git a/example/w-mirage/unikernel.ml b/example/w-mirage/unikernel.ml new file mode 100644 index 00000000..ec9be8eb --- /dev/null +++ b/example/w-mirage/unikernel.ml @@ -0,0 +1,13 @@ +module Hello_world + (Pclock : Mirage_clock.PCLOCK) + (Time : Mirage_time.S) + (Stack : Tcpip.Stack.V4V6) = +struct + module Dream = + Dream__mirage.Mirage.Make (Pclock) (Time) (Stack) + + let start _pclock _time stack = + Dream.http ~port:8080 (Stack.tcp stack) + @@ Dream.logger + @@ fun _ -> Dream.html "Good morning, world!" +end diff --git a/example/w-multipart-dump/esy.json b/example/w-multipart-dump/esy.json index aad42cdc..e540fdf2 100644 --- a/example/w-multipart-dump/esy.json +++ b/example/w-multipart-dump/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-multipart-dump/multipart_dump.eml.ml b/example/w-multipart-dump/multipart_dump.eml.ml index 6521436c..000d1e1b 100644 --- a/example/w-multipart-dump/multipart_dump.eml.ml +++ b/example/w-multipart-dump/multipart_dump.eml.ml @@ -1,7 +1,8 @@ let home request = - <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %> +
+ <%s! Dream.csrf_tag request %>

@@ -25,4 +26,3 @@ let () = body); ] - @@ Dream.not_found diff --git a/example/w-nginx/README.md b/example/w-nginx/README.md index 110286b2..904b50d8 100644 --- a/example/w-nginx/README.md +++ b/example/w-nginx/README.md @@ -45,7 +45,9 @@ events { The reference for [`nginx.conf`](https://github.com/aantron/dream/blob/master/example/w-nginx/nginx.conf) -can be found [here](https://nginx.org/en/docs/). +can be found [here](https://nginx.org/en/docs/). In particular, see +[this page](http://nginx.org/en/docs/http/websocket.html) for information on +WebSocket proxying. Our [`docker-compose.yml`](https://github.com/aantron/dream/blob/master/example/w-nginx/docker-compose.yml) @@ -93,7 +95,6 @@ let () = @@ Dream.router [ Dream.get "/" (fun _request -> Dream.html home) ] - @@ Dream.not_found ``` To build, run: diff --git a/example/w-nginx/esy.json b/example/w-nginx/esy.json index 509a2a4c..ab240865 100644 --- a/example/w-nginx/esy.json +++ b/example/w-nginx/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-nginx/server.eml.ml b/example/w-nginx/server.eml.ml index 0a9905bd..3167a8ab 100644 --- a/example/w-nginx/server.eml.ml +++ b/example/w-nginx/server.eml.ml @@ -14,4 +14,3 @@ let () = @@ Dream.router [ Dream.get "/" (fun _request -> Dream.html home) ] - @@ Dream.not_found diff --git a/example/w-one-binary/README.md b/example/w-one-binary/README.md index 47dbf2c2..47c78698 100644 --- a/example/w-one-binary/README.md +++ b/example/w-one-binary/README.md @@ -57,7 +57,6 @@ let () = @@ Dream.router [ Dream.get "/assets/**" (Dream.static ~loader "") ] - @@ Dream.not_found ``` [`Dream.static`](https://aantron.github.io/dream/#val-static) will take care of @@ -108,7 +107,7 @@ To add more files, just add them to the `assets/` directory and re-run **See also:** - [**`w-esy`**](../w-esy#files) for details on packaging with esy. -- [**`w-fswatch`**](../w-fswatch#files) for a primitive watcher, which can be +- [**`w-watch`**](../w-watch#files) for a primitive watcher, which can be extended to watch `assets/`. - [**`f-static`**](../f-static#files) shows the basics of [`Dream.static`](https://aantron.github.io/dream/#val-static). diff --git a/example/w-one-binary/esy.json b/example/w-one-binary/esy.json index 528d6f31..ac2c6bd9 100644 --- a/example/w-one-binary/esy.json +++ b/example/w-one-binary/esy.json @@ -1,9 +1,10 @@ { "dependencies": { + "@opam/conf-libssl": "3", "@opam/crunch": "*", - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-one-binary/one_binary.ml b/example/w-one-binary/one_binary.ml index ac200fb1..3fae42e0 100644 --- a/example/w-one-binary/one_binary.ml +++ b/example/w-one-binary/one_binary.ml @@ -9,4 +9,3 @@ let () = @@ Dream.router [ Dream.get "/assets/**" (Dream.static ~loader "") ] - @@ Dream.not_found diff --git a/example/w-postgres/README.md b/example/w-postgres/README.md index 8ce2d4f4..39b7f90a 100644 --- a/example/w-postgres/README.md +++ b/example/w-postgres/README.md @@ -36,7 +36,6 @@ let () = Dream.empty `Bad_Request); ] - @@ Dream.not_found ``` In addition, we now link with `caqti-driver-postgres` instead of diff --git a/example/w-postgres/esy.json b/example/w-postgres/esy.json index c7616dd7..73548f67 100644 --- a/example/w-postgres/esy.json +++ b/example/w-postgres/esy.json @@ -1,9 +1,10 @@ { "dependencies": { "@opam/caqti-driver-postgresql": "*", - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-postgres/postgres.eml.ml b/example/w-postgres/postgres.eml.ml index 52ecd53c..0494c4ab 100644 --- a/example/w-postgres/postgres.eml.ml +++ b/example/w-postgres/postgres.eml.ml @@ -4,16 +4,18 @@ module T = Caqti_type let list_comments = let query = - R.collect T.unit T.(tup2 int string) - "SELECT id, text FROM comment" in + let open Caqti_request.Infix in + (T.unit ->* T.(t2 int string)) + "SELECT id, text FROM comment" in fun (module Db : DB) -> let%lwt comments_or_error = Db.collect_list query () in Caqti_lwt.or_fail comments_or_error let add_comment = let query = - R.exec T.string - "INSERT INTO comment (text) VALUES ($1)" in + let open Caqti_request.Infix in + (T.string ->. T.unit) + "INSERT INTO comment (text) VALUES ($1)" in fun text (module Db : DB) -> let%lwt unit_or_error = Db.exec query text in Caqti_lwt.or_fail unit_or_error @@ -25,7 +27,8 @@ let render comments request = % comments |> List.iter (fun (_id, comment) ->

<%s comment %>

<% ); %> - <%s! Dream.form_tag ~action:"/" request %> + + <%s! Dream.csrf_tag request %>
@@ -52,4 +55,3 @@ let () = Dream.empty `Bad_Request); ] - @@ Dream.not_found diff --git a/example/w-query/README.md b/example/w-query/README.md index f40d215f..9c985fd8 100644 --- a/example/w-query/README.md +++ b/example/w-query/README.md @@ -8,7 +8,7 @@ This very simple example accesses a value in the query string with ```ocaml let () = Dream.run (fun request -> - match Dream.query "echo" request with + match Dream.query request "echo" with | None -> Dream.html "Use ?echo=foo to give a message to echo!" | Some message -> diff --git a/example/w-query/esy.json b/example/w-query/esy.json index 06c87a25..06844909 100644 --- a/example/w-query/esy.json +++ b/example/w-query/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-query/query.ml b/example/w-query/query.ml index 0dbff411..3d9b70ca 100644 --- a/example/w-query/query.ml +++ b/example/w-query/query.ml @@ -1,6 +1,6 @@ let () = Dream.run (fun request -> - match Dream.query "echo" request with + match Dream.query request "echo" with | None -> Dream.html "Use ?echo=foo to give a message to echo!" | Some message -> diff --git a/example/w-server-sent-events/esy.json b/example/w-server-sent-events/esy.json index ddf1ef7b..18f27f94 100644 --- a/example/w-server-sent-events/esy.json +++ b/example/w-server-sent-events/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-server-sent-events/server_sent_events.eml.ml b/example/w-server-sent-events/server_sent_events.eml.ml index 12a6dad2..25eab88b 100644 --- a/example/w-server-sent-events/server_sent_events.eml.ml +++ b/example/w-server-sent-events/server_sent_events.eml.ml @@ -38,7 +38,7 @@ let rec message_loop () = message_loop () -let rec forward_messages response = +let rec forward_messages stream = let%lwt messages = match !server_state with | [] -> @@ -58,9 +58,9 @@ let rec forward_messages response = |> List.map (Printf.sprintf "data: %s\n\n") |> String.concat "" |> fun text -> - let%lwt () = Dream.write response text in - let%lwt () = Dream.flush response in - forward_messages response + let%lwt () = Dream.write stream text in + let%lwt () = Dream.flush stream in + forward_messages stream let () = Lwt.async message_loop; @@ -77,4 +77,3 @@ let () = forward_messages); ] - @@ Dream.not_found diff --git a/example/w-stress-response/README.md b/example/w-stress-response/README.md index c513ff24..14d57492 100644 --- a/example/w-stress-response/README.md +++ b/example/w-stress-response/README.md @@ -10,19 +10,16 @@ by default. To use, $ npx esy start $ curl http://localhost:8080 > /dev/null &
-The `curl` command can be repeated for multiple concurrent clients. +The `curl` command can be repeated for multiple concurrent clients, to check +fairness or other effects. -
+The URL supports query parameters: `?mb=16384` sets the total number of +megabytes to respond with (16 GB in this case), and `?chunk=128` changes the +chunk size used during writing (128 KB in this case). -Writing currently slows down for very large streams. This is likely due to the -lack of server-side flow control for writers, which probably causes allocation -of huge internal buffers, which first triggers needless GC, and eventually page -thrashing at the virtual memory level. -[#34](https://github.com/aantron/dream/issues/34) should address this in one of -the early releases of Dream. +
-Nonetheless, for smaller streams, unoptimized Dream is able to peak out at -about 8 Gbits/s, which is more than one curl client can handle (2 Gbits/s). +Dream is currently able to peak out on my machine at about 10 Gbit/s.
diff --git a/example/w-stress-response/esy.json b/example/w-stress-response/esy.json index a66e71da..57ae886c 100644 --- a/example/w-stress-response/esy.json +++ b/example/w-stress-response/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-stress-response/stress_response.ml b/example/w-stress-response/stress_response.ml index 4ea54f0b..dcfefedc 100644 --- a/example/w-stress-response/stress_response.ml +++ b/example/w-stress-response/stress_response.ml @@ -1,10 +1,10 @@ -(* TODO Once concurrent writing is supported, send N concurrent streams and test - for fairness. *) -(* TODO There seems to be some GC thrashing and even page thrashing or similar - with very large streams, probably due to buffer growth from a lack of - server-side flow control. *) +let show_heap_size () = + Gc.((quick_stat ()).heap_words) * 8 + |> float_of_int + |> fun bytes -> bytes /. 1024. /. 1024. + |> Dream.log "Heap size: %.0f MB" -let stress ?(megabytes = 1024) ?(chunk = 64) response = +let stress ?(megabytes = 1024) ?(chunk = 64) stream = let limit = megabytes * 1024 * 1024 in let chunk = chunk * 1024 in @@ -15,12 +15,12 @@ let stress ?(megabytes = 1024) ?(chunk = 64) response = let rec loop sent = if sent >= limit then - let%lwt () = Dream.flush response in - let%lwt () = Dream.close_stream response in + let%lwt () = Dream.flush stream in + let%lwt () = Dream.close stream in Lwt.return (Unix.gettimeofday () -. start) else - let%lwt () = Dream.write response chunk_a in - let%lwt () = Dream.write response chunk_b in + let%lwt () = Dream.write stream chunk_a in + let%lwt () = Dream.write stream chunk_b in let%lwt () = Lwt.pause () in loop (sent + chunk + chunk) in @@ -28,13 +28,16 @@ let stress ?(megabytes = 1024) ?(chunk = 64) response = Dream.log "%.0f MB/s over %.1f s" ((float_of_int megabytes) /. elapsed) elapsed; + show_heap_size (); Lwt.return_unit -let query_int name request = - Dream.query name request |> Option.map int_of_string +let query_int request name = + Dream.query request name |> Option.map int_of_string let () = + show_heap_size (); + Dream.run @@ Dream.logger @@ Dream.router [ @@ -43,8 +46,7 @@ let () = Dream.stream ~headers:["Content-Type", "application/octet-stream"] (stress - ?megabytes:(query_int "mb" request) - ?chunk:(query_int "chunk" request))); + ?megabytes:(query_int request "mb") + ?chunk:(query_int request "chunk"))); ] - @@ Dream.not_found diff --git a/example/w-stress-websocket-send/README.md b/example/w-stress-websocket-send/README.md index a0188eb7..334f44a8 100644 --- a/example/w-stress-websocket-send/README.md +++ b/example/w-stress-websocket-send/README.md @@ -5,15 +5,8 @@ This example serves a client which opens four WebSockets. The server then floods each WebSocket with 1 GB of data in 64 KB one-frame messages. -At the moment, the naive and unoptimized Dream massively outpaces Chrome. Dream -sends the 4 GB in about 8 seconds, at a resulting speed of about 4 Gbits/s. -Chrome appears to receive all the messages, but the JavaScript engine processes -them in about 10 minutes (55 Mbit/s), causing massive buffering in Chrome. - -This test should be improved after flow control is added internally to Dream's -writers in [#34](https://github.com/aantron/dream/issues/34). It should probably -be run with multiple separate client tabs or processes, rather than one -JavaScript context. +At the moment, Dream greatly outpaces Chrome, which appears to limit WebSocket +traffic to 64 MB/s per tab.
diff --git a/example/w-stress-websocket-send/esy.json b/example/w-stress-websocket-send/esy.json index 2915cbe8..54c4570b 100644 --- a/example/w-stress-websocket-send/esy.json +++ b/example/w-stress-websocket-send/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-stress-websocket-send/stress_websocket_send.eml.ml b/example/w-stress-websocket-send/stress_websocket_send.eml.ml index 7a7c33ca..f8d910b3 100644 --- a/example/w-stress-websocket-send/stress_websocket_send.eml.ml +++ b/example/w-stress-websocket-send/stress_websocket_send.eml.ml @@ -26,6 +26,12 @@ let home = +let show_heap_size () = + Gc.((quick_stat ()).heap_words) * 8 + |> float_of_int + |> fun bytes -> bytes /. 1024. /. 1024. + |> Dream.log "Heap size: %.0f MB" + let frame = 64 * 1024 let frame_a = String.make frame 'a' @@ -39,8 +45,8 @@ let stress websocket = let%lwt () = Dream.close_websocket websocket in Lwt.return (Unix.gettimeofday () -. start) else - let%lwt () = Dream.send websocket frame_a ~kind:`Binary in - let%lwt () = Dream.send websocket frame_b ~kind:`Binary in + let%lwt () = Dream.send websocket frame_a ~text_or_binary:`Binary in + let%lwt () = Dream.send websocket frame_b ~text_or_binary:`Binary in let%lwt () = Lwt.pause () in loop (sent + frame + frame) in @@ -48,10 +54,13 @@ let stress websocket = Dream.log "%.0f MB/s over %.1f s" ((float_of_int limit) /. elapsed /. 1024. /. 1024.) elapsed; + show_heap_size (); Lwt.return_unit let () = + show_heap_size (); + Dream.run @@ Dream.logger @@ Dream.router [ @@ -63,4 +72,3 @@ let () = (fun _ -> Dream.websocket stress); ] - @@ Dream.not_found diff --git a/example/w-template-files/README.md b/example/w-template-files/README.md index eb2964cd..fc21eb8b 100644 --- a/example/w-template-files/README.md +++ b/example/w-template-files/README.md @@ -2,20 +2,12 @@
-While templates can be written with other code in `.ml` files, they can also -live in their own source files. This can be useful as templates become larger, -or when you have many templates. - -If your template file is mostly HTML, you can give it a name like -`template.eml.html`, to trigger HTML syntax highlighting by your editor. -Additionally, if you are using `ocamlformat`, the `.html` extension will -prevent errors that come from `ocamlformat` attempting to format the syntax of -the template. - -This example does just that. It splits the code of the basic template example, -[**7-template**](../7-template#files), into two files. The first is the +This example splits the code of the basic template example, +[**`7-template`**](../7-template#files), into two files. The first is the template, in -[`template.eml.html`](https://github.com/aantron/dream/blob/master/example/w-template-files/template.eml.html): +[`template.eml.html`](https://github.com/aantron/dream/blob/master/example/w-template-files/template.eml.html). +We use the `.html` extension because it is mostly HTML, and to prevent +`ocamlformat` from trying to format the file: ```html let render param = @@ -28,7 +20,7 @@ let render param = After preprocessing by the templater, this file becomes `template.ml`, so it defines a module `Template`, containing a function `Template.render`. We call -this function from the main server in +this function from the main server module in [`server.ml`](https://github.com/aantron/dream/blob/master/example/w-template-files/server.ml): ```ocaml @@ -44,7 +36,6 @@ let () = |> Dream.html); ] - @@ Dream.not_found ```
$ cd example/w-template-files
@@ -55,11 +46,12 @@ let () =
 
 **See also:**
 
-- [**7-template**](../7-template#files) for comments on the
-[`dune` file](https://github.com/aantron/dream/blob/master/example/w-template-files/dune)
-and [security
-information](https://github.com/aantron/dream/tree/master/example/7-template#security).
-- [**r-template-files**](../r-template-files) for the Reason syntax version of this example.
+- [**`7-template`**](../7-template#files) for comments on the [`dune`
+  file](https://github.com/aantron/dream/blob/master/example/w-template-files/dune)
+  and [security
+  information](https://github.com/aantron/dream/tree/master/example/7-template#security).
+- [**`r-template-files`**](../r-template-files) for the Reason syntax version of
+  this example.
 
 
diff --git a/example/w-template-files/esy.json b/example/w-template-files/esy.json index d78cda78..62d26806 100644 --- a/example/w-template-files/esy.json +++ b/example/w-template-files/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-template-files/server.ml b/example/w-template-files/server.ml index 07cbdb3e..fac41063 100644 --- a/example/w-template-files/server.ml +++ b/example/w-template-files/server.ml @@ -5,9 +5,8 @@ let () = Dream.get "/:word" (fun request -> - Dream.param "word" request + Dream.param request "word" |> Template.render |> Dream.html); ] - @@ Dream.not_found diff --git a/example/w-template-logic/README.md b/example/w-template-logic/README.md new file mode 100644 index 00000000..27b4e3bb --- /dev/null +++ b/example/w-template-logic/README.md @@ -0,0 +1,86 @@ +# `w-template-logic` + +
+ +OCaml control expressions can be used inside Dream templates. This example +shows a template with a loop written with `List.iter`, an `if`-expression, and +a `match`-expression. + +It's helpful to know that template fragments are written to a buffer +imperatively. That means that template fragments evaluate to `unit`, and the +surrounding OCaml code often needs semicolons. Templates also tend to use +`List.iter` rather than `List.map`. + +```ocaml +let render_home tasks = + + +% tasks |> List.iter begin fun (name, complete) -> +

Task <%s name %>: +% if complete then begin + complete! +% end +% else begin + not complete. +% end; +

+% end; + + + +let render_task tasks task = + + +% begin match List.assoc_opt task tasks with +% | Some complete -> +

Task: <%s task %>

+

Complete: <%B complete %>

+% | None -> +

Task not found!

+% end; + + + +let tasks = [ + ("Write documentation", true); + ("Create examples", true); + ("Publish website", true); + ("Profit", false); +] + +let () = + Dream.run + @@ Dream.logger + @@ Dream.router [ + + Dream.get "/" + (fun _ -> + render_home tasks + |> Dream.html); + + Dream.get "/:task" + (fun request -> + Dream.param request "task" + |> render_task tasks + |> Dream.html); + + ] +``` + +
$ cd example/w-template-logic
+$ npm install esy && npx esy
+$ npx esy start
+ +Try it in the [playground](http://dream.as/w-template-logic). + +
+ +**See also:** + +- [**`7-template`**](../7-template#files) for more information about templates. +- [**`r-template-logic`**](../r-template-logic#files) for the Reason syntax + version of this example. + +
+ +[Up to the example index](../#examples) diff --git a/example/w-advanced-template/dune b/example/w-template-logic/dune similarity index 100% rename from example/w-advanced-template/dune rename to example/w-template-logic/dune diff --git a/example/w-advanced-template/dune-project b/example/w-template-logic/dune-project similarity index 100% rename from example/w-advanced-template/dune-project rename to example/w-template-logic/dune-project diff --git a/example/r-advanced-template/esy.json b/example/w-template-logic/esy.json similarity index 76% rename from example/r-advanced-template/esy.json rename to example/w-template-logic/esy.json index 7d418fb4..ef14e89b 100644 --- a/example/r-advanced-template/esy.json +++ b/example/w-template-logic/esy.json @@ -1,9 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "@opam/reason": "^3.7.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-template-logic/template.eml.ml b/example/w-template-logic/template.eml.ml new file mode 100644 index 00000000..b2c60c66 --- /dev/null +++ b/example/w-template-logic/template.eml.ml @@ -0,0 +1,53 @@ +let render_home tasks = + + +% tasks |> List.iter begin fun (name, complete) -> +

Task <%s name %>: +% if complete then begin + complete! +% end +% else begin + not complete. +% end; +

+% end; + + + +let render_task tasks task = + + +% begin match List.assoc_opt task tasks with +% | Some complete -> +

Task: <%s task %>

+

Complete: <%B complete %>

+% | None -> +

Task not found!

+% end; + + + +let tasks = [ + ("Write documentation", true); + ("Create examples", true); + ("Publish website", true); + ("Profit", false); +] + +let () = + Dream.run + @@ Dream.logger + @@ Dream.router [ + + Dream.get "/" + (fun _ -> + render_home tasks + |> Dream.html); + + Dream.get "/:task" + (fun request -> + Dream.param request "task" + |> render_task tasks + |> Dream.html); + + ] diff --git a/example/w-template-stream/README.md b/example/w-template-stream/README.md index b9b5add3..ca5bb640 100644 --- a/example/w-template-stream/README.md +++ b/example/w-template-stream/README.md @@ -7,23 +7,20 @@ a response body. It sends one paragraph per second to the client: ```ocaml let render response = - let%lwt () = - %% response - - - -% let rec paragraphs index = -

<%i index %>

-% let%lwt () = Dream.flush response in -% let%lwt () = Lwt_unix.sleep 1. in -% paragraphs (index + 1) -% in -% let%lwt () = paragraphs 0 in - - - - in - Dream.close_stream response + %% response + + + +% let rec paragraphs index = +

<%i index %>

+% let%lwt () = Dream.flush response in +% let%lwt () = Lwt_unix.sleep 1. in +% paragraphs (index + 1) +% in +% let%lwt () = paragraphs 0 in + + + let () = Dream.run diff --git a/example/w-template-stream/esy.json b/example/w-template-stream/esy.json index d463fd73..93462f4d 100644 --- a/example/w-template-stream/esy.json +++ b/example/w-template-stream/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-template-stream/template_stream.eml.ml b/example/w-template-stream/template_stream.eml.ml index c617963f..abe787d1 100644 --- a/example/w-template-stream/template_stream.eml.ml +++ b/example/w-template-stream/template_stream.eml.ml @@ -1,21 +1,18 @@ let render response = - let%lwt () = - %% response - - + %% response + + -% let rec paragraphs index = -

<%i index %>

-% let%lwt () = Dream.flush response in -% let%lwt () = Lwt_unix.sleep 1. in -% paragraphs (index + 1) -% in -% let%lwt () = paragraphs 0 in +% let rec paragraphs index = +

<%i index %>

+% let%lwt () = Dream.flush response in +% let%lwt () = Lwt_unix.sleep 1. in +% paragraphs (index + 1) +% in +% let%lwt () = paragraphs 0 in - - - in - Dream.close_stream response + + let () = Dream.run diff --git a/example/w-tyxml/README.md b/example/w-tyxml/README.md index 448cc204..7da128b1 100644 --- a/example/w-tyxml/README.md +++ b/example/w-tyxml/README.md @@ -31,7 +31,6 @@ let () = (fun _ -> Dream.html (html_to_string (greet "world"))); ] - @@ Dream.not_found ```
$ cd example/w-tyxml
@@ -110,4 +109,4 @@ this, please [open an issue](https://github.com/aantron/dream/issues).
 
 
-[Up to the tutorial index](../#examples) +[Up to the example index](../#examples) diff --git a/example/w-tyxml/esy.json b/example/w-tyxml/esy.json index b09f6d15..59c2bd0f 100644 --- a/example/w-tyxml/esy.json +++ b/example/w-tyxml/esy.json @@ -1,9 +1,10 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", "@opam/tyxml": "*", - "ocaml": "4.12.x" + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-tyxml/tyxml.ml b/example/w-tyxml/tyxml.ml index ce23849d..132996d5 100644 --- a/example/w-tyxml/tyxml.ml +++ b/example/w-tyxml/tyxml.ml @@ -20,4 +20,3 @@ let () = (fun _ -> Dream.html (html_to_string (greet "world"))); ] - @@ Dream.not_found diff --git a/example/w-upload-stream/README.md b/example/w-upload-stream/README.md index ac03a22d..4868a3dc 100644 --- a/example/w-upload-stream/README.md +++ b/example/w-upload-stream/README.md @@ -11,7 +11,8 @@ the total size of each uploaded file: let home request = - <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %> +
+ <%s! Dream.csrf_tag request %>
@@ -56,7 +57,6 @@ let () = receive []); ] - @@ Dream.not_found ```
$ npm install esy && npx esy
@@ -70,7 +70,7 @@ Try it in the [playground](http://dream.as/w-upload-stream).
 
 The report page shows one file without a name ("None"). This is, in fact, the
 CSRF token generated by
-[`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) inside the
+[`Dream.csrf_tag`](https://aantron.github.io/dream/#val-csrf_tag) inside the
 template. To keep the example simple, we didn't check the CSRF token, nor filter
 out the `dream.csrf` field that it appears in. If you'd like to do so in your
 code, see
diff --git a/example/w-upload-stream/esy.json b/example/w-upload-stream/esy.json
index 8f7a60bc..b56b6c0b 100644
--- a/example/w-upload-stream/esy.json
+++ b/example/w-upload-stream/esy.json
@@ -1,8 +1,9 @@
 {
   "dependencies": {
-    "@opam/dream": "1.0.0~alpha2",
-    "@opam/dune": "^2.0",
-    "ocaml": "4.12.x"
+    "@opam/conf-libssl": "3",
+    "@opam/dream": "1.0.0~alpha5",
+    "@opam/dune": "^3.0",
+    "ocaml": "^4.14.0"
   },
   "devDependencies": {
     "@opam/ocaml-lsp-server": "*"
diff --git a/example/w-upload-stream/upload_stream.eml.ml b/example/w-upload-stream/upload_stream.eml.ml
index 743f5960..e6d086f9 100644
--- a/example/w-upload-stream/upload_stream.eml.ml
+++ b/example/w-upload-stream/upload_stream.eml.ml
@@ -1,7 +1,8 @@
 let home request =
   
   
-    <%s! Dream.form_tag ~action:"/" ~enctype:`Multipart_form_data request %>
+    
+ <%s! Dream.csrf_tag request %>
@@ -46,4 +47,3 @@ let () = receive []); ] - @@ Dream.not_found diff --git a/example/w-watch/README.md b/example/w-watch/README.md new file mode 100644 index 00000000..0695ede9 --- /dev/null +++ b/example/w-watch/README.md @@ -0,0 +1,22 @@ +# `w-watch` + +
+ +This example introduces dune's watch mode `exec -w` to recompile and run your server when you make changes. + +
$ cd example/w-watch
+$ dune exec -w ./hello.exe
+ +Note that this requires Dune 3.7.0 or higher. + +
+ +**See also:** + +- [**`w-live-reload`**](../w-live-reload#files) adds live reloading, so that + browsers reload when the server is restarted. +- [**`w-esy`**](../w-esy#files) discusses [esy](https://esy.sh/) packaging. + +
+ +[Up to the example index](../#examples) diff --git a/example/w-fswatch/dune b/example/w-watch/dune similarity index 100% rename from example/w-fswatch/dune rename to example/w-watch/dune diff --git a/example/w-fswatch/dune-project b/example/w-watch/dune-project similarity index 100% rename from example/w-fswatch/dune-project rename to example/w-watch/dune-project diff --git a/example/w-fswatch/esy.json b/example/w-watch/esy.json similarity index 76% rename from example/w-fswatch/esy.json rename to example/w-watch/esy.json index 3e71335d..3dc785b3 100644 --- a/example/w-fswatch/esy.json +++ b/example/w-watch/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/w-fswatch/hello.ml b/example/w-watch/hello.ml similarity index 100% rename from example/w-fswatch/hello.ml rename to example/w-watch/hello.ml diff --git a/example/z-docker-esy/README.md b/example/z-docker-esy/README.md index 8ac1695e..25ae9ea8 100644 --- a/example/z-docker-esy/README.md +++ b/example/z-docker-esy/README.md @@ -14,7 +14,6 @@ let () = Dream.get "/" (fun _ -> Dream.html "Dream started by Docker Compose, built with esy!"); ] - @@ Dream.not_found ``` It uses [Docker Compose](https://docs.docker.com/compose/), so that you can diff --git a/example/z-docker-esy/app.ml b/example/z-docker-esy/app.ml index e2c65265..fe07cf92 100644 --- a/example/z-docker-esy/app.ml +++ b/example/z-docker-esy/app.ml @@ -5,4 +5,3 @@ let () = Dream.get "/" (fun _ -> Dream.html "Dream started by Docker Compose, built with esy!"); ] - @@ Dream.not_found diff --git a/example/z-docker-esy/esy.json b/example/z-docker-esy/esy.json index 23132ab0..486351a1 100644 --- a/example/z-docker-esy/esy.json +++ b/example/z-docker-esy/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/z-docker-opam/Dockerfile b/example/z-docker-opam/Dockerfile index c5dea626..09fd7b86 100644 --- a/example/z-docker-opam/Dockerfile +++ b/example/z-docker-opam/Dockerfile @@ -15,7 +15,7 @@ RUN opam exec -- dune build -FROM alpine:3.12 as run +FROM alpine:3.18.4 as run RUN apk add --update libev diff --git a/example/z-docker-opam/app.ml b/example/z-docker-opam/app.ml index e7af6315..df300152 100644 --- a/example/z-docker-opam/app.ml +++ b/example/z-docker-opam/app.ml @@ -5,4 +5,3 @@ let () = Dream.get "/" (fun _ -> Dream.html "Dream started by Docker Compose, built with opam!"); ] - @@ Dream.not_found diff --git a/example/z-docker-opam/hello.opam b/example/z-docker-opam/hello.opam index 0e1b1ac4..884be05d 100644 --- a/example/z-docker-opam/hello.opam +++ b/example/z-docker-opam/hello.opam @@ -1,5 +1,7 @@ opam-version: "2.0" +synopsis: "Dream Docker Hello World example" +maintainer: "Anton Bachin " depends: [ "ocaml" {>= "4.08.0"} "dune" {>= "2.0.0"} diff --git a/example/z-fly/.dockerignore b/example/z-fly/.dockerignore new file mode 100644 index 00000000..e6f12490 --- /dev/null +++ b/example/z-fly/.dockerignore @@ -0,0 +1,15 @@ +# By ignoring _esy and _build, the final docker image size stays compact and uploaded quickly to Fly. + +# esy build environment +_esy/ + +# Dune build environment +_build/ + +# Git +.git/ +.gitignore + +# Development +Dockerfile +docker-compose.yml diff --git a/example/z-fly/.gitignore b/example/z-fly/.gitignore new file mode 100644 index 00000000..55e75253 --- /dev/null +++ b/example/z-fly/.gitignore @@ -0,0 +1,2 @@ +node_modules +_esy \ No newline at end of file diff --git a/example/z-fly/Dockerfile b/example/z-fly/Dockerfile new file mode 100644 index 00000000..f0515764 --- /dev/null +++ b/example/z-fly/Dockerfile @@ -0,0 +1,28 @@ +FROM debian:stable-slim as build + +RUN apt-get update +RUN apt-get install -y curl git libpq-dev m4 npm unzip + +WORKDIR /build + +RUN npm install esy + +# Install dependencies. +ADD esy.* . +RUN [ -f esy.lock ] || node_modules/.bin/esy solve +RUN node_modules/.bin/esy fetch +RUN node_modules/.bin/esy build-dependencies + +# Build project. +ADD . . +RUN node_modules/.bin/esy install +RUN node_modules/.bin/esy build + +FROM debian:stable-slim as run + +RUN apt-get update +RUN apt-get install -y libev4 libpq5 libssl1.1 + +COPY --from=build build/_esy/default/build/default/app.exe /bin/app + +ENTRYPOINT /bin/app diff --git a/example/z-fly/README.md b/example/z-fly/README.md new file mode 100644 index 00000000..fea881ea --- /dev/null +++ b/example/z-fly/README.md @@ -0,0 +1,83 @@ +# `z-fly` + +
+ +This example deploys a very simple Dream +[application](https://github.com/aantron/dream/blob/master/example/z-fly/app.ml) +to [Fly.io](https://www.fly.io/), a hosting platform that scales and smartly +moves your servers closer to your users. A low-usage app can be hosted for +[free](https://fly.io/docs/about/pricing/#free-tier). Fly.io offers +[`flyctl`](https://fly.io/docs/getting-started/installing-flyctl/), their CLI, +that makes [deployment](https://fly.io/docs/hands-on/start/) and +[scaling](https://fly.io/docs/reference/scaling/) very easy. + +```ocaml +let () = + Dream.run ~interface:"0.0.0.0" + @@ Dream.logger + @@ Dream.router [ + Dream.get "/" (fun _ -> Dream.html "Dream deployed on Fly.io!"); + ] +``` + +It uses [Docker Compose](https://docs.docker.com/compose/), so that you can +quickly expand it by adding databases and other services. + +```yaml +version: "3" + +services: + web: + build: . + ports: + - "8080:8080" + restart: always + logging: + driver: ${LOGGING_DRIVER:-json-file} +``` + +The setup can be run locally or on any server provider. + +The +[`Dockerfile`](https://github.com/aantron/dream/blob/master/example/z-docker-esy/Dockerfile) +has two stages: one for building our application, and one for the runtime that +only contains the final binary and its run-time dependencies. + +
+ +## Deploying + +Fly has a [setup guide](https://fly.io/docs/hands-on/start/) that we'll follow. + +1. Install `flyctl` with `curl -L https://fly.io/install.sh | sh` or follow the + specific [instructions](https://fly.io/docs/hands-on/installing/) for your + system. +2. Run `fly launch` to initialize and deploy your project. + +That should be it! Assuming no errors, the cli will print a link to your live +app. + +
+ +## Development + +For local development you can run your app with or without Docker. Setting up +the Docker build for the first time may take at least 4 minutes. Subsequent +builds are cached. + +With Docker: + +1. [Install Docker](https://www.docker.com/get-started). +2. Ensure Docker is running, then run `docker compose up`. Docker should build, + cache, and serve your app at `localhost:8080`. + +Without Docker: + +1. Make sure you have [esy](https://esy.sh) installed. +2. Run `esy` to install all dependencies. +3. To start your app, run `esy start`. This is an aliased command set up inside + `esy.json`. + +
+ +[Up to the example index](../#deploying) diff --git a/example/z-fly/app.ml b/example/z-fly/app.ml new file mode 100644 index 00000000..698d7581 --- /dev/null +++ b/example/z-fly/app.ml @@ -0,0 +1,6 @@ +let () = + Dream.run ~interface:"0.0.0.0" + @@ Dream.logger + @@ Dream.router [ + Dream.get "/" (fun _ -> Dream.html "Dream deployed on Fly.io!"); + ] diff --git a/example/z-fly/docker-compose.yml b/example/z-fly/docker-compose.yml new file mode 100644 index 00000000..3003b28b --- /dev/null +++ b/example/z-fly/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3" + +services: + web: + build: . + ports: + - "8080:8080" + restart: always + logging: + driver: ${LOGGING_DRIVER:-json-file} diff --git a/example/z-fly/dune b/example/z-fly/dune new file mode 100644 index 00000000..ef7bdf98 --- /dev/null +++ b/example/z-fly/dune @@ -0,0 +1,5 @@ +(executable + (name app) + (libraries dream)) + +(data_only_dirs _esy esy.lock lib node_modules) diff --git a/example/z-fly/dune-project b/example/z-fly/dune-project new file mode 100644 index 00000000..929c696e --- /dev/null +++ b/example/z-fly/dune-project @@ -0,0 +1 @@ +(lang dune 2.0) diff --git a/example/z-fly/esy.json b/example/z-fly/esy.json new file mode 100644 index 00000000..f4022d8c --- /dev/null +++ b/example/z-fly/esy.json @@ -0,0 +1,23 @@ +{ + "scripts": { + "start": "./_esy/default/build/default/app.exe", + "build": "dune build" + }, + "dependencies": { + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" + }, + "devDependencies": { + "@opam/ocaml-lsp-server": "*" + }, + "resolutions": { + "@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829" + }, + "esy": { + "build": [ + "dune build --root . ./app.exe" + ] + } +} diff --git a/example/z-heroku/README.md b/example/z-heroku/README.md index e4af03c7..45eecbce 100644 --- a/example/z-heroku/README.md +++ b/example/z-heroku/README.md @@ -21,7 +21,6 @@ let () = @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html "Dream running in Heroku!"); ] - @@ Dream.not_found ``` It is running at diff --git a/example/z-heroku/app.ml b/example/z-heroku/app.ml index c2c86bdb..232682e6 100644 --- a/example/z-heroku/app.ml +++ b/example/z-heroku/app.ml @@ -4,4 +4,3 @@ let () = @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html "Dream running in Heroku!"); ] - @@ Dream.not_found diff --git a/example/z-heroku/esy.json b/example/z-heroku/esy.json index 85cd3ee8..7020dae3 100644 --- a/example/z-heroku/esy.json +++ b/example/z-heroku/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.x" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/example/z-playground/opam-switch b/example/z-playground/opam-switch index 9d51863d..98425dfa 100644 --- a/example/z-playground/opam-switch +++ b/example/z-playground/opam-switch @@ -1,161 +1,119 @@ opam-version: "2.0" -compiler: [ - "base-bigarray.base" - "base-threads.base" - "base-unix.base" - "ocaml.4.12.0" - "ocaml-base-compiler.4.12.0" - "ocaml-config.2" - "ocaml-options-vanilla.1" +compiler: ["ocaml-base-compiler.5.0.0"] +roots: [ + "angstrom.0.15.0" + "base64.3.5.1" + "bigarray-compat.1.1.0" + "bigstringaf.0.9.1" + "camlp-streams.5.0.1" + "caqti.1.9.0" + "caqti-lwt.1.9.0" + "conf-libev.4-12" + "cstruct.6.2.0" + "digestif.1.1.4" + "dune.3.7.1" + "faraday.0.8.2" + "faraday-lwt-unix.0.8.2" + "fmt.0.9.0" + "graphql-lwt.0.14.0" + "graphql_parser.0.14.0" + "hmap.0.8.1" + "ke.0.6" + "logs.0.7.0" + "lwt.5.6.1" + "lwt_ppx.2.1.0" + "lwt_ssl.1.2.0" + "magic-mime.1.3.0" + "mirage-clock.4.2.0" + "mirage-crypto.0.11.1" + "mirage-crypto-rng.0.11.1" + "mirage-crypto-rng-lwt.0.11.1" + "multipart_form.0.5.0" + "multipart_form-lwt.0.5.0" + "ocaml-base-compiler.5.0.0" + "psq.0.2.1" + "ptime.1.1.0" + "result.1.5" + "ssl.0.5.13" + "uri.4.2.0" + "yojson.2.1.0" ] -roots: ["multipart_form.git" "ocaml-base-compiler.4.12.0"] installed: [ "angstrom.0.15.0" - "base.v0.14.1" "base-bigarray.base" "base-bytes.base" + "base-domains.base" + "base-nnp.base" "base-threads.base" "base-unix.base" - "base64.3.5.0" - "bigarray-compat.1.0.0" - "bigarray-overlap.0.2.0" - "bigstringaf.0.7.0" - "biniou.1.2.1" - "bisect_ppx.2.6.0" - "caqti.1.5.0" - "caqti-driver-sqlite3.1.5.0" - "caqti-lwt.1.3.0" - "cmdliner.1.0.4" - "conf-libev.4-11" - "conf-libssl.3" + "base64.3.5.1" + "bigarray-compat.1.1.0" + "bigarray-overlap.0.2.1" + "bigstringaf.0.9.1" + "camlp-streams.5.0.1" + "caqti.1.9.0" + "caqti-lwt.1.9.0" + "conf-libev.4-12" + "conf-libssl.4" "conf-pkg-config.2" - "conf-sqlite3.1" - "cppo.1.6.7" - "csexp.1.5.1" - "cstruct.6.0.0" - "cudf.0.9-1" - "digestif.1.0.0" - "dune.2.8.5" - "dune-configurator.2.8.5" - "duration.0.1.3" - "easy-format.1.3.2" - "eqaf.0.7" - "extlib.1.7.7-1" - "faraday.0.7.2" - "faraday-lwt.0.7.2" - "faraday-lwt-unix.0.7.2" - "fix.20201120" - "fmt.0.8.9" - "graphql.0.13.0" - "graphql-lwt.0.13.0" - "graphql_parser.0.13.0" + "cppo.1.6.9" + "csexp.1.5.2" + "cstruct.6.2.0" + "digestif.1.1.4" + "dune.3.7.1" + "dune-configurator.3.7.1" + "duration.0.2.1" + "eqaf.0.9" + "faraday.0.8.2" + "faraday-lwt.0.8.2" + "faraday-lwt-unix.0.8.2" + "fmt.0.9.0" + "graphql.0.14.0" + "graphql-lwt.0.14.0" + "graphql_parser.0.14.0" "hmap.0.8.1" - "ke.0.4" + "ke.0.6" "logs.0.7.0" - "lwt.5.4.0" - "lwt_ppx.2.0.2" - "lwt_ssl.1.1.3" - "magic-mime.1.1.3" - "markup.1.0.0-1" - "menhir.20210419" - "menhirLib.20210419" - "menhirSdk.20210419" - "merlin-extend.0.6" - "mirage-crypto.0.10.1" - "mirage-crypto-rng.0.10.1" - "mmap.1.1.0" - "mtime.1.2.0" - "multipart-form-data.0.3.0" - "multipart_form.git" - "ocaml.4.12.0" - "ocaml-base-compiler.4.12.0" - "ocaml-compiler-libs.v0.12.3" - "ocaml-config.2" - "ocaml-migrate-parsetree.2.1.0" + "lwt.5.6.1" + "lwt_ppx.2.1.0" + "lwt_ssl.1.2.0" + "magic-mime.1.3.0" + "menhir.20230415" + "menhirLib.20230415" + "menhirSdk.20230415" + "mirage-clock.4.2.0" + "mirage-crypto.0.11.1" + "mirage-crypto-rng.0.11.1" + "mirage-crypto-rng-lwt.0.11.1" + "mtime.2.0.0" + "multipart_form.0.5.0" + "multipart_form-lwt.0.5.0" + "ocaml.5.0.0" + "ocaml-base-compiler.5.0.0" + "ocaml-compiler-libs.v0.12.4" + "ocaml-config.3" "ocaml-options-vanilla.1" "ocaml-syntax-shims.1.0.0" - "ocamlbuild.0.14.0" - "ocamlfind.1.9.1" - "ocamlgraph.2.0.0" - "ocplib-endian.1.1" - "octavius.1.2.2" - "opam-core.2.1.0~beta4" - "opam-file-format.2.1.2" - "opam-format.2.1.0~beta4" - "opam-installer.2.1.0~beta4" - "pecu.0.5" + "ocamlbuild.0.14.2" + "ocamlfind.1.9.6" + "ocplib-endian.1.2" + "pecu.0.6" "ppx_derivers.1.2.1" - "ppx_js_style.v0.14.0" - "ppx_yojson_conv.v0.14.0" - "ppx_yojson_conv_lib.v0.14.0" - "ppxlib.0.22.0" - "prettym.0.0.1" - "psq.0.2.0" - "ptime.0.8.5" - "re.1.9.0" - "reason.3.7.0" + "ppxlib.0.29.1" + "prettym.0.0.3" + "psq.0.2.1" + "ptime.1.1.0" + "re.1.10.4" "result.1.5" - "rresult.0.6.0" + "rresult.0.7.0" "seq.base" - "sexplib0.v0.14.0" - "sqlite3.5.0.3" - "ssl.0.5.10" + "sexplib0.v0.15.1" + "ssl.0.5.13" "stdlib-shims.0.3.0" "stringext.1.6.0" - "topkg.1.0.3" - "tyxml.4.5.0" - "tyxml-jsx.4.5.0" - "tyxml-ppx.4.5.0" - "tyxml-syntax.4.5.0" - "uchar.0.0.2" - "unstrctrd.0.2" + "topkg.1.0.7" + "unstrctrd.0.3" "uri.4.2.0" - "uutf.1.0.2" - "yojson.1.7.0" + "uutf.1.0.3" + "yojson.2.1.0" ] -pinned: "multipart_form.git" -package "multipart_form" { - opam-version: "2.0" - version: "git" - synopsis: "Multipart-form: RFC2183, RFC2388 & RFC7578" - description: """ -Implementation of RFC7578 in OCaml - -Returning values from forms: multipart/form-data""" - maintainer: "Romain Calascibetta " - authors: "Romain Calascibetta " - license: "MIT" - homepage: "https://github.com/dinosaure/multipart_form" - doc: "https://dinosaure.github.io/multipart_form/" - bug-reports: "https://github.com/dinosaure/multipart_form/issues" - depends: [ - "ocaml" {>= "4.08.0"} - "dune" {>= "2.0.0"} - "angstrom" {>= "0.14.0"} - "base64" - "unstrctrd" {>= "0.2"} - "rresult" - "uutf" - "stdlib-shims" - "pecu" {>= "0.4"} - "lwt" - "prettym" - "fmt" - "logs" - "ke" {>= "0.4"} - "alcotest" {with-test} - "rosetta" {with-test} - "bigarray-compat" {>= "1.0.0"} - "bigstringaf" {>= "0.7.0"} - "result" {>= "1.5"} - ] - build: [ - ["dune" "build" "-p" name "-j" jobs] - ["dune" "runtest" "-p" name "-j" jobs] {with-test} - ] - dev-repo: "git+https://github.com/dinosaure/multipart_form.git" - url { - src: - "git+https://github.com/dinosaure/multipart_form.git#8f5b6cb77af1a385155b0aeac40437dba6e56577" - } -} diff --git a/example/z-playground/package-lock.json b/example/z-playground/package-lock.json index f34206be..1e3143e1 100644 --- a/example/z-playground/package-lock.json +++ b/example/z-playground/package-lock.json @@ -1,7 +1,20 @@ { "name": "dream-playground", + "lockfileVersion": 2, "requires": true, - "lockfileVersion": 1, + "packages": { + "": { + "name": "dream-playground", + "dependencies": { + "codemirror": "*" + } + }, + "node_modules/codemirror": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.0.tgz", + "integrity": "sha512-D3wYH90tYY1BsKlUe0oNj2JAhQ9TepkD51auk3N7q+4uz7A/cgJ5JsWHreT0PqieW1QhOuqxQ2reCXV1YXzecg==" + } + }, "dependencies": { "codemirror": { "version": "5.61.0", diff --git a/example/z-playground/playground.opam b/example/z-playground/playground.opam index d3ea9a3e..7f30f2f8 100644 --- a/example/z-playground/playground.opam +++ b/example/z-playground/playground.opam @@ -1,5 +1,7 @@ opam-version: "2.0" +synopsis: "Dream playground" +maintainer: "Anton Bachin " depends: [ "caqti-driver-sqlite3" "dune" {>= "2.0.0"} diff --git a/example/z-playground/runtime/runtime.opam b/example/z-playground/runtime/runtime.opam index 0d2ac090..a1c814d2 100644 --- a/example/z-playground/runtime/runtime.opam +++ b/example/z-playground/runtime/runtime.opam @@ -1,5 +1,7 @@ opam-version: "2.0" +synopsis: "Dream playground runtime" +maintainer: "Anton Bachin " depends: [ "dream" "dune" {>= "2.0.0"} diff --git a/example/z-playground/sandbox/ocaml/server.eml.ml b/example/z-playground/sandbox/ocaml/server.eml.ml index 4876aa13..b346a09e 100644 --- a/example/z-playground/sandbox/ocaml/server.eml.ml +++ b/example/z-playground/sandbox/ocaml/server.eml.ml @@ -4,4 +4,3 @@ let () = @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html Playground.welcome); ] - @@ Dream.not_found diff --git a/example/z-playground/sandbox/reason/server.eml.re b/example/z-playground/sandbox/reason/server.eml.re index 03f3fbca..38f9cefa 100644 --- a/example/z-playground/sandbox/reason/server.eml.re +++ b/example/z-playground/sandbox/reason/server.eml.re @@ -4,4 +4,3 @@ let () = @@ Dream.router([ Dream.get("/", _ => Dream.html(Playground.welcome)), ]) - @@ Dream.not_found diff --git a/example/z-playground/server/playground.ml b/example/z-playground/server/playground.ml index f2918ca3..3faa20bd 100644 --- a/example/z-playground/server/playground.ml +++ b/example/z-playground/server/playground.ml @@ -41,7 +41,7 @@ let sandbox_dune_no_eml = {|(executable (preprocess (pps lwt_ppx ppx_yojson_conv tyxml-jsx tyxml-ppx))) |} -let base_dockerfile = {|FROM ubuntu:focal-20210416 +let base_dockerfile = {|FROM ubuntu:jammy-20230425 RUN apt update && apt install -y openssl libev4 libsqlite3-0 WORKDIR /www COPY db.sqlite db.sqlite @@ -500,7 +500,7 @@ let () = (* Start the Web server. *) let playground_handler request = - let sandbox = Dream.param "id" request in + let sandbox = Dream.param request "id" in match validate_id sandbox with | false -> Dream.empty `Not_Found | true -> @@ -533,7 +533,7 @@ let () = nice error handling here, because a valid client won't trigger them. If they occur, they are harmless to the server. *) Dream.get "/socket" (fun request -> - match Dream.query "sandbox" request with + match Dream.query request "sandbox" with | None -> Dream.empty `Bad_Request | Some sandbox -> match validate_id sandbox with @@ -555,8 +555,7 @@ let () = Dream.get "/:id" playground_handler; Dream.get "/:id/**" playground_handler; - ] - @@ Dream.not_found; + ]; Dream.log "Killing all containers"; Sys.command "docker kill $(docker ps -q)" |> ignore; diff --git a/example/z-playground/server/setup.sh b/example/z-playground/server/setup.sh index 2657f90b..6213563a 100644 --- a/example/z-playground/server/setup.sh +++ b/example/z-playground/server/setup.sh @@ -22,7 +22,7 @@ sudo apt install -y docker-ce sudo apt install -y build-essential m4 unzip bubblewrap pkg-config # Install opam itself. -wget -O opam https://github.com/ocaml/opam/releases/download/2.0.8/opam-2.0.8-x86_64-linux +wget -O opam https://github.com/ocaml/opam/releases/download/2.1.4/opam-2.1.4-x86_64-linux sudo mv opam /usr/local/bin/ sudo chmod a+x /usr/local/bin/opam @@ -33,7 +33,7 @@ sudo apt install -y npm sudo apt install -y libev-dev libsqlite3-dev libssl-dev pkg-config # Create users. User playground is used for building and running the playground. -# The reason there isn't a separate user for buulding it is that the playground +# The reason there isn't a separate user for building it is that the playground # itself will use the build setup to build the sandboxes. User sandbox is for # the containers. sudo adduser --disabled-password playground @@ -45,7 +45,7 @@ sudo adduser --system sandbox # Initialize opam and install a compiler. sudo -H -u playground opam init --no-setup --bare -sudo -H -u playground opam switch create 4.12.0 +sudo -H -u playground opam switch create 5.0.0 # Set up UFW. sudo ufw allow ssh diff --git a/example/z-playground/server/sync.sh b/example/z-playground/server/sync.sh index 43b1db92..3dce1b2b 100644 --- a/example/z-playground/server/sync.sh +++ b/example/z-playground/server/sync.sh @@ -8,7 +8,7 @@ DIR=playground/example/z-playground rsync -v $HOST:$DIR/package-lock.json $HOST:$DIR/opam-switch . || true rsync -rlv --exclude node_modules \ - ../../dream.opam ../../dune-project ../../src $HOST:playground + ../../dream*.opam ../../dune-project ../../src $HOST:playground ssh $HOST "mkdir -p $DIR" rsync -rlv . $HOST:$DIR @@ -49,17 +49,18 @@ example h-sql example i-graphql example j-stream example k-websocket -example w-graphql-subscription +example w-query example w-flash -example w-content-security-policy +example w-tyxml +example w-chat +example w-graphql-subscription example w-long-polling -example w-multipart-dump -example w-query example w-server-sent-events +example w-template-logic example w-template-stream -example w-tyxml example w-upload-stream -example w-chat +example w-content-security-policy +example w-multipart-dump touch ./sync-temp/sandbox/w-tyxml/no-eml mv ./sync-temp/sandbox/w-tyxml/server.eml.ml \ ./sync-temp/sandbox/w-tyxml/server.ml @@ -76,6 +77,7 @@ function example_re { } example_re r-hello example_re r-template +example_re r-template-logic example_re r-template-stream example_re r-graphql example_re r-tyxml @@ -97,7 +99,8 @@ set +x echo echo "If this is the first sync, run as playground@$HOST in ~/playground:" -echo " opam install --deps-only ." +echo " opam install --deps-only ./dream-pure.opam ./dream-httpaf.opam ./dream.opam" +echo " cd example/z-playground/" echo " opam switch export opam-switch" echo " npm install" echo "Then, as root@$HOST:" diff --git a/example/z-systemd/README.md b/example/z-systemd/README.md index 73f023dc..944979eb 100644 --- a/example/z-systemd/README.md +++ b/example/z-systemd/README.md @@ -34,7 +34,6 @@ let () = @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html "Dream started by systemd!"); ] - @@ Dream.not_found ``` It is live at [http://systemd.dream.as](http://systemd.dream.as). As a second diff --git a/example/z-systemd/app.ml b/example/z-systemd/app.ml index 98261d50..87703ff6 100644 --- a/example/z-systemd/app.ml +++ b/example/z-systemd/app.ml @@ -4,4 +4,3 @@ let () = @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html "Dream started by systemd!"); ] - @@ Dream.not_found diff --git a/example/z-systemd/esy.json b/example/z-systemd/esy.json index 23132ab0..486351a1 100644 --- a/example/z-systemd/esy.json +++ b/example/z-systemd/esy.json @@ -1,8 +1,9 @@ { "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "ocaml": "4.12.x" + "@opam/conf-libssl": "3", + "@opam/dream": "1.0.0~alpha5", + "@opam/dune": "^3.0", + "ocaml": "^4.14.0" }, "devDependencies": { "@opam/ocaml-lsp-server": "*" diff --git a/src/certificate/dune b/src/certificate/dune index a75f8585..5b1642ce 100644 --- a/src/certificate/dune +++ b/src/certificate/dune @@ -1,9 +1,9 @@ (library - (public_name dream.localhost) - (name dream__localhost)) + (public_name dream.certificate) + (name dream__certificate)) (rule - (target dream__localhost.ml) + (target dream__certificate.ml) (deps (:certificate localhost.crt) (:key localhost.key)) @@ -11,10 +11,10 @@ (with-stdout-to %{target} (progn - (echo "let certificate = {ssl|") + (echo "let localhost_certificate = {ssl|") (cat %{certificate}) (echo "|ssl}\n\n") - (echo "let key = {key|") + (echo "let localhost_certificate_key = {key|") (cat %{key}) (echo "|key}\n") )))) diff --git a/src/cipher/cipher.ml b/src/cipher/cipher.ml index 1ba98a69..9eaaabca 100644 --- a/src/cipher/cipher.ml +++ b/src/cipher/cipher.ml @@ -11,6 +11,12 @@ (* TODO LATER Switch to AEAD_AES_256_GCM_SIV. See https://github.com/mirage/mirage-crypto/issues/111. *) + + +module Message = Dream_pure.Message + + + module type Cipher = sig val prefix : char @@ -111,3 +117,46 @@ struct | None -> None | Some plaintext -> Some (Cstruct.to_string plaintext) end + +let secrets_field = + Message.new_field + ~name:"dream.secret" + ~show_value:(fun _secrets -> "[redacted]") + () + +(* TODO Add warnings about secret length and such. *) +(* TODO Also add warnings about implicit secret generation. However, these + warnings might be pretty spammy. *) +(* TODO Update examples and docs. *) +let set_secret ?(old_secrets = []) secret = + let value = secret::old_secrets in + fun next_handler request -> + Message.set_field request secrets_field value; + next_handler request + +let fallback_secrets = + lazy [Random.random 32] + +let encryption_secret request = + match Message.field request secrets_field with + | Some secrets -> List.hd secrets + | None -> List.hd (Lazy.force fallback_secrets) + +let decryption_secrets request = + match Message.field request secrets_field with + | Some secrets -> secrets + | None -> Lazy.force fallback_secrets + +let encrypt ?associated_data request plaintext = + encrypt + (module AEAD_AES_256_GCM) + ?associated_data + (encryption_secret request) + plaintext + +let decrypt ?associated_data request ciphertext = + decrypt + (module AEAD_AES_256_GCM) + ?associated_data + (decryption_secrets request) + ciphertext diff --git a/src/cipher/dune b/src/cipher/dune index bc23d7db..aedd14ee 100644 --- a/src/cipher/dune +++ b/src/cipher/dune @@ -3,6 +3,7 @@ (name dream__cipher) (libraries cstruct + dream-pure mirage-crypto mirage-crypto-rng ) diff --git a/src/dream.ml b/src/dream.ml index 9a2c80e4..07b9248a 100644 --- a/src/dream.ml +++ b/src/dream.ml @@ -5,95 +5,400 @@ -module Method_and_status = +module Catch = Dream__server.Catch +module Cipher = Dream__cipher.Cipher +module Cookie = Dream__server.Cookie +module Csrf = Dream__server.Csrf +module Echo = Dream__server.Echo +module Error_handler = Dream__http.Error_handler +module Flash = Dream__server.Flash +module Form = Dream__server.Form +module Formats = Dream_pure.Formats +module Graphql = Dream__graphql.Graphql +module Helpers = Dream__server.Helpers +module Http = Dream__http.Http +module Livereload = Dream__server.Livereload +module Message = Dream_pure.Message +module Method = Dream_pure.Method +module Origin_referrer_check = Dream__server.Origin_referrer_check +module Query = Dream__server.Query +module Random = Dream__cipher.Random +module Router = Dream__server.Router +module Site_prefix = Dream__server.Site_prefix +module Sql = Dream__sql.Sql +module Sql_session = Dream__sql.Session +module Static = Dream__unix.Static +module Status = Dream_pure.Status +module Stream = Dream_pure.Stream +module Tag = Dream__server.Tag +module Upload = Dream__server.Upload + + + +(* Initialize clock handling and random number generator. These are + platform-specific, differing between Unix and Mirage. This is the Unix + initialization. *) + +module Log = struct - include Dream__pure.Method - include Dream__pure.Status + include Dream__server.Log + include Dream__server.Log.Make (Ptime_clock) end -include Dream__pure.Inmost +let default_log = + Log.sub_log (Logs.Src.name Logs.default) + +let () = + Log.initialize ~setup_outputs:Fmt_tty.setup_std_outputs -(* Eliminate optional arguments from the public interface for now. *) -let next ~buffer ~close ~exn request = - next ~buffer ~close ~exn request +let now () = + Ptime.to_float_s (Ptime.v (Ptime_clock.now_d_ps ())) -include Dream__middleware.Log -include Dream__middleware.Log.Make (Ptime_clock) -(* Initalize logs with the default reporter which uses [Ptime_clock], this - function is a part of [Dream__middleware.Log.Make], it's why it is not - prepended by a module name. *) let () = - initialize ~setup_outputs:Fmt_tty.setup_std_outputs -include Dream__middleware.Echo + Random.initialize (fun () -> + Mirage_crypto_rng_lwt.initialize (module Mirage_crypto_rng.Fortuna)) + +module Session = +struct + include Dream__server.Session + include Dream__server.Session.Make (Ptime_clock) +end + + + +(* Types *) + +type request = Message.request +type response = Message.response +type handler = Message.handler +type middleware = Message.middleware +type route = Router.route + +type 'a message = 'a Message.message +type client = Message.client +type server = Message.server +type 'a promise = 'a Message.promise + + + +(* Methods *) + +include Method + + + +(* Status codes *) + +include Status + + + +(* Requests *) + +let client = Helpers.client +let tls = Helpers.tls +let method_ = Message.method_ +let target = Message.target +let prefix = Router.prefix +let path = Router.path +let set_client = Helpers.set_client +let set_method_ = Message.set_method_ +let query = Query.query +let queries = Query.queries +let all_queries = Query.all_queries + + + +(* Responses *) + +let response = Helpers.response_with_body +let respond = Helpers.respond +let html = Helpers.html +let json = Helpers.json +let redirect = Helpers.redirect +let empty = Helpers.empty +let status = Message.status +let set_status = Message.set_status + + + +(* Headers *) + +let header = Message.header +let headers = Message.headers +let all_headers = Message.all_headers +let has_header = Message.has_header +let add_header = Message.add_header +let drop_header = Message.drop_header +let set_header = Message.set_header + + + +(* Cookies *) + +let set_cookie = Cookie.set_cookie +let drop_cookie = Cookie.drop_cookie +let cookie = Cookie.cookie +let all_cookies = Cookie.all_cookies + + + +(* Bodies *) + +let body = Message.body +let set_body = Message.set_body + + + +(* Streams *) + +type stream = Stream.stream +let body_stream = Message.server_stream +let stream = Helpers.stream +let read = Message.read +let write = Message.write +let flush = Message.flush +let close = Message.close +type buffer = Stream.buffer +let client_stream = Message.client_stream +let server_stream = Message.server_stream +let set_client_stream = Message.set_client_stream +let set_server_stream = Message.set_server_stream +let read_stream = Stream.read +let write_stream = Stream.write +let flush_stream = Stream.flush +let ping_stream = Stream.ping +let pong_stream = Stream.pong +let close_stream = Stream.close +let abort_stream = Stream.abort + + + +(* WebSockets *) + +type websocket = stream * stream +let websocket = Helpers.websocket +type text_or_binary = [ `Text | `Binary ] +type end_of_message = [ `End_of_message | `Continues ] +let send = Helpers.send +let receive = Helpers.receive +let receive_fragment = Helpers.receive_fragment +let close_websocket = Message.close_websocket + -let default_log = - Dream__middleware.Log.sub_log (Logs.Src.name Logs.default) +(* JSON *) + +let origin_referrer_check = Origin_referrer_check.origin_referrer_check + + + +(* Forms *) + +type 'a form_result = 'a Form.form_result +let form = Form.form ~now +type multipart_form = Upload.multipart_form +let multipart = Upload.multipart ~now +type part = Upload.part +let upload = Upload.upload +let upload_part = Upload.upload_part +type csrf_result = Csrf.csrf_result +let csrf_token = Csrf.csrf_token ~now +let verify_csrf_token = Csrf.verify_csrf_token ~now + + + +(* Templates *) + +let csrf_tag = Tag.csrf_tag ~now + + +(* Middleware *) + +let no_middleware = Message.no_middleware +let pipeline = Message.pipeline +let livereload = Livereload.livereload + + + +(* Routing *) + +let router = Router.router +let get = Router.get +let post = Router.post +let put = Router.put +let delete = Router.delete +let head = Router.head +let connect = Router.connect +let options = Router.options +let trace = Router.trace +let patch = Router.patch +let any = Router.any +let not_found = Helpers.not_found +let param = Router.param +let scope = Router.scope +let no_route = Router.no_route + + + +(* Static files *) + +let static = Static.static +let from_filesystem = Static.from_filesystem +let mime_lookup = Static.mime_lookup + + + +(* Sessions *) +(* TODO Internalize argument order and name changes. *) + +let session = Session.session +let session_field request name = session name request +let put_session = Session.put_session +let set_session_field request name value = put_session name value request +let drop_session_field = Session.drop_session_field +let all_session_values = Session.all_session_values +let all_session_fields = all_session_values +let invalidate_session = Session.invalidate_session +let memory_sessions = Session.memory_sessions +let cookie_sessions = Session.cookie_sessions +let sql_sessions = Sql_session.sql_sessions +let session_id = Session.session_id +let session_label = Session.session_label +let session_expires_at = Session.session_expires_at + + + +(* Flash messages *) +(* TODO Internalize argument order and name changes. *) + +let flash = Flash.flash_messages +let flash_messages = Flash.flash +let add_flash_message = Flash.put_flash + + + +(* GraphQL *) + +let graphql = Graphql.graphql +let graphiql = Graphql.graphiql + + + +(* SQL *) + +let sql_pool = Sql.sql_pool +let sql = Sql.sql + + + +(* Logging *) + +let logger = Log.logger +let log = Log.convenience_log +type ('a, 'b) conditional_log = ('a, 'b) Log.conditional_log +type log_level = Log.log_level let error = default_log.error let warning = default_log.warning let info = default_log.info let debug = default_log.debug +type sub_log = Log.sub_log = { + error : 'a. ('a, unit) conditional_log; + warning : 'a. ('a, unit) conditional_log; + info : 'a. ('a, unit) conditional_log; + debug : 'a. ('a, unit) conditional_log; +} +let sub_log = Log.sub_log +let initialize_log = Log.initialize_log +let set_log_level = Log.set_log_level -include Dream__middleware.Router -include Dream__unix.Static -include Dream__middleware.Session -include Dream__middleware.Session.Make (Ptime_clock) -let sql_sessions = Dream__sql.Session.middleware -include Dream__middleware.Flash +(* Errors *) -include Dream__middleware.Origin_referrer_check -include Dream__middleware.Form -include Dream__middleware.Upload -include Dream__middleware.Csrf +type error = Catch.error = { + condition : [ + | `Response of Message.response + | `String of string + | `Exn of exn + ]; + layer : [ + | `App + | `HTTP + | `HTTP2 + | `TLS + | `WebSocket + ]; + caused_by : [ + | `Server + | `Client + ]; + request : Message.request option; + response : Message.response option; + client : string option; + severity : Log.log_level; + will_send_response : bool; +} +type error_handler = Catch.error_handler +let error_template = Error_handler.customize +let debug_error_handler = Error_handler.debug_error_handler +let catch = Catch.catch -let content_length = - Dream__middleware.Content_length.content_length -include Dream__graphql.Graphql -include Dream__sql.Sql -include Dream__http.Http +(* Servers *) -include Dream__middleware.Lowercase_headers -include Dream__middleware.Catch -include Dream__middleware.Request_id -include Dream__middleware.Site_prefix +let run = Http.run +let serve = Http.serve +let with_site_prefix = Site_prefix.with_site_prefix -let error_template = - Dream__http.Error_handler.customize -let () = Dream__cipher.Random.initialize Mirage_crypto_rng_lwt.initialize -let random = - Dream__cipher.Random.random +(* Web formats *) -include Dream__pure.Formats +include Formats + + + +(* Cryptography *) + +let set_secret = Cipher.set_secret +let random = Random.random +let encrypt = Cipher.encrypt +let decrypt = Cipher.decrypt + + + +(* Custom fields *) + +type 'a field = 'a Message.field +let new_field = Message.new_field +let field = Message.field +let set_field = Message.set_field + + + +(* Testing. *) + +let request = Helpers.request_with_body (* TODO Restore the ability to test with a prefix and re-enable the corresponding tests. *) let test ?(prefix = "") handler request = - ignore prefix; let app = - content_length - @@ assign_request_id - @@ chop_site_prefix + Site_prefix.with_site_prefix prefix @@ handler in Lwt_main.run (app request) -let log = - Dream__middleware.Log.convenience_log +let sort_headers = Message.sort_headers +let echo = Echo.echo + -include Dream__middleware.Tag -let now () = Ptime.to_float_s (Ptime.v (Ptime_clock.now_d_ps ())) +(* Deprecated helpers. *) -let form = form ~now -let multipart = multipart ~now -let csrf_token = csrf_token ~now -let verify_csrf_token = verify_csrf_token ~now -let form_tag ?method_ ?target ?enctype ?csrf_token ~action request = - form_tag ~now ?method_ ?target ?enctype ?csrf_token ~action request +let with_path path message = + Router.set_path message path; + message diff --git a/src/dream.mli b/src/dream.mli index feebd85e..d9b5e482 100644 --- a/src/dream.mli +++ b/src/dream.mli @@ -10,11 +10,11 @@ Dream is built on just five types. The first two are the data types of Dream. Both are abstract, even though they appear to have definitions: *) -type request = incoming message +type request = client message (** HTTP requests, such as [GET /something HTTP/1.1]. See {!section-requests}. *) -and response = outgoing message +and response = server message (** HTTP responses, such as [200 OK]. See {!section-responses}. *) (** The remaining three types are for building up Web apps. *) @@ -100,7 +100,7 @@ and route (** {2 Helpers} *) -and 'a message +and 'a message = 'a Dream_pure.Message.message (** ['a message], pronounced “any message,” allows some functions to take either {!type-request} or {!type-response} as arguments, because both are defined in terms of ['a message]. For example, in {!section-headers}: @@ -109,13 +109,15 @@ and 'a message val Dream.header : string -> 'a message -> string option ]} *) -and incoming -and outgoing +and client = Dream_pure.Message.client +and server = Dream_pure.Message.server (** Type parameters for {!message} for {!type-request} and {!type-response}, respectively. These are “phantom” types. They have no meaning other than - they are different from each other. Dream only ever creates [incoming - message] and [outgoing message]. [incoming] and [outgoing] are never - mentioned again in the docs. *) + they are different from each other. Dream only ever creates [client message] + and [server message]. [client] and [server] are never mentioned again in the + docs. *) +(* TODO These docs need to be clarified. *) +(* TODO Hide all the Dream_pure type equalities. *) and 'a promise = 'a Lwt.t (** Dream uses {{:https://github.com/ocsigen/lwt} Lwt} for promises and @@ -175,6 +177,8 @@ val normalize_method : [< method_ ] -> method_ Dream.normalize_method (`Method "GET") = `GET ]} *) + + (** {1:status_codes Status codes} *) type informational = [ @@ -356,45 +360,52 @@ val normalize_status : [< status ] -> status val client : request -> string (** Client sending the request. For example, ["127.0.0.1:56001"]. *) -val https : request -> bool -(** Whether the request was sent over HTTPS. *) +val tls : request -> bool +(** Whether the request was sent over a TLS connection. *) val method_ : request -> method_ (** Request method. For example, [`GET]. *) val target : request -> string -(** Request target. For example, ["/foo/bar"]. See {!Dream.val-path}. *) +(** Request target. For example, ["/foo/bar"]. *) (**/**) val prefix : request -> string (**/**) +(**/**) val path : request -> string list +[@@ocaml.deprecated +"Router path access is being removed from the API. Comment at +https://github.com/aantron/dream/issues +"] (** Parsed request path. For example, ["foo"; "bar"]. *) +(* TODO If not removing this, move it to section Routing. *) +(**/**) -val version : request -> int * int -(** Protocol version. [(1, 1)] for HTTP/1.1 and [(2, 0)] for HTTP/2. *) - -val with_client : string -> request -> request +val set_client : request -> string -> unit (** Replaces the client. See {!Dream.val-client}. *) -val with_method_ : [< method_ ] -> request -> request +val set_method_ : request -> [< method_ ] -> unit (** Replaces the method. See {!Dream.type-method_}. *) +(**/**) val with_path : string list -> request -> request +[@@ocaml.deprecated +"Router path access is being removed from the API. Comment at +https://github.com/aantron/dream/issues +"] (** Replaces the path. See {!Dream.val-path}. *) +(**/**) -val with_version : int * int -> request -> request -(** Replaces the version. See {!Dream.version}. *) - -val query : string -> request -> string option +val query : request -> string -> string option (** First query parameter with the given name. See {{:https://tools.ietf.org/html/rfc3986#section-3.4} RFC 3986 §3.4} and example {{:https://github.com/aantron/dream/tree/master/example/w-query#files} [w-query]}. *) -val queries : string -> request -> string list +val queries : request -> string -> string list (** All query parameters with the given name. *) val all_queries : request -> (string * string) list @@ -471,53 +482,39 @@ val empty : status -> response promise (** Same as {!Dream.val-response} with the empty string for a body. *) -val stream : - ?status:[< status ] -> - ?code:int -> - ?headers:(string * string) list -> - (response -> unit promise) -> response promise -(** Same as {!Dream.val-respond}, but calls {!Dream.with_stream} internally to - prepare the response for stream writing, and then runs the callback - asynchronously to do it. See example - {{:https://github.com/aantron/dream/tree/master/example/j-stream#files} - [j-stream]}. - - {[ - fun request -> - Dream.stream (fun response -> - let%lwt () = Dream.write response "foo" in - Dream.close_stream response) - ]} *) - val status : response -> status (** Response {!type-status}. For example, [`OK]. *) +val set_status : response -> status -> unit +(** Sets the response status. *) + (** {1 Headers} *) -val header : string -> 'a message -> string option +val header : 'a message -> string -> string option (** First header with the given name. Header names are case-insensitive. See {{:https://tools.ietf.org/html/rfc7230#section-3.2} RFC 7230 §3.2} and {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers} MDN}. *) -val headers : string -> 'a message -> string list +val headers : 'a message -> string -> string list (** All headers with the given name. *) val all_headers : 'a message -> (string * string) list (** Entire header set as name-value list. *) -val has_header : string -> 'a message -> bool +val has_header : 'a message -> string -> bool (** Whether the message has a header with the given name. *) -val add_header : string -> string -> 'a message -> 'a message +val add_header : 'a message -> string -> string -> unit (** Appends a header with the given name and value. Does not remove any existing headers with the same name. *) +(* TODO Does this fit on one line in the docs now? *) -val drop_header : string -> 'a message -> 'a message +val drop_header : 'a message -> string -> unit (** Removes all headers with the given name. *) -val with_header : string -> string -> 'a message -> 'a message +val set_header : 'a message -> string -> string -> unit (** Equivalent to {!Dream.drop_header} followed by {!Dream.add_header}. *) @@ -531,8 +528,8 @@ val with_header : string -> string -> 'a message -> 'a message [c-cookie]} \[{{:http://dream.as/c-cookie} playground}\]. {[ - Dream.set_cookie "my.cookie" "foo" request response - Dream.cookie "my.cookie" request + Dream.set_cookie response request "my.cookie" "foo" + Dream.cookie request "my.cookie" ]} The {!Dream.cookie} call evaluates to [Some "foo"], but the actual cookie @@ -557,15 +554,15 @@ val set_cookie : ?secure:bool -> ?http_only:bool -> ?same_site:[< `Strict | `Lax | `None ] option -> - string -> string -> request -> response -> response + response -> request -> string -> string -> unit (** Appends a [Set-Cookie:] header to the {!type-response}. Infers the most secure defaults from the {!type-request}. {[ - Dream.set_cookie "my.cookie" "value" request response + Dream.set_cookie request response "my.cookie" "value" ]} - Specify {!Dream.run} argument [~secret], or the Web app will not be able to + Use the {!Dream.set_secret} middleware, or the Web app will not be able to decrypt cookies from prior starts. See example @@ -588,7 +585,7 @@ val set_cookie : - [~encrypt:false] disables cookie encryption. In that case, you must make sure that the cookie value does not contain [=], [;], or newlines. The easiest way to do so is to pass the value through an encoder like - {!Dream.to_base64url}. See {!Dream.run} argument [~secret]. + {!Dream.to_base64url}. See {!Dream.set_secret}. - [~expires] sets the [Expires=] attribute. The value is compatible with {{:https://caml.inria.fr/pub/docs/manual-ocaml/libref/Unix.html#VALgettimeofday} [Unix.gettimeofday]}. See @@ -613,7 +610,7 @@ val set_cookie : {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Path_attribute} MDN}. - [~secure] sets the [Secure] attribute. By default, [Secure] is set if - {!Dream.https} is [true] for the {!type-request}. See + {!Dream.tls} is [true] for the {!type-request}. See {{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.5} RFC 6265bis §4.1.2.5} and {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies} @@ -632,9 +629,21 @@ val set_cookie : MDN}. {!Dream.to_set_cookie} is a “raw” version of this function that does not do - any inference. - - *) + any inference. *) + + val drop_cookie : + ?prefix:[< `Host | `Secure ] option -> + ?domain:string -> + ?path:string option -> + ?secure:bool -> + ?http_only:bool -> + ?same_site:[< `Strict | `Lax | `None ] option -> + response -> request -> string -> unit +(** Deletes the given cookie. + + This function works by calling {!Dream.set_cookie}, and setting the cookie + to expire in the past. Pass all the same optional values that you would pass + to {!Dream.set_cookie} to make sure that the same cookie is deleted. *) val cookie : ?prefix:[< `Host | `Secure ] option -> @@ -642,13 +651,13 @@ val cookie : ?domain:string -> ?path:string option -> ?secure:bool -> - string -> request -> string option + request -> string -> string option (** First cookie with the given name. See example {{:https://github.com/aantron/dream/tree/master/example/c-cookie#files} [c-cookie]}. {[ - Dream.cookie "my.cookie" request + Dream.cookie request "my.cookie" ]} Pass the same optional arguments as to {!Dream.set_cookie} for the same @@ -664,39 +673,64 @@ val all_cookies : request -> (string * string) list (** {1 Bodies} *) val body : 'a message -> string promise -(** Retrieves the entire body. Retains a reference to the body, so {!Dream.body} - can be used multiple times. See example +(** Retrieves the entire body. See example {{:https://github.com/aantron/dream/tree/master/example/6-echo#files} [6-echo]}. *) -val with_body : string -> response -> response +val set_body : 'a message -> string -> unit (** Replaces the body. *) -(** {2 Streaming} *) -val read : request -> string option promise -(** Retrieves a body chunk. The chunk is not buffered, thus it can only be read - once. See example + +(** {1 Streams} *) + +type stream +(** Gradual reading of request bodies or gradual writing of response bodies. *) + +val body_stream : request -> stream +(** A stream that can be used to gradually read the request's body. *) + +val stream : + ?status:[< status ] -> + ?code:int -> + ?headers:(string * string) list -> + ?close:bool -> + (stream -> unit promise) -> response promise +(** Creates a response with a {!type-stream} open for writing, and passes the + stream to the callback when it is ready. See example {{:https://github.com/aantron/dream/tree/master/example/j-stream#files} - [j-stream]}. *) + [j-stream]}. + + {[ + fun request -> + Dream.stream (fun stream -> + Dream.write stream "foo") + ]} -val with_stream : response -> response -(** Makes the {!type-response} ready for stream writing with {!Dream.write}. You - should return it from your handler soon after — only one call to - {!Dream.write} will be accepted before then. See {!Dream.stream} for a more - convenient wrapper. *) + [Dream.stream] automatically closes the stream when the callback returns or + raises an exception. Pass [~close:false] to suppress this behavior. *) -val write : response -> string -> unit promise +val read : stream -> string option promise +(** Retrieves a body chunk. See example + {{:https://github.com/aantron/dream/tree/master/example/j-stream#files} + [j-stream]}. *) +(* TODO Document difference between receiving a request and receiving on a + WebSocket. *) + +val write : stream -> string -> unit promise (** Streams out the string. The promise is fulfilled when the response can accept more writes. *) +(* TODO Document clearly which of the writing functions can raise exceptions. *) -val flush : response -> unit promise -(** Flushes write buffers. Data is sent to the client. *) +val flush : stream -> unit promise +(** Flushes the stream's write buffer. Data is sent to the client. *) -val close_stream : response -> unit promise -(** Finishes the response stream. *) +val close : stream -> unit promise +(** Closes the stream. *) -(** {2 Low-level streaming} *) +(** {2 Low-level streaming} + + Note: this part of the API is still a work in progress. *) type buffer = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t @@ -711,24 +745,157 @@ type buffer = - {{:https://github.com/mirage/ocaml-cstruct/blob/9a8b9a79bdfa2a1b8455bc26689e0228cc6fac8e/lib/cstruct.mli#L139} [Cstruct.buffer]} in Cstruct. *) -val next : - buffer:(buffer -> int -> int -> unit) -> - (* ?string:(string -> int -> int -> unit) -> - ?flush:(unit -> unit) -> *) - close:(unit -> unit) -> +(* TODO Probably even close can be made optional. exn can be made optional. *) +(* TODO Argument order? *) +val read_stream : + stream -> + data:(buffer -> int -> int -> bool -> bool -> unit) -> + flush:(unit -> unit) -> + ping:(buffer -> int -> int -> unit) -> + pong:(buffer -> int -> int -> unit) -> + close:(int -> unit) -> exn:(exn -> unit) -> - request -> unit (** Waits for the next stream event, and calls: - - [~buffer] with an offset and length, if a {!type-buffer} is written, - - [~close] if close is requested, and + - [~data] with an offset and length, if a {!type-buffer} is received, + ~ [~flush] if a flush request is received, + - [~ping] if a ping is received (WebSockets only), + - [~pong] if a pong is received (WebSockets only), + - [~close] if the stream is closed, and - [~exn] to report an exception. *) -val write_buffer : - ?offset:int -> ?length:int -> response -> buffer -> unit promise -(** Streams out the {!buffer} slice. [~offset] defaults to zero. [~length] - defaults to the length of the {!buffer}, minus [~offset]. *) +val write_stream : + stream -> + buffer -> int -> int -> + bool -> bool -> + close:(int -> unit) -> + exn:(exn -> unit) -> + (unit -> unit) -> + unit +(** Writes a {!type-buffer} into the stream: + + {[ + write_stream stream buffer offset length binary fin ~close ~exn callback + ]} + + [write_stream] calls one of its three callback functions, depending on what + happens with the write: + + - [~close] if the stream is closed before the write completes, + - [~exn] to report an exception during or before the write, + - [callback] to report that the write has succeeded and the stream can + accept another write. + + [binary] and [fin] are for WebSockets only. [binary] marks the stream as + containing binary (non-text) data, and [fin] sets the [FIN] bit, indicating + the end of a message. These two parameters are ignored by non-WebSocket + streams. *) + +val flush_stream : + stream -> + close:(int -> unit) -> + exn:(exn -> unit) -> + (unit -> unit) -> + unit +(** Requests the stream be flushed. The callbacks have the same meaning as in + {!write_stream}. *) + +val ping_stream : + stream -> + buffer -> int -> int -> + close:(int -> unit) -> + exn:(exn -> unit) -> + (unit -> unit) -> + unit +(** Sends a ping frame on the WebSocket stream. The buffer is typically empty, + but may contain up to 125 bytes of data. *) + +val pong_stream : + stream -> + buffer -> int -> int -> + close:(int -> unit) -> + exn:(exn -> unit) -> + (unit -> unit) -> + unit +(** Like {!ping_stream}, but sends a pong event. *) + +val close_stream : stream -> int -> unit +(** Closes the stream. The integer parameter is a WebSocket close code, and is + ignored by non-WebSocket streams. *) + +val abort_stream : stream -> exn -> unit +(** Aborts the stream, causing all readers and writers to receive the given + exception. *) + +(* TODO Ergonomics of this stream surface API. *) + + + +(** {1 WebSockets} *) + +type websocket +(** A WebSocket connection. See {{:https://tools.ietf.org/html/rfc6455} RFC + 6455} and + {{:https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API} MDN}. *) + +val websocket : + ?headers:(string * string) list -> + ?close:bool -> + (websocket -> unit promise) -> response promise +(** Creates a fresh [101 Switching Protocols] response. Once this response is + returned to Dream's HTTP layer, the callback is passed a new + {!type-websocket}, and the application can begin using it. See example + {{:https://github.com/aantron/dream/tree/master/example/k-websocket#files} + [k-websocket]} \[{{:http://dream.as/k-websocket} playground}\]. + + {[ + let my_handler = fun request -> + Dream.websocket (fun websocket -> + let%lwt () = Dream.send websocket "Hello, world!"); + ]} + + [Dream.websocket] automatically closes the WebSocket when the callback + returns or raises an exception. Pass [~close:false] to suppress this + behavior. *) + +type text_or_binary = [ `Text | `Binary ] +(** See {!send} and {!receive_fragment}. *) + +type end_of_message = [ `End_of_message | `Continues ] +(** See {!send} and {!receive_fragment}. *) + +val send : + ?text_or_binary:[< text_or_binary ] -> + ?end_of_message:[< end_of_message ] -> + websocket -> string -> unit promise +(** Sends a single WebSocket message. The WebSocket is ready for another message + when the promise resolves. + + With [~text_or_binary:`Text], the default, the message is interpreted as a + UTF-8 string. The client will receive it transcoded to JavaScript's UTF-16 + representation. + + With [~text_or_binary:`Binary], the message will be received unmodified, as + either a [Blob] or an [ArrayBuffer]. See + {{:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType} + MDN, [WebSocket.binaryType]}. + + [~end_of_message] is ignored for now, as the WebSocket library underlying + Dream does not support sending message fragments yet. *) + +val receive : websocket -> string option promise +(** Receives a message. If the WebSocket is closed before a complete message + arrives, the result is [None]. *) + +val receive_fragment : + websocket -> (string * text_or_binary * end_of_message) option promise +(** Receives a single fragment of a message, streaming it. *) + +val close_websocket : ?code:int -> websocket -> unit promise +(** Closes the WebSocket. [~code] is usually not necessary, but is needed for + some protocols based on WebSockets. See + {{:https://tools.ietf.org/html/rfc6455#section-7.4} RFC 6455 §7.4}. *) @@ -772,12 +939,13 @@ val origin_referrer_check : middleware (** {1 Forms} - {!Dream.form_tag} and {!Dream.val-form} round-trip secure forms. - {!Dream.form_tag} is used inside a template to generate a form header with a - CSRF token: + {!Dream.csrf_tag} and {!Dream.val-form} round-trip secure forms. + {!Dream.csrf_tag} is used inside a form template to generate a hidden field + with a CSRF token: {[ - <%s! Dream.form_tag ~action:"/" request %> +
+ <%s! Dream.csrf_tag request %>
]} @@ -816,15 +984,15 @@ type 'a form_result = [ activity, or tokens so old that decryption keys have since been rotated on the server. *) -val form : request -> (string * string) list form_result promise +val form : ?csrf:bool -> request -> (string * string) list form_result promise (** Parses the request body as a form. Performs CSRF checks. Use - {!Dream.form_tag} in a template to transparently generate forms that will - pass these checks. See {!section-templates} and example + {!Dream.csrf_tag} in a form template to transparently generate forms that + will pass these checks. See {!section-templates} and example {{:https://github.com/aantron/dream/tree/master/example/d-form#readme} [d-form]}. - [Content-Type:] must be [application/x-www-form-urlencoded]. - - The form must have a field named [dream.csrf]. {!Dream.form_tag} adds such + - The form must have a field named [dream.csrf]. {!Dream.csrf_tag} adds such a field. - {!Dream.form} calls {!Dream.verify_csrf_token} to check the token in [dream.csrf]. @@ -832,7 +1000,11 @@ val form : request -> (string * string) list form_result promise The call must be done under a session middleware, since each CSRF token is scoped to a session. See {!section-sessions}. - Form fields are sorted for easy pattern matching: + CSRF token checking can be bypassed by passing [~csrf:false]. + + The returned form fields are sorted in alphabetical order for reliable + pattern matching. This is because browsers can transmit the form fields in a + different order from how they appear in the HTML: {[ match%lwt Dream.form request with @@ -845,7 +1017,7 @@ val form : request -> (string * string) list form_result promise {[ match%lwt Dream.form request with | `Ok ["email", email; "name", name] -> (* ... *) - | `Expired ["email", email; "name", name] -> (* ... *) + | `Expired (["email", email; "name", name], _) -> (* ... *) | _ -> Dream.empty `Bad_Request ]} @@ -897,8 +1069,8 @@ type multipart_form = If a file field has zero files when submitted, browsers send ["field-name", [Some ""; ""]]. {!Dream.multipart} replaces this with - ["field-name", []]. Use the advanced interface {!Dream.upload} for the raw - behavior. + ["field-name", []]. Use the advanced interface, {!Dream.val-upload}, for the + raw behavior. Non-file fields always have one value, which might be the empty string. @@ -907,17 +1079,16 @@ type multipart_form = OWASP {i File Upload Cheat Sheet}} for security precautions for upload forms. *) -val multipart : request -> multipart_form form_result promise +val multipart : ?csrf:bool -> request -> multipart_form form_result promise (** Like {!Dream.form}, but also reads files, and [Content-Type:] must be - [multipart/form-data]. The [
] tag and CSRF token can be generated in a - template with + [multipart/form-data]. The CSRF token can be generated in a template with {[ - <%s! Dream.form_tag ~action:"/" - ~enctype:`Multipart_form_data request %> + + <%s! Dream.csrf_tag request %> ]} - See {!Dream.form_tag}, section {!section-templates}, and example + See section {!section-templates}, and example {{:https://github.com/aantron/dream/tree/master/example/g-upload#files} [g-upload]}. @@ -949,10 +1120,10 @@ val upload : request -> part option promise again. [None] from {!Dream.val-upload} indicates that all parts have been received. - {!Dream.upload} does not verify a CSRF token. There are several ways to add - CSRF protection for an upload stream, including: + {!Dream.val-upload} does not verify a CSRF token. There are several ways to + add CSRF protection for an upload stream, including: - - Generate the form with {!Dream.form_tag}. Check for + - Generate a CSRF token with {!Dream.csrf_tag}. Check for [`Field ("dream.csrf", token)] during upload and call {!Dream.verify_csrf_token}. - Use {{:https://developer.mozilla.org/en-US/docs/Web/API/FormData} @@ -966,8 +1137,9 @@ val upload_part : request -> string option promise It's usually not necessary to handle CSRF tokens directly. - - Form tag generator {!Dream.form_tag} generates and inserts a CSRF token - that {!Dream.val-form} and {!Dream.val-multipart} transparently verify. + - CSRF token field generator {!Dream.csrf_tag} generates and inserts a CSRF + token that {!Dream.val-form} and {!Dream.val-multipart} transparently + verify. - AJAX can be protected from CSRF by {!Dream.origin_referrer_check}. CSRF functions are exposed for creating custom schemes, and for @@ -996,9 +1168,9 @@ type csrf_result = [ val csrf_token : ?valid_for:float -> request -> string (** Returns a fresh CSRF token bound to the given request's and signed with the - [~secret] given to {!Dream.run}. [~valid_for] is the token's lifetime, in - seconds. The default value is one hour ([3600.]). Dream uses signed tokens - that are not stored server-side. *) + secret given to {!Dream.set_secret}. [~valid_for] is the token's lifetime, + in seconds. The default value is one hour ([3600.]). Dream uses signed + tokens that are not stored server-side. *) val verify_csrf_token : request -> string -> csrf_result promise (** Checks that the CSRF token is valid for the {!type-request}'s session. *) @@ -1090,18 +1262,13 @@ let render message = unquoted attribute values, CSS in [
+
diff --git a/src/graphiql/index.html b/src/graphiql/index.html index 009fb820..a2600b63 100644 --- a/src/graphiql/index.html +++ b/src/graphiql/index.html @@ -23,7 +23,7 @@ - +
@@ -31,10 +31,9 @@ var endpoint = "%%ENDPOINT%%"; var wsSchema = location.protocol === "https:" ? "wss:" : "ws:"; var wsEndpoint = wsSchema + "//" + window.location.host + endpoint; - var fetcher = GraphiQL.createFetcher({ url: endpoint, - subscriptionUrl: wsEndpoint + subscriptionUrl: wsEndpoint, }); var defaultQuery = "%%DEFAULT_QUERY%%"; @@ -43,9 +42,10 @@ React.createElement(GraphiQL, { fetcher: fetcher, defaultQuery: defaultQuery, - defaultSecondaryEditorOpen: true + defaultSecondaryEditorOpen: true, }), - document.getElementById("graphiql")); + document.getElementById("graphiql") + ); diff --git a/src/graphiql/package-lock.json b/src/graphiql/package-lock.json index e32d13fb..39e9e9d6 100644 --- a/src/graphiql/package-lock.json +++ b/src/graphiql/package-lock.json @@ -1,28 +1,4149 @@ { "name": "dream-graphiql", + "lockfileVersion": 2, "requires": true, - "lockfileVersion": 1, + "packages": { + "": { + "name": "dream-graphiql", + "dependencies": { + "graphiql": "^2.4.1", + "graphql": "^16.6.0", + "inliner": "aantron/inliner#fork", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.0.0.tgz", + "integrity": "sha512-rtjk5ifyMzOna1c7PBu7J1VCt0PvA5wy3o8eMVnxMKb7z8KA7JFecvD04dSn14vj/bBaAbqRsGed5OjtofEnLA==", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", + "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", + "peer": true + }, + "node_modules/@codemirror/view": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.11.0.tgz", + "integrity": "sha512-PRpPRkqMkAKKxEuiUBxapE0YR+wqs9At92ujbJo93PwTZ0jEJDzx9wahrDcXEhQ43Pe0RK9DdZMLWrt+QN80DA==", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.1.4", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@graphiql/react": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@graphiql/react/-/react-0.17.2.tgz", + "integrity": "sha512-x8iTeYSq8C520UYGk3f3S+AWOm8nf7x3OuePWg+k0KkMAWJeaeFBLmxBAGQLzK5/8/dPoZfNKblXcLwByeMHew==", + "dependencies": { + "@graphiql/toolkit": "^0.8.4", + "@reach/combobox": "^0.17.0", + "@reach/dialog": "^0.17.0", + "@reach/listbox": "^0.17.0", + "@reach/menu-button": "^0.17.0", + "@reach/tooltip": "^0.17.0", + "@reach/visually-hidden": "^0.17.0", + "clsx": "^1.2.1", + "codemirror": "^5.65.3", + "codemirror-graphql": "^2.0.6", + "copy-to-clipboard": "^3.2.0", + "graphql-language-service": "^5.1.4", + "markdown-it": "^12.2.0", + "set-value": "^4.1.0" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/combobox": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/combobox/-/combobox-0.17.0.tgz", + "integrity": "sha512-2mYvU5agOBCQBMdlM4cri+P1BbNwp05P1OuDyc33xJSNiBG7BMy4+ZSHJ0X4fyle6rHwSgCAOCLOeWV1XUYjoQ==", + "dependencies": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/portal": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/combobox/node_modules/@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "dependencies": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/combobox/node_modules/@reach/descendants": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.17.0.tgz", + "integrity": "sha512-c7lUaBfjgcmKFZiAWqhG+VnXDMEhPkI4kAav/82XKZD6NVvFjsQOTH+v3tUkskrAPV44Yuch0mFW/u5Ntifr7Q==", + "dependencies": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/combobox/node_modules/@reach/popover": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.17.0.tgz", + "integrity": "sha512-yYbBF4fMz4Ml4LB3agobZjcZ/oPtPsNv70ZAd7lEC2h7cvhF453pA+zOBGYTPGupKaeBvgAnrMjj7RnxDU5hoQ==", + "dependencies": { + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "tabbable": "^4.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/combobox/node_modules/@reach/popover/node_modules/@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "dependencies": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/combobox/node_modules/@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "dependencies": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/combobox/node_modules/@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "dependencies": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/dialog": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/dialog/-/dialog-0.17.0.tgz", + "integrity": "sha512-AnfKXugqDTGbeG3c8xDcrQDE4h9b/vnc27Sa118oQSquz52fneUeX9MeFb5ZEiBJK8T5NJpv7QUTBIKnFCAH5A==", + "dependencies": { + "@reach/portal": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "react-focus-lock": "^2.5.2", + "react-remove-scroll": "^2.4.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/dialog/node_modules/@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "dependencies": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/dialog/node_modules/@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "dependencies": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/listbox": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/listbox/-/listbox-0.17.0.tgz", + "integrity": "sha512-AMnH1P6/3VKy2V/nPb4Es441arYR+t4YRdh9jdcFVrCOD6y7CQrlmxsYjeg9Ocdz08XpdoEBHM3PKLJqNAUr7A==", + "dependencies": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/machine": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/listbox/node_modules/@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "dependencies": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/listbox/node_modules/@reach/descendants": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.17.0.tgz", + "integrity": "sha512-c7lUaBfjgcmKFZiAWqhG+VnXDMEhPkI4kAav/82XKZD6NVvFjsQOTH+v3tUkskrAPV44Yuch0mFW/u5Ntifr7Q==", + "dependencies": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/listbox/node_modules/@reach/machine": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/machine/-/machine-0.17.0.tgz", + "integrity": "sha512-9EHnuPgXzkbRENvRUzJvVvYt+C2jp7PGN0xon7ffmKoK8rTO6eA/bb7P0xgloyDDQtu88TBUXKzW0uASqhTXGA==", + "dependencies": { + "@reach/utils": "0.17.0", + "@xstate/fsm": "1.4.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/listbox/node_modules/@reach/popover": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.17.0.tgz", + "integrity": "sha512-yYbBF4fMz4Ml4LB3agobZjcZ/oPtPsNv70ZAd7lEC2h7cvhF453pA+zOBGYTPGupKaeBvgAnrMjj7RnxDU5hoQ==", + "dependencies": { + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "tabbable": "^4.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/listbox/node_modules/@reach/popover/node_modules/@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "dependencies": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/listbox/node_modules/@reach/popover/node_modules/@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "dependencies": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/listbox/node_modules/@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "dependencies": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/menu-button": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/menu-button/-/menu-button-0.17.0.tgz", + "integrity": "sha512-YyuYVyMZKamPtivoEI6D0UEILYH3qZtg4kJzEAuzPmoR/aHN66NZO75Fx0gtjG1S6fZfbiARaCOZJC0VEiDOtQ==", + "dependencies": { + "@reach/dropdown": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x", + "react-is": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/menu-button/node_modules/@reach/dropdown": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/dropdown/-/dropdown-0.17.0.tgz", + "integrity": "sha512-qBTIGInhxtPHtdj4Pl2XZgZMz3e37liydh0xR3qc48syu7g71sL4nqyKjOzThykyfhA3Pb3/wFgsFJKGTSdaig==", + "dependencies": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/menu-button/node_modules/@reach/dropdown/node_modules/@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "dependencies": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/menu-button/node_modules/@reach/dropdown/node_modules/@reach/descendants": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.17.0.tgz", + "integrity": "sha512-c7lUaBfjgcmKFZiAWqhG+VnXDMEhPkI4kAav/82XKZD6NVvFjsQOTH+v3tUkskrAPV44Yuch0mFW/u5Ntifr7Q==", + "dependencies": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/menu-button/node_modules/@reach/popover": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.17.0.tgz", + "integrity": "sha512-yYbBF4fMz4Ml4LB3agobZjcZ/oPtPsNv70ZAd7lEC2h7cvhF453pA+zOBGYTPGupKaeBvgAnrMjj7RnxDU5hoQ==", + "dependencies": { + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "tabbable": "^4.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/menu-button/node_modules/@reach/popover/node_modules/@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "dependencies": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/menu-button/node_modules/@reach/popover/node_modules/@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "dependencies": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/menu-button/node_modules/@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "dependencies": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/tooltip": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/tooltip/-/tooltip-0.17.0.tgz", + "integrity": "sha512-HP8Blordzqb/Cxg+jnhGmWQfKgypamcYLBPlcx6jconyV5iLJ5m93qipr1giK7MqKT2wlsKWy44ZcOrJ+Wrf8w==", + "dependencies": { + "@reach/auto-id": "0.17.0", + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "@reach/visually-hidden": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/tooltip/node_modules/@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "dependencies": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/tooltip/node_modules/@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "dependencies": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/tooltip/node_modules/@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "dependencies": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/tooltip/node_modules/@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "dependencies": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/react/node_modules/@reach/visually-hidden": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.17.0.tgz", + "integrity": "sha512-T6xF3Nv8vVnjVkGU6cm0+kWtvliLqPAo8PcZ+WxkKacZsaHTjaZb4v1PaCcyQHmuTNT/vtTVNOJLG0SjQOIb7g==", + "dependencies": { + "prop-types": "^15.7.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@graphiql/toolkit": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.8.4.tgz", + "integrity": "sha512-cFUGqh3Dau+SD3Vq9EFlZrhzYfaHKyOJveFtaCR+U5Cn/S68p7oy+vQBIdwtO6J2J58FncnwBbVRfr+IvVfZqQ==", + "dependencies": { + "@n1ru4l/push-pull-async-iterable-iterator": "^3.1.0", + "meros": "^1.1.4" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0", + "graphql-ws": ">= 4.5.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + } + } + }, + "node_modules/@lezer/common": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz", + "integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==", + "peer": true + }, + "node_modules/@lezer/highlight": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.4.tgz", + "integrity": "sha512-IECkFmw2l7sFcYXrV8iT9GeY4W0fU4CxX0WMwhmhMIVjoDdD1Hr6q3G2NqVtLg/yVe5n7i4menG3tJ2r4eCrPQ==", + "peer": true, + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.4.tgz", + "integrity": "sha512-7o+e4og/QoC/6btozDPJqnzBhUaD1fMfmvnEKQO1wRRiTse1WxaJ3OMEXZJnkgT6HCcTVOctSoXK9jGJw2oe9g==", + "peer": true, + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@n1ru4l/push-pull-async-iterable-iterator": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.2.0.tgz", + "integrity": "sha512-3fkKj25kEjsfObL6IlKPAlHYPq/oYwUkkQ03zsTTiDjD7vg/RxjdiLeCydqtxHZP0JgsXL3D/X5oAkMGzuUp/Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@reach/observe-rect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz", + "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==" + }, + "node_modules/@types/q": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" + }, + "node_modules/@xstate/fsm": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.4.0.tgz", + "integrity": "sha512-uTHDeu2xI5E1IFwf37JFQM31RrH7mY7877RqPBS4ZqSNUwoLDuct8AhBWaXGnVizBAYyimVwgCyGa9z/NiRhXA==" + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/charset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", + "dependencies": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cheerio/node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/cheerio/node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" + }, + "node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dependencies": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/coa/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/codemirror": { + "version": "5.65.13", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.13.tgz", + "integrity": "sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg==" + }, + "node_modules/codemirror-graphql": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-2.0.6.tgz", + "integrity": "sha512-ZuvT4+iBYabyLWaqVHdsGyiB2atvu0v1eSnGUuziX7x7tJmo5WziZGvc2j6w6EnL5aUjvYnJU0aCBhTgCdzJVg==", + "dependencies": { + "graphql-language-service": "5.1.4" + }, + "peerDependencies": { + "@codemirror/language": "6.0.0", + "codemirror": "^5.65.3", + "graphql": "^15.5.0 || ^16.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/configstore": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz", + "integrity": "sha512-Zcx2SVdZC06IuRHd2MhkVYFNJBkZBj166LGdsJXRcqNC8Gs5Bwh8mosStNeCBBmtIm4wNii2uarD50qztjKOjw==", + "dependencies": { + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "object-assign": "^4.0.1", + "os-tmpdir": "^1.0.0", + "osenv": "^0.1.0", + "uuid": "^2.0.1", + "write-file-atomic": "^1.1.2", + "xdg-basedir": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/configstore/node_modules/uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha512-FULf7fayPdpASncVy4DLh3xydlXEJJpvIELjYjNeQWYUZ9pclcpvCZSr2gkmN2FrrGcI7G/cJsIEwk5/8vfXpg==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details." + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", + "dependencies": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "node_modules/css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "engines": { + "node": "*" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "node_modules/dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dependencies": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/duplexify/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexify/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-promise": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", + "integrity": "sha512-oyOjMhyKMLEjOOtvkwg0G4pAzLQ9WdbbeX7WdqKzvYXu+UFgD0Zo/Brq5Q49zNmnGPPzV5rmYvrr0jz1zWx8Iw==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/focus-lock": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.11.6.tgz", + "integrity": "sha512-KSuV3ur4gf2KqMNoZx3nXNVhqCkn42GuTYCX4tXPEwf0MjpFQmNMiN6m7dXaUXgIoivL6/65agoUMg4RLS0Vbg==", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", + "integrity": "sha512-7chPlc0pWHjvq7B6dEEXz4GphoDupOvBSSl6AwRsAJX7GPTZ+bturaZiIigX4Dp6KrAP67nvzuKkNc0SLA0DKg==", + "dependencies": { + "duplexify": "^3.2.0", + "infinity-agent": "^2.0.0", + "is-redirect": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "nested-error-stacks": "^1.0.0", + "object-assign": "^3.0.0", + "prepend-http": "^1.0.0", + "read-all-stream": "^3.0.0", + "timed-out": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/got/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphiql": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-2.4.2.tgz", + "integrity": "sha512-oQ1zJAVpOqajKH1oc5WzNIK1FNUI2m8tPgyEeA22ySmR79+/k27FlG4YxRXQOstlWeVcag0FZaqG5VyR6Nc1iw==", + "dependencies": { + "@graphiql/react": "^0.17.2", + "@graphiql/toolkit": "^0.8.4", + "graphql-language-service": "^5.1.4", + "markdown-it": "^12.2.0" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/graphql": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-language-service": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.1.4.tgz", + "integrity": "sha512-i6cYIDL8dFd6e9LqpRFJRoVa+MZ/egMzUERsZlK65S/M7ra9SxKrZuclKgmPOq0KqHypInsEaI4D7dN5a+yEAA==", + "dependencies": { + "nullthrows": "^1.0.0", + "vscode-languageserver-types": "^3.17.1" + }, + "bin": { + "graphql": "dist/temp-bin.js" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/infinity-agent": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz", + "integrity": "sha512-CnfUJe5o2S9aAQWXGMhDZI4UL39MAJV3guOTfHHIdos4tuVHkl1j/J+1XLQn+CLIvqcpgQR/p+xXYXzcrhCe5w==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inliner": { + "resolved": "git+ssh://git@github.com/aantron/inliner.git#7f2efdbe24a6a085f633d456b97c970ac2c0ca8a", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^1.4.0", + "ansi-styles": "^2.2.1", + "chalk": "^1.1.3", + "charset": "^1.0.0", + "cheerio": "^0.22.0", + "debug": "^2.2.0", + "es6-promise": "^2.3.0", + "iconv-lite": "^0.4.11", + "jschardet": "^1.3.0", + "lodash.assign": "^3.2.0", + "lodash.defaults": "^3.1.2", + "lodash.foreach": "^3.0.3", + "mime": "^1.3.4", + "minimist": "^1.1.3", + "request": "^2.74.0", + "svgo": "^1.2.2", + "then-fs": "^2.0.0", + "uglify-js": "^2.8.0", + "update-notifier": "^0.5.0" + }, + "bin": { + "inliner": "cli/index.js" + } + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", + "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "node_modules/jschardet": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.6.0.tgz", + "integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/latest-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-1.0.1.tgz", + "integrity": "sha512-HERbxp4SBlmI380+eM0B0u4nxjfTaPeydIMzl9+9UQ4nSu3xMWKlX9WoT34e4wy7VWe67c53Nv9qPVjS8fHKgg==", + "dependencies": { + "package-json": "^1.0.0" + }, + "bin": { + "latest-version": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/lodash._arrayeach": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", + "integrity": "sha512-Mn7HidOVcl3mkQtbPsuKR0Fj0N6Q6DQB77CtYncZcJc0bx5qv2q4Gl6a0LC1AN+GSxpnBDNnK3CKEm9XNA4zqQ==" + }, + "node_modules/lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha512-t3N26QR2IdSN+gqSy9Ds9pBu/J1EAFEshKlUHpJG3rvyJOYgcELIxcIeKKfZk7sjOz11cFfzJRsyFry/JyabJQ==", + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==" + }, + "node_modules/lodash._baseeach": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", + "integrity": "sha512-IqUZ9MQo2UT1XPGuBntInqTOlc+oV+bCo0kMp+yuKGsfvRSNgUW0YjWVZUrG/gs+8z/Eyuc0jkJjOBESt9BXxg==", + "dependencies": { + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ==" + }, + "node_modules/lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha512-LziVL7IDnJjQeeV95Wvhw6G28Z8Q6da87LWKOPWmzBLv4u6FAT/x5v00pyGW0u38UoogNF2JnD3bGgZZDaNEBw==", + "dependencies": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==" + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==" + }, + "node_modules/lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha512-/VVxzgGBmbphasTg51FrztxQJ/VgAUpol6zmJuSVSGcNg4g7FA4z7rQV8Ovr9V3vFBNWZhvKWHfpAytjTVUfFA==", + "dependencies": { + "lodash._baseassign": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==" + }, + "node_modules/lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==" + }, + "node_modules/lodash.defaults": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", + "integrity": "sha512-X7135IXFQt5JDFnYxOVAzVz+kFvwDn3N8DJYf+nrz/mMWEuSu7+OL6rWqsk3+VR1T4TejFCSu5isBJOLSID2bg==", + "dependencies": { + "lodash.assign": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, + "node_modules/lodash.foreach": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.3.tgz", + "integrity": "sha512-PA7Lp7pe2HMJBoB1vELegEIF3waUFnM0fWDKJVYolwZ4zHh6WTmnq0xmzfQksD66gx2quhDNyBdyaE2T8/DP3Q==", + "dependencies": { + "lodash._arrayeach": "^3.0.0", + "lodash._baseeach": "^3.0.0", + "lodash._bindcallback": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==" + }, + "node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" + }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" + }, + "node_modules/lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==" + }, + "node_modules/lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==" + }, + "node_modules/lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==" + }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, + "node_modules/meros": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.2.1.tgz", + "integrity": "sha512-R2f/jxYqCAGI19KhAvaxSOxALBMkaXWH2a7rOyqQw+ZmizX5bKkEYWLzdhC+U82ZVVPVp6MCXe3EkVligh+12g==", + "engines": { + "node": ">=13" + }, + "peerDependencies": { + "@types/node": ">=13" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/nested-error-stacks": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz", + "integrity": "sha512-o32anp9JA7oezPOFSfG2BBXSdHepOm5FpJvwxHWDtfJ3Bg3xdi68S6ijPlEOfUg6quxZWyvJM+8fHk1yMDKspA==", + "dependencies": { + "inherits": "~2.0.1" + } + }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", + "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", + "dependencies": { + "array.prototype.reduce": "^1.0.5", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "safe-array-concat": "^1.0.0" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz", + "integrity": "sha512-knDtirWWqKVJrLY3gEBLflVvueTMpyjbAwX/9j/EKi2DsjNemp5voS8cyKyGh57SNaMJNhNRZbIaWdneOcLU1g==", + "dependencies": { + "got": "^3.2.0", + "registry-url": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-clientside-effect": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", + "dependencies": { + "@babel/runtime": "^7.12.13" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-focus-lock": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.9.4.tgz", + "integrity": "sha512-7pEdXyMseqm3kVjhdVH18sovparAzLg5h6WvIx7/Ck3ekjhrrDMEegHSa3swwC8wgfdd7DIdUVRGeiHT9/7Sgg==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^0.11.6", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "peer": true + }, + "node_modules/react-remove-scroll": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.6.tgz", + "integrity": "sha512-bO856ad1uDYLefgArk559IzUNeQ6SWH4QnrevIUjH+GczV56giDfl3h0Idptf2oIKxQmd1p9BN25jleKodTALg==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/read-all-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", + "integrity": "sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==", + "dependencies": { + "pinkie-promise": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-all-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/read-all-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/read-all-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/read-all-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/repeating": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", + "integrity": "sha512-Nh30JLeMHdoI+AsQ5eblhZ7YlTsM9wiJQe/AHIunlK3KWzvXhXb36IJ7K1IOeRjIOtzMjdUHjwXUFxKJoPTSOg==", + "dependencies": { + "is-finite": "^1.0.0" + }, + "bin": { + "repeating": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==", + "dependencies": { + "semver": "^5.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz", + "integrity": "sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==", + "funding": [ + "https://github.com/sponsors/jonschlinkert", + "https://paypal.me/jonathanschlinkert", + "https://jonschlinkert.dev/sponsor" + ], + "dependencies": { + "is-plain-object": "^2.0.4", + "is-primitive": "^3.0.1" + }, + "engines": { + "node": ">=11.0" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "engines": { + "node": "*" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", + "integrity": "sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw==", + "dependencies": { + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-mod": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.3.tgz", + "integrity": "sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==", + "peer": true + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "dependencies": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/svgo/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/svgo/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tabbable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz", + "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==" + }, + "node_modules/then-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz", + "integrity": "sha512-5ffcBcU+vFUCYDNi/o507IqjqrTkuGsLVZ1Fp50hwgZRY7ufVFa9jFfTy5uZ2QnSKacKigWKeaXkOqLa4DsjLw==", + "dependencies": { + "promise": ">=3.2 <8" + } + }, + "node_modules/timed-out": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", + "integrity": "sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "node_modules/uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", + "dependencies": { + "source-map": "~0.5.1", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + } + }, + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", + "optional": true + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" + }, + "node_modules/update-notifier": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz", + "integrity": "sha512-zOGOlUKDAgDlLHLv7Oiszz3pSj8fKlSJ3i0u49sEakjXUEVJ6DMjo/Mh/B6mg2eOALvRTJkd0kbChcipQoYCng==", + "dependencies": { + "chalk": "^1.0.0", + "configstore": "^1.0.0", + "is-npm": "^1.0.0", + "latest-version": "^1.0.0", + "repeating": "^1.1.2", + "semver-diff": "^2.0.0", + "string-length": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", + "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", + "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + }, + "node_modules/w3c-keyname": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", + "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", + "peer": true + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", + "integrity": "sha512-NF1pPn594TaRSUO/HARoB4jK8I+rWgcpVlpQCK6/6o5PHyLUt2CSiDrpUZbQ6rROck+W2EwF8mBJcTs+W98J9w==", + "dependencies": { + "os-homedir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + }, "dependencies": { + "@babel/runtime": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@codemirror/language": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.0.0.tgz", + "integrity": "sha512-rtjk5ifyMzOna1c7PBu7J1VCt0PvA5wy3o8eMVnxMKb7z8KA7JFecvD04dSn14vj/bBaAbqRsGed5OjtofEnLA==", + "peer": true, + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "@codemirror/state": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", + "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", + "peer": true + }, + "@codemirror/view": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.11.0.tgz", + "integrity": "sha512-PRpPRkqMkAKKxEuiUBxapE0YR+wqs9At92ujbJo93PwTZ0jEJDzx9wahrDcXEhQ43Pe0RK9DdZMLWrt+QN80DA==", + "peer": true, + "requires": { + "@codemirror/state": "^6.1.4", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, + "@graphiql/react": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@graphiql/react/-/react-0.17.2.tgz", + "integrity": "sha512-x8iTeYSq8C520UYGk3f3S+AWOm8nf7x3OuePWg+k0KkMAWJeaeFBLmxBAGQLzK5/8/dPoZfNKblXcLwByeMHew==", + "requires": { + "@graphiql/toolkit": "^0.8.4", + "@reach/combobox": "^0.17.0", + "@reach/dialog": "^0.17.0", + "@reach/listbox": "^0.17.0", + "@reach/menu-button": "^0.17.0", + "@reach/tooltip": "^0.17.0", + "@reach/visually-hidden": "^0.17.0", + "clsx": "^1.2.1", + "codemirror": "^5.65.3", + "codemirror-graphql": "^2.0.6", + "copy-to-clipboard": "^3.2.0", + "graphql-language-service": "^5.1.4", + "markdown-it": "^12.2.0", + "set-value": "^4.1.0" + }, + "dependencies": { + "@reach/combobox": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/combobox/-/combobox-0.17.0.tgz", + "integrity": "sha512-2mYvU5agOBCQBMdlM4cri+P1BbNwp05P1OuDyc33xJSNiBG7BMy4+ZSHJ0X4fyle6rHwSgCAOCLOeWV1XUYjoQ==", + "requires": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/portal": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "dependencies": { + "@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "requires": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + }, + "@reach/descendants": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.17.0.tgz", + "integrity": "sha512-c7lUaBfjgcmKFZiAWqhG+VnXDMEhPkI4kAav/82XKZD6NVvFjsQOTH+v3tUkskrAPV44Yuch0mFW/u5Ntifr7Q==", + "requires": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + }, + "@reach/popover": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.17.0.tgz", + "integrity": "sha512-yYbBF4fMz4Ml4LB3agobZjcZ/oPtPsNv70ZAd7lEC2h7cvhF453pA+zOBGYTPGupKaeBvgAnrMjj7RnxDU5hoQ==", + "requires": { + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "tabbable": "^4.0.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "requires": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + } + } + }, + "@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "requires": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "requires": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + } + } + }, + "@reach/dialog": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/dialog/-/dialog-0.17.0.tgz", + "integrity": "sha512-AnfKXugqDTGbeG3c8xDcrQDE4h9b/vnc27Sa118oQSquz52fneUeX9MeFb5ZEiBJK8T5NJpv7QUTBIKnFCAH5A==", + "requires": { + "@reach/portal": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "react-focus-lock": "^2.5.2", + "react-remove-scroll": "^2.4.3", + "tslib": "^2.3.0" + }, + "dependencies": { + "@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "requires": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "requires": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + } + } + }, + "@reach/listbox": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/listbox/-/listbox-0.17.0.tgz", + "integrity": "sha512-AMnH1P6/3VKy2V/nPb4Es441arYR+t4YRdh9jdcFVrCOD6y7CQrlmxsYjeg9Ocdz08XpdoEBHM3PKLJqNAUr7A==", + "requires": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/machine": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2" + }, + "dependencies": { + "@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "requires": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + }, + "@reach/descendants": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.17.0.tgz", + "integrity": "sha512-c7lUaBfjgcmKFZiAWqhG+VnXDMEhPkI4kAav/82XKZD6NVvFjsQOTH+v3tUkskrAPV44Yuch0mFW/u5Ntifr7Q==", + "requires": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + }, + "@reach/machine": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/machine/-/machine-0.17.0.tgz", + "integrity": "sha512-9EHnuPgXzkbRENvRUzJvVvYt+C2jp7PGN0xon7ffmKoK8rTO6eA/bb7P0xgloyDDQtu88TBUXKzW0uASqhTXGA==", + "requires": { + "@reach/utils": "0.17.0", + "@xstate/fsm": "1.4.0", + "tslib": "^2.3.0" + } + }, + "@reach/popover": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.17.0.tgz", + "integrity": "sha512-yYbBF4fMz4Ml4LB3agobZjcZ/oPtPsNv70ZAd7lEC2h7cvhF453pA+zOBGYTPGupKaeBvgAnrMjj7RnxDU5hoQ==", + "requires": { + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "tabbable": "^4.0.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "requires": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "requires": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + } + } + }, + "@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "requires": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + } + } + }, + "@reach/menu-button": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/menu-button/-/menu-button-0.17.0.tgz", + "integrity": "sha512-YyuYVyMZKamPtivoEI6D0UEILYH3qZtg4kJzEAuzPmoR/aHN66NZO75Fx0gtjG1S6fZfbiARaCOZJC0VEiDOtQ==", + "requires": { + "@reach/dropdown": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "dependencies": { + "@reach/dropdown": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/dropdown/-/dropdown-0.17.0.tgz", + "integrity": "sha512-qBTIGInhxtPHtdj4Pl2XZgZMz3e37liydh0xR3qc48syu7g71sL4nqyKjOzThykyfhA3Pb3/wFgsFJKGTSdaig==", + "requires": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "requires": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + }, + "@reach/descendants": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.17.0.tgz", + "integrity": "sha512-c7lUaBfjgcmKFZiAWqhG+VnXDMEhPkI4kAav/82XKZD6NVvFjsQOTH+v3tUkskrAPV44Yuch0mFW/u5Ntifr7Q==", + "requires": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + } + } + }, + "@reach/popover": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.17.0.tgz", + "integrity": "sha512-yYbBF4fMz4Ml4LB3agobZjcZ/oPtPsNv70ZAd7lEC2h7cvhF453pA+zOBGYTPGupKaeBvgAnrMjj7RnxDU5hoQ==", + "requires": { + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "tabbable": "^4.0.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "requires": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "requires": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + } + } + }, + "@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "requires": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + } + } + }, + "@reach/tooltip": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/tooltip/-/tooltip-0.17.0.tgz", + "integrity": "sha512-HP8Blordzqb/Cxg+jnhGmWQfKgypamcYLBPlcx6jconyV5iLJ5m93qipr1giK7MqKT2wlsKWy44ZcOrJ+Wrf8w==", + "requires": { + "@reach/auto-id": "0.17.0", + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "@reach/visually-hidden": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "dependencies": { + "@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "requires": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + }, + "@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "requires": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "requires": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "requires": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + } + } + }, + "@reach/visually-hidden": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.17.0.tgz", + "integrity": "sha512-T6xF3Nv8vVnjVkGU6cm0+kWtvliLqPAo8PcZ+WxkKacZsaHTjaZb4v1PaCcyQHmuTNT/vtTVNOJLG0SjQOIb7g==", + "requires": { + "prop-types": "^15.7.2", + "tslib": "^2.3.0" + } + } + } + }, "@graphiql/toolkit": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.2.0.tgz", - "integrity": "sha512-T8fdGSh1bYqpQUurIBnNbXHMOFqV/btTdlcAw3+snItA619GgZfc471lYIT95/cywxbH2Ync/gqGgeSTeZhlTg==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.8.4.tgz", + "integrity": "sha512-cFUGqh3Dau+SD3Vq9EFlZrhzYfaHKyOJveFtaCR+U5Cn/S68p7oy+vQBIdwtO6J2J58FncnwBbVRfr+IvVfZqQ==", + "requires": { + "@n1ru4l/push-pull-async-iterable-iterator": "^3.1.0", + "meros": "^1.1.4" + } + }, + "@lezer/common": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz", + "integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==", + "peer": true + }, + "@lezer/highlight": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.4.tgz", + "integrity": "sha512-IECkFmw2l7sFcYXrV8iT9GeY4W0fU4CxX0WMwhmhMIVjoDdD1Hr6q3G2NqVtLg/yVe5n7i4menG3tJ2r4eCrPQ==", + "peer": true, + "requires": { + "@lezer/common": "^1.0.0" + } + }, + "@lezer/lr": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.4.tgz", + "integrity": "sha512-7o+e4og/QoC/6btozDPJqnzBhUaD1fMfmvnEKQO1wRRiTse1WxaJ3OMEXZJnkgT6HCcTVOctSoXK9jGJw2oe9g==", + "peer": true, "requires": { - "@n1ru4l/push-pull-async-iterable-iterator": "^2.0.1", - "graphql-ws": "^4.3.2", - "meros": "^1.1.4", - "subscriptions-transport-ws": "^0.9.18" + "@lezer/common": "^1.0.0" } }, "@n1ru4l/push-pull-async-iterable-iterator": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-2.1.2.tgz", - "integrity": "sha512-KwZGeX2XK7Xj9ksWwei5923QnqIGoEuLlh3O46OW9vc8hQxjzmMTKCgJMVZ5ne5xaWFQYDT2dMpbUhq6hEOhxA==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.2.0.tgz", + "integrity": "sha512-3fkKj25kEjsfObL6IlKPAlHYPq/oYwUkkQ03zsTTiDjD7vg/RxjdiLeCydqtxHZP0JgsXL3D/X5oAkMGzuUp/Q==" + }, + "@reach/observe-rect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz", + "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==" }, "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" + }, + "@xstate/fsm": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.4.0.tgz", + "integrity": "sha512-uTHDeu2xI5E1IFwf37JFQM31RrH7mY7877RqPBS4ZqSNUwoLDuct8AhBWaXGnVizBAYyimVwgCyGa9z/NiRhXA==" }, "ajv": { "version": "6.12.6", @@ -38,7 +4159,7 @@ "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -48,35 +4169,53 @@ "ansi-escapes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==" }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==" }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", "requires": { - "sprintf-js": "~1.0.2" + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "array.prototype.reduce": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" } }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "requires": { "safer-buffer": "~2.1.0" } @@ -84,39 +4223,32 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "optional": true + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "optional": true + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "requires": { "tweetnacl": "^0.14.3" } @@ -124,7 +4256,7 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "call-bind": { "version": "1.0.2", @@ -138,17 +4270,17 @@ "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==" }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", "requires": { "align-text": "^0.1.3", "lazy-cache": "^1.0.3" @@ -157,7 +4289,7 @@ "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -174,7 +4306,7 @@ "cheerio": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", "requires": { "css-select": "~1.2.0", "dom-serializer": "~0.1.0", @@ -194,33 +4326,33 @@ "lodash.some": "^4.4.0" }, "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, "lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" } } }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", "requires": { "center-align": "^0.1.1", "right-align": "^0.1.1", "wordwrap": "0.0.2" } }, + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + }, "coa": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", @@ -260,17 +4392,16 @@ } }, "codemirror": { - "version": "5.60.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.60.0.tgz", - "integrity": "sha512-AEL7LhFOlxPlCL8IdTcJDblJm8yrAGib7I+DErJPdZd4l6imx8IMgKK3RblVgBQqz3TZJR4oknQ03bz+uNjBYA==" + "version": "5.65.13", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.13.tgz", + "integrity": "sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg==" }, "codemirror-graphql": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-1.0.1.tgz", - "integrity": "sha512-5ttMpv2kMn99Rmf2aZ5P6/hMd3y11cN8LP/x5MUeF0ipcalZA/GE/OxxXkhV0YJE/uW5QIcPyZDkvtSsGZa23A==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-2.0.6.tgz", + "integrity": "sha512-ZuvT4+iBYabyLWaqVHdsGyiB2atvu0v1eSnGUuziX7x7tJmo5WziZGvc2j6w6EnL5aUjvYnJU0aCBhTgCdzJVg==", "requires": { - "graphql-language-service-interface": "^2.8.2", - "graphql-language-service-parser": "^1.9.0" + "graphql-language-service": "5.1.4" } }, "color-convert": { @@ -284,7 +4415,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "combined-stream": { "version": "1.0.8", @@ -297,7 +4428,7 @@ "configstore": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz", - "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=", + "integrity": "sha512-Zcx2SVdZC06IuRHd2MhkVYFNJBkZBj166LGdsJXRcqNC8Gs5Bwh8mosStNeCBBmtIm4wNii2uarD50qztjKOjw==", "requires": { "graceful-fs": "^4.1.2", "mkdirp": "^0.5.0", @@ -312,14 +4443,14 @@ "uuid": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" + "integrity": "sha512-FULf7fayPdpASncVy4DLh3xydlXEJJpvIELjYjNeQWYUZ9pclcpvCZSr2gkmN2FrrGcI7G/cJsIEwk5/8vfXpg==" } } }, "copy-to-clipboard": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", - "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", "requires": { "toggle-selection": "^1.0.6" } @@ -327,12 +4458,12 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", "requires": { "boolbase": "~1.0.0", "css-what": "2.1", @@ -386,7 +4517,7 @@ "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "requires": { "assert-plus": "^1.0.0" } @@ -402,7 +4533,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" }, "deep-extend": { "version": "0.6.0", @@ -410,17 +4541,23 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", "requires": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, "dom-serializer": { "version": "0.1.1", @@ -429,13 +4566,6 @@ "requires": { "domelementtype": "^1.3.0", "entities": "^1.1.1" - }, - "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - } } }, "domelementtype": { @@ -454,17 +4584,12 @@ "domutils": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", "requires": { "dom-serializer": "0", "domelementtype": "1" } }, - "dset": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.0.tgz", - "integrity": "sha512-7xTQ5DzyE59Nn+7ZgXDXjKAGSGmXZHqttMVVz1r4QNfmGpyj+cm2YtI3II0c/+4zS4a9yq2mBhgdeq2QnpcYlw==" - }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -476,10 +4601,15 @@ "stream-shift": "^1.0.0" }, "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -508,7 +4638,7 @@ "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -523,31 +4653,64 @@ } }, "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" } }, "es-to-primitive": { @@ -563,24 +4726,18 @@ "es6-promise": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", - "integrity": "sha1-lu258v2wGZWCKyY92KratnSBgbw=" + "integrity": "sha512-oyOjMhyKMLEjOOtvkwg0G4pAzLQ9WdbbeX7WdqKzvYXu+UFgD0Zo/Brq5Q49zNmnGPPzV5rmYvrr0jz1zWx8Iw==" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", - "optional": true - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -589,7 +4746,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" }, "fast-deep-equal": { "version": "3.1.3", @@ -601,10 +4758,26 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "focus-lock": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.11.6.tgz", + "integrity": "sha512-KSuV3ur4gf2KqMNoZx3nXNVhqCkn42GuTYCX4tXPEwf0MjpFQmNMiN6m7dXaUXgIoivL6/65agoUMg4RLS0Vbg==", + "requires": { + "tslib": "^2.0.3" + } + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" }, "form-data": { "version": "2.3.3", @@ -621,28 +4794,74 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" + } + }, + "get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" } }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "requires": { "assert-plus": "^1.0.0" } }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "requires": { + "define-properties": "^1.1.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "got": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz", - "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=", + "integrity": "sha512-7chPlc0pWHjvq7B6dEEXz4GphoDupOvBSSl6AwRsAJX7GPTZ+bturaZiIigX4Dp6KrAP67nvzuKkNc0SLA0DKg==", "requires": { "duplexify": "^3.2.0", "infinity-agent": "^2.0.0", @@ -659,86 +4878,44 @@ "object-assign": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==" } } }, "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "graphiql": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-1.4.1.tgz", - "integrity": "sha512-C7S36lTgCw2/C/Dt90eJSI9VdxQfohrUoDV1dt/WecS7dm5HcaQUIYFqvLQMZG1cSRJttRKwNwP1rYfs73v8SQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-2.4.2.tgz", + "integrity": "sha512-oQ1zJAVpOqajKH1oc5WzNIK1FNUI2m8tPgyEeA22ySmR79+/k27FlG4YxRXQOstlWeVcag0FZaqG5VyR6Nc1iw==", "requires": { - "@graphiql/toolkit": "^0.2.0", - "codemirror": "^5.54.0", - "codemirror-graphql": "^1.0.0", - "copy-to-clipboard": "^3.2.0", - "dset": "^3.1.0", - "entities": "^2.0.0", - "graphql-language-service": "^3.1.2", - "markdown-it": "^10.0.0" + "@graphiql/react": "^0.17.2", + "@graphiql/toolkit": "^0.8.4", + "graphql-language-service": "^5.1.4", + "markdown-it": "^12.2.0" } }, "graphql": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz", - "integrity": "sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==" + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==" }, "graphql-language-service": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-3.1.3.tgz", - "integrity": "sha512-MTJT8QOpsJbG68wbkrmitlctvaajrQkJEN24AW+KzNxHWFEHnnqil6fFbVccHkRbG3Bk7D0f57fjtffSh37aEw==", - "requires": { - "graphql-language-service-interface": "^2.8.2", - "graphql-language-service-types": "^1.8.0" - } - }, - "graphql-language-service-interface": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.3.tgz", - "integrity": "sha512-Gh4Q3dlCT1MrZGO0eaz7v31gkp8fh+ig94YH/A+1Th2q+k3RsRqfSJm5tKZ8TJ4rSADZ/dj+hzOpWCGzLyCiHQ==", - "requires": { - "graphql-language-service-parser": "^1.9.0", - "graphql-language-service-types": "^1.8.0", - "graphql-language-service-utils": "^2.5.1", - "vscode-languageserver-types": "^3.15.1" - } - }, - "graphql-language-service-parser": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.1.tgz", - "integrity": "sha512-GySsDrYxzxu6r1vF282xXDR2KlfVL5aOW7pgc75fF3UFiuqGm/SeoIljNM0mLpRl5KSxo1HNOxhkWoFBoy/h2w==", - "requires": { - "graphql-language-service-types": "^1.8.0" - } - }, - "graphql-language-service-types": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.1.tgz", - "integrity": "sha512-IpYS0mEHEmRsFlq+loWCpSYYYizAID7Alri6GoFN1QqUdux+8rp1Tkp2NGsGDpDmm3Dbz5ojmJWzNWQGpuwveA==" - }, - "graphql-language-service-utils": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.2.tgz", - "integrity": "sha512-hXGd4ARhyD7WTmTwuYmCYo6BcY8FtTp+1JHLaUG0Q63k0NpZTuFuRZ+N7TSP9mcRb7labeozs3DYgaqStsDe1A==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.1.4.tgz", + "integrity": "sha512-i6cYIDL8dFd6e9LqpRFJRoVa+MZ/egMzUERsZlK65S/M7ra9SxKrZuclKgmPOq0KqHypInsEaI4D7dN5a+yEAA==", "requires": { - "graphql-language-service-types": "^1.8.0", - "nullthrows": "^1.0.0" + "nullthrows": "^1.0.0", + "vscode-languageserver-types": "^3.17.1" } }, - "graphql-ws": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.4.1.tgz", - "integrity": "sha512-kHgDohfRQFDdzXzLqsV4wZM141sO1ukaXW/RSLlmIUsxT4N3r/4eQYTbkeLd4yRXaDkmv/rYf1EHL09Y5KO+Uw==" - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" }, "har-validator": { "version": "5.1.5", @@ -760,25 +4937,46 @@ "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", "requires": { "ansi-regex": "^2.0.0" } }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" }, "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } }, "htmlparser2": { "version": "3.10.1", @@ -791,19 +4989,12 @@ "entities": "^1.1.1", "inherits": "^2.0.1", "readable-stream": "^3.1.1" - }, - "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - } } }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -821,12 +5012,12 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, "infinity-agent": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz", - "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=" + "integrity": "sha512-CnfUJe5o2S9aAQWXGMhDZI4UL39MAJV3guOTfHHIdos4tuVHkl1j/J+1XLQn+CLIvqcpgQR/p+xXYXzcrhCe5w==" }, "inherits": { "version": "2.0.4", @@ -839,8 +5030,8 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inliner": { - "version": "github:aantron/inliner#7f2efdbe24a6a085f633d456b97c970ac2c0ca8a", - "from": "github:aantron/inliner#fork", + "version": "git+ssh://git@github.com/aantron/inliner.git#7f2efdbe24a6a085f633d456b97c970ac2c0ca8a", + "from": "inliner@aantron/inliner#fork", "requires": { "ansi-escapes": "^1.4.0", "ansi-styles": "^2.2.1", @@ -863,17 +5054,49 @@ "update-notifier": "^0.5.0" } }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } }, "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "requires": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, "is-buffer": { @@ -882,14 +5105,17 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" }, "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-finite": { "version": "1.1.0", @@ -897,43 +5123,70 @@ "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" + "integrity": "sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg==" }, "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-primitive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", + "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==" }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==" }, "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "requires": { "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" } }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" }, "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-symbol": { "version": "1.0.4", @@ -943,26 +5196,45 @@ "has-symbols": "^1.0.2" } }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "requires": { + "call-bind": "^1.0.2" + } }, "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "iterall": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", - "optional": true + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "js-tokens": { "version": "4.0.0", @@ -976,12 +5248,22 @@ "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + } } }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, "jschardet": { "version": "1.6.0", @@ -989,9 +5271,9 @@ "integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==" }, "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-schema-traverse": { "version": "0.4.1", @@ -1001,23 +5283,23 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" } }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", "requires": { "is-buffer": "^1.1.5" } @@ -1025,7 +5307,7 @@ "latest-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-1.0.1.tgz", - "integrity": "sha1-cs/Ebj6NG+ZR4eu1Tqn26pbzdLs=", + "integrity": "sha512-HERbxp4SBlmI380+eM0B0u4nxjfTaPeydIMzl9+9UQ4nSu3xMWKlX9WoT34e4wy7VWe67c53Nv9qPVjS8fHKgg==", "requires": { "package-json": "^1.0.0" } @@ -1033,12 +5315,12 @@ "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" }, "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", "requires": { "uc.micro": "^1.0.1" } @@ -1046,12 +5328,12 @@ "lodash._arrayeach": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" + "integrity": "sha512-Mn7HidOVcl3mkQtbPsuKR0Fj0N6Q6DQB77CtYncZcJc0bx5qv2q4Gl6a0LC1AN+GSxpnBDNnK3CKEm9XNA4zqQ==" }, "lodash._baseassign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "integrity": "sha512-t3N26QR2IdSN+gqSy9Ds9pBu/J1EAFEshKlUHpJG3rvyJOYgcELIxcIeKKfZk7sjOz11cFfzJRsyFry/JyabJQ==", "requires": { "lodash._basecopy": "^3.0.0", "lodash.keys": "^3.0.0" @@ -1060,12 +5342,12 @@ "lodash._basecopy": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==" }, "lodash._baseeach": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", - "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", + "integrity": "sha512-IqUZ9MQo2UT1XPGuBntInqTOlc+oV+bCo0kMp+yuKGsfvRSNgUW0YjWVZUrG/gs+8z/Eyuc0jkJjOBESt9BXxg==", "requires": { "lodash.keys": "^3.0.0" } @@ -1073,12 +5355,12 @@ "lodash._bindcallback": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + "integrity": "sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ==" }, "lodash._createassigner": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "integrity": "sha512-LziVL7IDnJjQeeV95Wvhw6G28Z8Q6da87LWKOPWmzBLv4u6FAT/x5v00pyGW0u38UoogNF2JnD3bGgZZDaNEBw==", "requires": { "lodash._bindcallback": "^3.0.0", "lodash._isiterateecall": "^3.0.0", @@ -1088,17 +5370,17 @@ "lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==" }, "lodash._isiterateecall": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==" }, "lodash.assign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", - "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "integrity": "sha512-/VVxzgGBmbphasTg51FrztxQJ/VgAUpol6zmJuSVSGcNg4g7FA4z7rQV8Ovr9V3vFBNWZhvKWHfpAytjTVUfFA==", "requires": { "lodash._baseassign": "^3.0.0", "lodash._createassigner": "^3.0.0", @@ -1108,17 +5390,17 @@ "lodash.assignin": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==" }, "lodash.bind": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==" }, "lodash.defaults": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", - "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", + "integrity": "sha512-X7135IXFQt5JDFnYxOVAzVz+kFvwDn3N8DJYf+nrz/mMWEuSu7+OL6rWqsk3+VR1T4TejFCSu5isBJOLSID2bg==", "requires": { "lodash.assign": "^3.0.0", "lodash.restparam": "^3.0.0" @@ -1127,17 +5409,17 @@ "lodash.filter": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==" }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" }, "lodash.foreach": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-3.0.3.tgz", - "integrity": "sha1-b9fvt5aRrs1n/erCdhyY5wHWw5o=", + "integrity": "sha512-PA7Lp7pe2HMJBoB1vELegEIF3waUFnM0fWDKJVYolwZ4zHh6WTmnq0xmzfQksD66gx2quhDNyBdyaE2T8/DP3Q==", "requires": { "lodash._arrayeach": "^3.0.0", "lodash._baseeach": "^3.0.0", @@ -1148,17 +5430,17 @@ "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" }, "lodash.isarray": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==" }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", "requires": { "lodash._getnative": "^3.0.0", "lodash.isarguments": "^3.0.0", @@ -1168,7 +5450,7 @@ "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==" }, "lodash.merge": { "version": "4.6.2", @@ -1178,32 +5460,32 @@ "lodash.pick": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" }, "lodash.reduce": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" }, "lodash.reject": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" + "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==" }, "lodash.restparam": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==" }, "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" + "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==" }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==" }, "loose-envify": { "version": "1.4.0", @@ -1219,21 +5501,21 @@ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" }, "dependencies": { "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" } } }, @@ -1245,12 +5527,13 @@ "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, "meros": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz", - "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.2.1.tgz", + "integrity": "sha512-R2f/jxYqCAGI19KhAvaxSOxALBMkaXWH2a7rOyqQw+ZmizX5bKkEYWLzdhC+U82ZVVPVp6MCXe3EkVligh+12g==", + "requires": {} }, "mime": { "version": "1.6.0", @@ -1258,40 +5541,40 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.47.0" + "mime-db": "1.52.0" } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "nested-error-stacks": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz", - "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=", + "integrity": "sha512-o32anp9JA7oezPOFSfG2BBXSdHepOm5FpJvwxHWDtfJ3Bg3xdi68S6ijPlEOfUg6quxZWyvJM+8fHk1yMDKspA==", "requires": { "inherits": "~2.0.1" } @@ -1317,12 +5600,12 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==" + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" }, "object-keys": { "version": "1.1.1", @@ -1330,41 +5613,42 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", + "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", "requires": { + "array.prototype.reduce": "^1.0.5", "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "safe-array-concat": "^1.0.0" } }, "object.values": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", - "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "requires": { "wrappy": "1" } @@ -1372,12 +5656,12 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==" }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" }, "osenv": { "version": "0.1.5", @@ -1391,7 +5675,7 @@ "package-json": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz", - "integrity": "sha1-yOysCUInzfdqMWh07QXifMk5oOA=", + "integrity": "sha512-knDtirWWqKVJrLY3gEBLflVvueTMpyjbAwX/9j/EKi2DsjNemp5voS8cyKyGh57SNaMJNhNRZbIaWdneOcLU1g==", "requires": { "got": "^3.2.0", "registry-url": "^3.0.0" @@ -1400,17 +5684,17 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", "requires": { "pinkie": "^2.0.0" } @@ -1418,7 +5702,7 @@ "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==" }, "process-nextick-args": { "version": "2.0.1", @@ -1433,25 +5717,42 @@ "asap": "~2.0.3" } }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, "rc": { "version": "1.2.8", @@ -1465,37 +5766,98 @@ } }, "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" + } + }, + "react-clientside-effect": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", + "requires": { + "@babel/runtime": "^7.12.13" } }, "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "requires": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.0" + } + }, + "react-focus-lock": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.9.4.tgz", + "integrity": "sha512-7pEdXyMseqm3kVjhdVH18sovparAzLg5h6WvIx7/Ck3ekjhrrDMEegHSa3swwC8wgfdd7DIdUVRGeiHT9/7Sgg==", + "requires": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^0.11.6", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "peer": true + }, + "react-remove-scroll": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.6.tgz", + "integrity": "sha512-bO856ad1uDYLefgArk559IzUNeQ6SWH4QnrevIUjH+GczV56giDfl3h0Idptf2oIKxQmd1p9BN25jleKodTALg==", + "requires": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + } + }, + "react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "requires": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + } + }, + "react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "requires": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" } }, "read-all-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", - "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", + "integrity": "sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==", "requires": { "pinkie-promise": "^2.0.0", "readable-stream": "^2.0.0" }, "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1522,19 +5884,34 @@ } }, "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, "registry-url": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", "requires": { "rc": "^1.0.1" } @@ -1542,12 +5919,12 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" }, "repeating": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", - "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", + "integrity": "sha512-Nh30JLeMHdoI+AsQ5eblhZ7YlTsM9wiJQe/AHIunlK3KWzvXhXb36IJ7K1IOeRjIOtzMjdUHjwXUFxKJoPTSOg==", "requires": { "is-finite": "^1.0.0" } @@ -1582,16 +5959,37 @@ "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", "requires": { "align-text": "^0.1.1" } }, + "safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1603,12 +6001,11 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "semver": { @@ -1619,15 +6016,34 @@ "semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "integrity": "sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==", "requires": { "semver": "^5.0.3" } }, + "set-value": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz", + "integrity": "sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==", + "requires": { + "is-plain-object": "^2.0.4", + "is-primitive": "^3.0.1" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==" }, "source-map": { "version": "0.6.1", @@ -1637,12 +6053,12 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -1665,44 +6081,56 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", - "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=", + "integrity": "sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw==", "requires": { "strip-ansi": "^3.0.0" } }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", "requires": { - "safe-buffer": "~5.2.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "requires": { "ansi-regex": "^2.0.0" } @@ -1710,25 +6138,18 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" }, - "subscriptions-transport-ws": { - "version": "0.9.18", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz", - "integrity": "sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA==", - "optional": true, - "requires": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0" - } + "style-mod": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.3.tgz", + "integrity": "sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==", + "peer": true }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==" }, "svgo": { "version": "1.3.2", @@ -1803,16 +6224,15 @@ } } }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "optional": true + "tabbable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz", + "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==" }, "then-fs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz", - "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=", + "integrity": "sha512-5ffcBcU+vFUCYDNi/o507IqjqrTkuGsLVZ1Fp50hwgZRY7ufVFa9jFfTy5uZ2QnSKacKigWKeaXkOqLa4DsjLw==", "requires": { "promise": ">=3.2 <8" } @@ -1820,12 +6240,17 @@ "timed-out": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", - "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=" + "integrity": "sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, "toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, "tough-cookie": { "version": "2.5.0", @@ -1836,10 +6261,15 @@ "punycode": "^2.1.1" } }, + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "requires": { "safe-buffer": "^5.0.1" } @@ -1847,7 +6277,17 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } }, "uc.micro": { "version": "1.0.6", @@ -1857,7 +6297,7 @@ "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", "requires": { "source-map": "~0.5.1", "uglify-to-browserify": "~1.0.0", @@ -1867,36 +6307,36 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" } } }, "uglify-to-browserify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", "optional": true }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" } }, "unquote": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" }, "update-notifier": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz", - "integrity": "sha1-B7XcIGazYnqztPUwEw9+3doHpMw=", + "integrity": "sha512-zOGOlUKDAgDlLHLv7Oiszz3pSj8fKlSJ3i0u49sEakjXUEVJ6DMjo/Mh/B6mg2eOALvRTJkd0kbChcipQoYCng==", "requires": { "chalk": "^1.0.0", "configstore": "^1.0.0", @@ -1915,10 +6355,27 @@ "punycode": "^2.1.0" } }, + "use-callback-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", + "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", + "requires": { + "tslib": "^2.0.0" + } + }, + "use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "requires": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "util.promisify": { "version": "1.0.1", @@ -1939,7 +6396,7 @@ "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -1947,9 +6404,15 @@ } }, "vscode-languageserver-types": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", - "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", + "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + }, + "w3c-keyname": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", + "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", + "peer": true }, "which-boxed-primitive": { "version": "1.0.2", @@ -1963,44 +6426,48 @@ "is-symbol": "^1.0.3" } }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==" }, "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==" }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write-file-atomic": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", "slide": "^1.1.5" } }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "optional": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, "xdg-basedir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", - "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", + "integrity": "sha512-NF1pPn594TaRSUO/HARoB4jK8I+rWgcpVlpQCK6/6o5PHyLUt2CSiDrpUZbQ6rROck+W2EwF8mBJcTs+W98J9w==", "requires": { "os-homedir": "^1.0.0" } @@ -2008,7 +6475,7 @@ "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", "requires": { "camelcase": "^1.0.2", "cliui": "^2.1.0", diff --git a/src/graphiql/package.json b/src/graphiql/package.json index ec6fe59f..97dbeff8 100644 --- a/src/graphiql/package.json +++ b/src/graphiql/package.json @@ -2,10 +2,10 @@ "name": "dream-graphiql", "private": true, "dependencies": { - "graphiql": "^1.4.1", - "graphql": "^15.5.0", + "graphiql": "^2.4.1", + "graphql": "^16.6.0", "inliner": "aantron/inliner#fork", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "^18.2.0", + "react-dom": "^18.2.0" } } diff --git a/src/graphql/dune b/src/graphql/dune index 4989280c..7397ce17 100644 --- a/src/graphql/dune +++ b/src/graphql/dune @@ -3,8 +3,8 @@ (name dream__graphql) (libraries dream.graphiql - dream.middleware - dream.pure + dream-pure + dream.server graphql_parser graphql-lwt lwt diff --git a/src/graphql/graphql.ml b/src/graphql/graphql.ml index f22b1f42..25c548b5 100644 --- a/src/graphql/graphql.ml +++ b/src/graphql/graphql.ml @@ -5,7 +5,11 @@ -module Dream = Dream__pure.Inmost +module Helpers = Dream__server.Helpers +module Log = Dream__server.Log +module Message = Dream_pure.Message +module Method = Dream_pure.Method +module Stream = Dream_pure.Stream @@ -21,7 +25,7 @@ module Dream = Dream__pure.Inmost https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md *) let log = - Dream__middleware.Log.sub_log "dream.graphql" + Log.sub_log "dream.graphql" @@ -75,13 +79,9 @@ let operation_id json = Yojson.Basic.Util.(json |> member "id" |> to_string_option) let close_and_clean ?code subscriptions websocket = - match%lwt Dream.close_websocket ?code websocket with - | _ -> - Hashtbl.iter (fun _ close -> close ()) subscriptions; - Lwt.return_unit - | exception _ -> - Hashtbl.iter (fun _ close -> close ()) subscriptions; - Lwt.return_unit + let%lwt () = Message.close_websocket ?code websocket in + Hashtbl.iter (fun _ close -> close ()) subscriptions; + Lwt.return_unit let ack_message = `Assoc [ @@ -116,7 +116,7 @@ let complete_message id = (* TODO Test client complete racing against a stream. *) let handle_over_websocket make_context schema subscriptions request websocket = let rec loop inited = - match%lwt Dream.receive websocket with + match%lwt Helpers.receive websocket with | None -> log.info (fun log -> log ~request "GraphQL WebSocket closed by client"); close_and_clean subscriptions websocket @@ -145,7 +145,7 @@ let handle_over_websocket make_context schema subscriptions request websocket = close_and_clean subscriptions websocket ~code:4429 end else begin - let%lwt () = Dream.send websocket ack_message in + let%lwt () = Helpers.send websocket ack_message in loop true end @@ -193,13 +193,13 @@ let handle_over_websocket make_context schema subscriptions request websocket = log.warning (fun log -> log ~request "subscribe: error %s" (Yojson.Basic.to_string json)); - Dream.send websocket (error_message id json) + Helpers.send websocket (error_message id json) (* It's not clear that this case ever occurs, because graphql-ws is only used for subscriptions, at the protocol level. *) | Ok (`Response json) -> - let%lwt () = Dream.send websocket (data_message id json) in - let%lwt () = Dream.send websocket (complete_message id) in + let%lwt () = Helpers.send websocket (data_message id json) in + let%lwt () = Helpers.send websocket (complete_message id) in Lwt.return_unit | Ok (`Stream (stream, close)) -> @@ -216,15 +216,15 @@ let handle_over_websocket make_context schema subscriptions request websocket = let%lwt () = stream |> Lwt_stream.iter_s (function | Ok json -> - Dream.send websocket (data_message id json) + Helpers.send websocket (data_message id json) | Error json -> log.warning (fun log -> log ~request "Subscription: error %s" (Yojson.Basic.to_string json)); - Dream.send websocket (error_message id json)) + Helpers.send websocket (error_message id json)) in - let%lwt () = Dream.send websocket (complete_message id) in + let%lwt () = Helpers.send websocket (complete_message id) in Hashtbl.remove subscriptions id; Lwt.return_unit @@ -235,17 +235,17 @@ let handle_over_websocket make_context schema subscriptions request websocket = log.error (fun log -> log ~request "%s" (Printexc.to_string exn)); backtrace - |> Dream__middleware.Log.iter_backtrace (fun line -> + |> Log.iter_backtrace (fun line -> log.error (fun log -> log ~request "%s" line)); try%lwt let%lwt () = - Dream.send + Helpers.send websocket (error_message id (make_error "Internal Server Error")) in if !subscribed then - Dream.send websocket (complete_message id) + Helpers.send websocket (complete_message id) else Lwt.return_unit with _ -> @@ -271,52 +271,55 @@ let handle_over_websocket make_context schema subscriptions request websocket = carrying WebSocket upgrade headers. *) let graphql make_context schema = fun request -> - match Dream.method_ request with + match Message.method_ request with | `GET -> - let upgrade = Dream.header "Upgrade" request - and protocol = Dream.header "Sec-WebSocket-Protocol" request in + let upgrade = Message.header request "Upgrade" + and protocol = Message.header request "Sec-WebSocket-Protocol" in begin match upgrade, protocol with | Some "websocket", Some "graphql-transport-ws" -> - Dream.websocket + Helpers.websocket ~headers:["Sec-WebSocket-Protocol", "graphql-transport-ws"] (handle_over_websocket make_context schema (Hashtbl.create 16) request) | _ -> log.warning (fun log -> log ~request "Upgrade: websocket header missing"); - Dream.empty `Not_Found + Message.response ~status:`Not_Found Stream.empty Stream.null + |> Lwt.return end | `POST -> - begin match Dream.header "Content-Type" request with + begin match Message.header request "Content-Type" with | Some "application/json" -> - let%lwt body = Dream.body request in + let%lwt body = Message.body request in (* TODO This almost certainly raises exceptions... *) let json = Yojson.Basic.from_string body in begin match%lwt run_query make_context schema request json with | Error json -> Yojson.Basic.to_string json - |> Dream.json + |> Helpers.json | Ok (`Response json) -> Yojson.Basic.to_string json - |> Dream.json + |> Helpers.json | Ok (`Stream _) -> make_error "Subscriptions and streaming should use WebSocket transport" |> Yojson.Basic.to_string - |> Dream.json + |> Helpers.json end | _ -> log.warning (fun log -> log ~request "Content-Type not 'application/json'"); - Dream.empty `Bad_Request + Message.response ~status:`Bad_Request Stream.empty Stream.null + |> Lwt.return end | method_ -> log.error (fun log -> log ~request - "Method %s; must be GET or POST" (Dream.method_to_string method_)); - Dream.empty `Not_Found + "Method %s; must be GET or POST" (Method.method_to_string method_)); + Message.response ~status:`Not_Found Stream.empty Stream.null + |> Lwt.return @@ -341,4 +344,4 @@ let graphiql ?(default_query = "") graphql_endpoint = in fun _request -> - Dream.html (Lazy.force html) + Helpers.html (Lazy.force html) diff --git a/src/http/adapt.ml b/src/http/adapt.ml index c55d9882..8f822c0b 100644 --- a/src/http/adapt.ml +++ b/src/http/adapt.ml @@ -5,7 +5,11 @@ -module Dream = Dream__pure.Inmost +module Httpaf = Dream_httpaf_.Httpaf +module H2 = Dream_h2.H2 + +module Stream = Dream_pure.Stream +module Message = Dream_pure.Message @@ -18,57 +22,71 @@ let address_to_string : Unix.sockaddr -> string = function (* TODO Write a test simulating client exit during SSE; this was killing the server at some point. *) -(* TODO LATER Will also need to monitor buffer accumulation and use flush. *) -(* TODO Rewrite using Dream.next. *) let forward_body_general - (response : Dream.response) - (write_string : ?off:int -> ?len:int -> string -> unit) - (write_buffer : ?off:int -> ?len:int -> Dream.buffer -> unit) + (response : Message.response) + (_write_string : ?off:int -> ?len:int -> string -> unit) + (write_buffer : ?off:int -> ?len:int -> Stream.buffer -> unit) http_flush close = + let abort _exn = close 1000 in + + let bytes_since_flush = ref 0 in + let rec send () = - response - |> Dream.next - ~buffer - ~string - ~flush - ~close - ~exn:ignore - - and buffer chunk off len = + Message.client_stream response + |> fun stream -> + Stream.read + stream + ~data + ~flush + ~ping + ~pong + ~close + ~exn:abort + + and data chunk off len _binary _fin = write_buffer ~off ~len chunk; - send () - - and string chunk off len = - write_string ~off ~len chunk; - send () + bytes_since_flush := !bytes_since_flush + len; + if !bytes_since_flush >= 4096 then begin + bytes_since_flush := 0; + http_flush send + end + else + send () and flush () = + bytes_since_flush := 0; http_flush send + and ping _buffer _offset _length = + send () + + and pong _buffer _offset _length = + send () + in send () let forward_body - (response : Dream.response) - (body : [ `write ] Httpaf.Body.t) = + (response : Message.response) + (body : Httpaf.Body.Writer.t) = forward_body_general response - (Httpaf.Body.write_string body) - (Httpaf.Body.write_bigstring body) - (Httpaf.Body.flush body) - (fun () -> Httpaf.Body.close_writer body) + (Httpaf.Body.Writer.write_string body) + (Httpaf.Body.Writer.write_bigstring body) + (Httpaf.Body.Writer.flush body) + (fun _code -> Httpaf.Body.Writer.close body) let forward_body_h2 - (response : Dream.response) - (body : [ `write ] H2.Body.t) = + (response : Message.response) + (body : H2.Body.Writer.t) = forward_body_general response - (H2.Body.write_string body) - (H2.Body.write_bigstring body) - (H2.Body.flush body) - (fun () -> H2.Body.close_writer body) + (H2.Body.Writer.write_string body) + (H2.Body.Writer.write_bigstring body) + (H2.Body.Writer.flush body) + (fun _code -> H2.Body.Writer.close body) diff --git a/src/http/dune b/src/http/dune index 1162a6f2..76ddb65e 100644 --- a/src/http/dune +++ b/src/http/dune @@ -2,24 +2,23 @@ (public_name dream.http) (name dream__http) (libraries - bigarray-compat - bigstringaf digestif + dream.certificate dream.cipher - dream.localhost - dream.middleware - dream.pure - dream.gluten - dream.gluten-lwt-unix - dream.h2 - dream.h2-lwt-unix - dream.httpaf - dream.httpaf-lwt-unix + dream-pure + dream.server + dream-httpaf + dream-httpaf.dream-gluten + dream-httpaf.dream-gluten-lwt-unix + dream-httpaf.dream-h2 + dream-httpaf.dream-h2-lwt-unix + dream-httpaf.dream-httpaf_ + dream-httpaf.dream-httpaf_-lwt-unix lwt lwt.unix lwt_ssl ssl - dream.websocketaf + dream-httpaf.dream-websocketaf ) (preprocess (pps lwt_ppx)) (instrumentation (backend bisect_ppx))) diff --git a/src/http/error_handler.ml b/src/http/error_handler.ml index 8eb7c227..7d9bb21a 100644 --- a/src/http/error_handler.ml +++ b/src/http/error_handler.ml @@ -5,7 +5,18 @@ -module Dream = Dream__pure.Inmost +module Httpaf = Dream_httpaf_.Httpaf +module H2 = Dream_h2.H2 +module Websocketaf = Dream_websocketaf.Websocketaf + +module Catch = Dream__server.Catch +module Error_template = Dream__server.Error_template +module Method = Dream_pure.Method +module Helpers = Dream__server.Helpers +module Log = Dream__server.Log +module Message = Dream_pure.Message +module Status = Dream_pure.Status +module Stream = Dream_pure.Stream @@ -15,7 +26,7 @@ module Dream = Dream__pure.Inmost an app. *) let log = - Dream__middleware.Log.sub_log "dream.http" + Log.sub_log "dream.http" let select_log = function | `Error -> log.error @@ -25,14 +36,14 @@ let select_log = function -let dump (error : Dream.error) = +let dump (error : Catch.error) = let buffer = Buffer.create 4096 in let p format = Printf.bprintf buffer format in begin match error.condition with | `Response response -> - let status = Dream.status response in - p "%i %s\n" (Dream.status_to_int status) (Dream.status_to_string status) + let status = Message.status response in + p "%i %s\n" (Status.status_to_int status) (Status.status_to_string status) | `String "" -> p "(Library error without description payload)\n" @@ -43,7 +54,7 @@ let dump (error : Dream.error) = | `Exn exn -> let backtrace = Printexc.get_backtrace () in p "%s\n" (Printexc.to_string exn); - backtrace |> Dream__middleware.Log.iter_backtrace (p "%s\n") + backtrace |> Log.iter_backtrace (p "%s\n") end; p "\n"; @@ -83,29 +94,21 @@ let dump (error : Dream.error) = begin match error.request with | None -> () | Some request -> - let last = Dream.last request in - - let major, minor = Dream.version last in - p "\n\n%s %s HTTP/%i.%i" - (Dream.method_to_string (Dream.method_ last)) - (Dream.target last) - major minor; + p "\n\n%s %s" + (Method.method_to_string (Message.method_ request)) + (Message.target request); - Dream.all_headers last + Message.all_headers request |> List.iter (fun (name, value) -> p "\n%s: %s" name value); - let show_variables kind = - kind (fun name value first -> - if first then - p "\n"; - p "\n%s: %s" name value; - false) - true - request - |> ignore - in - show_variables Dream.fold_locals; - show_variables Dream.fold_globals + Message.fold_fields (fun name value first -> + if first then + p "\n"; + p "\n%s: %s" name value; + false) + true + request + |> ignore end; Buffer.contents buffer @@ -113,11 +116,11 @@ let dump (error : Dream.error) = (* TODO LATER Some library is registering S-exp-based printers for expressions, which are calling functions that use exceptions during parsing, which are clobbering the backtrace. *) -let customize template (error : Dream.error) = +let customize template (error : Catch.error) = (* First, log the error. *) - let log_handler condition (error : Dream.error) = + let log_handler condition (error : Catch.error) = let client = match error.client with | None -> "" @@ -145,18 +148,20 @@ let customize template (error : Dream.error) = select_log error.severity (fun log -> log ?request:error.request "%s" message); - backtrace |> Dream__middleware.Log.iter_backtrace (fun line -> + backtrace |> Log.iter_backtrace (fun line -> select_log error.severity (fun log -> log ?request:error.request "%s" line)) in + begin match error.condition with | `Response _ -> () - (* TODO is this the right place to handle lower level connection resets? *) + (* TODO Is this the right place to handle lower level connection resets? *) | `Exn (Unix.Unix_error (Unix.ECONNRESET, _, _)) -> - let condition = `String "Connection Reset at Client" in - let severity = `Info in - log_handler condition {error with condition; severity} - | `String _ | `Exn _ as condition -> log_handler condition error + let condition = `String "Connection Reset at Client" in + let severity = `Info in + log_handler condition {error with condition; severity} + | `String _ | `Exn _ as condition -> + log_handler condition error end; (* If Dream will not send a response for this error, we are done after @@ -167,11 +172,7 @@ let customize template (error : Dream.error) = Lwt.return_none else - let debug_dump = - match error.debug with - | false -> None - | true -> Some (dump error) - in + let debug_dump = dump error in let response = match error.condition with @@ -182,36 +183,34 @@ let customize template (error : Dream.error) = | `Server -> `Internal_Server_Error | `Client -> `Bad_Request in - Dream.response ~status "" + Message.response ~status Stream.empty Stream.null in (* No need to catch errors when calling the template, because every call site of the error handler already has error handlers for catching double faults. *) - response - |> template error debug_dump - |> Lwt.map (fun response -> Some response) + let%lwt response = template error debug_dump response in + Lwt.return (Some response) -let default_template _error debug_dump response = - match debug_dump with - | None -> Lwt.return response - | Some debug_dump -> - let status = Dream.status response in - let code = Dream.status_to_int status - and reason = Dream.status_to_string status in - response - |> Dream.with_header "Content-Type" Dream__pure.Formats.text_html - |> Dream.with_body - (Dream__middleware.Error_template.render ~debug_dump ~code ~reason) - |> Lwt.return - +let default_template _error _debug_dump response = + Lwt.return response +let debug_template _error debug_dump response = + let status = Message.status response in + let code = Status.status_to_int status + and reason = Status.status_to_string status in + Message.set_header response "Content-Type" Dream_pure.Formats.text_html; + Message.set_body response (Error_template.render ~debug_dump ~code ~reason); + Lwt.return response let default = customize default_template +let debug_error_handler = + customize debug_template + (* Error reporters (called in various places by the framework). *) @@ -226,7 +225,7 @@ let double_faults f default = log "Error handler raised: %s" (Printexc.to_string exn)); backtrace - |> Dream__middleware.Log.iter_backtrace (fun line -> + |> Log.iter_backtrace (fun line -> log.error (fun log -> log "%s" line)); default () @@ -244,9 +243,12 @@ let respond_with_option f = f () |> Lwt.map (function | Some response -> response - | None -> Dream.response ~status:`Internal_Server_Error "")) + | None -> + Message.response + ~status:`Internal_Server_Error Stream.empty Stream.null)) (fun () -> - Dream.empty `Internal_Server_Error) + Message.response ~status:`Internal_Server_Error Stream.empty Stream.null + |> Lwt.return) @@ -264,70 +266,16 @@ let app respond_with_option (fun () -> user's_error_handler error) -(* let app - app user's_error_handler = - fun next_handler request -> - - Lwt.try_bind - - (fun () -> - next_handler request) - - (fun response -> - let status = Dream.status response in - - if Dream.is_client_error status || Dream.is_server_error status then begin - let caused_by, severity = - if Dream.is_client_error status then - `Client, `Warning - else - `Server, `Error - in - - let error = Error.{ - condition = `Response response; - layer = `App; - caused_by; - request = Some request; - response = Some response; - client = Some (Dream.client request); - severity = severity; - debug = Dream.debug app; - will_send_response = true; - } in - - respond_with_option (fun () -> user's_error_handler error) - end - else - Lwt.return response) - - (* This exception handler is partially redundant, in that the HTTP-level - handlers will also catch exceptions. However, this handler is able to - capture more relevant context. We leave the HTTP-level handlers for truly - severe protocol-level errors and integration mistakes. *) - (fun exn -> - let error = Error.{ - condition = `Exn exn; - layer = `App; - caused_by = `Server; - request = Some request; - response = None; - client = Some (Dream.client request); - severity = `Error; - debug = Dream.debug app; - will_send_response = true; - } in - - respond_with_option (fun () -> user's_error_handler error)) *) - let default_response = function - | `Server -> Dream.response ~status:`Internal_Server_Error "" - | `Client -> Dream.response ~status:`Bad_Request "" + | `Server -> + Message.response ~status:`Internal_Server_Error Stream.empty Stream.null + | `Client -> + Message.response ~status:`Bad_Request Stream.empty Stream.null let httpaf - app user's_error_handler = + user's_error_handler = fun client_address ?request error start_response -> ignore (request : Httpaf.Request.t option); @@ -354,14 +302,13 @@ let httpaf in let error = { - Dream.condition; + Catch.condition; layer = `HTTP; caused_by; request = None; response = None; client = Some (Adapt.address_to_string client_address); severity; - debug = Dream.debug app; will_send_response = true; } in @@ -375,7 +322,7 @@ let httpaf | None -> default_response caused_by in - let headers = Httpaf.Headers.of_list (Dream.all_headers response) in + let headers = Httpaf.Headers.of_list (Message.all_headers response) in let body = start_response headers in Adapt.forward_body response body; @@ -388,7 +335,7 @@ let httpaf let h2 - app user's_error_handler = + user's_error_handler = fun client_address ?request error start_response -> ignore request; (* TODO Recover something from the request. *) @@ -413,14 +360,13 @@ let h2 in let error = { - Dream.condition; + Catch.condition; layer = `HTTP2; caused_by; request = None; response = None; client = Some (Adapt.address_to_string client_address); severity; - debug = Dream.debug app; will_send_response = true; } in @@ -434,7 +380,7 @@ let h2 | None -> default_response caused_by in - let headers = H2.Headers.of_list (Dream.all_headers response) in + let headers = H2.Headers.of_list (Message.all_headers response) in let body = start_response headers in Adapt.forward_body_h2 response body; @@ -452,17 +398,16 @@ let h2 However, SSL protocol errors are not wrapped in any of these, so we add an edditional top-level handler to catch them. *) let tls - app user's_error_handler client_address error = + user's_error_handler client_address error = let error = { - Dream.condition = `Exn error; + Catch.condition = `Exn error; layer = `TLS; caused_by = `Client; request = None; response = None; client = Some (Adapt.address_to_string client_address); severity = `Warning; - debug = Dream.debug app; will_send_response = false; } in @@ -487,14 +432,13 @@ let websocket let `Exn exn = error in let error = { - Dream.condition = `Exn exn; + Catch.condition = `Exn exn; layer = `WebSocket; caused_by = `Server; request = Some request; response = Some response; - client = Some (Dream.client request); + client = Some (Helpers.client request); severity = `Warning; (* Not sure what these errors are, yet. *) - debug = Dream.debug (Dream.app request); will_send_response = false; } in @@ -510,14 +454,13 @@ let websocket_handshake fun request response error_string -> let error = { - Dream.condition = `String error_string; + Catch.condition = `String error_string; layer = `WebSocket; caused_by = `Client; request = Some request; response = Some response; - client = Some (Dream.client request); + client = Some (Helpers.client request); severity = `Warning; - debug = Dream.debug (Dream.app request); will_send_response = true; } in diff --git a/src/http/error_handler.mli b/src/http/error_handler.mli index fac335cb..f2a06390 100644 --- a/src/http/error_handler.mli +++ b/src/http/error_handler.mli @@ -5,17 +5,24 @@ -module Dream = Dream__pure.Inmost +module Httpaf = Dream_httpaf_.Httpaf +module H2 = Dream_h2.H2 +module Websocketaf = Dream_websocketaf.Websocketaf + +module Catch = Dream__server.Catch +module Log = Dream__server.Log +module Message = Dream_pure.Message (* User's error handlers and defaults. These actually generate error response templates and/or do logging. *) -val default : Dream.error_handler +val default : Catch.error_handler +val debug_error_handler : Catch.error_handler val customize : - (Dream.error -> string option -> Dream.response -> Dream.response Lwt.t) -> - Dream.error_handler + (Catch.error -> string -> Message.response -> Message.response Lwt.t) -> + Catch.error_handler @@ -31,36 +38,33 @@ val customize : Dream.middleware *) val app : - Dream.error_handler -> - (Dream.error -> Dream.response Lwt.t) + Catch.error_handler -> + (Catch.error -> Message.response Lwt.t) val httpaf : - Dream.app -> - Dream.error_handler -> + Catch.error_handler -> (Unix.sockaddr -> Httpaf.Server_connection.error_handler) val h2 : - Dream.app -> - Dream.error_handler -> + Catch.error_handler -> (Unix.sockaddr -> H2.Server_connection.error_handler) val tls : - Dream.app -> - Dream.error_handler -> + Catch.error_handler -> (Unix.sockaddr -> exn -> unit) val websocket : - Dream.error_handler -> - Dream.request -> - Dream.response -> + Catch.error_handler -> + Message.request -> + Message.response -> (Websocketaf.Wsd.t -> [ `Exn of exn ] -> unit) val websocket_handshake : - Dream.error_handler -> - (Dream.request -> Dream.response -> string -> Dream.response Lwt.t) + Catch.error_handler -> + (Message.request -> Message.response -> string -> Message.response Lwt.t) (* Logger also used by elsewhere in the HTTP integration. *) -val log : Dream__middleware.Log.sub_log +val log : Log.sub_log diff --git a/src/http/http.ml b/src/http/http.ml index 239960b7..7b77bc41 100644 --- a/src/http/http.ml +++ b/src/http/http.ml @@ -5,7 +5,21 @@ -module Dream = Dream__pure.Inmost +module Gluten = Dream_gluten.Gluten +module Gluten_lwt_unix = Dream_gluten_lwt_unix.Gluten_lwt_unix +module Httpaf = Dream_httpaf_.Httpaf +module Httpaf_lwt_unix = Dream_httpaf__lwt_unix.Httpaf_lwt_unix +module H2 = Dream_h2.H2 +module H2_lwt_unix = Dream_h2_lwt_unix.H2_lwt_unix +module Websocketaf = Dream_websocketaf.Websocketaf + +module Catch = Dream__server.Catch +module Helpers = Dream__server.Helpers +module Log = Dream__server.Log +module Message = Dream_pure.Message +module Method = Dream_pure.Method +module Status = Dream_pure.Status +module Stream = Dream_pure.Stream @@ -14,107 +28,22 @@ module Dream = Dream__pure.Inmost let to_dream_method method_ = - Httpaf.Method.to_string method_ |> Dream.string_to_method + Httpaf.Method.to_string method_ |> Method.string_to_method let to_httpaf_status status = - Dream.status_to_int status |> Httpaf.Status.of_code + Status.status_to_int status |> Httpaf.Status.of_code let to_h2_status status = - Dream.status_to_int status |> H2.Status.of_code + Status.status_to_int status |> H2.Status.of_code -(* TODO Contact upstream: this is from websocketaf/lwt/websocketaf_lwt.ml, but - it is not exposed. *) let sha1 s = s |> Digestif.SHA1.digest_string |> Digestif.SHA1.to_raw_string -(* TODO It appears that backpressure is impossible in the underlying - implementation... *) -let websocket_handler user's_websocket_handler socket = - - (* Frames of the current partial message, in reverse order. *) - let message_frames = ref [] in - - (* Queue of received messages. There doesn't appear to be a nice way to - achieve backpressure with the current API of websocketaf, so that will have - to be added later. The user-facing API of Dream does support backpressure. - It's just that this code here reads no matter whether there is a - higher-level reader. *) - let messages, push_message = Lwt_stream.create () in - - let send kind message = - let kind = (kind :> [ `Text | `Binary | `Continuation ]) in - Websocketaf.Wsd.send_bytes - socket - ~kind - (Bytes.unsafe_of_string message) - ~off:0 - ~len:(String.length message); - Lwt.return_unit - in - - let receive () = - Lwt_stream.get messages in - - let close code = - let code = Option.map (fun code -> `Other code) code in - Websocketaf.Wsd.close ?code socket; - Lwt.return_unit - in - - let websocket = { - Dream.send; - receive; - close; - } in - - (* TODO Needs error handling like the top-level app has! *) - Lwt.async (fun () -> - user's_websocket_handler websocket); - - (* The code isn't very efficient at the moment, doing multiple copies while - assembling a message. However, multi-fragment messages should be relatively - rare. *) - - (* This function is called on each frame received. In this high-level handler. - we automatically respond to all control opcodes. *) - let frame ~opcode ~is_fin buffer ~off ~len = - match opcode with - | `Connection_close -> - Websocketaf.Wsd.close socket; - push_message None; - | `Ping -> - Websocketaf.Wsd.send_pong socket - | `Pong -> - () - | `Other _ -> - () - - | `Text - | `Binary -> - let fragment = Lwt_bytes.to_string (Lwt_bytes.proxy buffer off len) in - if is_fin then - push_message (Some fragment) - else - message_frames := [fragment] - - | `Continuation -> - let fragment = Lwt_bytes.to_string (Lwt_bytes.proxy buffer off len) in - message_frames := fragment::!message_frames; - if is_fin then begin - let message = String.concat "" (List.rev !message_frames) in - message_frames := []; - push_message (Some message) - end - in +let websocket_log = + Log.sub_log "dream.websocket" - let eof () = - push_message None; - Websocketaf.Wsd.close socket - in - - Websocketaf.Server_connection.{frame; eof} (* Wraps the user's Dream handler in the kind of handler expected by http/af. @@ -129,12 +58,12 @@ let websocket_handler user's_websocket_handler socket = chance to tell the user that something is wrong with their app. *) (* TODO Rename conn like in the body branch. *) let wrap_handler - app - (user's_error_handler : Dream.error_handler) - (user's_dream_handler : Dream.handler) = + tls + (user's_error_handler : Catch.error_handler) + (user's_dream_handler : Message.handler) = let httpaf_request_handler = fun client_address (conn : _ Gluten.Reqd.t) -> - Dream__middleware.Log.set_up_exception_hook (); + Log.set_up_exception_hook (); let conn, upgrade = conn.reqd, conn.upgrade in @@ -148,39 +77,30 @@ let wrap_handler to_dream_method httpaf_request.meth in let target = httpaf_request.target in - let version = - (httpaf_request.version.major, httpaf_request.version.minor) in let headers = Httpaf.Headers.to_list httpaf_request.headers in let body = Httpaf.Reqd.request_body conn in + (* TODO Review per-chunk allocations. *) + (* TODO Should the stream be auto-closed? It doesn't even have a closed + state. The whole thing is just a wrapper for whatever the http/af + behavior is. *) + let read ~data ~flush:_ ~ping:_ ~pong:_ ~close ~exn:_ = + Httpaf.Body.Reader.schedule_read + body + ~on_eof:(fun () -> close 1000) + ~on_read:(fun buffer ~off ~len -> data buffer off len true false) + in + let close _code = + Httpaf.Body.Reader.close body in + let body = + Stream.reader ~read ~close ~abort:close in + let body = + Stream.stream body Stream.no_writer in - let request : Dream.request = - Dream.request_from_http ~app ~client ~method_ ~target ~version ~headers in - - (* TODO Could use a private variant of flush that causes no app-observable - side-effects. *) - (* TODO It would still help to have a fully pull-based writing API. *) - (* TODO The whole body-reading model seems to be broken. How does one detect - an exception? *) - (* The request body stream. *) - Lwt.async begin fun () -> - let%lwt () = Dream.flush request in - let on_eof () = Dream.close_stream request |> ignore in - - let rec loop () = - Httpaf.Body.schedule_read - body - ~on_eof - ~on_read:(fun buffer ~off ~len -> - Lwt.on_success - (Dream__pure.Body.write_bigstring buffer off len request.body) - loop) - in - loop (); - Lwt.return_unit - end; + let request : Message.request = + Helpers.request ~client ~method_ ~target ~tls ~headers body in (* Call the user's handler. If it raises an exception or returns a promise that rejects with an exception, pass the exception up to Httpaf. This @@ -207,18 +127,13 @@ let wrap_handler 2. Upon failure to establish a WebSocket, the function is called to transmit the resulting error response. *) let forward_response response = + Message.set_content_length_headers response; + let headers = - Httpaf.Headers.of_list (Dream.all_headers response) in + Httpaf.Headers.of_list (Message.all_headers response) in - (* let version = - match Dream.version_override response with - | None -> None - | Some (major, minor) -> Some Httpaf.Version.{major; minor} - in *) let status = - to_httpaf_status (Dream.status response) in - (* let reason = - Dream.reason_override response in *) + to_httpaf_status (Message.status response) in let httpaf_response = Httpaf.Response.create ~headers status in @@ -230,36 +145,23 @@ let wrap_handler Lwt.return_unit in - match Dream.is_websocket response with + match Message.get_websocket response with | None -> - forward_response response - - | Some user's_websocket_handler -> - + | Some (client_stream, _server_stream) -> let error_handler = Error_handler.websocket user's_error_handler request response in - (* TODO This needs to be done in a more disciplined fashion. *) - (* TODO This could be considerably simplified using just a mutable - request_id field in requests. *) - let user's_websocket_handler websocket = - Lwt.with_value - Dream__middleware.Request_id.lwt_key - (Dream__middleware.Request_id.get_option - ~request:(Dream.last request) ()) - (fun () -> user's_websocket_handler websocket) - in - let proceed () = Websocketaf.Server_connection.create_websocket - ~error_handler (websocket_handler user's_websocket_handler) + ~error_handler + (Dream_httpaf.Websocket.websocket_handler client_stream) |> Gluten.make (module Websocketaf.Server_connection) |> upgrade in let headers = - Httpaf.Headers.of_list (Dream.all_headers response) in + Httpaf.Headers.of_list (Message.all_headers response) in Websocketaf.Handshake.respond_with_upgrade ~headers ~sha1 conn proceed |> function @@ -270,7 +172,6 @@ let wrap_handler user's_error_handler request response error_string in forward_response response - end @@ fun exn -> (* TODO There was something in the fork changelogs about not requiring @@ -286,12 +187,12 @@ let wrap_handler (* TODO Factor out what is in common between the http/af and h2 handlers. *) let wrap_handler_h2 - app - (_user's_error_handler : Dream.error_handler) - (user's_dream_handler : Dream.handler) = + tls + (_user's_error_handler : Catch.error_handler) + (user's_dream_handler : Message.handler) = let httpaf_request_handler = fun client_address (conn : H2.Reqd.t) -> - Dream__middleware.Log.set_up_exception_hook (); + Log.set_up_exception_hook (); (* Covert the h2 request to a Dream request. *) let httpaf_request : H2.Request.t = @@ -303,33 +204,26 @@ let wrap_handler_h2 to_dream_method httpaf_request.meth in let target = httpaf_request.target in - let version = - (2, 0) in let headers = H2.Headers.to_list httpaf_request.headers in let body = H2.Reqd.request_body conn in + let read ~data ~flush:_ ~ping:_ ~pong:_ ~close ~exn:_ = + H2.Body.Reader.schedule_read + body + ~on_eof:(fun () -> close 1000) + ~on_read:(fun buffer ~off ~len -> data buffer off len true false) + in + let close _code = + H2.Body.Reader.close body in + let body = + Stream.reader ~read ~close ~abort:close in + let body = + Stream.stream body Stream.no_writer in - let request : Dream.request = - Dream.request_from_http ~app ~client ~method_ ~target ~version ~headers in - - Lwt.async begin fun () -> - let%lwt () = Dream.flush request in - let on_eof () = Dream.close_stream request |> ignore in - - let rec loop () = - H2.Body.schedule_read - body - ~on_eof - ~on_read:(fun buffer ~off ~len -> - Dream__pure.Body.write_bigstring buffer off len request.body - |> ignore; - loop ()) - in - loop (); - Lwt.return_unit - end; + let request : Message.request = + Helpers.request ~client ~method_ ~target ~tls ~headers body in (* Call the user's handler. If it raises an exception or returns a promise that rejects with an exception, pass the exception up to Httpaf. This @@ -349,10 +243,12 @@ let wrap_handler_h2 (* Extract the Dream response's headers. *) let forward_response response = + Message.drop_content_length_headers response; + Message.lowercase_headers response; let headers = - H2.Headers.of_list (Dream.all_headers response) in + H2.Headers.of_list (Message.all_headers response) in let status = - to_h2_status (Dream.status response) in + to_h2_status (Message.status response) in let h2_response = H2.Response.create ~headers status in let body = @@ -363,17 +259,15 @@ let wrap_handler_h2 Lwt.return_unit in - match Dream.is_websocket response with + match Message.get_websocket response with | None -> forward_response response - + | Some _ -> (* TODO DOC H2 appears not to support WebSocket upgrade at present. RFC 8441. *) (* TODO DOC Do we need a CONNECT method? Do users need to be informed of this? *) - | Some _user's_websocket_handler -> Lwt.return_unit - end @@ fun exn -> (* TODO LATER There was something in the fork changelogs about not @@ -396,9 +290,8 @@ type tls_library = { create_handler : certificate_file:string -> key_file:string -> - app:Dream.app -> - handler:Dream.handler -> - error_handler:Dream.error_handler -> + handler:Message.handler -> + error_handler:Catch.error_handler -> Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t; @@ -407,35 +300,33 @@ type tls_library = { let no_tls = { create_handler = begin fun ~certificate_file:_ ~key_file:_ - ~app ~handler ~error_handler -> Httpaf_lwt_unix.Server.create_connection_handler ?config:None - ~request_handler:(wrap_handler app error_handler handler) - ~error_handler:(Error_handler.httpaf app error_handler) + ~request_handler:(wrap_handler false error_handler handler) + ~error_handler:(Error_handler.httpaf error_handler) end; } let openssl = { create_handler = begin fun ~certificate_file ~key_file - ~app ~handler ~error_handler -> let httpaf_handler = Httpaf_lwt_unix.Server.SSL.create_connection_handler ?config:None - ~request_handler:(wrap_handler app error_handler handler) - ~error_handler:(Error_handler.httpaf app error_handler) + ~request_handler:(wrap_handler true error_handler handler) + ~error_handler:(Error_handler.httpaf error_handler) in let h2_handler = H2_lwt_unix.Server.SSL.create_connection_handler ?config:None - ~request_handler:(wrap_handler_h2 app error_handler handler) - ~error_handler:(Error_handler.h2 app error_handler) + ~request_handler:(wrap_handler_h2 true error_handler handler) + ~error_handler:(Error_handler.h2 error_handler) in let perform_tls_handshake = @@ -480,37 +371,41 @@ let openssl = { let ocaml_tls = { create_handler = fun ~certificate_file ~key_file - ~app ~handler ~error_handler -> Httpaf_lwt_unix.Server.TLS.create_connection_handler_with_default ~certfile:certificate_file ~keyfile:key_file ?config:None - ~request_handler:(wrap_handler app error_handler handler) - ~error_handler:(Error_handler.httpaf app error_handler) + ~request_handler:(wrap_handler true error_handler handler) + ~error_handler:(Error_handler.httpaf error_handler) } -let built_in_middleware = - Dream.pipeline [ - Dream__middleware.Lowercase_headers.lowercase_headers; - Dream__middleware.Content_length.content_length; - Dream__middleware.Catch.catch_errors; - Dream__middleware.Request_id.assign_request_id; - Dream__middleware.Site_prefix.chop_site_prefix; +let check_headers_middleware next_handler request = + let%lwt response = next_handler request in + let invalid_headers_exist = + Message.all_headers response + |> List.exists (fun (name, _) -> String.trim name = "") + in + if invalid_headers_exist then + log.warning (fun log -> + log ~request "A response header is empty or contains only whitespace"); + Lwt.return response + +let built_in_middleware error_handler = + Message.pipeline [ + check_headers_middleware; + Catch.catch (Error_handler.app error_handler); ] - - let serve_with_details caller_function_for_error_messages tls_library ~interface - ~port + ~network ~stop ~error_handler - ~app ~certificate_file ~key_file ~builtins @@ -521,7 +416,7 @@ let serve_with_details let user's_dream_handler = if builtins then - built_in_middleware user's_dream_handler + built_in_middleware error_handler user's_dream_handler else user's_dream_handler in @@ -531,13 +426,12 @@ let serve_with_details tls_library.create_handler ~certificate_file ~key_file - ~app ~handler:user's_dream_handler ~error_handler in (* TODO Should probably move out to the TLS library options. *) - let tls_error_handler = Error_handler.tls app error_handler in + let tls_error_handler = Error_handler.tls error_handler in (* Some parts of the various HTTP servers that are under heavy development ( *cough* Gluten SSL/TLS at the moment) leak exceptions out of the @@ -565,14 +459,20 @@ let serve_with_details (* Look up the low-level address corresponding to the interface. Hopefully, this is a local interface. *) - let%lwt addresses = Lwt_unix.getaddrinfo interface (string_of_int port) [] in - match addresses with - | [] -> - Printf.ksprintf failwith "Dream.%s: no interface with address %s" - caller_function_for_error_messages interface - | address::_ -> - let listen_address = Lwt_unix.(address.ai_addr) in - + let%lwt listen_address = + match network with + | `Unix path -> + Lwt.return (Lwt_unix.ADDR_UNIX path) + | `Inet port -> + let%lwt addresses = + Lwt_unix.getaddrinfo interface (string_of_int port) [] in + match addresses with + | [] -> + Printf.ksprintf failwith "Dream.%s: no interface with address %s" + caller_function_for_error_messages interface + | address::_ -> + Lwt.return Lwt_unix.(address.ai_addr) + in (* Bring up the HTTP server. Wait for the server to actually get started. Then, wait for the ~stop promise. If the ~stop promise ever resolves, stop @@ -590,67 +490,44 @@ let serve_with_details let is_localhost interface = interface = "localhost" || interface = "127.0.0.1" -(* TODO Validate the prefix here. *) let serve_with_maybe_https caller_function_for_error_messages ~interface - ~port + ~network ~stop - ?debug ~error_handler - ?(secret = Dream__cipher.Random.random 32) - ?(old_secrets = []) - ~prefix - ~https + ~tls ?certificate_file ?key_file ?certificate_string ?key_string ~builtins user's_dream_handler = - let prefix = - prefix - |> Dream__pure.Formats.from_path - |> Dream__pure.Formats.drop_trailing_slash - in - let app = Dream.new_app (Error_handler.app error_handler) prefix in - try%lwt - begin match debug with - | Some debug -> Dream.set_debug debug app - | None -> () - end; - (* This check will at least catch secrets like "foo" when used on a public interface. *) - if not (is_localhost interface) then + (* if not (is_localhost interface) then if String.length secret < 32 then begin log.warning (fun log -> log "Using a short key on a public interface"); log.warning (fun log -> log "Consider using Dream.to_base64url (Dream.random 32)"); - end; + end; *) + (* TODO Make sure there is a similar check in cipher.ml now.Hpack *) - (* TODO The interface needs to allow not messing with the secret if an app - is passed. *) - Dream.set_secrets (secret::old_secrets) app; - - match https with + match tls with | `No -> serve_with_details caller_function_for_error_messages no_tls ~interface - ~port + ~network ~stop ~error_handler - ~app ~certificate_file:"" ~key_file:"" ~builtins user's_dream_handler | `OpenSSL | `OCaml_TLS as tls_library -> - app.https <- true; - (* TODO Writing temporary files is extremely questionable for anything except the fake localhost certificate. This needs loud warnings. IIRC the SSL binding already supports in-memory certificates. Does TLS? In @@ -667,7 +544,11 @@ let serve_with_maybe_https log "See arguments ~certificate_file and ~key_file"); end; - `Memory (Dream__localhost.certificate, Dream__localhost.key, `Silent) + `Memory ( + Dream__certificate.localhost_certificate, + Dream__certificate.localhost_certificate_key, + `Silent + ) | Some certificate_file, Some key_file, None, None -> `File (certificate_file, key_file) @@ -703,10 +584,9 @@ let serve_with_maybe_https caller_function_for_error_messages tls_library ~interface - ~port + ~network ~stop ~error_handler - ~app ~certificate_file ~key_file ~builtins @@ -732,10 +612,9 @@ let serve_with_maybe_https caller_function_for_error_messages tls_library ~interface - ~port + ~network ~stop ~error_handler - ~app ~certificate_file ~key_file ~builtins @@ -749,7 +628,7 @@ let serve_with_maybe_https log.error (fun log -> log "Dream.%s: exception %s" caller_function_for_error_messages (Printexc.to_string exn)); - backtrace |> Dream__middleware.Log.iter_backtrace (fun line -> + backtrace |> Log.iter_backtrace (fun line -> log.error (fun log -> log "%s" line)); raise exn @@ -759,18 +638,18 @@ let default_interface = "localhost" let default_port = 8080 let never = fst (Lwt.wait ()) - +let network ~port ~socket_path = + match socket_path with + | None -> `Inet port + | Some path -> `Unix path let serve ?(interface = default_interface) ?(port = default_port) + ?socket_path ?(stop = never) - ?debug ?(error_handler = Error_handler.default) - ?secret - ?old_secrets - ?(prefix = "") - ?(https = false) + ?(tls = false) ?certificate_file ?key_file ?(builtins = true) @@ -779,14 +658,10 @@ let serve serve_with_maybe_https "serve" ~interface - ~port + ~network:(network ~port ~socket_path) ~stop - ?debug ~error_handler - ?secret - ?old_secrets - ~prefix - ~https:(if https then `OpenSSL else `No) + ~tls:(if tls then `OpenSSL else `No) ?certificate_file ?key_file ?certificate_string:None @@ -799,13 +674,10 @@ let serve let run ?(interface = default_interface) ?(port = default_port) + ?socket_path ?(stop = never) - ?debug ?(error_handler = Error_handler.default) - ?secret - ?old_secrets - ?(prefix = "") - ?(https = false) + ?(tls = false) ?certificate_file ?key_file ?(builtins = true) @@ -855,18 +727,20 @@ let run create_handler Sys.sigint; create_handler Sys.sigterm; - let log = Dream__middleware.Log.convenience_log in + let log = Log.convenience_log in if greeting then begin let scheme = - if https then + if tls then "https" else "http" in - begin match interface with - | "localhost" | "127.0.0.1" -> + begin match interface, socket_path with + | _, Some path -> + log "Running on %s" path + | ("localhost" | "127.0.0.1"), None -> log "Running at %s://localhost:%i" scheme port | _ -> log "Running on %s:%i (%s://localhost:%i)" interface port scheme port @@ -879,14 +753,10 @@ let run serve_with_maybe_https "run" ~interface - ~port + ~network:(network ~port ~socket_path) ~stop - ?debug ~error_handler - ?secret - ?old_secrets - ~prefix - ~https:(if https then `OpenSSL else `No) + ~tls:(if tls then `OpenSSL else `No) ?certificate_file ?key_file ?certificate_string:None ?key_string:None ~builtins diff --git a/src/http/shared/dune b/src/http/shared/dune new file mode 100644 index 00000000..31b3c06a --- /dev/null +++ b/src/http/shared/dune @@ -0,0 +1,10 @@ +(library + (public_name dream-httpaf) + (name dream_httpaf) + (libraries + bigstringaf + dream-pure + dream-httpaf.dream-websocketaf + ) + (preprocess (pps lwt_ppx)) + (instrumentation (backend bisect_ppx))) diff --git a/src/http/shared/websocket.ml b/src/http/shared/websocket.ml new file mode 100644 index 00000000..0f51d26e --- /dev/null +++ b/src/http/shared/websocket.ml @@ -0,0 +1,239 @@ +(* This file is part of Dream, released under the MIT license. See LICENSE.md + for details, or visit https://github.com/aantron/dream. + + Copyright 2022 Anton Bachin *) + + + +module Websocketaf = Dream_websocketaf.Websocketaf + +module Stream = Dream_pure.Stream + + + +let websocket_handler stream socket = + (* Queue of received frames. There doesn't appear to be a nice way to achieve + backpressure with the current API of websocket/af, so that will have to be + added later. The user-facing API of Dream does support backpressure. *) + let frames, push_frame = Lwt_stream.create () in + let message_is_binary = ref `Binary in + + (* Frame reader called by websocket/af on each frame received. There is no + good way to truly throttle this, hence this frame reader pushes frame + objects into the above frame queue for the reader to take from later. See + https://github.com/anmonteiro/websocketaf/issues/34. *) + let frame ~opcode ~is_fin ~len:_ payload = + match opcode with + | `Connection_close -> + push_frame (Some (`Close, payload)) + | `Ping -> + push_frame (Some (`Ping, payload)) + | `Pong -> + push_frame (Some (`Pong, payload)) + | `Other _ -> + push_frame (Some (`Other, payload)) + | `Text -> + message_is_binary := `Text; + push_frame (Some (`Data (`Text, is_fin), payload)) + | `Binary -> + message_is_binary := `Binary; + push_frame (Some (`Data (`Binary, is_fin), payload)) + | `Continuation -> + push_frame (Some (`Data (!message_is_binary, is_fin), payload)) + in + + let eof () = + push_frame None in + + (* The reader retrieves the next frame. If it is a data frame, it keeps a + reference to the payload across multiple reader calls, until the payload is + exhausted. *) + let closed = ref false in + let close_code = ref 1005 in + let current_payload = ref None in + + (* Used to convert the separate on_eof payload reading callback into a FIN bit + on the last chunk read. See + https://github.com/anmonteiro/websocketaf/issues/35. *) + let last_chunk = ref None in + (* TODO Review per-chunk allocations, including current_payload contents. *) + + (* For control frames, the payload can be at most 125 bytes long. We assume + that the first chunk will contain the whole payload, and discard any other + chunks that may be reported by websocket/af. *) + let first_chunk_received = ref false in + let first_chunk = ref Bigstringaf.empty in + let first_chunk_offset = ref 0 in + let first_chunk_length = ref 0 in + let rec drain_payload payload continuation = + Websocketaf.Payload.schedule_read + payload + ~on_read:(fun buffer ~off ~len -> + if not !first_chunk_received then begin + first_chunk := buffer; + first_chunk_offset := off; + first_chunk_length := len; + first_chunk_received := true + end + else + (* TODO How to integrate this thing with logging? *) + (* websocket_log.warning (fun log -> + log "Received fragmented control frame"); *) + (); + drain_payload payload continuation) + ~on_eof:(fun () -> + let payload = !first_chunk in + let offset = !first_chunk_offset in + let length = !first_chunk_length in + first_chunk_received := false; + first_chunk := Bigstringaf.empty; + first_chunk_offset := 0; + first_chunk_length := 0; + continuation payload offset length) + in + + (* TODO Can this be canceled by a user's close? i.e. will that eventually + cause a call to eof above? *) + let rec read ~data ~flush ~ping ~pong ~close ~exn = + if !closed then + close !close_code + else + match !current_payload with + | None -> + Lwt.on_success (Lwt_stream.get frames) begin function + | None -> + if not !closed then begin + closed := true; + close_code := 1005 + end; + Websocketaf.Wsd.close socket; + close !close_code + | Some (`Close, payload) -> + drain_payload payload @@ fun buffer offset length -> + let code = + if length < 2 then + 1005 + else + let high_byte = Char.code buffer.{offset} + and low_byte = Char.code buffer.{offset + 1} in + high_byte lsl 8 lor low_byte + in + if not !closed then + close_code := code; + close !close_code + | Some (`Ping, payload) -> + drain_payload payload @@ + ping + | Some (`Pong, payload) -> + drain_payload payload @@ + pong + | Some (`Other, payload) -> + drain_payload payload @@ fun _buffer _offset length -> + ignore length; (* TODO log instead *) + (* websocket_log.warning (fun log -> + log "Unknown frame type with length %i" length); *) + read ~data ~flush ~ping ~pong ~close ~exn + | Some (`Data properties, payload) -> + current_payload := Some (properties, payload); + read ~data ~flush ~ping ~pong ~close ~exn + end + | Some ((binary, fin), payload) -> + Websocketaf.Payload.schedule_read + payload + ~on_read:(fun buffer ~off ~len -> + match !last_chunk with + | None -> + last_chunk := Some (buffer, off, len); + read ~data ~flush ~ping ~pong ~close ~exn + | Some (last_buffer, last_offset, last_length) -> + last_chunk := Some (buffer, off, len); + let binary = binary = `Binary in + data last_buffer last_offset last_length binary false) + ~on_eof:(fun () -> + current_payload := None; + match !last_chunk with + | None -> + read ~data ~flush ~ping ~pong ~close ~exn + | Some (last_buffer, last_offset, last_length) -> + last_chunk := None; + let binary = binary = `Binary in + data last_buffer last_offset last_length binary fin) + in + + let bytes_since_flush = ref 0 in + + let flush ~close ok = + bytes_since_flush := 0; + if !closed then + close !close_code + else + Websocketaf.Wsd.flushed socket ok + in + + let close code = + if not !closed then begin + (* TODO Really need to work out the "close handshake" and how it is + exposed in the Stream API. *) + (* closed := true; *) + Websocketaf.Wsd.close ~code:(`Other code) socket + end + in + + let abort _exn = close 1005 in + + let reader = Stream.reader ~read ~close ~abort in + Stream.forward reader stream; + + let rec outgoing_loop () = + Stream.read + stream + ~data:(fun buffer offset length binary fin -> + let kind = if binary then `Binary else `Text in + if !closed then + close !close_code + else begin + Websocketaf.Wsd.schedule + socket ~is_fin:fin ~kind buffer ~off:offset ~len:length; + bytes_since_flush := !bytes_since_flush + length; + if !bytes_since_flush >= 4096 then + flush ~close outgoing_loop + else + outgoing_loop () + end) + ~flush:(fun () -> flush ~close outgoing_loop) + ~ping:(fun buffer offset length -> + if length > 125 then + raise (Failure "Ping payload cannot exceed 125 bytes"); + if !closed then + close !close_code + else begin + if length = 0 then + Websocketaf.Wsd.send_ping socket + else + Websocketaf.Wsd.send_ping + ~application_data:{Faraday.buffer; off = offset; len = length} + socket; + outgoing_loop () + end) + ~pong:(fun buffer offset length -> + (* TODO Is there any way for the peer to send a ping payload with more + than 125 bytes, forcing a too-large pong and an exception? *) + if length > 125 then + raise (Failure "Pong payload cannot exceed 125 bytes"); + if !closed then + close !close_code + else begin + if length = 0 then + Websocketaf.Wsd.send_pong socket + else + Websocketaf.Wsd.send_pong + ~application_data:{Faraday.buffer; off = offset; len = length} + socket; + outgoing_loop () + end) + ~close + ~exn:abort + in + outgoing_loop (); + + Websocketaf.Websocket_connection.{frame; eof} diff --git a/src/middleware/content_length.ml b/src/middleware/content_length.ml deleted file mode 100644 index c252a8d9..00000000 --- a/src/middleware/content_length.ml +++ /dev/null @@ -1,55 +0,0 @@ -(* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin *) - - - -module Dream = Dream__pure.Inmost - - - -(* TODO This belongs in the core module. *) -(* let add_header response buffered_body = - let length = - match buffered_body with - | `Empty -> 0 - | `String body -> String.length body - in - Lwt.return - (Dream.add_header "Content-Length" (string_of_int length) response) *) - -(* TODO Also mind Connection: close. *) -(* TODO Test in integration with HTTP/2. *) -(* Add a Content-Length header to HTTP 1.x responses that have a fixed body but - don't yet have the header. *) -let content_length next_handler request = - - if fst (Dream.version request) <> 1 then - next_handler request - - else - let%lwt (response : Dream.response) = next_handler request in - - let body_length = - match !(response.body) with - | `Empty -> Some 0 - | `String string -> Some (String.length string) - | `Stream _ | `Exn _ -> None - in - - match body_length with - | Some length -> - if Dream.has_header "Content-Length" response then - Lwt.return response - else - response - |> Dream.add_header "Content-Length" (string_of_int length) - |> Lwt.return - | None -> - if Dream.has_header "Transfer-Encoding" response then - Lwt.return response - else - response - |> Dream.add_header "Transfer-Encoding" "chunked" - |> Lwt.return diff --git a/src/middleware/lowercase_headers.ml b/src/middleware/lowercase_headers.ml deleted file mode 100644 index 9d5afec3..00000000 --- a/src/middleware/lowercase_headers.ml +++ /dev/null @@ -1,26 +0,0 @@ -(* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin *) - - - -module Dream = Dream__pure.Inmost - - - -(* TODO This middleware might need to be applied right in the h2 adapter, - because error handlers might generate headers that cannot be rewritten - inside the normal stack. *) -(* TODO This can be optimized not to convert a header if it is already - lowercase. Another option is to use memoization to reduce GC pressure. *) -let lowercase_headers inner_handler request = - if fst (Dream.version request) = 1 then - inner_handler request - else - let%lwt response = inner_handler request in - response - |> Dream.all_headers - |> List.map (fun (name, value) -> String.lowercase_ascii name, value) - |> fun headers -> Dream.with_all_headers headers response - |> Lwt.return diff --git a/src/middleware/request_id.ml b/src/middleware/request_id.ml deleted file mode 100644 index b4b7f51b..00000000 --- a/src/middleware/request_id.ml +++ /dev/null @@ -1,94 +0,0 @@ -(* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin *) - - - -(* TODO The other major built-in middleware, prefix, is actually just going to - be built-in code. So it's probably best to look into building in request_id, - and getting rid of the concept of built-in middleware. *) - -module Dream = Dream__pure.Inmost - - - -let name = - "dream.request_id" - -let last_id = - Dream.new_global - (fun () -> ref 0) - ~name:"dream.request_id.last_id" - ~show_value:(fun id -> string_of_int !id) - -let id = - Dream.new_local - () - ~name - ~show_value:(fun id -> id) - -(* TODO Expose this in a more organized fashion? It is used in several - places. *) -let lwt_key = - Lwt.new_key () - - - -(* TODO Restore the prefix, make the id random, or something else. *) -(* TODO Now that the request id is built in, there is no good way for the user - to pass in a prefix... except perhaps through the app. However, this is - probably worth it, because adding request_id to every single middleware - stack is extremely annoying, given that you always want it and it's so cheap - that there is no reason not to use it. It's probably very rare that someone - needs a prefix. *) -let assign_request_id next_handler request = - - (* Get the last id for this request's app. *) - let last_id_ref : int ref = - Dream.global last_id request in - - incr last_id_ref; - - let new_id = - string_of_int !last_id_ref in - - (* Store the new id in the request and in the Lwt promise values map for - best-effort delivery to all code that might want the id. Continue into the - rest of the app. *) - let request = - Dream.with_local id new_id request in - - Lwt.with_value - lwt_key - (Some new_id) - (fun () -> - next_handler request) - - - -let get_option ?request () = - - (* First, try to get the id from the request, if one was provided. *) - let request_id = - match request with - | None -> None - | Some request -> - Dream.local id request - in - - (* If no id was found from the maybe-request, look in the promise-chain-local - storage. *) - match request_id with - | Some _ -> request_id - | None -> - Lwt.get lwt_key - - - -(* TODO LATER Maybe it's better to build the request id straight into the - runtime? There's no real cost to it... is there? And when wouldn't the user - want a request id? *) -(* TODO LATER List arguments for built-in middlewares: 0 or so cost, highly - beneficial, in some cases (prefix) actually necessary for correct operation - of a website. *) diff --git a/src/middleware/router.mli b/src/middleware/router.mli deleted file mode 100644 index fd3cccd9..00000000 --- a/src/middleware/router.mli +++ /dev/null @@ -1,41 +0,0 @@ -(* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin *) - - - -module Dream = Dream__pure.Inmost - -type route - -(* Leaf routes. *) -val get : string -> Dream.handler -> route -val post : string -> Dream.handler -> route -val put : string -> Dream.handler -> route -val delete : string -> Dream.handler -> route -val head : string -> Dream.handler -> route -val connect : string -> Dream.handler -> route -val options : string -> Dream.handler -> route -val trace : string -> Dream.handler -> route -val patch : string -> Dream.handler -> route -val any : string -> Dream.handler -> route -val no_route : route - -(* Route groups. *) -val scope : string -> Dream.middleware list -> route list -> route - -(* The middleware and the path parameter retriever. With respect to path - parameters, the middleware is the setter, and the retriever is, of course, - the getter. *) -val router : route list -> Dream.middleware -val param : string -> Dream.request -> string - -(**/**) - -type token = - | Literal of string - | Param of string - | Wildcard of string - -val parse : string -> token list diff --git a/src/middleware/tag.eml.ml b/src/middleware/tag.eml.ml deleted file mode 100644 index 6e012436..00000000 --- a/src/middleware/tag.eml.ml +++ /dev/null @@ -1,44 +0,0 @@ -(* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin *) - - - -module Dream = -struct - include Dream__pure.Formats - include Dream__pure.Method -end - -(* TODO Include the path prefix. *) -let form_tag - ~now ?method_ ?target ?enctype ?csrf_token ~action request = - - let method_ = - match method_ with - | None -> Dream.method_to_string `POST - | Some method_ -> Dream.method_to_string method_ - in - let target = - match target with - | Some target -> " target=\"" ^ Dream.html_escape target ^ "\"" - | None -> "" - in - let enctype = - match enctype with - | Some `Multipart_form_data -> " enctype=\"multipart/form-data\"" - | None -> "" - in - let csrf_token = - match csrf_token with - | None -> true - | Some csrf_token -> csrf_token - in - <%s! enctype %>> -% if csrf_token then begin -% let token = Csrf.csrf_token ~now request in - -% end; diff --git a/src/mirage/adapt.ml b/src/mirage/adapt.ml index 2fc999ba..36ef4fee 100644 --- a/src/mirage/adapt.ml +++ b/src/mirage/adapt.ml @@ -6,61 +6,79 @@ XXX(dinosaure): same as [src/http/adapt.ml] without [address_to_string] - which depends on [Unix]. *) -module Dream = Dream__pure.Inmost +module Httpaf = Dream_httpaf_.Httpaf +module H2 = Dream_h2.H2 + +module Dream = Dream_pure +module Stream = Dream_pure.Stream (* TODO Write a test simulating client exit during SSE; this was killing the server at some point. *) (* TODO LATER Will also need to monitor buffer accumulation and use flush. *) (* TODO Rewrite using Dream.next. *) let forward_body_general - (response : Dream.response) - (write_string : ?off:int -> ?len:int -> string -> unit) - (write_buffer : ?off:int -> ?len:int -> Dream.buffer -> unit) + (response : Dream.Message.response) + (_write_string : ?off:int -> ?len:int -> string -> unit) + (write_buffer : ?off:int -> ?len:int -> Stream.buffer -> unit) http_flush close = + let bytes_since_flush = ref 0 in + let abort _exn = Printf.printf "ABORT\n%!"; close 1000 in let rec send () = - response - |> Dream.next - ~buffer - ~string - ~flush - ~close - ~exn:ignore - - and buffer chunk off len = - write_buffer ~off ~len chunk; - send () + Dream.Message.client_stream response + |> fun stream -> + Stream.read + stream + ~data + ~close + ~flush + ~ping + ~pong + ~exn:abort - and string chunk off len = - write_string ~off ~len chunk; - send () + and data chunk off len _binary _fin = + write_buffer ~off ~len chunk; + bytes_since_flush := !bytes_since_flush + len; + if !bytes_since_flush >= 4096 then begin + bytes_since_flush := 0; + http_flush send + end + else + send () and flush () = + bytes_since_flush := 0; http_flush send + and ping _buffer _offset _length = + send () + + and pong _buffer _offset _length = + send () + in send () let forward_body - (response : Dream.response) - (body : [ `write ] Httpaf.Body.t) = + (response : Dream.Message.response) + (body : Httpaf.Body.Writer.t) = forward_body_general response - (Httpaf.Body.write_string body) - (Httpaf.Body.write_bigstring body) - (Httpaf.Body.flush body) - (fun () -> Httpaf.Body.close_writer body) + (Httpaf.Body.Writer.write_string body) + (Httpaf.Body.Writer.write_bigstring body) + (Httpaf.Body.Writer.flush body) + (fun _code -> Httpaf.Body.Writer.close body) let forward_body_h2 - (response : Dream.response) - (body : [ `write ] H2.Body.t) = + (response : Dream.Message.response) + (body : H2.Body.Writer.t) = forward_body_general response - (H2.Body.write_string body) - (H2.Body.write_bigstring body) - (H2.Body.flush body) - (fun () -> H2.Body.close_writer body) + (H2.Body.Writer.write_string body) + (H2.Body.Writer.write_bigstring body) + (H2.Body.Writer.flush body) + (fun _code -> H2.Body.Writer.close body) diff --git a/src/mirage/dune b/src/mirage/dune index bd97dc89..242a8676 100644 --- a/src/mirage/dune +++ b/src/mirage/dune @@ -6,15 +6,16 @@ bigstringaf digestif dream.cipher - dream.localhost - dream.middleware - dream.pure - dream.h2 - dream.httpaf + dream.server + dream.certificate + dream-pure + dream-httpaf.dream-h2 lwt - dream-mirage.paf - dream-mirage.paf.alpn - dream-mirage.paf.mirage + rresult + tcpip + dream-mirage.dream-paf + dream-mirage.dream-paf.alpn + dream-mirage.dream-paf.mirage ) (preprocess (pps lwt_ppx)) (instrumentation (backend bisect_ppx))) diff --git a/src/mirage/error_handler.ml b/src/mirage/error_handler.ml index de42ac3e..958a01a8 100644 --- a/src/mirage/error_handler.ml +++ b/src/mirage/error_handler.ml @@ -1,7 +1,17 @@ -module Dream = Dream__pure.Inmost +module Httpaf = Dream_httpaf_.Httpaf + +module Catch = Dream__server.Catch +module Error_template = Dream__server.Error_template +module Method = Dream_pure.Method +module Helpers = Dream__server.Helpers +module Log = Dream__server.Log +module Message = Dream_pure.Message +module Status = Dream_pure.Status +module Stream = Dream_pure.Stream + let log = - Dream__middleware.Log.sub_log "dream.mirage" + Dream__server.Log.sub_log "dream.mirage" let select_log = function | `Error -> log.error @@ -9,77 +19,72 @@ let select_log = function | `Info -> log.info | `Debug -> log.debug -let dump (error : Dream.error) = - let buffer = Buffer.create 4096 in - let p format = Printf.bprintf buffer format in + let dump (error : Catch.error) = + let buffer = Buffer.create 4096 in + let p format = Printf.bprintf buffer format in - begin match error.condition with - | `Response response -> - let status = Dream.status response in - p "%i %s\n" (Dream.status_to_int status) (Dream.status_to_string status) + begin match error.condition with + | `Response response -> + let status = Message.status response in + p "%i %s\n" (Status.status_to_int status) (Status.status_to_string status) - | `String "" -> - p "(Library error without description payload)\n" + | `String "" -> + p "(Library error without description payload)\n" - | `String string -> - p "%s\n" string + | `String string -> + p "%s\n" string - | `Exn exn -> - let backtrace = Printexc.get_backtrace () in - p "%s\n" (Printexc.to_string exn); - backtrace |> Dream__middleware.Log.iter_backtrace (p "%s\n") - end; - - p "\n"; - - let layer = - match error.layer with - | `TLS -> "TLS library" - | `HTTP -> "HTTP library" - | `HTTP2 -> "HTTP2 library" - | `WebSocket -> "WebSocket library" - | `App -> "Application" - in - - let blame = - match error.caused_by with - | `Server -> "Server" - | `Client -> "Client" - in - - let severity = - match error.severity with - | `Error -> "Error" - | `Warning -> "Warning" - | `Info -> "Info" - | `Debug -> "Debug" - in - - p "From: %s\n" layer; - p "Blame: %s\n" blame; - p "Severity: %s" severity; - - begin match error.client with - | None -> () - | Some client -> p "\n\nClient: %s" client - end; - - begin match error.request with - | None -> () - | Some request -> - let last = Dream.last request in - - let major, minor = Dream.version last in - p "\n\n%s %s HTTP/%i.%i" - (Dream.method_to_string (Dream.method_ last)) - (Dream.target last) - major minor; - - Dream.all_headers last - |> List.iter (fun (name, value) -> p "\n%s: %s" name value); - - let show_variables kind = - kind (fun name value first -> + | `Exn exn -> + let backtrace = Printexc.get_backtrace () in + p "%s\n" (Printexc.to_string exn); + backtrace |> Log.iter_backtrace (p "%s\n") + end; + + p "\n"; + + let layer = + match error.layer with + | `TLS -> "TLS library" + | `HTTP -> "HTTP library" + | `HTTP2 -> "HTTP2 library" + | `WebSocket -> "WebSocket library" + | `App -> "Application" + in + + let blame = + match error.caused_by with + | `Server -> "Server" + | `Client -> "Client" + in + + let severity = + match error.severity with + | `Error -> "Error" + | `Warning -> "Warning" + | `Info -> "Info" + | `Debug -> "Debug" + in + + p "From: %s\n" layer; + p "Blame: %s\n" blame; + p "Severity: %s" severity; + + begin match error.client with + | None -> () + | Some client -> p "\n\nClient: %s" client + end; + + begin match error.request with + | None -> () + | Some request -> + p "\n\n%s %s" + (Method.method_to_string (Message.method_ request)) + (Message.target request); + + Message.all_headers request + |> List.iter (fun (name, value) -> p "\n%s: %s" name value); + + Message.fold_fields (fun name value first -> if first then p "\n"; p "\n%s: %s" name value; @@ -87,102 +92,86 @@ let dump (error : Dream.error) = true request |> ignore - in - show_variables Dream.fold_locals; - show_variables Dream.fold_globals - end; + end; - Buffer.contents buffer + Buffer.contents buffer -let customize template (error : Dream.error) = + let customize template (error : Catch.error) = - (* First, log the error. *) + (* First, log the error. *) - begin match error.condition with - | `Response _ -> () - | `String _ | `Exn _ as condition -> + begin match error.condition with + | `Response _ -> () + | `String _ | `Exn _ as condition -> - let client = - match error.client with - | None -> "" - | Some client -> " (" ^ client ^ ")" - in + let client = + match error.client with + | None -> "" + | Some client -> " (" ^ client ^ ")" + in - let layer = - match error.layer with - | `TLS -> ["TLS" ^ client] - | `HTTP -> ["HTTP" ^ client] - | `HTTP2 -> ["HTTP/2" ^ client] - | `WebSocket -> ["WebSocket" ^ client] - | `App -> [] - in + let layer = + match error.layer with + | `TLS -> ["TLS" ^ client] + | `HTTP -> ["HTTP" ^ client] + | `HTTP2 -> ["HTTP/2" ^ client] + | `WebSocket -> ["WebSocket" ^ client] + | `App -> [] + in - let description, backtrace = - match condition with - | `String string -> string, "" - | `Exn exn -> - let backtrace = Printexc.get_backtrace () in - Printexc.to_string exn, backtrace - in + let description, backtrace = + match condition with + | `String string -> string, "" + | `Exn exn -> + let backtrace = Printexc.get_backtrace () in + Printexc.to_string exn, backtrace + in - let message = String.concat ": " (layer @ [description]) in + let message = String.concat ": " (layer @ [description]) in - select_log error.severity (fun log -> - log ?request:error.request "%s" message); - backtrace |> Dream__middleware.Log.iter_backtrace (fun line -> select_log error.severity (fun log -> - log ?request:error.request "%s" line)) - end; - - (* If Dream will not send a response for this error, we are done after - logging. Otherwise, if debugging is enabled, gather a bunch of information. - Then, call the template, and return the response. *) - - if not error.will_send_response then - Lwt.return_none - - else - let debug_dump = - match error.debug with - | false -> None - | true -> Some (dump error) - in - - let response = - match error.condition with - | `Response response -> response - | _ -> - let status = - match error.caused_by with - | `Server -> `Internal_Server_Error - | `Client -> `Bad_Request - in - Dream.response ~status "" - in - - (* No need to catch errors when calling the template, because every call - site of the error handler already has error handlers for catching double - faults. *) - response - |> template debug_dump - |> Lwt.map (fun response -> Some response) - -let default_response = function - | `Server -> Dream.response ~status:`Internal_Server_Error "" - | `Client -> Dream.response ~status:`Bad_Request "" - -let default_template debug_dump response = - match debug_dump with - | None -> Lwt.return response - | Some debug_dump -> - let status = Dream.status response in - let code = Dream.status_to_int status - and reason = Dream.status_to_string status in - response - |> Dream.with_header "Content-Type" Dream__pure.Formats.text_html - |> Dream.with_body - (Dream__middleware.Error_template.render ~debug_dump ~code ~reason) - |> Lwt.return + log ?request:error.request "%s" message); + backtrace |> Log.iter_backtrace (fun line -> + select_log error.severity (fun log -> + log ?request:error.request "%s" line)) + end; + + (* If Dream will not send a response for this error, we are done after + logging. Otherwise, if debugging is enabled, gather a bunch of information. + Then, call the template, and return the response. *) + + if not error.will_send_response then + Lwt.return_none + + else + let debug_dump = dump error in + + let response = + match error.condition with + | `Response response -> response + | _ -> + let status = + match error.caused_by with + | `Server -> `Internal_Server_Error + | `Client -> `Bad_Request + in + Message.response ~status Stream.empty Stream.null + in + + (* No need to catch errors when calling the template, because every call + site of the error handler already has error handlers for catching double + faults. *) + let%lwt response = template error debug_dump response in + Lwt.return (Some response) + + let default_response = function + | `Server -> + Message.response ~status:`Internal_Server_Error Stream.empty Stream.null + | `Client -> + Message.response ~status:`Bad_Request Stream.empty Stream.null + +let default_template _error _debug_dump response = + Lwt.return response let default = customize default_template @@ -190,14 +179,18 @@ let default = let double_faults f default = Lwt.catch f begin fun exn -> let backtrace = Printexc.get_backtrace () in - log.error (fun log -> log "Error handler raised: %s" (Printexc.to_string exn)); + + log.error (fun log -> + log "Error handler raised: %s" (Printexc.to_string exn)); + backtrace - |> Dream__middleware.Log.iter_backtrace (fun line -> + |> Log.iter_backtrace (fun line -> log.error (fun log -> log "%s" line)); + default () end -let httpaf app user's_error_handler = fun client_address ?request:_ error start_response -> +let httpaf user's_error_handler = fun client_address ?request:_ error start_response -> let condition, severity, caused_by = match error with | `Exn exn -> `Exn exn, @@ -213,14 +206,13 @@ let httpaf app user's_error_handler = fun client_address ?request:_ error start_ `Error, `Server in let error = { - Dream.condition; + Catch.condition; layer = `HTTP; caused_by; request = None; response = None; client= Some client_address; severity; - debug = Dream.debug app; will_send_response = true; } in @@ -230,7 +222,7 @@ let httpaf app user's_error_handler = fun client_address ?request:_ error start_ let response = match response with | Some response -> response | None -> default_response caused_by in - let headers = Httpaf.Headers.of_list (Dream.all_headers response) in + let headers = Httpaf.Headers.of_list (Message.all_headers response) in let body = start_response headers in Adapt.forward_body response body; Lwt.return_unit @@ -241,12 +233,16 @@ let httpaf app user's_error_handler = fun client_address ?request:_ error start_ let respond_with_option f = double_faults (fun () -> - f () - |> Lwt.map (function - | Some response -> response - | None -> Dream.response ~status:`Internal_Server_Error "")) + f () + |> Lwt.map (function + | Some response -> response + | None -> + Message.response + ~status:`Internal_Server_Error Stream.empty Stream.null)) (fun () -> - Dream.empty `Internal_Server_Error) + Message.response ~status:`Internal_Server_Error Stream.empty Stream.null + |> Lwt.return) + let app user's_error_handler = fun error -> respond_with_option (fun () -> user's_error_handler error) diff --git a/src/mirage/mirage.ml b/src/mirage/mirage.ml index 03fb47ed..86af0c20 100644 --- a/src/mirage/mirage.ml +++ b/src/mirage/mirage.ml @@ -1,50 +1,55 @@ -[@@@warning "-32"] +module Httpaf = Dream_httpaf_.Httpaf +module Paf_mirage = Dream_paf_mirage.Paf_mirage +module Alpn = Dream_alpn.Alpn + +module Catch = Dream__server.Catch +module Error_template = Dream__server.Error_template +module Method = Dream_pure.Method +module Helpers = Dream__server.Helpers +module Log = Dream__server.Log +module Message = Dream_pure.Message +module Status = Dream_pure.Status +module Stream = Dream_pure.Stream +module Random = Dream__cipher.Random +module Router = Dream__server.Router +module Query = Dream__server.Query +module Cookie = Dream__server.Cookie +module Tag = Dream__server.Tag -module Dream = Dream__pure.Inmost open Rresult open Lwt.Infix -let to_dream_method meth = Httpaf.Method.to_string meth |> Dream.string_to_method -let to_httpaf_status status = Dream.status_to_int status |> Httpaf.Status.of_code -let to_h2_status status = Dream.status_to_int status |> H2.Status.of_code -let sha1 str = Digestif.SHA1.(to_raw_string (digest_string str)) -let const x = fun _ -> x +let to_dream_method meth = Httpaf.Method.to_string meth |> Method.string_to_method +let to_httpaf_status status = Status.status_to_int status |> Httpaf.Status.of_code let ( >>? ) = Lwt_result.bind -let rec transmit_body request stream () = - Lwt_stream.get stream >>= function - | Some (buffer, off, len) -> - Dream__pure.Body.write_bigstring buffer off len request.Dream.body >>= - transmit_body request stream - | None -> Dream.close_stream request - -let wrap_handler_httpaf app (_user's_error_handler : Dream.error_handler) (user's_dream_handler : Dream.handler) = - let httpaf_request_handler = fun client reqd -> +let wrap_handler_httpaf _user's_error_handler user's_dream_handler = + let httpaf_request_handler = fun _ reqd -> let httpaf_request = Httpaf.Reqd.request reqd in let method_ = to_dream_method httpaf_request.meth in let target = httpaf_request.target in - let version = (httpaf_request.version.major, httpaf_request.version.minor) in + let _version = (httpaf_request.version.major, httpaf_request.version.minor) in let headers = Httpaf.Headers.to_list httpaf_request.headers in let body = Httpaf.Reqd.request_body reqd in - let request = Dream.request_from_http ~app ~client ~method_ ~target ~version ~headers in - Lwt.async begin fun () -> - let%lwt () = Dream.flush request in - let on_eof () = Dream.close_stream request |> ignore in - - let rec loop () = - Httpaf.Body.schedule_read - body - ~on_eof - ~on_read:(fun buffer ~off ~len -> - Lwt.on_success - (Dream__pure.Body.write_bigstring buffer off len request.body) - loop) - in - loop (); - Lwt.return_unit - end; + let read ~data ~flush:_ ~ping:_ ~pong:_ ~close ~exn:_ = + Httpaf.Body.Reader.schedule_read + body + ~on_eof:(fun () -> close 1000) + ~on_read:(fun buffer ~off ~len -> data buffer off len true false) + in + let close _close = + Httpaf.Body.Reader.close body in + let abort _close = + Httpaf.Body.Reader.close body in + let body = + Stream.reader ~read ~close ~abort in + + let client_stream = Stream.(stream no_reader no_writer) in + let server_stream = Stream.(stream body no_writer) in + + let request = Message.request ~method_ ~target ~headers client_stream server_stream in (* Call the user's handler. If it raises an exception or returns a promise that rejects with an exception, pass the exception up to Httpaf. This @@ -71,8 +76,10 @@ let wrap_handler_httpaf app (_user's_error_handler : Dream.error_handler) (user' 2. Upon failure to establish a WebSocket, the function is called to transmit the resulting error response. *) let forward_response response = + Message.set_content_length_headers response; + let headers = - Httpaf.Headers.of_list (Dream.all_headers response) in + Httpaf.Headers.of_list (Message.all_headers response) in (* let version = match Dream.version_override response with @@ -80,7 +87,7 @@ let wrap_handler_httpaf app (_user's_error_handler : Dream.error_handler) (user' | Some (major, minor) -> Some Httpaf.Version.{major; minor} in *) let status = - to_httpaf_status (Dream.status response) in + to_httpaf_status (Message.status response) in (* let reason = Dream.reason_override response in *) @@ -107,87 +114,226 @@ let wrap_handler_httpaf app (_user's_error_handler : Dream.error_handler) (user' httpaf_request_handler let request_handler - : Dream.app -> Dream.error_handler -> Dream.handler -> string -> [ `write ] Alpn.reqd_handler -> unit - = fun app - (user's_error_handler : Dream.error_handler) - (user's_dream_handler : Dream.handler) -> (); - fun client_address -> function - | Alpn.Reqd_handler (Alpn.HTTP_1_1, reqd) -> wrap_handler_httpaf app user's_error_handler user's_dream_handler client_address reqd + : type reqd headers request response ro wo. + Catch.error_handler -> Message.handler -> + _ -> _ -> reqd -> + (reqd, headers, request, response, ro, wo) Alpn.protocol -> unit + = fun (user's_error_handler : Catch.error_handler) + (user's_dream_handler : Message.handler) -> (); + fun _ _ reqd -> function + | Alpn.HTTP_1_1 _ -> + wrap_handler_httpaf user's_error_handler user's_dream_handler () reqd | _ -> assert false let error_handler - : Dream.app -> Dream.error_handler -> string -> ?request:Alpn.request -> Alpn.server_error -> - (Alpn.headers -> [ `write ] Alpn.body) -> unit - = fun app - (user's_error_handler : Dream.error_handler) -> (); - fun client ?request error start_response -> - match request with - | Some (Alpn.Request (Alpn.HTTP_1_1, request)) -> - let start_response hdrs : [ `write ] Httpaf.Body.t = match start_response Alpn.(Headers (HTTP_1_1, hdrs)) with - | Alpn.(Body (HTTP_1_1, body)) -> body - | Alpn.(Body (HTTP_2_0, _)) -> Fmt.failwith "Impossible to respond with an h2 respond to an HTTP/1.1 client" in - Error_handler.httpaf app user's_error_handler client ?request:(Some request) error start_response - | _ -> assert false (* TODO *) - -module Make (Pclock : Mirage_clock.PCLOCK) (Time : Mirage_time.S) (Stack : Mirage_stack.V4V6) = struct - module Method_and_status = - struct - include Dream__pure.Method - include Dream__pure.Status - end + : type reqd headers request response ro wo. + Catch.error_handler -> + _ -> (reqd, headers, request, response, ro, wo) Alpn.protocol -> + ?request:request -> _ -> (headers -> wo) -> unit + = fun + (user's_error_handler : Catch.error_handler) -> (); + fun client protocol ?request error respond -> + match protocol with + | Alpn.HTTP_1_1 _ -> + let start_response hdrs : Httpaf.Body.Writer.t = + respond hdrs + in + Error_handler.httpaf user's_error_handler client ?request:(Some request) error start_response + | _ -> assert false (* TODO *) + +let handler user_err user_resp = + { + Alpn.error=(fun edn protocol ?request error respond -> + error_handler user_err edn protocol ?request error respond); + request=(fun flow edn reqd protocol -> + request_handler user_err user_resp flow edn reqd protocol) + } - include Dream__pure.Inmost - (* Eliminate optional arguments from the public interface for now. *) - let next ~buffer ~close ~exn request = - next ~buffer ~close ~exn request - - include Dream__middleware.Log - include Dream__middleware.Log.Make (Pclock) - include Dream__middleware.Echo - +module Make (Pclock : Mirage_clock.PCLOCK) (Time : Mirage_time.S) (Stack : Tcpip.Stack.V4V6) = struct + include Dream_pure + include Method + include Status + + include Log + include Log.Make (Pclock) + include Dream__server.Echo + let default_log = - Dream__middleware.Log.sub_log (Logs.Src.name Logs.default) - + Log.sub_log (Logs.Src.name Logs.default) + let error = default_log.error let warning = default_log.warning let info = default_log.info let debug = default_log.debug - - include Dream__middleware.Router - - include Dream__middleware.Session - include Dream__middleware.Session.Make (Pclock) - - (* include Dream__middleware.Flash_message *) - - include Dream__middleware.Origin_referrer_check - include Dream__middleware.Form - include Dream__middleware.Upload - include Dream__middleware.Csrf - - let content_length = - Dream__middleware.Content_length.content_length - - include Dream__middleware.Lowercase_headers - include Dream__middleware.Catch - include Dream__middleware.Request_id - include Dream__middleware.Site_prefix + + module Session = struct + include Dream__server.Session + include Dream__server.Session.Make (Pclock) + end + module Flash = Dream__server.Flash + + + include Dream__server.Origin_referrer_check + include Dream__server.Form + include Dream__server.Upload + include Dream__server.Csrf + + + include Dream__server.Catch + include Dream__server.Site_prefix let error_template = Error_handler.customize - +(* let random = Dream__cipher.Random.random - (* XXX(dinosaure): [Mirage_crypto_rng_mirage] should already be initialized by - * the [main.ml] generated by [mirage]. *) + *) + include Formats + + (* Types *) + + type request = Message.request + type response = Message.response + type handler = Message.handler + type middleware = Message.middleware + type route = Router.route + + type 'a message = 'a Message.message + type client = Message.client + type server = Message.server + type 'a promise = 'a Message.promise + + + (* Requests *) + + + let body_stream = Message.server_stream + let client = Helpers.client + let method_ = Message.method_ + let target = Message.target + let prefix = Router.prefix + let path = Router.path + let set_client = Helpers.set_client + let set_method_ = Message.set_method_ + let query = Query.query + let queries = Query.queries + let all_queries = Query.all_queries + + (* Responses *) + + let response = Helpers.response_with_body + let respond = Helpers.respond + let html = Helpers.html + let json = Helpers.json + let redirect = Helpers.redirect + let empty = Helpers.empty + let stream = Helpers.stream + let status = Message.status + let read = Message.read + let write = Message.write + let flush = Message.flush + + + (* Headers *) + + let header = Message.header + let headers = Message.headers + let all_headers = Message.all_headers + let has_header = Message.has_header + let add_header = Message.add_header + let drop_header = Message.drop_header + let set_header = Message.set_header + + (* Cookies *) + + let set_cookie = Cookie.set_cookie + let drop_cookie = Cookie.drop_cookie + let cookie = Cookie.cookie + let all_cookies = Cookie.all_cookies + + + (* Bodies *) + + let body = Message.body + let set_body = Message.set_body + let close = Message.close + type buffer = Stream.buffer + type stream = Stream.stream + let client_stream = Message.client_stream + let server_stream = Message.server_stream + let set_client_stream = Message.set_client_stream + let set_server_stream = Message.set_server_stream + let read_stream = Stream.read + let write_stream = Stream.write + let flush_stream = Stream.flush + let ping_stream = Stream.ping + let pong_stream = Stream.pong + let close_stream = Stream.close + let abort_stream = Stream.abort + + + (* websockets *) + + type websocket = stream * stream + let websocket = Helpers.websocket + type text_or_binary = [ `Text | `Binary ] + type end_of_message = [ `End_of_message | `Continues ] + let send = Helpers.send + let receive = Helpers.receive + let receive_fragment = Helpers.receive_fragment + let close_websocket = Message.close_websocket + + + (* Middleware *) + + let no_middleware = Message.no_middleware + let pipeline = Message.pipeline + + + (* Routing *) + + let router (r: route list): handler = Router.router r + let get = Router.get + let post = Router.post + let put = Router.put + let delete = Router.delete + let head = Router.head + let connect = Router.connect + let options = Router.options + let trace = Router.trace + let patch = Router.patch + let any = Router.any + let not_found = Helpers.not_found + let param = Router.param + let scope = Router.scope + let no_route = Router.no_route + + (* Sessions *) + + let session = Session.session + let put_session = Session.put_session + let all_session_values = Session.all_session_values + let invalidate_session = Session.invalidate_session + let memory_sessions = Session.memory_sessions + let cookie_sessions = Session.cookie_sessions + let session_id = Session.session_id + let session_label = Session.session_label + let session_expires_at = Session.session_expires_at + + + + (* Flash messages *) + + let flash_messages = Flash.flash_messages + let flash = Flash.flash + let put_flash = Flash.put_flash + - include Dream__pure.Formats let log = - Dream__middleware.Log.convenience_log + Log.convenience_log - include Dream__middleware.Tag let now () = Ptime.to_float_s (Ptime.v (Pclock.now_d_ps ())) @@ -195,55 +341,92 @@ module Make (Pclock : Mirage_clock.PCLOCK) (Time : Mirage_time.S) (Stack : Mirag let multipart = multipart ~now let csrf_token = csrf_token ~now let verify_csrf_token = verify_csrf_token ~now - let form_tag = form_tag ~now - - include Dream__pure.Formats - - include Paf_mirage.Make (Time) (Stack) + let csrf_tag = Tag.csrf_tag ~now + + (* Errors *) + + type error = Catch.error = { + condition : [ + | `Response of Message.response + | `String of string + | `Exn of exn + ]; + layer : [ + | `App + | `HTTP + | `HTTP2 + | `TLS + | `WebSocket + ]; + caused_by : [ + | `Server + | `Client + ]; + request : Message.request option; + response : Message.response option; + client : string option; + severity : Log.log_level; + will_send_response : bool; + } + type error_handler = Catch.error_handler +(* let error_template = Error_handler.customize *) + let catch = Catch.catch + + (* Cryptography *) + + let set_secret = Cipher.set_secret + let random = Random.random + let encrypt = Cipher.encrypt + let decrypt = Cipher.decrypt + + (* Custom fields *) + + type 'a field = 'a Message.field + let new_field = Message.new_field + let field = Message.field + let set_field = Message.set_field + + open Paf_mirage.Make (Stack.TCP) let alpn = let module R = (val Mimic.repr tls_protocol) in let alpn (_, flow) = match TLS.epoch flow with | Ok { Tls.Core.alpn_protocol; _ } -> alpn_protocol | Error _ -> None in - let peer ((ipaddr, port), _) = Fmt.strf "%a:%d" Ipaddr.pp ipaddr port in + let peer ((ipaddr, port), _) = Fmt.str "%a:%d" Ipaddr.pp ipaddr port in let injection (_, flow) = R.T flow in { Alpn.alpn; peer; injection; } - let built_in_middleware = - Dream__pure.Inmost.pipeline [ - Dream__middleware.Lowercase_headers.lowercase_headers; - Dream__middleware.Content_length.content_length; - Dream__middleware.Catch.catch_errors; - Dream__middleware.Request_id.assign_request_id; - Dream__middleware.Site_prefix.chop_site_prefix; + let built_in_middleware prefix error_handler= + Message.pipeline [ + Dream__server.Catch.catch (Error_handler.app error_handler); + Dream__server.Site_prefix.with_site_prefix prefix; ] let localhost_certificate = let crts = Rresult.R.failwith_error_msg - (X509.Certificate.decode_pem_multiple (Cstruct.of_string Dream__localhost.certificate)) in + (X509.Certificate.decode_pem_multiple (Cstruct.of_string Dream__certificate.localhost_certificate)) in let key = Rresult.R.failwith_error_msg - (X509.Private_key.decode_pem (Cstruct.of_string Dream__localhost.key)) in + (X509.Private_key.decode_pem (Cstruct.of_string Dream__certificate.localhost_certificate_key)) in `Single (crts, key) let https ?stop ~port ?(prefix= "") stack ?(cfg= Tls.Config.server ~certificates:localhost_certificate ()) - ?error_handler:(user's_error_handler= Error_handler.default) user's_dream_handler = - let prefix = prefix - |> Dream__pure.Formats.from_path - |> Dream__pure.Formats.drop_trailing_slash in - initialize ~setup_outputs:ignore ; - let app = Dream__pure.Inmost.new_app (Error_handler.app user's_error_handler) prefix in - let accept t = accept t >>? fun flow -> - let edn = Stack.TCP.dst flow in - TLS.server_of_flow cfg flow >>= function + ?error_handler:(user's_error_handler : error_handler = Error_handler.default) (user's_dream_handler : Message.handler) = + initialize ~setup_outputs:ignore ; + let connect flow = + let edn = TCP.dst flow in + TLS.server_of_flow cfg flow + >>= function | Ok flow -> Lwt.return_ok (edn, flow) - | Error err -> Lwt.return (R.error_msgf "%a" TLS.pp_write_error err) in + | Error err -> + TCP.close flow >>= fun () -> + Lwt.return (R.error_msgf "%a" TLS.pp_write_error err) + in let user's_dream_handler = - built_in_middleware user's_dream_handler in - let error_handler = error_handler app user's_error_handler in - let request_handler = request_handler app user's_error_handler user's_dream_handler in - let service = Alpn.service alpn ~error_handler ~request_handler accept close in + built_in_middleware prefix user's_error_handler user's_dream_handler in + let handler = handler user's_error_handler user's_dream_handler in + let service = Alpn.service alpn handler connect accept close in init ~port stack >>= fun t -> let `Initialized th = serve ?stop service t in th @@ -253,26 +436,89 @@ module Make (Pclock : Mirage_clock.PCLOCK) (Time : Mirage_time.S) (Stack : Mirag | `HTTP_1_1 -> "http/1.1" in let module R = (val Mimic.repr tcp_protocol) in let alpn _ = Some protocol in - let peer ((ipaddr, port), _) = Fmt.strf "%a:%d" Ipaddr.pp ipaddr port in + let peer ((ipaddr, port), _) = Fmt.str "%a:%d" Ipaddr.pp ipaddr port in let injection (_, flow) = R.T flow in { Alpn.alpn; peer; injection; } - let http ?stop ~port ?(prefix= "") ?(protocol= `HTTP_1_1) stack ?error_handler:(user's_error_handler= Error_handler.default) user's_dream_handler = - let prefix = prefix - |> Dream__pure.Formats.from_path - |> Dream__pure.Formats.drop_trailing_slash in + let http ?stop ~port ?(prefix= "") ?(protocol= `HTTP_1_1) stack + ?error_handler:(user's_error_handler= Error_handler.default) + user's_dream_handler = initialize ~setup_outputs:ignore ; - let app = Dream__pure.Inmost.new_app (Error_handler.app user's_error_handler) prefix in let accept t = accept t >>? fun flow -> - let edn = Stack.TCP.dst flow in + let edn = TCP.dst flow in Lwt.return_ok (edn, flow) in let user's_dream_handler = - built_in_middleware user's_dream_handler in - let error_handler = error_handler app user's_error_handler in - let request_handler = request_handler app user's_error_handler user's_dream_handler in - let service = Alpn.service (alpn protocol) ~error_handler ~request_handler accept close in + built_in_middleware prefix user's_error_handler user's_dream_handler in + let handler = handler user's_error_handler user's_dream_handler in + let service = Alpn.service (alpn protocol) handler Lwt.return_ok accept close in init ~port stack >>= fun t -> let `Initialized th = serve ?stop service t in th + + + let validate_path request = + let path = Dream__server.Router.path request in + + let has_slash component = String.contains component '/' in + let has_backslash component = String.contains component '\\' in + let has_slash = List.exists has_slash path in + let has_backslash = List.exists has_backslash path in + let has_dot = List.exists ((=) Filename.current_dir_name) path in + let has_dotdot = List.exists ((=) Filename.parent_dir_name) path in + let has_empty = List.exists ((=) "") path in + let is_empty = path = [] in + + if has_slash || + has_backslash || + has_dot || + has_dotdot || + has_empty || + is_empty then + None + + else + let path = String.concat Filename.dir_sep path in + if Filename.is_relative path then + Some path + else + None + + (* Static files *) + + let mime_lookup filename = + let content_type = + match Magic_mime.lookup filename with + | "text/html" -> Formats.text_html + | content_type -> content_type + in + ["Content-Type", content_type] + + let static ~loader local_root = fun request -> + + if not @@ Method.methods_equal (Message.method_ request) `GET then + Message.response ~status:`Not_Found Stream.empty Stream.null + |> Lwt.return + + else + match validate_path request with + | None -> + Message.response ~status:`Not_Found Stream.empty Stream.null + |> Lwt.return + + | Some path -> + let%lwt response = loader local_root path request in + if not (Message.has_header response "Content-Type") then begin + match Message.status response with + | `OK + | `Non_Authoritative_Information + | `No_Content + | `Reset_Content + | `Partial_Content -> + Message.add_header response "Content-Type" (Magic_mime.lookup path) + | _ -> + () + end; + Lwt.return response + end -include Dream +include Message diff --git a/src/mirage/mirage.mli b/src/mirage/mirage.mli index 314aecbd..99f8f31f 100644 --- a/src/mirage/mirage.mli +++ b/src/mirage/mirage.mli @@ -1,15 +1,147 @@ -type request -type response - +type client +type server +type 'a message +type request = client message +type response = server message type handler = request -> response Lwt.t +type middleware = handler -> handler module Make - (Pclock : Mirage_clock.PCLOCK) - (Time : Mirage_time.S) - (Stack : Mirage_stack.V4V6) : sig - type middleware = handler -> handler - - type route + (Pclock : Mirage_clock.PCLOCK) + (Time : Mirage_time.S) + (Stack : Tcpip.Stack.V4V6) : sig + (* This file is part of Dream, released under the MIT license. See LICENSE.md + for details, or visit https://github.com/aantron/dream. + + Copyright 2021 Anton Bachin *) + + (** {1 Types} + Dream is built on just five types. The first two are the data types of + Dream. Both are abstract, even though they appear to have definitions: *) + + type request = client message + (** HTTP requests, such as [GET /something HTTP/1.1]. See + {!section-requests}. *) + + and response = server message + (** HTTP responses, such as [200 OK]. See {!section-responses}. *) + + (** The remaining three types are for building up Web apps. *) + + and handler = request -> response promise + (** Handlers are asynchronous functions from requests to responses. Example + {{:https://github.com/aantron/dream/tree/master/example/1-hello#files} + [1-hello]} \[{{:http://dream.as/1-hello} playground}\] shows the simplest + handler, an anonymous function which we pass to {!Dream.run}. This creates a + complete Web server! You can also see the Reason version in example + {{:https://github.com/aantron/dream/tree/master/example/r-hello#files} + [r-hello]}. + + {[ + let () = + Dream.run (fun _ -> + Dream.html "Good morning, world!") + ]} *) + + and middleware = handler -> handler + (** Middlewares are functions that take a {!handler}, and run some code before + or after — producing a “bigger” handler. Example + {{:https://github.com/aantron/dream/tree/master/example/2-middleware#files} + [2-middleware]} inserts the {!Dream.logger} middleware into a Web app: + + {[ + let () = + Dream.run + @@ Dream.logger + @@ fun _ -> Dream.html "Good morning, world!" + ]} + + Examples + {{:https://github.com/aantron/dream/tree/master/example/4-counter#files} + [4-counter]} \[{{:http://dream.as/4-counter} playground}\] and + {{:https://github.com/aantron/dream/tree/master/example/5-promise#files} + [5-promise]} show user-defined middlewares: + + {[ + let count_requests inner_handler request = + count := !count + 1; + inner_handler request + ]} + + In case you are wondering why the example middleware [count_requests] takes + two arguments, while the type says it should take only one, it's because: + + {[ + middleware + = handler -> handler + = handler -> (request -> response promise) + = handler -> request -> response promise + ]} *) + + and route + (** Routes tell {!Dream.router} which handler to select for each request. See + {!section-routing} and example + {{:https://github.com/aantron/dream/tree/master/example/3-router#files} + [3-router]} \[{{:http://dream.as/3-router/echo/foo} playground}\]. Routes + are created by helpers such as {!Dream.get} and {!Dream.scope}: + + {[ + Dream.router [ + Dream.scope "/admin" [Dream.memory_sessions] [ + Dream.get "/" admin_handler; + Dream.get "/logout" admin_logout_handler; + ]; + ] + ]} *) + + (** {2 Algebra} + + The three handler-related types have a vaguely algebraic interpretation: + + - Each literal {!handler} is an atom. + - {!type-middleware} is for sequential composition (product-like). + {!Dream.no_middleware} is {b 1}. + - {!type-route} is for alternative composition (sum-like). {!Dream.no_route} + is {b 0}. + + {!Dream.scope} implements a left distributive law, making Dream a ring-like + structure. *) + (** {2 Helpers} *) + + and 'a message = 'a Dream_pure.Message.message + (** ['a message], pronounced “any message,” allows some functions to take either + {!type-request} or {!type-response} as arguments, because both are defined + in terms of ['a message]. For example, in {!section-headers}: + + {[ + val Dream.header : string -> 'a message -> string option + ]} *) + + and client = Dream_pure.Message.client + + and server = Dream_pure.Message.server + (** Type parameters for {!message} for {!type-request} and {!type-response}, + respectively. These are “phantom” types. They have no meaning other than + they are different from each other. Dream only ever creates [client message] + and [server message]. [client] and [server] are never mentioned again in the + docs. *) + (* TODO These docs need to be clarified. *) + (* TODO Hide all the Dream_pure type equalities. *) + + and 'a promise = 'a Lwt.t + (** Dream uses {{:https://github.com/ocsigen/lwt} Lwt} for promises and + asynchronous I/O. See example + {{:https://github.com/aantron/dream/tree/master/example/5-promise#files} + [5-promise]} \[{{:http://dream.as/5-promise} playground}\]. + + Use [raise] to reject promises. If you are writing a library, you may prefer + using + {{:https://github.com/ocsigen/lwt/blob/9943ba77a5508feaea5e1fb60b011db4179f9c61/src/core/lwt.mli#L459} + [Lwt.fail]} in some places, in order to avoid clobbering your user's current + exception backtrace — though, in most cases, you should still extend it with + [raise] and [let%lwt], instead. *) + + (** {1 Methods} *) type method_ = [ `GET @@ -22,11 +154,46 @@ module Make | `TRACE | `PATCH | `Method of string ] + (** HTTP request methods. See + {{:https://tools.ietf.org/html/rfc7231#section-4.3} RFC 7231 §4.2}, + {{:https://tools.ietf.org/html/rfc5789#page-2} RFC 5789 §2}, and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods} MDN}. *) + + val method_to_string : [< method_] -> string + (** Evaluates to a string representation of the given method. For example, + [`GET] is converted to ["GET"]. *) + + val string_to_method : string -> method_ + (** Evaluates to the {!type-method_} corresponding to the given method + string. *) + + val methods_equal : [< method_] -> [< method_] -> bool + (** Compares two methods, such that equal methods are detected even if one is + represented as a string. For example, + + {[ + Dream.methods_equal `GET (`Method "GET") = true + ]} *) + + val normalize_method : [< method_] -> method_ + (** Converts methods represented as strings to variants. Methods generated by + Dream are always normalized. + + {[ + Dream.normalize_method (`Method "GET") = `GET + ]} *) + + (** {1:status_codes Status codes} *) type informational = [ `Continue | `Switching_Protocols ] - + (** Informational ([1xx]) status codes. See + {{:https://tools.ietf.org/html/rfc7231#section-6.2} RFC 7231 §6.2} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#information_responses} + MDN}. [101 Switching Protocols] is generated internally by + {!Dream.val-websocket}. It is usually not necessary to use it directly. *) + type successful = [ `OK | `Created @@ -35,7 +202,12 @@ module Make | `No_Content | `Reset_Content | `Partial_Content ] - + (** Successful ([2xx]) status codes. See + {{:https://tools.ietf.org/html/rfc7231#section-6.3} RFC 7231 §6.3}, + {{:https://tools.ietf.org/html/rfc7233#section-4.1} RFC 7233 §4.1} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#successful_responses} + MDN}. The most common is [200 OK]. *) + type redirection = [ `Multiple_Choices | `Moved_Permanently @@ -44,7 +216,14 @@ module Make | `Not_Modified | `Temporary_Redirect | `Permanent_Redirect ] - + (** Redirection ([3xx]) status codes. See + {{:https://tools.ietf.org/html/rfc7231#section-6.4} RFC 7231 §6.4} and + {{:https://tools.ietf.org/html/rfc7538#section-3} RFC 7538 §3}, and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages} + MDN}. Use [303 See Other] to direct clients to follow up with a [GET] + request, especially after a form submission. Use [301 Moved Permanently] + for permanent redirections. *) + type client_error = [ `Bad_Request | `Unauthorized @@ -71,7 +250,27 @@ module Make | `Too_Many_Requests | `Request_Header_Fields_Too_Large | `Unavailable_For_Legal_Reasons ] - + (** Client error ([4xx]) status codes. The most common are [400 Bad Request], + [401 Unauthorized], [403 Forbidden], and, of course, [404 Not Found]. + + See + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses} + MDN}, and + + - {{:https://tools.ietf.org/html/rfc7231#section-6.5} RFC 7231 §6.5} for + most client error status codes. + - {{:https://tools.ietf.org/html/rfc7233#section-4.4} RFC 7233 §4.4} for + [416 Range Not Satisfiable]. + - {{:https://tools.ietf.org/html/rfc7540#section-9.1.2} RFC 7540 §9.1.2} for + [421 Misdirected Request]. + - {{:https://tools.ietf.org/html/rfc8470#section-5.2} RFC 8470 §5.2} for + [425 Too Early]. + - {{:https://tools.ietf.org/html/rfc6585} RFC 6585} for + [428 Precondition Required], [429 Too Many Requests], and [431 Request + Headers Too Large]. + - {{:https://tools.ietf.org/html/rfc7725} RFC 7725} for + [451 Unavailable For Legal Reasons]. *) + type server_error = [ `Internal_Server_Error | `Not_Implemented @@ -79,121 +278,1780 @@ module Make | `Service_Unavailable | `Gateway_Timeout | `HTTP_Version_Not_Supported ] - + (** Server error ([5xx]) status codes. See + {{:https://tools.ietf.org/html/rfc7231#section-6.6} RFC 7231 §6.6} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#server_error_responses} + MDN}. The most common of these is [500 Internal Server Error]. *) + type standard_status = [ informational | successful | redirection | client_error | server_error ] + (** Sum of all the status codes declared above. *) type status = [ standard_status | `Status of int ] + (** Status codes, including codes directly represented as integers. See the + types above for the full list and references. *) - val random : int -> string + val status_to_string : [< status] -> string + (** Evaluates to a string representation of the given status. For example, + [`Not_Found] and [`Status 404] are both converted to ["Not Found"]. Numbers + are used for unknown status codes. For example, [`Status 567] is converted + to ["567"]. *) - val log : ('a, Format.formatter, unit, unit) format4 -> 'a + val status_to_reason : [< status] -> string option + (** Converts known status codes to their string representations. Evaluates to + [None] for unknown status codes. *) - type ('a, 'b) conditional_log = - ((?request:request -> - ('a, Format.formatter, unit, 'b) format4 -> 'a) -> 'b) -> - unit + val status_to_int : [< status] -> int + (** Evaluates to the numeric value of the given status code. *) + + val int_to_status : int -> status + (** Evaluates to the symbolic representation of the status code with the given + number. *) + + val is_informational : [< status] -> bool + (** Evaluates to [true] if the given status is either from type + {!Dream.informational}, or is in the range [`Status 100] — [`Status 199]. *) + + val is_successful : [< status] -> bool + (** Like {!Dream.is_informational}, but for type {!Dream.successful} and numeric + codes [2xx]. *) + + val is_redirection : [< status] -> bool + (** Like {!Dream.is_informational}, but for type {!Dream.redirection} and + numeric codes [3xx]. *) + + val is_client_error : [< status] -> bool + (** Like {!Dream.is_informational}, but for type {!Dream.client_error} and + numeric codes [4xx]. *) + + val is_server_error : [< status] -> bool + (** Like {!Dream.is_informational}, but for type {!Dream.server_error} and + numeric codes [5xx]. *) + + val status_codes_equal : [< status] -> [< status] -> bool + (** Compares two status codes, such that equal codes are detected even if one is + represented as a number. For example, + + {[ + Dream.status_codes_equal `Not_Found (`Status 404) = true + ]} *) + + val normalize_status : [< status] -> status + (** Converts status codes represented as numbers to variants. Status codes + generated by Dream are always normalized. + + {[ + Dream.normalize_status (`Status 404) = `Not_Found + ]} *) + + (** {1 Requests} *) + + val client : request -> string + (** Client sending the request. For example, ["127.0.0.1:56001"]. *) + + val method_ : request -> method_ + (** Request method. For example, [`GET]. *) + + val target : request -> string + (** Request target. For example, ["/foo/bar"]. *) + + (**/**) + + val prefix : request -> string + + (**/**) + + (**/**) + val path : request -> string list + [@@ocaml.deprecated + "Router path access is being removed from the API. Comment at + https://github.com/aantron/dream/issues + "] + (** Parsed request path. For example, ["foo"; "bar"]. *) + (* TODO If not removing this, move it to section Routing. *) + (**/**) + + val set_client : request -> string -> unit + (** Replaces the client. See {!Dream.val-client}. *) + + val set_method_ : request -> [< method_ ] -> unit + (** Replaces the method. See {!Dream.type-method_}. *) + + val query : request -> string -> string option + (** First query parameter with the given name. See + {{:https://tools.ietf.org/html/rfc3986#section-3.4} RFC 3986 §3.4} and + example + {{:https://github.com/aantron/dream/tree/master/example/w-query#files} + [w-query]}. *) + + val queries : request -> string -> string list + (** All query parameters with the given name. *) + + val all_queries : request -> (string * string) list + (** Entire query string as a name-value list. *) + + (** {1 Responses} *) + + val response : + ?status:[< status] -> + ?code:int -> + ?headers:(string * string) list -> + string -> + response + (** Creates a new {!type-response} with the given string as body. [~code] and + [~status] are two ways to specify the {!type-status} code, which is [200 OK] + by default. The headers are empty by default. + + Note that browsers may interpret lack of a [Content-Type:] header as if its + value were [application/octet-stream] or [text/html; charset=us-ascii], + which will prevent correct interpretation of UTF-8 strings. Either add a + [Content-Type:] header using [~headers] or {!Dream.add_header}, or use a + wrapper like {!Dream.html}. The modern [Content-Type:] for HTML is + [text/html; charset=utf-8]. See {!Dream.text_html}. *) + + val respond : + ?status:[< status] -> + ?code:int -> + ?headers:(string * string) list -> + string -> + response promise + (** Same as {!Dream.val-response}, but the new {!type-response} is wrapped in a + {!type-promise}. *) + + val html : + ?status:[< status] -> + ?code:int -> + ?headers:(string * string) list -> + string -> + response promise + (** Same as {!Dream.respond}, but adds [Content-Type: text/html; charset=utf-8]. + See {!Dream.text_html}. + + As your Web app develops, consider adding [Content-Security-Policy] headers, + as described in example + {{:https://github.com/aantron/dream/tree/master/example/w-content-security-policy#files} + [w-content-security-policy]}. These headers are completely optional, but + they can provide an extra layer of defense for a mature app. *) + + val json : + ?status:[< status] -> + ?code:int -> + ?headers:(string * string) list -> + string -> + response promise + (** Same as {!Dream.respond}, but adds [Content-Type: application/json]. See + {!Dream.application_json}. *) + + val redirect : + ?status:[< redirection] -> + ?code:int -> + ?headers:(string * string) list -> + request -> + string -> + response promise + (** Creates a new {!type-response}. Adds a [Location:] header with the given + string. The default status code is [303 See Other], for a temporary + redirection. Use [~status:`Moved_Permanently] or [~code:301] for a permanent + redirection. + + If you use [~code], be sure the number follows the pattern [3xx], or most + browsers and other clients won't actually perform a redirect. + + The {!type-request} is used for retrieving the site prefix, if the string is + an absolute path. Most applications don't have a site prefix. *) + + val empty : ?headers:(string * string) list -> status -> response promise + (** Same as {!Dream.val-response} with the empty string for a body. *) + + type websocket + (** A WebSocket connection. See {{:https://tools.ietf.org/html/rfc6455} RFC + 6455} and + {{:https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API} MDN}. *) + + val websocket : + ?headers:(string * string) list -> + ?close:bool -> + (websocket -> unit promise) -> + response promise + (** Creates a fresh [101 Switching Protocols] response. Once this response is + returned to Dream's HTTP layer, the callback is passed a new + {!type-websocket}, and the application can begin using it. See example + {{:https://github.com/aantron/dream/tree/master/example/k-websocket#files} + [k-websocket]} \[{{:http://dream.as/k-websocket} playground}\]. + + {[ + let my_handler = fun request -> + Dream.websocket (fun websocket -> + let%lwt () = Dream.send websocket "Hello, world!"); + ]} + + [Dream.websocket] automatically closes the WebSocket when the callback + returns or raises an exception. Pass [~close:false] to suppress this + behavior. *) + + type text_or_binary = + [ `Text + | `Binary ] + (** See {!send} and {!receive_fragment}. *) + + type end_of_message = + [ `End_of_message + | `Continues ] + (** See {!send} and {!receive_fragment}. *) + + val send : + ?text_or_binary:[< text_or_binary] -> + ?end_of_message:[< end_of_message] -> + websocket -> + string -> + unit promise + (** Sends a single WebSocket message. The WebSocket is ready another message + when the promise resolves. + + With [~text_or_binary:`Text], the default, the message is interpreted as a + UTF-8 string. The client will receive it transcoded to JavaScript's UTF-16 + representation. + + With [~text_or_binary:`Binary], the message will be received unmodified, as + either a [Blob] or an [ArrayBuffer]. See + {{:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType} + MDN, [WebSocket.binaryType]}. + + [~end_of_message] is ignored for now, as the WebSocket library underlying + Dream does not support sending message fragments yet. *) + + val receive : websocket -> string option promise + (** Receives a message. If the WebSocket is closed before a complete message + arrives, the result is [None]. *) + + val receive_fragment : + websocket -> (string * text_or_binary * end_of_message) option promise + (** Receives a single fragment of a message, streaming it. *) + + val close_websocket : ?code:int -> websocket -> unit promise + (** Closes the WebSocket. [~code] is usually not necessary, but is needed for + some protocols based on WebSockets. See + {{:https://tools.ietf.org/html/rfc6455#section-7.4} RFC 6455 §7.4}. *) + + val status : response -> status + (** Response {!type-status}. For example, [`OK]. *) + + (** {1 Headers} *) + + val header : 'a message -> string -> string option + (** First header with the given name. Header names are case-insensitive. See + {{:https://tools.ietf.org/html/rfc7230#section-3.2} RFC 7230 §3.2} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers} MDN}. *) + + val headers : 'a message -> string -> string list + (** All headers with the given name. *) + + val all_headers : 'a message -> (string * string) list + (** Entire header set as name-value list. *) + + val has_header : 'a message -> string -> bool + (** Whether the message has a header with the given name. *) + + val add_header : 'a message -> string -> string -> unit + (** Appends a header with the given name and value. Does not remove any existing + headers with the same name. *) + + val drop_header : 'a message -> string -> unit + (** Removes all headers with the given name. *) + + val set_header : 'a message -> string -> string -> unit + (** Equivalent to {!Dream.drop_header} followed by {!Dream.add_header}. *) + + (** {1 Cookies} + + {!Dream.set_cookie} and {!Dream.cookie} are designed for round-tripping + secure cookies. The most secure settings applicable to the current server + are inferred automatically. See example + {{:https://github.com/aantron/dream/tree/master/example/c-cookie#files} + [c-cookie]} \[{{:http://dream.as/c-cookie} playground}\]. + + {[ + Dream.set_cookie "my.cookie" "foo" request response + Dream.cookie "my.cookie" request + ]} + + The {!Dream.cookie} call evaluates to [Some "foo"], but the actual cookie + that is exchanged may look like: + + {v + __Host-my.cookie=AL7NLA8-so3e47uy0R5E2MpEQ0TtTWztdhq5pTEUT7KSFg; \ + Path=/; Secure; HttpOnly; SameSite=Strict + v} + + {!Dream.set_cookie} has a large number of optional arguments for tweaking + the inferred security settings. If you use them, pass the same arguments to + {!Dream.cookie} to automatically undo the result. *) + + val set_cookie : + ?prefix:[< `Host | `Secure] option -> + ?encrypt:bool -> + ?expires:float -> + ?max_age:float -> + ?domain:string -> + ?path:string option -> + ?secure:bool -> + ?http_only:bool -> + ?same_site:[< `Strict | `Lax | `None] option -> + response -> + request -> + string -> + string -> + unit + (** Appends a [Set-Cookie:] header to the {!type-response}. Infers the most + secure defaults from the {!type-request}. + + {[ + Dream.set_cookie "my.cookie" "value" request response + ]} + + Specify {!Dream.run} argument [~secret], or the Web app will not be able to + decrypt cookies from prior starts. + + See example + {{:https://github.com/aantron/dream/tree/master/example/c-cookie#files} + [c-cookie]}. + + Most of the optional arguments are for overriding inferred defaults. + [~expires] and [~max_age] are independently useful. In particular, to delete + a cookie, use [~expires:0.] + + - [~prefix] sets [__Host-], [__Secure-], or no prefix, from most secure to + least. A conforming client will refuse to accept the cookie if [~domain], + [~path], and [~secure] don't match the constraints implied by the prefix. + By default, {!Dream.set_cookie} chooses the most restrictive prefix based + on the other settings and the {!type-request}. See + {{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.3} + RFC 6265bis §4.1.3} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Cookie_prefixes} + MDN}. + - [~encrypt:false] disables cookie encryption. In that case, you must make + sure that the cookie value does not contain [=], [;], or newlines. The + easiest way to do so is to pass the value through an encoder like + {!Dream.to_base64url}. See {!Dream.run} argument [~secret]. + - [~expires] sets the [Expires=] attribute. The value is compatible with + {{:https://caml.inria.fr/pub/docs/manual-ocaml/libref/Unix.html#VALgettimeofday} + [Unix.gettimeofday]}. See + {{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.1} + RFC 6265bis §4.1.2.1} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie} + MDN}. + - [~max_age] sets the [Max-Age=] attribute. See + {{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.2} + RFC 6265bis §4.1.2.2} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie} + MDN}. + - [~domain] sets the [Domain=] attribute. See + {{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.3} + RFC 6265bis §4.1.2.3} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Domain_attribute} + MDN}. + - [~path] sets the [Path=] attribute. By default, [Path=] set to the site + prefix in the {!type-request}, which is usually [/]. See + {{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.4} + RFC 6265bis §4.1.2.4} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Path_attribute} + MDN}. + - [~secure] sets the [Secure] attribute. By default, [Secure] is set if + {!Dream.https} is [true] for the {!type-request}. See + {{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.5} + RFC 6265bis §4.1.2.5} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies} + MDN}. + - [~http_only] sets the [HttpOnly] attribute. [HttpOnly] is set by default. + See + {{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.6} + RFC 6265bis §4.1.2.6} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies} + MDN}. + - [~same_site] sets the [SameSite=] attribute. [SameSite] is set to [Strict] + by default. See + {{:https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.2.7} + RFC 6265bis §4.1.2.7} and + {{:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_attribute} + MDN}. + + {!Dream.to_set_cookie} is a “raw” version of this function that does not do + any inference. *) + + val drop_cookie : + ?prefix:[< `Host | `Secure] option -> + ?domain:string -> + ?path:string option -> + ?secure:bool -> + ?http_only:bool -> + ?same_site:[< `Strict | `Lax | `None] option -> + response -> + request -> + string -> + unit + (** Deletes the given cookie. + + This function works by calling {!Dream.set_cookie}, and setting the cookie + to expire in the past. Pass all the same optional values that you would pass + to {!Dream.set_cookie}, to make sure that the same cookie is deleted. *) + + val cookie : + ?prefix:[< `Host | `Secure] option -> + ?decrypt:bool -> + ?domain:string -> + ?path:string option -> + ?secure:bool -> + request -> + string -> + string option + (** First cookie with the given name. See example + {{:https://github.com/aantron/dream/tree/master/example/c-cookie#files} + [c-cookie]}. + + {[ + Dream.cookie "my.cookie" request + ]} + + Pass the same optional arguments as to {!Dream.set_cookie} for the same + cookie. This will allow {!Dream.cookie} to infer the cookie name prefix, + implementing a transparent cookie round trip with the most secure attributes + applicable. *) + + val all_cookies : request -> (string * string) list + (** All cookies, with raw names and values. *) + + (** {1 Bodies} *) + + val body : 'a message -> string promise + (** Retrieves the entire body. See example + {{:https://github.com/aantron/dream/tree/master/example/6-echo#files} + [6-echo]}. *) + + val set_body : 'a message -> string -> unit + (** Replaces the body. *) + + (** {2 Low-level streaming} *) + + type buffer = + (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t + (** Byte arrays in the C heap. See + {{:http://caml.inria.fr/pub/docs/manual-ocaml/libref/Bigarray.Array1.html} + [Bigarray.Array1]}. This type is also found in several libraries installed + by Dream, so their functions can be used with {!Dream.buffer}: + + - {{:https://github.com/inhabitedtype/bigstringaf/blob/353cb283aef4c261597f68154eb27a138e7ef112/lib/bigstringaf.mli} + [Bigstringaf.t]} in bigstringaf. + - {{:https://ocsigen.org/lwt/latest/api/Lwt_bytes} [Lwt_bytes.t]} in Lwt. + - {{:https://github.com/mirage/ocaml-cstruct/blob/9a8b9a79bdfa2a1b8455bc26689e0228cc6fac8e/lib/cstruct.mli#L139} + [Cstruct.buffer]} in Cstruct. *) + + (* TODO What should the body stream retrieval function be called? *) + (* TODO Remove old functions from signature. *) + (* TODO Should there be a section for this somewhere? Probably "low-level + streaming" should be promoted to a top-level section, Streaming. *) + + (** {2 Streaming} *) + + type stream + + val body_stream : request -> stream + (** A stream that can be used to gradually read the request's body. *) + + val stream : + ?status:[< status] -> + ?code:int -> + ?headers:(string * string) list -> + ?close:bool -> + (stream -> unit promise) -> + response promise + (** Creates a response with a {!type-stream} open for writing, and passes the + stream to the callback when it is ready. See example + {{:https://github.com/aantron/dream/tree/master/example/j-stream#files} + [j-stream]}. + + {[ + fun request -> + Dream.stream (fun stream -> + Dream.write stream "foo") + ]} + + [Dream.stream] automatically closes the stream when the callback returns or + raises an exception. Pass [~close:false] to suppress this behavior. *) + + val read : stream -> string option promise + (** Retrieves a body chunk. See example + {{:https://github.com/aantron/dream/tree/master/example/j-stream#files} + [j-stream]}. *) + (* TODO Document difference between receiving a request and receiving on a + WebSocket. *) + + val write : stream -> string -> unit promise + (** Streams out the string. The promise is fulfilled when the response can + accept more writes. *) + (* TODO Document clearly which of the writing functions can raise exceptions. *) + + val flush : stream -> unit promise + (** Flushes the stream's write buffer. Data is sent to the client. *) + + val close : stream -> unit promise + (** Closes the stream. *) + + val client_stream : 'a message -> stream + val server_stream : 'a message -> stream + + (* TODO Document that this is for middlewares that are transforming a response + stream or a WebSocket. *) + val set_client_stream : 'a message -> stream -> unit + + (* TODO Normalize with with_stream, or add a separate with_server_stream. *) + val set_server_stream : 'a message -> stream -> unit + + (* TODO Probably even close can be made optional. exn can be made optional. *) + (* TODO Argument order? *) + val read_stream : + stream -> + data:(buffer -> int -> int -> bool -> bool -> unit) -> + flush:(unit -> unit) -> + ping:(buffer -> int -> int -> unit) -> + pong:(buffer -> int -> int -> unit) -> + close:(int -> unit) -> + exn:(exn -> unit) -> + unit + (** Waits for the next stream event, and calls: + + - [~buffer] with an offset and length, if a {!type-buffer} is written, + - [~close] if close is requested, and + - [~exn] to report an exception. *) + + val write_stream : + stream -> + buffer -> + int -> + int -> + bool -> + bool -> + close:(int -> unit) -> + exn:(exn -> unit) -> + (unit -> unit) -> + unit + + val flush_stream : + stream -> close:(int -> unit) -> exn:(exn -> unit) -> (unit -> unit) -> unit + + val ping_stream : + stream -> + buffer -> + int -> + int -> + close:(int -> unit) -> + exn:(exn -> unit) -> + (unit -> unit) -> + unit + + val pong_stream : + stream -> + buffer -> + int -> + int -> + close:(int -> unit) -> + exn:(exn -> unit) -> + (unit -> unit) -> + unit + + val close_stream : stream -> int -> unit + val abort_stream : stream -> exn -> unit + (* TODO Line widths above. *) + + (* TODO Ergonomics of this stream surface API. *) + + (** {1 JSON} + + Dream presently recommends using + {{:https://github.com/ocaml-community/yojson#readme} Yojson}. See also + {{:https://github.com/janestreet/ppx_yojson_conv#readme} ppx_yojson_conv} + for generating JSON parsers and serializers for OCaml data types. + + See example + {{:https://github.com/aantron/dream/tree/master/example/e-json#files} + [e-json]}. *) + + val origin_referrer_check : middleware + (** CSRF protection for AJAX requests. Either the method must be [`GET] or + [`HEAD], or: + + - [Origin:] or [Referer:] must be present, and + - their value must match [Host:] + + Responds with [400 Bad Request] if the check fails. See example + {{:https://github.com/aantron/dream/tree/master/example/e-json#security} + [e-json]}. + + Implements the + {{:https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#verifying-origin-with-standard-headers} + OWASP {i Verifying Origin With Standard Headers}} CSRF defense-in-depth + technique, which is good enough for basic usage. Do not allow [`GET] or + [`HEAD] requests to trigger important side effects if relying only on + {!Dream.origin_referrer_check}. + + Future extensions to this function may use [X-Forwarded-Host] or host + whitelists. + + For more thorough protection, generate CSRF tokens with {!Dream.csrf_token}, + send them to the client (for instance, in [] tags of a single-page + application), and require their presence in an [X-CSRF-Token:] header. *) + + (** {1 Forms} + + {!Dream.csrf_tag} and {!Dream.val-form} round-trip secure forms. + {!Dream.csrf_tag} is used inside a form template to generate a hidden field + with a CSRF token: + + {[ + + <%s! Dream.csrf_tag request %> + +
+ ]} + + {!Dream.val-form} recieves the form and checks the CSRF token: + + {[ + match%lwt Dream.form request with + | `Ok ["my.field", value] -> (* ... *) + | _ -> Dream.empty `Bad_Request + ]} + + See example + {{:https://github.com/aantron/dream/tree/master/example/d-form#files} + [d-form]} \[{{:http://dream.as/d-form} playground}\]. *) + + type 'a form_result = + [ `Ok of 'a + | `Expired of 'a * float + | `Wrong_session of 'a + | `Invalid_token of 'a + | `Missing_token of 'a + | `Many_tokens of 'a + | `Wrong_content_type ] + (** Form CSRF checking results, in order from least to most severe. See + {!Dream.val-form} and example + {{:https://github.com/aantron/dream/tree/master/example/d-form#files} + [d-form]}. + + The first three constructors, [`Ok], [`Expired], and [`Wrong_session] can + occur in regular usage. + + The remaining constructors, [`Invalid_token], [`Missing_token], + [`Many_tokens], [`Wrong_content_type] correspond to bugs, suspicious + activity, or tokens so old that decryption keys have since been rotated on + the server. *) + + val form : ?csrf:bool -> request -> (string * string) list form_result promise + (** Parses the request body as a form. Performs CSRF checks. Use + {!Dream.csrf_tag} in a template to transparently generate forms that will + pass these checks. See {!section-templates} and example + {{:https://github.com/aantron/dream/tree/master/example/d-form#readme} + [d-form]}. + + - [Content-Type:] must be [application/x-www-form-urlencoded]. + - The form must have a field named [dream.csrf]. {!Dream.csrf_tag} adds such + a field. + - {!Dream.form} calls {!Dream.verify_csrf_token} to check the token in + [dream.csrf]. + + The call must be done under a session middleware, since each CSRF token is + scoped to a session. See {!section-sessions}. + + Form fields are sorted for easy pattern matching: + + {[ + match%lwt Dream.form request with + | `Ok ["email", email; "name", name] -> (* ... *) + | _ -> Dream.empty `Bad_Request + ]} + + To recover from conditions like expired forms, add extra cases: + + {[ + match%lwt Dream.form request with + | `Ok ["email", email; "name", name] -> (* ... *) + | `Expired ["email", email; "name", name] -> (* ... *) + | _ -> Dream.empty `Bad_Request + ]} + + It is recommended not to mutate state or send back sensitive data in the + [`Expired] and [`Wrong_session] cases, as they {e may} indicate an attack + against a client. + + The remaining cases, including unexpected field sets and the remaining + constructors of {!Dream.type-form_result}, usually indicate either bugs or + attacks. It's usually fine to respond to all of them with [400 Bad + Request]. *) + + (** {2 Upload} *) + + type multipart_form = (string * (string option * string) list) list + (** Submitted file upload forms, [
]. For + example, if a form + + {v + + + v} + + is submitted with two files and a text value, it will be received by + {!Dream.multipart} as + + {[ + [ + "files", [ + Some "file1.ext", "file1-content"; + Some "file2.ext", "file2-content"; + ]; + "text", [ + None, "text-value" + ]; + ] + ]} + + See example + {{:https://github.com/aantron/dream/tree/master/example/g-upload#files} + [g-upload]} \[{{:http://dream.as/g-upload} playground}\] and + {{:https://datatracker.ietf.org/doc/html/rfc7578} RFC 7578}. + + Note that clients such as curl can send files with no filename ([None]), + though most browsers seem to insert at least an empty filename ([Some ""]). + Don't use use the presence of a filename to determine if the field value is + a file. Use the field name and knowledge about the form instead. + + If a file field has zero files when submitted, browsers send + ["field-name", [Some ""; ""]]. {!Dream.multipart} replaces this with + ["field-name", []]. Use the advanced interface {!Dream.upload} for the raw + behavior. + + Non-file fields always have one value, which might be the empty string. + + See + {{:https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html} + OWASP {i File Upload Cheat Sheet}} for security precautions for upload + forms. *) + + val multipart : ?csrf:bool -> request -> multipart_form form_result promise + (** Like {!Dream.form}, but also reads files, and [Content-Type:] must be + [multipart/form-data]. The [] tag and CSRF token can be generated in a + template with + + {[ + <%s! Dream.form_tag ~action:"/" + ~enctype:`Multipart_form_data request %> + ]} + + See {!Dream.form_tag}, section {!section-templates}, and example + {{:https://github.com/aantron/dream/tree/master/example/g-upload#files} + [g-upload]}. + + Note that, like {!Dream.form}, this function sorts form fields by field + name. + + {!Dream.multipart} reads entire files into memory, so it is only suitable + for prototyping, or with yet-to-be-added file size and count limits. See + {!Dream.val-upload} below for a streaming version. *) + + (** {2 Streaming uploads} *) + + type part = string option * string option * (string * string) list + (** Upload form parts. + + A value [Some (name, filename, headers)] received by {!Dream.val-upload} + begins a {e part} in the stream. A part represents either a form field, or + a single, complete file. + + Note that, in the general case, [filename] and [headers] are not reliable. + [name] is the form field name. *) + + val upload : request -> part option promise + (** Retrieves the next upload part. + + Upon getting [Some (name, filename, headers)] from this function, the user + should call {!Dream.upload_part} to stream chunks of the part's data, until + that function returns [None]. The user should then call {!Dream.val-upload} + again. [None] from {!Dream.val-upload} indicates that all parts have been + received. + + {!Dream.upload} does not verify a CSRF token. There are several ways to add + CSRF protection for an upload stream, including: + + - Generate the form with {!Dream.form_tag}. Check for + [`Field ("dream.csrf", token)] during upload and call + {!Dream.verify_csrf_token}. + - Use {{:https://developer.mozilla.org/en-US/docs/Web/API/FormData} + [FormData]} in the client to submit [multipart/form-data] by AJAX, and + include a custom header. *) + + val upload_part : request -> string option promise + (** Retrieves a part chunk. *) - type log_level = [ `Error | `Warning | `Info | `Debug ] + (** {2 CSRF tokens} - val error : ('a, unit) conditional_log - val warning : ('a, unit) conditional_log - val info : ('a, unit) conditional_log - val debug : ('a, unit) conditional_log + It's usually not necessary to handle CSRF tokens directly. - val html : ?status:status -> ?code:int -> ?headers:(string * string) list -> string -> response Lwt.t + - CSRF token field generator {!Dream.csrf_tag} generates and inserts a CSRF + token that {!Dream.val-form} and {!Dream.val-multipart} transparently + verify. + - AJAX can be protected from CSRF by {!Dream.origin_referrer_check}. - val param : string -> request -> string + CSRF functions are exposed for creating custom schemes, and for + defense-in-depth purposes. See + {{:https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html} + OWASP {i Cross-Site Request Forgery Prevention Cheat Sheet}}. *) type csrf_result = [ `Ok | `Expired of float | `Wrong_session | `Invalid ] + (** CSRF token verification outcomes. + + [`Expired] and [`Wrong_session] can occur in normal usage, when a user's + form or session expire, respectively. However, they can also indicate + attacks, including stolen tokens, stolen tokens from other sessions, or + attempts to use a token from an invalidated pre-session after login. + + [`Invalid] indicates a token with a bad signature, a payload that was not + generated by Dream, or other serious errors that cannot usually be triggered + by normal users. [`Invalid] usually corresponds to bugs or attacks. + [`Invalid] can also occur for very old tokens after old keys are no longer + in use on the server. *) val csrf_token : ?valid_for:float -> request -> string - val verify_csrf_token : request -> string -> csrf_result Lwt.t + (** Returns a fresh CSRF token bound to the given request's and signed with the + [~secret] given to {!Dream.run}. [~valid_for] is the token's lifetime, in + seconds. The default value is one hour ([3600.]). Dream uses signed tokens + that are not stored server-side. *) - type 'a form_result = - [ `Ok of 'a - | `Expired of 'a * float - | `Wrong_session of 'a - | `Invalid_token of 'a - | `Missing_token of 'a - | `Many_tokens of 'a - | `Wrong_content_type ] + val verify_csrf_token : request -> string -> csrf_result promise + (** Checks that the CSRF token is valid for the {!type-request}'s session. *) - type multipart_form = - (string * ((string option * string) list)) list + (** {1 Templates} - val form : request -> (string * string) list form_result Lwt.t - val multipart : request -> multipart_form form_result Lwt.t + Dream includes a template preprocessor that allows interleaving OCaml and + HTML in the same file: - val form_tag : - ?method_:method_ -> - ?target:string -> - ?enctype:[ `Multipart_form_data ] -> - ?csrf_token:bool -> - action:string -> request -> string + {v + let render message = + + +

The message is <%s message %>!

+ + + v} - val lowercase_headers : middleware - val content_length : middleware + See examples + {{:https://github.com/aantron/dream/tree/master/example/7-template#files} + [7-template]} \[{{:http://dream.as/7-template} playground}\] and + {{:https://github.com/aantron/dream/tree/master/example/r-template#files} + [r-template]} \[{{:http://dream.as/r-template} playground}\]. - val logger : middleware - val router : route list -> middleware + There is also a typed alternative, provided by an external library, + {{:https://github.com/ocsigen/tyxml} TyXML}. It is shown in example + {{:https://github.com/aantron/dream/tree/master/example/w-tyxml#files} + [w-tyxml]} \[{{:http://dream.as/w-tyxml} playground}\]. If you are using + Reason syntax, TyXML can be used with + {{:https://ocsigen.org/tyxml/latest/manual/jsx} server-side JSX}. See + example + {{:https://github.com/aantron/dream/tree/master/example/r-tyxml#files} + [r-tyxml]} \[{{:http://dream.as/r-tyxml} playground}\]. + + To use the built-in templates, add this to [dune]: + + {v + (rule + (targets template.ml) + (deps template.eml.ml) + (action (run dream_eml %{deps} --workspace %{workspace_root}))) + v} + + A template begins... + + - {e Implicitly} on a line that starts with [<], perhaps with leading + whitespace. The line is part of the template. + - {e Explicitly} after a line that starts with [%%]. The [%%] line is not + part of the template. + + A [%%] line can also be used to set template options. The only option + supported presently is [%% response] for streaming the template using + {!Dream.write}, to a {!type-response} that is in scope. This is shown in + examples + {{:https://github.com/aantron/dream/tree/master/example/w-template-stream#files} + [w-template-stream]} and + {{:https://github.com/aantron/dream/tree/master/example/r-template-stream#files} + [r-template-stream]}. + + A template ends... + + - {e Implicitly}, when the indentation level is less than that of the + beginning line. + - {e Explicitly} on a line that starts with another [%%]. + + Everything outside a template is ordinary OCaml code. + + OCaml code can also be inserted into a template: + + - [<%s code %>] expects [code] to evaluate to a [string], and inserts the + [string] into the template. + - A line that begins with [%] in the first column is OCaml code inside the + template. Its value is not inserted into the template. Indeed, it can be + fragments of control-flow constructs. + - [<% code %>] is a variant of [%] that can be used for short snippets + within template lines. + + The [s] in [<%s code %>] is actually a + {{:https://caml.inria.fr/pub/docs/manual-ocaml/libref/Printf.html} + Printf}-style format specification. So, for example, one can print two hex + digits using [<%02X code %>]. + + [<%s code %>] automatically escapes the result of [code] using + {!Dream.html_escape}. This can be suppressed with [!]. [<%s! code %>] prints + the result of [code] literally. {!Dream.html_escape} is only safe for use in + HTML text and quoted attribute values. It does not offer XSS protection in + unquoted attribute values, CSS in [