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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ Examples of runtimes that work with this model:

- [Bun.sh] -> [Fable.Bun] + Bix.Bun
- [Deno] -> [Fable.Deno] + Bix.Deno
- Service Workers -> (Adapter and Investigation in progress)
- [Cloudflare Workers] -> (Under Investigation)
- Service Workers
- Browser Service Worker
- [Cloudflare Workers] -> Bix.Cloudflare

This microframework is heavily inspired by [Giraffe], and [Saturn] frameworks from F# land so if you have ever used that server model then Bix will feel fairly similar.

Expand Down
82 changes: 77 additions & 5 deletions samples/Worker/src/Worker.fs
Original file line number Diff line number Diff line change
@@ -1,15 +1,87 @@
open Bix
open Bix.Router
open Bix.Types
open Bix.Handlers
open Bix.Cloudflare

let private routes =
let private postHandler: HttpHandler =
fun next ctx ->
promise {
let! content = ctx.Request.json ()
return! (sendJson content) next ctx
}


let private basicRouter =
Router.Empty
|> Router.get ("/", sendHtml "<h1>Hello, World!</h1>")
|> Router.get ("/text", sendText "Hello World!")
|> Router.get ("/json", sendJson {| message = "Hello, World!" |})
|> Router.get ("/basic", sendHtml "<h1>Hello, World!</h1>")
|> Router.post ("/text", postHandler)
|> Router.patch ("/json", sendJson {| message = "posted to /json" |})
|> Router.subRoute (
"/profiles",
(Router.get ("/", sendText "profiles")
>> Router.post ("/", sendText "create profile")
>> Router.put ("/:id", sendText "update profile")
>> Router.delete ("/:id", sendText "delete profile"))
)

[<RequireQualifiedAccess>]
module GiraffeLike =

open Bix.Router.Giraffe

let routes =
choose
[ route "/g" (sendText "g")
GET [ route "/g1" (sendText "G1") ]
POST [ route "/g2" postHandler ]
subRoute
"/products"
[ GET
[ route "/" (sendText "hit get all products")
route "/:id" (sendText "hit get id product") ]
POST
[ route "/" (sendText "hit products post")
route "/:id/category" (sendText "hit products id category post") ]
subRoute
"/:id/offers"
[ route "/admin" (sendText "hit all offers for product id admin")
GET
[ route "" (sendText "hit get offers")
route "/:id" (sendText "hit get offer id get") ] ] ] ]

[<RequireQualifiedAccess>]
module SaturnLike =
open Bix.Router.Saturn

let routes =
router {
get "/" (sendHtml "<h1>Hello, World!</h1>")
post "/json" postHandler

forward
"/offers"
(router {
get "/" (sendText "hit get offers")
forward "/:id/tags" (router { get "/" (sendText "hit get offers id tags") })
})

forward
"/todos"
(controller {
find (fun _ -> Text "Found Hit" |> Promise.lift)
findOne (fun id _ -> Text $"Found Hit %A{id}" |> Promise.lift)
create (fun _ -> Text $"Create Hit" |> Promise.lift)
update (fun _ -> Text $"Update Hit" |> Promise.lift)
updateOne (fun id _ -> Text $"Update Hit %A{id}" |> Promise.lift)
delete (fun id _ -> Text $"Delete Hit %A{id}" |> Promise.lift)
})
}

let private worker = Worker.Empty |> Worker.withRouter routes |> Worker.build
let private worker =
Worker.Empty
|> Worker.withRouter [ yield! basicRouter; yield! SaturnLike.routes; yield! GiraffeLike.routes ]
|> Worker.build

// ES Modules need to export the fetch handler as per cloudflare documentation
Fable.Core.JsInterop.exportDefault worker
1 change: 1 addition & 0 deletions src/Bix.Cloudflare/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Cloudflare Workers
17 changes: 15 additions & 2 deletions src/Bix.Cloudflare/Worker.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
open Fable.Core
open Browser.Types
open Bix
open Bix.Router
open Bix.Handlers
open Bix.Types
open Fetch
Expand All @@ -26,7 +25,7 @@ type BixWorkerArgs = Fetch of response: (Request -> JS.Promise<Response>)
type WorkerServer(url: URL) =
interface IHostServer with
override _.hostname: string option = url.origin |> Option.ofObj
override _.port: int = 0
override _.port: int = url.port |> int
override _.development: bool = false
override _.env: Map<string, string> = Map.empty

Expand All @@ -42,10 +41,24 @@ let BixHandler (routes: RouteDefinition list) (notFound: HttpHandler option) (re
let Empty = ResizeArray<BixWorkerArgs>()

let withRouter (routes: RouteDefinition list) (args: ResizeArray<BixWorkerArgs>) =
#if DEBUG

for route in routes |> List.sortBy (fun r -> unbox<string> r.pattern) do
let verb = route.method.asString
let pattern = route.pattern
printfn $"Registered: {verb} -> {pattern}"
#endif
args.Add(Fetch(BixHandler routes None))
args

let withRouterAndNotFound (routes: RouteDefinition list) (notFound: HttpHandler) (args: ResizeArray<BixWorkerArgs>) =
#if DEBUG

for route in routes |> List.sortBy (fun r -> unbox<string> r.pattern) do
let verb = route.method.asString
let pattern = route.pattern
printfn $"Registered: {verb} -> {pattern}"
#endif
args.Add(Fetch(BixHandler routes (Some notFound)))
args

Expand Down
2 changes: 2 additions & 0 deletions src/Bix/Bix.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<Compile Include="Handlers.fs" />
<Compile Include="Bix.fs" />
<Compile Include="Router.fs" />
<Compile Include="Giraffe.fs" />
<Compile Include="Saturn.fs" />
</ItemGroup>
<ItemGroup>
<Content Include="*.fsproj; *.fs;" PackagePath="fable\" />
Expand Down
2 changes: 1 addition & 1 deletion src/Bix/Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Extensions

open Bix.Types

let compose (h1: HttpHandler) (h2: HttpHandler) : HttpHandler =
let inline compose (h1: HttpHandler) (h2: HttpHandler) : HttpHandler =
fun final ->
let fn = final |> h2 |> h1

Expand Down
56 changes: 56 additions & 0 deletions src/Bix/Giraffe.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Bix.Router.Giraffe

open Fable.Core
open Bix.Types

type GiraffeRoute =
| Route of RouteDefinition
| Routes of RouteDefinition list

let inline route url handler =
{ method = All
pattern = unbox url
handler = handler }
|> Route

let inline flattenGRoutes (routeType: RouteType) (routes: GiraffeRoute list) : GiraffeRoute =
routes
|> List.fold
(fun (prev: RouteDefinition list) next ->
match next with
| Route route -> { route with method = routeType } :: prev
| Routes routes -> (routes |> List.map (fun r -> { r with method = routeType })) @ prev)
List.empty<RouteDefinition>
|> Routes

let inline choose (routes: GiraffeRoute list) : RouteDefinition list =
[ for routes in routes do
match routes with
| Route route -> route
| Routes routes -> yield! routes ]

let inline subRoute (url: string) (routes: GiraffeRoute list) =
[ for route in routes do
match route with
| Route route -> { route with pattern = unbox $"{url}{route.pattern}" }
| Routes routes ->
yield!
routes
|> List.map (fun route -> { route with pattern = unbox $"{url}{route.pattern}" }) ]
|> Routes

let inline GET (routes: GiraffeRoute list) = flattenGRoutes Get routes

let inline POST (routes: GiraffeRoute list) = flattenGRoutes Post routes

let inline PUT (routes: GiraffeRoute list) = flattenGRoutes Put routes

let inline PATCH (routes: GiraffeRoute list) = flattenGRoutes Patch routes

let inline DELETE (routes: GiraffeRoute list) = flattenGRoutes Delete routes

let inline HEAD (routes: GiraffeRoute list) = flattenGRoutes Head routes

let inline OPTIONS (routes: GiraffeRoute list) = flattenGRoutes Options routes

let inline CUSTOM (method: string) (routes: GiraffeRoute list) = flattenGRoutes (Custom method) routes
Loading