Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions moon.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"name": "oboard/mocket",
"version": "0.4.2",
"deps": {
"rami3l/js-ffi": "0.3.1",
"yj-qin/regexp": "0.3.6"
"yj-qin/regexp": "0.3.6",
"illusory0x0/native": "0.2.1",
"moonbitlang/x": "0.4.33",
"tonyfettes/uri": "0.1.0"
},
"readme": "README.md",
"repository": "https://github.com/oboard/mocket",
Expand Down
14 changes: 0 additions & 14 deletions src/content_wrapper.mbt

This file was deleted.

2 changes: 1 addition & 1 deletion src/event.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
pub struct HttpEvent {
req : HttpRequest
res : HttpResponse
mut params : Map[String, String]
params : Map[String, String]
}
32 changes: 18 additions & 14 deletions src/example/main.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,65 @@ fn main {
// Register global middleware
app
..use_middleware(event => println(
"📝 Request: \{event.req.method_} \{event.req.url}",
"📝 Request: \{event.req.http_method} \{event.req.url}",
))

// Text Response
..get("/", _event => "⚡️ Tadaa!")
..get("/", _event => Text("⚡️ Tadaa!"))

// Hello World
..on("GET", "/hello", _ => "Hello world!")
..on("GET", "/hello", _ => Text("Hello world!"))
..group("/api", group => {
// 添加组级中间件
group.use_middleware(event => println(
"🔒 API Group Middleware: \{event.req.method_} \{event.req.url}",
"🔒 API Group Middleware: \{event.req.http_method} \{event.req.url}",
))
group.get("/hello", _ => "Hello world!")
group.get("/json", _ => {
group.get("/hello", _ => Text("Hello world!"))
group.get("/json", _ => Json({
"name": "John Doe",
"age": 30,
"city": "New York",
})
}))
})

// JSON Response
..get("/json", _event => { "name": "John Doe", "age": 30, "city": "New York" })
..get("/json", _event => Json({
"name": "John Doe",
"age": 30,
"city": "New York",
}))

// Async Response
..get("/async_data", async fn(_event) noraise {
{ "name": "John Doe", "age": 30, "city": "New York" }
Json({ "name": "John Doe", "age": 30, "city": "New York" })
})

// Dynamic Routes
// /hello2/World = Hello, World!
..get("/hello/:name", fn(event) {
let name = event.params.get("name").unwrap_or("World")
"Hello, \{name}!"
Text("Hello, \{name}!")
})
// /hello2/World = Hello, World!
..get("/hello2/*", fn(event) {
let name = event.params.get("_").unwrap_or("World")
"Hello, \{name}!"
Text("Hello, \{name}!")
})

// Wildcard Routes
// /hello3/World/World = Hello, World/World!
..get("/hello3/**", fn(event) {
let name = event.params.get("_").unwrap_or("World")
"Hello, \{name}!"
Text("Hello, \{name}!")
})

// Echo Server
..post("/echo", e => e.req.body.to_json())
..post("/echo", e => e.req.body)

// 404 Page
..get("/404", e => {
e.res.status_code = 404
@mocket.html(
HTML(
(
#|<html>
#|<body>
Expand Down
58 changes: 35 additions & 23 deletions src/index.mbt
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
///|
pub(all) enum HttpBody {
Json(Json)
Text(String)
HTML(String)
Bytes(Bytes)
Empty
}

///|
pub(all) struct Mocket {
base_path : String
mappings : Map[(String, String), async (HttpEvent) -> Json noraise]
mappings : Map[(String, String), async (HttpEvent) -> HttpBody noraise]
middlewares : Array[(String, async (HttpEvent) -> Unit noraise)]
// 添加静态路由缓存(精确匹配的路由)
static_routes : Map[String, Map[String, async (HttpEvent) -> Json noraise]]
static_routes : Map[
String,
Map[String, async (HttpEvent) -> HttpBody noraise],
]
// 添加动态路由缓存(包含参数的路由)
dynamic_routes : Map[
String,
Array[(String, async (HttpEvent) -> Json noraise)],
Array[(String, async (HttpEvent) -> HttpBody noraise)],
]
// 日志记录器
logger : Logger
Expand All @@ -34,17 +46,17 @@ pub fn new(

///|
pub struct HttpRequest {
method_ : String
http_method : String
url : String
headers : Map[String, String]
mut body : String
mut body : HttpBody
}

///|
pub(all) struct HttpResponse {
mut status_code : Int
headers : Map[String, String]
// body : String
// body : Body
}

///|
Expand Down Expand Up @@ -78,7 +90,7 @@ pub fn on(
self : Mocket,
event : String,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
let path = self.base_path + path
self.logger.route_register(event, path)
Expand All @@ -89,14 +101,14 @@ pub fn on(
// 静态路径,直接缓存
self.logger.route_static(event, path)
match self.static_routes.get(event) {
Some(method_routes) => {
Some(http_methodroutes) => {
self.logger.route_merge_existing(event)
method_routes.set(path, handler)
http_methodroutes.set(path, handler)
self.logger.route_added(path)
}
None => {
self.logger.route_merge_new(event)
let new_routes : Map[String, async (HttpEvent) -> Json noraise] = {}
let new_routes : Map[String, async (HttpEvent) -> HttpBody noraise] = {}
new_routes.set(path, handler)
self.static_routes.set(event, new_routes)
self.logger.route_created(path)
Expand All @@ -119,7 +131,7 @@ pub fn on(
pub fn get(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("GET", path, handler)
}
Expand All @@ -128,7 +140,7 @@ pub fn get(
pub fn post(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("POST", path, handler)
}
Expand All @@ -137,7 +149,7 @@ pub fn post(
pub fn patch(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("PATCH", path, handler)
}
Expand All @@ -146,7 +158,7 @@ pub fn patch(
pub fn connect(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("CONNECT", path, handler)
}
Expand All @@ -155,7 +167,7 @@ pub fn connect(
pub fn put(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("PUT", path, handler)
}
Expand All @@ -164,7 +176,7 @@ pub fn put(
pub fn delete(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("DELETE", path, handler)
}
Expand All @@ -173,7 +185,7 @@ pub fn delete(
pub fn head(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("HEAD", path, handler)
}
Expand All @@ -182,7 +194,7 @@ pub fn head(
pub fn options(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("OPTIONS", path, handler)
}
Expand All @@ -191,7 +203,7 @@ pub fn options(
pub fn trace(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("TRACE", path, handler)
}
Expand All @@ -200,7 +212,7 @@ pub fn trace(
pub fn all(
self : Mocket,
path : String,
handler : async (HttpEvent) -> Json noraise,
handler : async (HttpEvent) -> HttpBody noraise,
) -> Unit {
self.on("*", path, handler)
}
Expand All @@ -219,12 +231,12 @@ pub fn group(
group.static_routes
.iter()
.each(i => {
let method_ = i.0
let http_method = i.0
let group_routes = i.1
match self.static_routes.get(method_) {
match self.static_routes.get(http_method) {
Some(existing_routes) =>
group_routes.iter().each(route => existing_routes.set(route.0, route.1))
None => self.static_routes.set(method_, group_routes)
None => self.static_routes.set(http_method, group_routes)
}
})
group.dynamic_routes
Expand Down
89 changes: 89 additions & 0 deletions src/js/async.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
///|
pub async fn[T, E : Error] suspend(
f : ((T) -> Unit, (E) -> Unit) -> Unit,
) -> T raise E = "%async.suspend"

///|
pub fn async_run(f : async () -> Unit noraise) -> Unit = "%async.run"

///|
/// # Safety
///
/// You should always `Promise::wait` on any `Promise` you own to attach
/// exception handlers, as long as you are responsible for the control flow
/// (i.e. this `Promise` is not handled by an external JavaScript API).
/// This makes sure that when the operation in the this `Promise` errs out,
/// the error is caught by the MoonBit runtime.
#external
pub type Promise

///|
extern "js" fn Promise::wait_ffi(
self : Promise,
on_ok : (Value) -> Unit,
on_err : (Value) -> Unit,
) -> Unit =
#| (self, on_ok, on_err) => self.then((t) => on_ok(t), (e) => on_err(e))

///|
pub async fn Promise::wait(self : Promise) -> Value raise {
suspend(fn(k, ke) { Promise::wait_ffi(self, k, fn(e) { ke(Error_(e)) }) })
}

///|
/// # Safety
///
/// You should always `Promise::wait` on the result of this function to attach
/// exception handlers, as long as you are responsible for the control flow
/// (i.e. the resulting `Promise` is not handled by an external JavaScript API).
/// This makes sure that when `op` errs out, the error is caught by the MoonBit runtime.
///
/// If you don't care about the result of the operation, you can use `spawn_detach` instead.
pub fn[T] Promise::unsafe_new(op : async () -> T raise) -> Promise {
Promise::new_ffi(fn() { Value::cast_from(op()) })
}

///|
extern "js" fn Promise::new_ffi(op : async () -> Value raise) -> Promise =
#| (op) => new Promise((k, ke) => op(k, ke))

///|
pub fn[T, E : Error] spawn_detach(op : async () -> T raise E) -> Unit {
async_run(fn() {
try op() |> ignore catch {
_ => ()
}
})
}

///|
/// # Note
/// The return type is guaranteed to be a `Promise` of an `Array`, but we omit that detail for simplicity.
pub extern "js" fn Promise::all(promises : Array[Promise]) -> Promise = "(ps) => Promise.all(ps)"

///|
/// Wraps each given `async fn` in a `Promise` and waits for all of them to resolve.
pub async fn[T] async_all(ops : Array[async () -> T raise]) -> Array[T] raise {
async_all_raw(ops.map(fn(op) { async fn() raise { Value::cast_from(op()) } })).map(
Value::cast,
)
}

///|
async fn async_all_raw(
ops : Array[async () -> Value raise],
) -> Array[Value] raise {
Promise::all(ops.map(Promise::unsafe_new)).wait().cast()
}

///|
pub fn async_test(op : async () -> Unit raise) -> Unit {
async_run(async fn() noraise {
op() catch {
e => {
println("ERROR in `async_test`: \{e}")
panic()
}
}
})
}
Loading
Loading