summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md268
1 files changed, 267 insertions, 1 deletions
diff --git a/README.md b/README.md
index afe2fd2..5a40fa9 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,271 @@
+# Luna
+
+Luna is a simple web server programmable via opinionated Lua API. It has the
+following key features:
+
+* support for both PUC Lua and luajit
+* sqlite3 for data storage
+* asynchronous I/O operations
+* small and maintainable codebase
+* handles relatively high number of requests per second
+
+
+## Build
+
+You'll need `lua` or `luajit` libraries installed on your system:
+
+```sh
+git clone https://git.sr.ht/~unwox/luna
+cd luna
+go build -tags=puc . # for lua
+go build -tags=jit . # for luajit
+```
+
+To install the server to the $GOBIN ($GOPATH/bin by default) directory:
+
+```sh
+go install -tags=puc . # for lua
+go install -tags=jit . # for luajit
+```
+
+
+## Usage
+
+```
+luna [options] LUAFILE
+ -D Debug/development mode
+ -l string
+ Address HTTP-server will listen to (default "127.0.0.1:3000")
+ -n int
+ Number of HTTP-workers to start (default $(nproc))
+```
+
+LUAFILE must specify a Lua file that may or may not register routes to handle
+(via calling `luna.route.static` or `luna.route.route`). Whatever the LUAFILE
+returns is ignored. If LUAFILE defines no routes luna will execute the file and
+then exit. This behavior is useful when you want to have a script file with an
+access to the luna API.
+
+If luna is started with -D flag it will accept user input into its stdio. The
+input is executed in Lua state as is or (if `luna.evalfn` was called in
+LUAFILE) is passed to a custom eval handler.
+
+## Lua API
+
+All API functions return 2 values: the first one is a boolean indicating if the
+call was successful (true for successful and false if there was an error) and
+the second one is the value (or an error text if the call was unsuccessful).
+
+API names are hyphenated since I am mostly calling the functions from
+[fennel language](https://fennel-lang.org/) and for lisps hyphenation is the
+norm.
+
+Every argument in every API function is required: luna does not support optinal
+arguments yet. Pass an empty value for corresponding type instead if you do not
+want to specify an argument: {} for tables, "" for strings, 0 for numbers.
+
+
+__luna.router.route(pattern, handler)__:
+registers a new route for Go to proxy to Lua. For pattern specification see
+[Go documentation](https://pkg.go.dev/net/http#hdr-Patterns-ServeMux). Handler
+is a Lua function that accepts a table representing request and returns 3
+values: response status code, headers and body.
+
+```lua
+luna.router.route("GET /test", function (request)
+ return 200, {["Content-Type"] = "application/json"}, "{\"foo\": \"world\"}"
+end)
+```
+
+
+__luna.router.static(pattern, directory)__:
+registers a new route for Go to serve static files.
+
+```lua
+luna.router.static("GET /static/", "./whatever/path/static")
+```
+
+
+__luna.http.request(method, url, headers, body)__:
+sends an HTTP request returning a response object:
+`{status = ..., headers = ..., body = ...}`.
+
+```lua
+luna.http.request("GET", "https://git.sr.ht", {Accept = "text/html"}, "")
+```
+
+
+__luna.http\["encode-url"\](string)__:
+encodes a STRING for a safe usage in URLs.
+
+
+__luna.db.open(file)__:
+opens a connection to an sqlite database.
+For FILE specification see
+[mattn/go-sqlite3 library documentation](https://github.com/mattn/go-sqlite3?tab=readme-ov-file#connection-string).
+
+```lua
+local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL")
+```
+
+
+__luna.db.begin(db)__:
+starts a transaction for a DB.
+
+```lua
+local ok, tx = luna.db.begin(db)
+```
+
+
+__luna.db.commit(tx)__:
+commits a transaction TX.
+
+```lua
+local ok, tx = luna.db.begin(db)
+local ok, err = luna.db["exec-tx"](tx, "DELETE FROM foobar;")
+local ok, err = luna.db.commit(tx)
+```
+
+
+__luna.db.rollback(tx)__:
+rolls back a transaction TX.
+
+```lua
+local ok, tx = luna.db.begin(db)
+local ok, err = luna.db["exec-tx"](tx, "DELETE FROM foobar;", {})
+local ok, err = luna.db.rollback(tx)
+-- the tx was rolled back so "DELETE FROM foobar;" isn't executed
+```
+
+
+__luna.db\["exec-tx"\](tx, query, args)__:
+executes a QUERY with ARGS in the context of a given transaction TX.
+
+```lua
+local ok, tx = luna.db.begin(db)
+local ok, err = luna.db["exec-tx"](
+ tx,
+ "INSERT INTO foobar VALUES (?, ?, ?);"
+ {1, "hello!", "2024-12-26 12:00:00"}
+)
+```
+
+
+__luna.db.exec(db, query, args)__:
+executes a QUERY with ARGS in a given DB:
+
+```lua
+local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL")
+local ok, err = luna.db.exec(
+ db,
+ "INSERT INTO foobar VALUES (?, ?, ?);"
+ {1, "hello!", "2024-12-26 12:00:00"}
+)
+```
+
+
+__luna.db.query(db, query, args)__:
+executes a QUERY with ARGS in a given DB and returns result as an array of
+arrays:
+
+```lua
+local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL")
+local ok, res = luna.db.query(
+ db,
+ "SELECT * FROM foobar WHERE name = '?'",
+ {"hello!"}
+)
+-- if the table foobar has 3 columns (id, name, creation_time) the RES variable
+-- would contain something like this:
+-- {{1, "hello!", "2024-12-26 12:00:00"}}
+```
+
+
+__luna.db.\["query\*"\](db, query, args)__:
+executes a QUERY with ARGS in a given DB and returns result as an array of
+tables where keys are column names and values are associated value:
+
+```lua
+local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL")
+local ok, res = luna.db["query*"](
+ db,
+ "SELECT * FROM foobar WHERE name = '?'",
+ {"hello!"}
+)
+-- if the table foobar has 3 columns (id, name, creation_time) the RES variable
+-- would contain something like this:
+-- {{id = 1, name = "hello!", creation_time = "2024-12-26 12:00:00"}}
+```
+
+
+__luna.db.close(db)__:
+closes a connection to DB.
+
+```lua
+local ok, db = luna.db.open("file:var/db.sqlite?_journal=WAL&_sync=NORMAL")
+local ok, err = luna.db.close(db)
+```
+
+
+__luna.utf8.len(string)__:
+returns a number of UTF-8 symbols in a STRING.
+
+
+__luna.utf8.lower(string)__:
+returns a copy of a STRING with all letters mapped to their lower case.
+
+
+__luna.utf8.upper(string)__:
+returns a copy of a STRING with all letters mapped to their upper case.
+
+
+__luna.utf8.sub(string, start, length)__:
+returns a substring of a STRING starting at index START (1-indexed position)
+with a LENGTH.
+
+
+__luna.crypto.sha1(string)__:
+returns hex-encoded SHA1 hash of a STRING.
+
+
+__luna.evalfn(handler)__:
+sets an eval handler for server commands. Handler accepts one argument TEXT. If
+the server is started with -D flag it starts listening for input from stdio. In
+this case if eval handler is set the handler will receive the user input and
+should handle it appropriately. If eval handler is not set REPL input is
+executed in Lua state as is.
+
+```
+local fennel = require("fennel")
+luna.evalfn(function (text) fennel.eval(text, {env = _G}) end)
+```
+
+
+__luna.debug__:
+a variable that indicates whether the server was started with -D (debug) flag.
+
+
+## Contribution
+
+Send patches to me@unwox.com. Possible ideas for patches are
+
+* adding more *useful* API functions. It's easy to do, see `main.go` for that.
+* replacing sqlite with something else since its sqlite3_step function is
+ blocking and it's the major perfomance bottleneck right now.
+* supporting redis for synchronization, maybe?
+* supporting websocket connections, maybe?
+* compiling Lua code and luna into one binary, maybe?
+
+
+I will not accept patches that are
+
+* relying on too many Go (or external) dependencies.
+* adding too much complexity into the existing codebase.
+* adding a new major functionality that should be implemented outside
+ (for example service management, use systemd, shepher or whatever).
+
+
## Useful links
* https://lucasklassmann.com/blog/2019-02-02-embedding-lua-in-c/
* https://pgl.yoyo.org/luai/i/3.7+Functions+and+Types
-