From 0b42e52548057551cd8e1aaffa32845248e199e6 Mon Sep 17 00:00:00 2001 From: Bilal2453 Date: Mon, 17 Jul 2023 03:18:07 +0300 Subject: [PATCH] add details for installation, clean up --- README.md | 2 +- docs/coro-channel.md | 90 +++++++++++++++++++++++++++--------------- docs/coro-fs.md | 42 +++++++++++++++----- docs/coro-http.md | 9 ++++- docs/coro-net.md | 13 ++++-- docs/coro-spawn.md | 21 ++++++---- docs/coro-split.md | 23 ++++++++--- docs/coro-websocket.md | 13 ++++-- docs/coro-wrapper.md | 16 ++++++-- 9 files changed, 163 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 0b2c6f2..b3665bf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # coro-docs -Unofficial docs for the luvit libraries coro-*. +Documentation for the [Luvit](https://luvit.io/) coro-* libraries. ## Index diff --git a/docs/coro-channel.md b/docs/coro-channel.md index 38379c0..cec8906 100644 --- a/docs/coro-channel.md +++ b/docs/coro-channel.md @@ -2,11 +2,18 @@ layout: doc --- -# Documentation +# coro-channel -Unofficial documentation for the [coro-channel](https://github.com/luvit/lit/blob/master/deps/coro-channel.lua) module, version 3.0.2. +Documentation for the [coro-channel](https://github.com/luvit/lit/blob/master/deps/coro-channel.lua) module, version 3.0.3. -[coro-channel](https://github.com/luvit/lit/blob/master/deps/coro-channel.lua) is a wrapper module that provides manipulating read/write streams in a sync style of code using Lua coroutines without blocking the event loop. +[coro-channel](https://github.com/luvit/lit/blob/master/deps/coro-channel.lua) is a wrapper module that wraps [stream handles](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) (or any other handle that inherits stream) to provide a sync style read/write interface making use of Lua coroutines, and without blocking the event loop. + +### Installation + +```sh +lit install creationix/coro-channel +``` +[On Lit search.](https://luvit.io/lit.html#coro-channel) ---- @@ -16,7 +23,7 @@ Unofficial documentation for the [coro-channel](https://github.com/luvit/lit/blo ### wrapRead(socket) {#wrapRead} -Wraps a [stream handle](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) for a coroutine based reading. +Wraps a [stream handle](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) for coroutine based reading. *This method does not require running in a coroutine* @@ -38,28 +45,32 @@ Wraps a [stream handle](https://github.com/luvit/luv/blob/master/docs.md#uv_stre - A standard input echo example
(Type something in the console to get an echo) ```lua -local uv = require("uv") -- or luv outside of Luvi -local stdin = uv.new_tty(0, true) -- Create a readable stream, a TTY stream in this case -local reader, closer = wrapRead(stdin) -- Wrap the readable stream -for chunk in reader do -- Each time something is typed in console: execute reader and store the result in chunk - if not chunk or chunk == '\n' then break end -- If the user inputs an empty chunk then stop reading - print("You typed: " .. chunk) -- Output what the user have input +local uv = require("uv") -- or "luv" outside of Luvi +local wrapRead = require("coro-channel").wrapRead + +local stdin = uv.new_tty(0, true) -- create a readable stream, a TTY stream in this case +local reader, closer = wrapRead(stdin) -- wrap the readable stream +for chunk in reader do -- each time something is typed in console: execute reader and store the result in chunk + if chunk == '\n' then break end -- if the input is a new line (Enter) stop reading + print("You typed: " .. chunk) -- output the user input end -closer() -- Close the handle if reading has stopped +closer() -- close the handle if reading has stopped print("You have exited") ``` +Note: In the above example we won't receive an End of Stream (EoS), so we break manually on new lines, but this is not always the case, for example the coro-net reader will return `nil` when an EoS is received which would automatically break out of the for loop. + ---- ### wrapWrite(socket) {#wrapWrite} -Wraps a [stream](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) handle for a coroutine based writing. +Wraps a [stream handle](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) for coroutine based writing. #### Parameters {#wrapWrite-parameters} | Param | Type | Description | |:------:|:------:|:------------| -| socket | [uv_stream_t](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) | The stream [handle](https://github.com/luvit/luv/blob/master/docs.md#uv_handle_t--base-handle) to be wrapped for writing. | +| socket | [uv_stream_t](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) | The [stream handle](https://github.com/luvit/luv/blob/master/docs.md#uv_handle_t--base-handle) to be wrapped for writing. | #### Returns {#wrapWrite-returns} @@ -73,24 +84,26 @@ Wraps a [stream](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--s - Timer writing to stdout TTY every second ```lua -local uv = require("uv") -- or luv outside of Luvi -local stdout = uv.new_tty(1, false) -- Create a writable stdout stream -local writer, closer = wrapWrite(stdout) -- Wrap the writable stream +local uv = require("uv") -- or "luv" outside of Luvi +local wrapWrite = require("coro-channel").wrapWrite + +local stdout = uv.new_tty(1, false) -- create a writable stdout stream +local writer, closer = wrapWrite(stdout) -- wrap the writable stream -local passed = 0 -- How many second have passed? -local function callback() -- A callback to be executed every second - coroutine.wrap(function() -- A new coroutine each time +local passed = 0 -- how many second have passed? +local function callback() -- a callback to be executed every second + coroutine.wrap(function() -- a new coroutine each time passed = passed + 1 - writer(passed .. " seconds has passed!\n") -- Write to stdout + writer(passed .. " seconds has passed!\n") -- write to stdout end)() end writer("Hello to the timer! To exit press Ctrl + C\n") -local timer = uv.new_timer() -- Create a timer handle -timer:start(1000, 1000, callback) -- Start it and repeat every second +local timer = uv.new_timer() -- create a timer handle +timer:start(1000, 1000, callback) -- start it and repeat every second ``` -Note: In the previous example the callback could be defined in the following way: +Note: In the previous example the callback could be defined in the following way to avoid creating a new one everytime: ```lua local callback = coroutine.wrap(function() while true do @@ -105,9 +118,9 @@ end) ### wrapSteam(socket) {#wrapStream} -Wraps a [stream](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) handle for both writing and reading. +Wraps a [stream handle](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) for both writing and reading. -Has the same effect to calling `wrapRead` and `wrapWrite` on a stream, such as: +Has the same effect to calling `wrapRead` and `wrapWrite` on the handle, such as: ```lua local reader = wrapRead(stream) local writer, closer = wrapWrite(stream) @@ -117,7 +130,7 @@ local writer, closer = wrapWrite(stream) | Param | Type | Description | |:------:|:------:|:------------| -| socket | [uv_stream_t](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) | The stream [handle](https://github.com/luvit/luv/blob/master/docs.md#uv_handle_t--base-handle) to be wrapped for writing. | +| socket | [uv_stream_t](https://github.com/luvit/luv/blob/master/docs.md#uv_stream_t--stream-handle) | The [stream handle](https://github.com/luvit/luv/blob/master/docs.md#uv_handle_t--base-handle) to be wrapped for reading and writing. | #### Returns {#wrapStream-returns} @@ -136,6 +149,7 @@ local writer, closer = wrapWrite(stream) ### reader() {#reader} Yields the running coroutine and resumes it after receiving a chunk of data. +Effectively: wait until there is data to read, then return the read data. #### Returns {#reader-returns} @@ -148,14 +162,19 @@ Yields the running coroutine and resumes it after receiving a chunk of data. - Using a reader in an iterator -```lua +```lua +local uv = require("uv") -- or "luv" outside of luvi +local wrapRead = require("coro-channel").wrapRead + local handle = uv.new_tty(1, true) -- or any kind of streams -local reader = coro_channel.wrapRead(handle) +local reader = wrapRead(handle) for chunk, err in reader do if err then print("An error has occurred: " .. err) break + elseif chunk == '\n' then -- press Enter to exit the tty + break end print("Data chunk successfully read: " .. chunk) end @@ -168,12 +187,13 @@ print("Reading data done") ### writer(chunk) {#writer} Yields the running coroutine and resumes it when done writing the provided chunk of data. +Effectively: write the data and wait until it has been written, then return. #### Parameters {#writer-parameters} | Param | Type | Description | |:------:|:------:|:------------| -| chunk | string / table / nil | `nil` indicates EOF and will completely close the stream if there's nothing to read, otherwise it will shutdown the writing duplex only.
If the provided value is a string it will be written to the stream as a single chunk.
If a table of strings is provided the strings will be written in a one system call as if they were concatenated. | +| chunk | string / table / nil | `nil` indicates End of Stream and will completely close the stream if there's nothing to read, if there is, it will only shutdown the writing duplex.
If the provided value is a string it will be written to the stream as a single chunk.
If a table of strings is provided the string values will be concatenated and written in a single system call. | #### Returns {#writer-returns} @@ -187,8 +207,11 @@ Yields the running coroutine and resumes it when done writing the provided chunk Assuming the following example of using `writer` with a TTY handle running in a coroutine: ```lua +local uv = require("uv") -- or "luv" outside of Luvi +local wrapWrite = require("coro-channel").wrapWrite + local handle = uv.new_tty(0, false) -- or any kind of streams -local writer = coro_channel.wrapWrite(handle) +local writer = wrapWrite(handle) local tbl = {"Hello ", "There", "!!", "\n"} local success, err = writer(tbl) @@ -198,13 +221,16 @@ if not success then end ``` -Note: usually though you don't use it like this with a TTY handle, it was used like this in the above example just to make it simple. +Note: usually though this example is not how you use it with a TTY handle, the example is simplified and for explanation purposes only. ---- ### closer() {#closer} -Closes the wrapped stream completely if it wasn't already closed. You cannot read or write from a closed stream. Note that this returns immediately, even if the stream isn't closed yet. +Closes the wrapped stream handle if it hasn't been already closed. +You cannot read or write from/to a closed stream. + +Note: This call returns immediately, even if the stream hasn't fully closed yet. ---- diff --git a/docs/coro-fs.md b/docs/coro-fs.md index 3cff40f..d1022b2 100644 --- a/docs/coro-fs.md +++ b/docs/coro-fs.md @@ -2,17 +2,17 @@ layout: doc --- -# Documentation +# coro-fs Documentation for the module [coro-fs](https://github.com/luvit/lit/blob/master/deps/coro-fs.lua), version 2.2.4. -[coro-fs](https://github.com/luvit/lit/blob/master/deps/coro-fs.lua) is a library for asynchronous non-blocking filesystem manipulations while keeping the sync style of your code, making use of Lua coroutines. +[coro-fs](https://github.com/luvit/lit/blob/master/deps/coro-fs.lua) is a library for asynchronous non-blocking filesystem operations that keeps the synchronous code style, making use of Lua coroutines. -Luvit's built-in `fs` module already has asynchronous non-blocking operations (the calls not suffixed with `Sync`, as `Sync` calls *are* blocking!) but they use the ugly callbacks! +Luvit's built-in `fs` module already has asynchronous non-blocking operations (the calls not suffixed with `Sync`, as `Sync` calls *will* block a thread in the threadpool!) but they use the ugly callbacks! Note: that by choosing to do asynchronous FS, you are making a tradeoff. -First Luvit (and coro-fs) uses Libuv for filesystem IO, while the synchronous blocking calls in the Luvit `fs` API (e.x. `fs.writeFileSync`) will block the main thread (when executed outside of a luv callback), they *still* allow other I/O operations to happen, and the event loop can still tick just fine, this is because Libuv has a threadpool which all I/O happens in (by default 4 threads and can be changed with the `UV_THREADPOOL_SIZE` environment variable). So you can aboslutely be doing 4 blocking reads/writes (by default) before you actually run into issues where you need asynchronous FS. For example, if you are running a web server where you read a file and send it back, it is possible that you could receive 4 connections at the same time, they all concurrently read some file and send the responses back without interrupting each other. -The tradeoff you make by using asynchronous FS is performance, synchronous I/O are much faster but they block a thread in the threadpool, while asynchronous I/O is much slower but won't block any threads. Unless you are running a very busy server, I can't imagine you need asynchronous FS! +First off, Luvit (and coro-fs) uses Libuv for filesystem IO, while the synchronous blocking calls in the Luvit `fs` API (e.x. `fs.writeFileSync`) will block the main thread (when executed outside of a luv callback), they *still* allow other I/O operations to happen, and the event loop can still tick just fine, this is because Libuv has a threadpool which all I/O happens in (by default 4 threads and can be changed with the `UV_THREADPOOL_SIZE` environment variable). So you can aboslutely be doing 4 blocking reads/writes (by default) before you actually run into issues where you need asynchronous FS. For example, if you are running a web server where you read a file and send it back, it is possible that you could receive 4 connections at the same time, they all concurrently read some file and send the responses back without interrupting each other. +The tradeoff you make by using asynchronous FS is performance, synchronous I/O is much faster but it blocks a thread in the threadpool, while asynchronous I/O is much slower but won't block any threads. Unless you are running a very busy server, I can't imagine you need asynchronous FS! Another Note: Luvit built-in async fs *ALREADY* has coroutine support which achieve a similar purpose to this module: ```lua @@ -28,6 +28,13 @@ local contents = coro_fs.readFile('./my_file.txt') print("The file contains: ", contents) ``` +### Installation + +```sh +lit install creationix/coro-fs +``` +[On Lit search.](https://luvit.io/lit.html#coro-fs) + ---- ## Errors @@ -176,13 +183,10 @@ Identical to [stat](#stat), except that instead of accepting a path to file/dire ---- -### symlink(target, path) {#symlink} +### symlink(target, path, flags) {#symlink} Creates a symbolic link (also known as soft link) at `path` that points up to `target`. -***WARNING***: There is currently a bug with this, it was fixed in the latest GH version but not yet published to Lit. -***TODO***: In the next release of coro-fs a new parameter will be added, document it when it is published to Lit. - ***This method MUST be run in a coroutine*** #### Parameters {#symlink-parameters} @@ -191,6 +195,7 @@ Creates a symbolic link (also known as soft link) at `path` that points up to `t |:------|:------:|:------------| | target| string | The path of the file you want to link against. | | path | string | The path to where to create the said link at. | +| flags | table / integer / nil | Optional table with `dir` and `junction` boolean fields that control how the symlink is created on Windows. See [Libuv page](https://docs.libuv.org/en/v1.x/fs.html#c.uv_fs_symlink) for more details. | #### Returns {#symlink-returns} @@ -520,6 +525,23 @@ For example `mkdir("./a/b/c/")` this call will create a directory `a` that conta ### chroot(base) {#chroot} -***TODO***: Document this. +Returns a version of this module that can only operate inside `base` directory. +This is supposed to be safe and unescapable, and is used in sensitive places such as the Lit server. + +The table returned is identical to the module table, except an additional `base` field that is equal to the passed argument. + +***WARNING***: In version 2.2.1 and before, there was a bug that allowed an attacker to escape the chroot. If you are using a vulnerable version, please update to `2.2.2` or newer! *This method does not require running in a coroutine* + +#### Parameters {#mkdirp-parameters} + +| Param | Type | Description | Optional | +|:------|:------:|:------------|:--------:| +| base | string | The path to the directory FS operations will be contained in. | ❌ | + +#### Returns {#mkdirp-returns} + +| Name | Type | Description | Provided On | +|:-----|:------:|:------------|:-----------:| +| chroot | table | A table containing all methods this module contains. | Always | diff --git a/docs/coro-http.md b/docs/coro-http.md index c1f5e2f..f950eda 100644 --- a/docs/coro-http.md +++ b/docs/coro-http.md @@ -2,7 +2,7 @@ layout: doc --- -# Documentation +# coro-http Documentation for the library [coro-http](https://github.com/luvit/lit/blob/master/deps/coro-http.lua), version 3.2.3. @@ -14,6 +14,13 @@ Keep in mind, Luvit (starting with [version 2.18](https://github.com/luvit/luvit Many thanks for [@trumedian](https://github.com/truemedian) for helping out with this! +### Installation + +```sh +lit install creationix/coro-http +``` +[On Lit search.](https://luvit.io/lit.html#coro-http) + ---- ## Functions diff --git a/docs/coro-net.md b/docs/coro-net.md index 3ba8c0d..47ff750 100644 --- a/docs/coro-net.md +++ b/docs/coro-net.md @@ -2,11 +2,18 @@ layout: doc --- -# Documentation +# coro-net -Unofficial docs for the module [coro-net](https://github.com/luvit/lit/blob/master/deps/coro-net.lua) version 3.2.1. +Documentation for the module [coro-net](https://github.com/luvit/lit/blob/master/deps/coro-net.lua), version 3.2.1. -[coro-net](https://github.com/luvit/lit/blob/master/deps/coro-net.lua) is a library for TCP and pipes manipulations, with optional secure-layer support while keeping the sync code style. +[coro-net](https://github.com/luvit/lit/blob/master/deps/coro-net.lua) is a library for handling TCP, UDP and generally pipes, with an optional secure-layer support using a synchronous style interface. + +### Installation + +```sh +lit install creationix/coro-net +``` +[On Lit search.](https://luvit.io/lit.html#coro-net) ---- diff --git a/docs/coro-spawn.md b/docs/coro-spawn.md index ee78a62..cb29c1c 100644 --- a/docs/coro-spawn.md +++ b/docs/coro-spawn.md @@ -2,13 +2,20 @@ layout: doc --- -# Documentation +# coro-spawn -Unofficial docs for the [coro-spawn](https://github.com/luvit/lit/blob/master/deps/coro-spawn.lua) module, version 3.0.3. +Documentation for the [coro-spawn](https://github.com/luvit/lit/blob/master/deps/coro-spawn.lua) module, version 3.0.3. -[coro-spawn](https://github.com/luvit/lit/blob/master/deps/coro-spawn.lua) is single-function module that provides manipulating child-process in a sync style of code using Lua coroutines. +[coro-spawn](https://github.com/luvit/lit/blob/master/deps/coro-spawn.lua) is single-function module that provides spawning child-processes with a synchronous interface making use of Lua coroutines. -We will call the only function return of this module `spawn`. +Throughout the documentation, we will refer to the function return of this module as `spawn`. And it is obtained by calling `require("coro-spawn")`. + +### Installation + +```sh +lit install creationix/coro-spawn +``` +[On Lit search.](https://luvit.io/lit.html#coro-spawn) ---- @@ -18,9 +25,9 @@ We will call the only function return of this module `spawn`. ### spawn(path, options) {#spawn} -Spawns a process of an executable at `path` using the said `options`. +Spawns a process of an executable at `path` using the provided `options`. -Note: The `options` parameter is NOT optional, if you don't want to provide any options just pass an empty table. +Note: The `options` parameter is *NOT* optional, if you don't want to provide any options, pass an empty table. *This method does not require running in a coroutine* @@ -28,7 +35,7 @@ Note: The `options` parameter is NOT optional, if you don't want to provide any | Param | Type | Description | |:-----:|:------:|:------------| -| path | string | The path of the executable (defined by system) to be spawned. | +| path | string | The path of the executable to spawn. | | options | table| Various options to control the process flow. See [Spawn Options](#spawn-options). | #### Returns {#spawn-returns} diff --git a/docs/coro-split.md b/docs/coro-split.md index 10f2f57..642c10a 100644 --- a/docs/coro-split.md +++ b/docs/coro-split.md @@ -2,13 +2,20 @@ layout: doc --- -# Documentation +# coro-split -Unofficial docs for the [coro-split](https://github.com/luvit/lit/blob/master/deps/coro-split.lua) module, version 2.0.2. +Documentation for the [coro-split](https://github.com/luvit/lit/blob/master/deps/coro-split.lua) module, version 2.0.2. -[coro-split](https://github.com/luvit/lit/blob/master/deps/coro-split.lua) is single-function module that takes multiple functions as input, and runs them in concurrent coroutines. +[coro-split](https://github.com/luvit/lit/blob/master/deps/coro-split.lua) is single-function module that takes multiple functions as input, wraps each of them in a coroutine and runs it, and yields until all of the provided tasks are completed. -We will call the only function return of this module `split`. You should always call this inside a coroutine. +Throughout the documentation, we will refer to the function return of this module as `split`. And it is obtained by calling `require("coro-split")`. + +### Installation + +```sh +lit install creationix/coro-split +``` +[On Lit search.](https://luvit.io/lit.html#coro-split) ---- @@ -18,7 +25,9 @@ We will call the only function return of this module `split`. You should always ### split(...) {#split} -Takes multiple functions (tasks) and concurrently run them in coroutines while yielding the coroutine that called this, then resume it when all of the supplied tasks finishes. +Takes multiple functions (tasks) and runs them in coroutines while yielding the coroutine that made this call, then resumes it when all of the supplied tasks finishes. + +Returns the first return of every task. The tasks will be called directly without running them in protected mode, therefor any errors raised out by any of the provided tasks will be propagated, unless you call `split` using [pcall](https://www.lua.org/manual/5.4/manual.html#pdf-pcall) or [similar](https://www.lua.org/manual/5.4/manual.html#pdf-xpcall) functions. @@ -38,7 +47,7 @@ The tasks will be called directly without running them in protected mode, theref #### Examples {#split-examples} -- Concurrent FileSystem manipulations +- Concurrent Filesystem manipulations ```lua local fs = require('fs') @@ -70,4 +79,6 @@ split( ) ``` +TODO: This example is awful and meaningless, rewrite is needed. + ---- diff --git a/docs/coro-websocket.md b/docs/coro-websocket.md index d108ab2..b6ba9b8 100644 --- a/docs/coro-websocket.md +++ b/docs/coro-websocket.md @@ -2,11 +2,18 @@ layout: doc --- -# Documentation +# coro-websocket -Unofficial docs for the module [coro-websocket](https://github.com/luvit/lit/blob/master/deps/coro-websocket.lua) version 3.1.0. +Documentation for the module [coro-websocket](https://github.com/luvit/lit/blob/master/deps/coro-websocket.lua), version 3.1.0. -[coro-websocket](https://github.com/luvit/lit/blob/master/deps/coro-websocket.lua) is a library that implements the WebSocket WS(s) protocol with synchronous manipulations. +[coro-websocket](https://github.com/luvit/lit/blob/master/deps/coro-websocket.lua) is a library that implements the WebSocket WS(s) protocol with a synchronous style interface making use of Lua coroutines. + +### Installation + +```sh +lit install creationix/coro-websocket +``` +[On Lit search.](https://luvit.io/lit.html#coro-websocket) ---- diff --git a/docs/coro-wrapper.md b/docs/coro-wrapper.md index 9ab95bf..7354fd0 100644 --- a/docs/coro-wrapper.md +++ b/docs/coro-wrapper.md @@ -2,11 +2,21 @@ layout: doc --- -# Documentation +# coro-wrapper -Unofficial docs for the [coro-wrapper](https://github.com/luvit/lit/blob/master/deps/coro-wrapper.lua) module, version 3.1.0. +Documentation for the [coro-wrapper](https://github.com/luvit/lit/blob/master/deps/coro-wrapper.lua) module, version 3.1.0. -[coro-wrapper](https://github.com/luvit/lit/blob/master/deps/coro-wrapper.lua) is an adapter module for applying decoders to [coro-channel](https://bilal2453.github.io/coro-docs/docs/coro-channel.html). +[coro-wrapper](https://github.com/luvit/lit/blob/master/deps/coro-wrapper.lua) is an adapter module for applying decoders/encoders/merger adapters to [coro-channel](https://bilal2453.github.io/coro-docs/docs/coro-channel.html) readers and writers. + +Understanding this and how it works can be a bit tricky. But think about it like this: You have a reader that returns an HTTP chunk back as the raw HTTP string, and you want to make that reader return a parsed version of that HTTP chunk, so you apply the `decoder` adapter on that reader to provide an abstraction over the lower level return, this is in fact how [coro-http](https://bilal2453.github.io/coro-docs/docs/coro-http.html) does it! +The rest of the adapters has a similar concept. + +### Installation + +```sh +lit install creationix/coro-wrapper +``` +[On Lit search.](https://luvit.io/lit.html#coro-wrapper) ----