README.md
1 # CRUD Example (Phase 6) 2 3 This example shows how Phase 6 APIs fit together for an ergonomic app-facing flow: 4 5 - `lightspeed/router` resolves paths to views and typed event decoders. 6 - `lightspeed/event` enforces event names and decodes typed payloads. 7 - `lightspeed/form` binds form fields into typed values. 8 - `lightspeed/component/helpers` builds navigation/subscription/patch commands. 9 10 ```gleam 11 import lightspeed/component/helpers 12 import lightspeed/event 13 import lightspeed/form 14 import lightspeed/router 15 16 type Msg { 17 SaveTodo(id: Int, title: String) 18 DeleteTodo(id: Int) 19 } 20 21 fn todo_decoder( 22 inbound: event.InboundEvent, 23 params: List(router.RouteParam), 24 ) -> Result(Msg, event.DecodeError) { 25 case inbound.name { 26 "delete" -> 27 case params { 28 [router.RouteParam("id", id_text)] -> 29 case form.int(form.parse_payload("id=" <> id_text), "id") { 30 Ok(id) -> Ok(DeleteTodo(id)) 31 Error(error) -> Error(event.InvalidForm(error)) 32 } 33 _ -> Error(event.InvalidForm(form.MissingField("id"))) 34 } 35 36 _ -> 37 event.decode_form(inbound, "save", fn(payload) { 38 case params { 39 [router.RouteParam("id", id_text)] -> 40 case form.int(form.parse_payload("id=" <> id_text), "id") { 41 Error(error) -> Error(error) 42 Ok(id) -> 43 case form.require(payload, "title") { 44 Error(error) -> Error(error) 45 Ok(title) -> Ok(SaveTodo(id, title)) 46 } 47 } 48 _ -> Error(form.MissingField("id")) 49 } 50 }) 51 } 52 } 53 54 fn app_router() { 55 router.new("todos_not_found") 56 |> router.add("/todos/:id/edit", "todos_edit", todo_decoder) 57 } 58 59 fn update(model, msg) { 60 case msg { 61 SaveTodo(_, _) -> 62 #(model, [helpers.navigate("/todos")]) 63 64 DeleteTodo(_) -> 65 #(model, [helpers.navigate("/todos")]) 66 } 67 } 68 ``` 69 70 This flow lets application code stay in typed Gleam while runtime internals remain separate. 71