Type-safe GraphQL client generator for Gleam

move registry to unstable_registry since its not ready yet

+6
CHANGELOG.md
··· 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 8 ## [1.1.0] - 2025-11-12 9 10 ### Added
··· 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 8 + ## [1.1.1] - 2025-11-12 9 + 10 + ### Changed 11 + 12 + - **Renamed `registry` module to `unstable_registry`** - The cache registry module is experimental 13 + 14 ## [1.1.0] - 2025-11-12 15 16 ### Added
-24
examples/03-unstable-cache/README.md
··· 1 - # cache_example 2 - 3 - [![Package Version](https://img.shields.io/hexpm/v/cache_example)](https://hex.pm/packages/cache_example) 4 - [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/cache_example/) 5 - 6 - ```sh 7 - gleam add cache_example@1 8 - ``` 9 - ```gleam 10 - import cache_example 11 - 12 - pub fn main() -> Nil { 13 - // TODO: An example of the project in use 14 - } 15 - ``` 16 - 17 - Further documentation can be found at <https://hexdocs.pm/cache_example>. 18 - 19 - ## Development 20 - 21 - ```sh 22 - gleam run # Run the project 23 - gleam test # Run the tests 24 - ```
···
-27
examples/03-unstable-cache/gleam.toml
··· 1 - name = "cache_example" 2 - version = "1.0.0" 3 - target = "javascript" 4 - 5 - # Fill out these fields if you intend to generate HTML documentation or publish 6 - # your project to the Hex package manager. 7 - # 8 - # description = "" 9 - # licences = ["Apache-2.0"] 10 - # repository = { type = "github", user = "", repo = "" } 11 - # links = [{ title = "Website", href = "" }] 12 - # 13 - # For a full reference of all the available options, you can have a look at 14 - # https://gleam.run/writing-gleam/gleam-toml/. 15 - 16 - [dependencies] 17 - gleam_stdlib = ">= 0.44.0 and < 2.0.0" 18 - lustre = ">= 5.0.0 and < 6.0.0" 19 - gleam_json = ">= 3.0.0 and < 4.0.0" 20 - squall = { path = "../../" } 21 - squall_cache = { path = "/Users/chadmiller/code/squall_cache" } 22 - modem = ">= 2.0.0 and < 3.0.0" 23 - gleam_http = ">= 4.3.0 and < 5.0.0" 24 - 25 - [dev-dependencies] 26 - gleeunit = ">= 1.0.0 and < 2.0.0" 27 - lustre_dev_tools = ">= 2.2.2 and < 3.0.0"
···
-15
examples/03-unstable-cache/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 - <title>Squall Cache Example - Rick and Morty Characters</title> 7 - </head> 8 - <body> 9 - <div id="app"></div> 10 - <script type="module"> 11 - import { main } from "./build/dev/javascript/cache_example/cache_example.mjs"; 12 - main(); 13 - </script> 14 - </body> 15 - </html>
···
-61
examples/03-unstable-cache/manifest.toml
··· 1 - # This file was generated by Gleam 2 - # You typically do not need to edit this file 3 - 4 - packages = [ 5 - { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, 6 - { name = "booklet", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "booklet", source = "hex", outer_checksum = "08E0FDB78DC4D8A5D3C80295B021505C7D2A2E7B6C6D5EAB7286C36F4A53C851" }, 7 - { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, 8 - { name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, 9 - { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, 10 - { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 11 - { name = "glam", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "237C2CE218A2A0A5D46D625F8EF5B78F964BC91018B78D692B17E1AB84295229" }, 12 - { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, 13 - { name = "gleam_community_colour", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "E34DD2C896AC3792151EDA939DA435FF3B69922F33415ED3C4406C932FBE9634" }, 14 - { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, 15 - { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, 16 - { name = "gleam_fetch", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "2CBF9F2E1C71AEBBFB13A9D5720CD8DB4263EB02FE60C5A7A1C6E17B0151C20C" }, 17 - { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, 18 - { name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" }, 19 - { name = "gleam_javascript", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" }, 20 - { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, 21 - { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, 22 - { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, 23 - { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, 24 - { name = "gleam_time", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "D560E672C7279C89908981E068DF07FD16D0C859DCA266F908B18F04DF0EB8E6" }, 25 - { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, 26 - { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 27 - { name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" }, 28 - { name = "glisten", version = "8.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "534BB27C71FB9E506345A767C0D76B17A9E9199934340C975DC003C710E3692D" }, 29 - { name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" }, 30 - { name = "group_registry", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "group_registry", source = "hex", outer_checksum = "BC798A53D6F2406DB94E27CB45C57052CB56B32ACF7CC16EA20F6BAEC7E36B90" }, 31 - { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, 32 - { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, 33 - { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 34 - { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, 35 - { name = "lustre", version = "5.4.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "40E097BABCE65FB7C460C073078611F7F5802EB07E1A9BFB5C229F71B60F8E50" }, 36 - { name = "lustre_dev_tools", version = "2.2.2", build_tools = ["gleam"], requirements = ["argv", "booklet", "filepath", "gleam_community_ansi", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_regexp", "gleam_stdlib", "glint", "group_registry", "justin", "lustre", "mist", "polly", "simplifile", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "C66A09B0B268B596F021971E4E4111E4B2B4A1DF5BDFE6C28A1E1FE4CA371F1A" }, 37 - { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, 38 - { name = "mist", version = "5.0.3", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7C4BE717A81305323C47C8A591E6B9BA4AC7F56354BF70B4D3DF08CC01192668" }, 39 - { name = "modem", version = "2.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre"], otp_app = "modem", source = "hex", outer_checksum = "3F9682EBCBF4D26045F1038A7507E8C7967E49D43F9CA6BA68EF0C971B195A7F" }, 40 - { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, 41 - { name = "polly", version = "2.1.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "simplifile"], otp_app = "polly", source = "hex", outer_checksum = "1BA4D0ACE9BCF52AEA6AD9DE020FD8220CCA399A379E50A1775FC5C1204FCF56" }, 42 - { name = "simplifile", version = "2.3.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "957E0E5B75927659F1D2A1B7B75D7B9BA96FAA8D0C53EA71C4AD9CD0C6B848F6" }, 43 - { name = "snag", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "274F41D6C3ECF99F7686FDCE54183333E41D2C1CA5A3A673F9A8B2C7A4401077" }, 44 - { name = "squall", version = "1.0.1", build_tools = ["gleam"], requirements = ["argv", "filepath", "glam", "gleam_http", "gleam_httpc", "gleam_json", "gleam_stdlib", "simplifile", "swell"], source = "local", path = "../.." }, 45 - { name = "squall_cache", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_fetch", "gleam_http", "gleam_javascript", "gleam_json", "gleam_stdlib", "lustre", "squall"], source = "local", path = "../../../squall_cache" }, 46 - { name = "swell", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "swell", source = "hex", outer_checksum = "CA28A95AB9A01E51D1FCFC0FC4F7102C7F47D04B9711138248D08C2A9F1FE6A3" }, 47 - { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, 48 - { name = "tom", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "74D0C5A3761F7A7D06994755D4D5AD854122EF8E9F9F76A3E7547606D8C77091" }, 49 - { name = "wisp", version = "2.1.0", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "362BDDD11BF48EB38CDE51A73BC7D1B89581B395CA998E3F23F11EC026151C54" }, 50 - ] 51 - 52 - [requirements] 53 - gleam_json = { version = ">= 3.0.0 and < 4.0.0" } 54 - gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 55 - gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 56 - lustre = { version = ">= 5.0.0 and < 6.0.0" } 57 - lustre_dev_tools = { version = ">= 2.2.2 and < 3.0.0" } 58 - modem = { version = ">= 2.0.0 and < 3.0.0" } 59 - squall = { path = "../../" } 60 - squall_cache = { path = "../../../squall_cache" } 61 - gleam_http = { version = ">= 4.3.0 and < 5.0.0" }
···
-316
examples/03-unstable-cache/src/cache_example.gleam
··· 1 - import components/character_detail 2 - import components/character_list 3 - import components/episode_list 4 - import components/location_list 5 - import generated/queries 6 - import generated/queries/get_character 7 - import generated/queries/get_characters 8 - import generated/queries/get_episodes 9 - import generated/queries/get_locations 10 - import gleam/json.{type Json} 11 - import gleam/string 12 - import gleam/uri 13 - import lustre 14 - import lustre/attribute 15 - import lustre/effect.{type Effect} 16 - import lustre/element.{type Element} 17 - import lustre/element/html 18 - import modem 19 - import squall/registry 20 - import squall_cache 21 - 22 - pub fn main() { 23 - let app = lustre.application(init, update, view) 24 - let assert Ok(_) = lustre.start(app, "#app", Nil) 25 - } 26 - 27 - // MODEL 28 - 29 - pub type Route { 30 - Home 31 - Characters 32 - CharacterDetail(id: String) 33 - Episodes 34 - Locations 35 - } 36 - 37 - pub type Model { 38 - Model(cache: squall_cache.Cache, registry: registry.Registry, route: Route) 39 - } 40 - 41 - fn init(_flags) -> #(Model, Effect(Msg)) { 42 - // Create cache - optionally with headers for authentication 43 - let cache = squall_cache.new("https://rickandmortyapi.com/graphql") 44 - // To add headers (e.g., for authentication): 45 - // let cache = squall_cache.new_with_headers(fn() { 46 - // [#("Authorization", "Bearer " <> get_auth_token())] 47 - // }) 48 - 49 - let reg = queries.init_registry() 50 - 51 - // Parse the initial route from the current URL 52 - let initial_route = case modem.initial_uri() { 53 - Ok(uri) -> parse_route(uri) 54 - Error(_) -> Home 55 - } 56 - 57 - // Determine what data needs to be fetched based on initial route 58 - let #(initial_cache, data_effects) = case initial_route { 59 - Characters -> { 60 - let #(new_cache, _) = 61 - squall_cache.lookup( 62 - cache, 63 - "GetCharacters", 64 - json.object([]), 65 - get_characters.parse_get_characters_response, 66 - ) 67 - let #(final_cache, fx) = 68 - squall_cache.process_pending(new_cache, reg, HandleQueryResponse, fn() { 69 - 0 70 - }) 71 - #(final_cache, fx) 72 - } 73 - CharacterDetail(id) -> { 74 - let variables = json.object([#("id", json.string(id))]) 75 - let #(new_cache, _) = 76 - squall_cache.lookup( 77 - cache, 78 - "GetCharacter", 79 - variables, 80 - get_character.parse_get_character_response, 81 - ) 82 - let #(final_cache, fx) = 83 - squall_cache.process_pending(new_cache, reg, HandleQueryResponse, fn() { 84 - 0 85 - }) 86 - #(final_cache, fx) 87 - } 88 - Episodes -> { 89 - let #(new_cache, _) = 90 - squall_cache.lookup( 91 - cache, 92 - "GetEpisodes", 93 - json.object([]), 94 - get_episodes.parse_get_episodes_response, 95 - ) 96 - let #(final_cache, fx) = 97 - squall_cache.process_pending(new_cache, reg, HandleQueryResponse, fn() { 98 - 0 99 - }) 100 - #(final_cache, fx) 101 - } 102 - Locations -> { 103 - let #(new_cache, _) = 104 - squall_cache.lookup( 105 - cache, 106 - "GetLocations", 107 - json.object([]), 108 - get_locations.parse_get_locations_response, 109 - ) 110 - let #(final_cache, fx) = 111 - squall_cache.process_pending(new_cache, reg, HandleQueryResponse, fn() { 112 - 0 113 - }) 114 - #(final_cache, fx) 115 - } 116 - Home -> #(cache, []) 117 - } 118 - 119 - // Combine modem effect with data fetching effects 120 - let modem_effect = modem.init(on_url_change) 121 - let combined_effects = effect.batch([modem_effect, ..data_effects]) 122 - 123 - #( 124 - Model(cache: initial_cache, registry: reg, route: initial_route), 125 - combined_effects, 126 - ) 127 - } 128 - 129 - // UPDATE 130 - 131 - pub type Msg { 132 - HandleQueryResponse(String, Json, Result(String, String)) 133 - OnRouteChange(Route) 134 - } 135 - 136 - fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { 137 - case msg { 138 - HandleQueryResponse(query_name, variables, Ok(response_body)) -> { 139 - // Store response in cache as raw string with the correct variables 140 - let cache_with_data = 141 - squall_cache.store_query( 142 - model.cache, 143 - query_name, 144 - variables, 145 - response_body, 146 - 0, 147 - ) 148 - 149 - // Process any new pending fetches 150 - let #(final_cache, effects) = 151 - squall_cache.process_pending( 152 - cache_with_data, 153 - model.registry, 154 - HandleQueryResponse, 155 - fn() { 0 }, 156 - ) 157 - 158 - #(Model(..model, cache: final_cache), effect.batch(effects)) 159 - } 160 - 161 - HandleQueryResponse(_query_name, _variables, Error(_err)) -> { 162 - #(model, effect.none()) 163 - } 164 - 165 - OnRouteChange(route) -> { 166 - // When route changes, trigger lookups for that route's data 167 - let effects = case route { 168 - Characters -> { 169 - let #(new_cache, _result) = 170 - squall_cache.lookup( 171 - model.cache, 172 - "GetCharacters", 173 - json.object([]), 174 - get_characters.parse_get_characters_response, 175 - ) 176 - let #(final_cache, fx) = 177 - squall_cache.process_pending( 178 - new_cache, 179 - model.registry, 180 - HandleQueryResponse, 181 - fn() { 0 }, 182 - ) 183 - [#(final_cache, fx)] 184 - } 185 - CharacterDetail(id) -> { 186 - let variables = json.object([#("id", json.string(id))]) 187 - let #(new_cache, _result) = 188 - squall_cache.lookup( 189 - model.cache, 190 - "GetCharacter", 191 - variables, 192 - get_character.parse_get_character_response, 193 - ) 194 - let #(final_cache, fx) = 195 - squall_cache.process_pending( 196 - new_cache, 197 - model.registry, 198 - HandleQueryResponse, 199 - fn() { 0 }, 200 - ) 201 - [#(final_cache, fx)] 202 - } 203 - Episodes -> { 204 - let #(new_cache, _result) = 205 - squall_cache.lookup( 206 - model.cache, 207 - "GetEpisodes", 208 - json.object([]), 209 - get_episodes.parse_get_episodes_response, 210 - ) 211 - let #(final_cache, fx) = 212 - squall_cache.process_pending( 213 - new_cache, 214 - model.registry, 215 - HandleQueryResponse, 216 - fn() { 0 }, 217 - ) 218 - [#(final_cache, fx)] 219 - } 220 - Locations -> { 221 - let #(new_cache, _result) = 222 - squall_cache.lookup( 223 - model.cache, 224 - "GetLocations", 225 - json.object([]), 226 - get_locations.parse_get_locations_response, 227 - ) 228 - let #(final_cache, fx) = 229 - squall_cache.process_pending( 230 - new_cache, 231 - model.registry, 232 - HandleQueryResponse, 233 - fn() { 0 }, 234 - ) 235 - [#(final_cache, fx)] 236 - } 237 - Home -> [] 238 - } 239 - 240 - case effects { 241 - [#(final_cache, fx)] -> #( 242 - Model(..model, cache: final_cache, route: route), 243 - effect.batch(fx), 244 - ) 245 - _ -> #(Model(..model, route: route), effect.none()) 246 - } 247 - } 248 - } 249 - } 250 - 251 - // VIEW 252 - 253 - fn view(model: Model) -> Element(Msg) { 254 - html.div([attribute.style("padding", "20px")], [ 255 - html.h1([], [html.text("Rick and Morty API - Cached")]), 256 - html.p([], [ 257 - html.text( 258 - "Navigate between pages - watch how cached data loads instantly!", 259 - ), 260 - ]), 261 - // Navigation 262 - html.nav([attribute.style("margin", "20px 0")], [ 263 - html.a([attribute.href("/"), attribute.style("margin-right", "10px")], [ 264 - html.text("Home"), 265 - ]), 266 - html.a( 267 - [attribute.href("/characters"), attribute.style("margin-right", "10px")], 268 - [html.text("Characters")], 269 - ), 270 - html.a( 271 - [attribute.href("/episodes"), attribute.style("margin-right", "10px")], 272 - [html.text("Episodes")], 273 - ), 274 - html.a([attribute.href("/locations")], [html.text("Locations")]), 275 - ]), 276 - html.hr([]), 277 - // Route content 278 - case model.route { 279 - Home -> 280 - html.div([], [ 281 - html.h2([], [html.text("Welcome!")]), 282 - html.p([], [ 283 - html.text( 284 - "Click the links above to explore. Notice how data loads instantly on revisit!", 285 - ), 286 - ]), 287 - ]) 288 - Characters -> character_list.view(model.cache) 289 - CharacterDetail(id) -> character_detail.view(model.cache, id) 290 - Episodes -> episode_list.view(model.cache) 291 - Locations -> location_list.view(model.cache) 292 - }, 293 - ]) 294 - } 295 - 296 - // ROUTING 297 - 298 - fn on_url_change(uri: uri.Uri) -> Msg { 299 - OnRouteChange(parse_route(uri)) 300 - } 301 - 302 - fn parse_route(uri: uri.Uri) -> Route { 303 - case uri.path { 304 - "/" -> Home 305 - "/characters" -> Characters 306 - "/episodes" -> Episodes 307 - "/locations" -> Locations 308 - _ -> { 309 - // Try to parse /character/:id 310 - case string.split(uri.path, "/") { 311 - ["", "character", id] -> CharacterDetail(id) 312 - _ -> Home 313 - } 314 - } 315 - } 316 - }
···
-100
examples/03-unstable-cache/src/components/character_detail.gleam
··· 1 - import generated/queries/get_character 2 - import gleam/json 3 - import gleam/option 4 - import lustre/attribute 5 - import lustre/element.{type Element} 6 - import lustre/element/html 7 - import squall_cache.{type Cache} 8 - 9 - /// Fetch a single character by ID from Rick and Morty API 10 - /// 11 - /// ```graphql 12 - /// query GetCharacter($id: ID!) { 13 - /// character(id: $id) { 14 - /// id 15 - /// name 16 - /// status 17 - /// species 18 - /// type 19 - /// gender 20 - /// image 21 - /// } 22 - /// } 23 - /// ``` 24 - pub fn view(cache: Cache, id: String) -> Element(msg) { 25 - let variables = json.object([#("id", json.string(id))]) 26 - 27 - let #(_cache, result) = 28 - squall_cache.lookup( 29 - cache, 30 - "GetCharacter", 31 - variables, 32 - get_character.parse_get_character_response, 33 - ) 34 - 35 - html.div([], [ 36 - html.h2([], [html.text("Character Detail")]), 37 - case result { 38 - squall_cache.Loading -> html.p([], [html.text("Loading character...")]) 39 - 40 - squall_cache.Failed(message) -> 41 - html.p([attribute.style("color", "red")], [ 42 - html.text("Error: " <> message), 43 - ]) 44 - 45 - squall_cache.Data(data) -> render_character(data) 46 - }, 47 - ]) 48 - } 49 - 50 - fn render_character(data: get_character.GetCharacterResponse) -> Element(msg) { 51 - case data.character { 52 - option.None -> html.p([], [html.text("Character not found")]) 53 - option.Some(char) -> { 54 - html.div([attribute.style("max-width", "600px")], [ 55 - html.div( 56 - [ 57 - attribute.style("display", "flex"), 58 - attribute.style("gap", "20px"), 59 - ], 60 - [ 61 - case char.image { 62 - option.Some(img) -> 63 - html.img([ 64 - attribute.src(img), 65 - attribute.style("width", "300px"), 66 - attribute.style("border-radius", "8px"), 67 - ]) 68 - option.None -> html.div([], []) 69 - }, 70 - html.div([], [ 71 - html.h3([], [ 72 - html.text(char.name |> option.unwrap("Unknown")), 73 - ]), 74 - html.p([], [ 75 - html.strong([], [html.text("Status: ")]), 76 - html.text(char.status |> option.unwrap("Unknown")), 77 - ]), 78 - html.p([], [ 79 - html.strong([], [html.text("Species: ")]), 80 - html.text(char.species |> option.unwrap("Unknown")), 81 - ]), 82 - html.p([], [ 83 - html.strong([], [html.text("Gender: ")]), 84 - html.text(char.gender |> option.unwrap("Unknown")), 85 - ]), 86 - case char.type_ { 87 - option.Some("") | option.None -> html.div([], []) 88 - option.Some(type_) -> 89 - html.p([], [ 90 - html.strong([], [html.text("Type: ")]), 91 - html.text(type_), 92 - ]) 93 - }, 94 - ]), 95 - ], 96 - ), 97 - ]) 98 - } 99 - } 100 - }
···
-91
examples/03-unstable-cache/src/components/character_list.gleam
··· 1 - import generated/queries/get_characters 2 - import gleam/json 3 - import gleam/list 4 - import gleam/option 5 - import lustre/attribute 6 - import lustre/element.{type Element} 7 - import lustre/element/html 8 - import squall_cache.{type Cache} 9 - 10 - /// Fetch all characters from Rick and Morty API 11 - /// 12 - /// ```graphql 13 - /// query GetCharacters { 14 - /// characters { 15 - /// results { 16 - /// id 17 - /// name 18 - /// status 19 - /// species 20 - /// } 21 - /// } 22 - /// } 23 - /// ``` 24 - pub fn view(cache: Cache) -> Element(msg) { 25 - // Just read from cache - fetching already happened in init 26 - // Pass generated parser directly! 27 - let #(_cache, result) = 28 - squall_cache.lookup( 29 - cache, 30 - "GetCharacters", 31 - json.object([]), 32 - get_characters.parse_get_characters_response, 33 - ) 34 - 35 - html.div([], [ 36 - html.h2([], [html.text("Characters")]), 37 - case result { 38 - squall_cache.Loading -> html.p([], [html.text("Loading characters...")]) 39 - 40 - squall_cache.Failed(message) -> 41 - html.p([attribute.style("color", "red")], [ 42 - html.text("Error: " <> message), 43 - ]) 44 - 45 - squall_cache.Data(data) -> render_characters(data) 46 - }, 47 - ]) 48 - } 49 - 50 - fn render_characters(data: get_characters.GetCharactersResponse) -> Element(msg) { 51 - case data.characters { 52 - option.None -> html.p([], [html.text("No characters data")]) 53 - option.Some(characters) -> 54 - case characters.results { 55 - option.None -> html.p([], [html.text("No results")]) 56 - option.Some(results) -> { 57 - html.ul( 58 - [], 59 - results 60 - |> list.map(fn(char) { 61 - html.li([attribute.style("margin", "10px 0")], [ 62 - html.div([], [ 63 - html.a( 64 - [ 65 - attribute.href( 66 - "/character/" <> option.unwrap(char.id, ""), 67 - ), 68 - ], 69 - [ 70 - html.strong([], [ 71 - html.text( 72 - char.name 73 - |> option.unwrap("Unknown"), 74 - ), 75 - ]), 76 - ], 77 - ), 78 - html.text( 79 - " - Status: " <> option.unwrap(char.status, "Unknown"), 80 - ), 81 - html.text( 82 - ", Species: " <> option.unwrap(char.species, "Unknown"), 83 - ), 84 - ]), 85 - ]) 86 - }), 87 - ) 88 - } 89 - } 90 - } 91 - }
···
-84
examples/03-unstable-cache/src/components/episode_list.gleam
··· 1 - import generated/queries/get_episodes 2 - import gleam/json 3 - import gleam/list 4 - import gleam/option 5 - import lustre/attribute 6 - import lustre/element.{type Element} 7 - import lustre/element/html 8 - import squall_cache.{type Cache} 9 - 10 - /// Fetch all episodes from Rick and Morty API 11 - /// 12 - /// ```graphql 13 - /// query GetEpisodes { 14 - /// episodes { 15 - /// results { 16 - /// id 17 - /// name 18 - /// air_date 19 - /// episode 20 - /// } 21 - /// } 22 - /// } 23 - /// ``` 24 - pub fn view(cache: Cache) -> Element(msg) { 25 - let #(_cache, result) = 26 - squall_cache.lookup( 27 - cache, 28 - "GetEpisodes", 29 - json.object([]), 30 - get_episodes.parse_get_episodes_response, 31 - ) 32 - 33 - html.div([], [ 34 - html.h2([], [html.text("Episodes")]), 35 - case result { 36 - squall_cache.Loading -> html.p([], [html.text("Loading episodes...")]) 37 - 38 - squall_cache.Failed(message) -> 39 - html.p([attribute.style("color", "red")], [ 40 - html.text("Error: " <> message), 41 - ]) 42 - 43 - squall_cache.Data(data) -> render_episodes(data) 44 - }, 45 - ]) 46 - } 47 - 48 - fn render_episodes(data: get_episodes.GetEpisodesResponse) -> Element(msg) { 49 - case data.episodes { 50 - option.None -> html.p([], [html.text("No episodes data")]) 51 - option.Some(episodes) -> 52 - case episodes.results { 53 - option.None -> html.p([], [html.text("No results")]) 54 - option.Some(results) -> { 55 - html.ul( 56 - [], 57 - results 58 - |> list.map(fn(ep) { 59 - html.li([attribute.style("margin", "10px 0")], [ 60 - html.div([], [ 61 - html.strong([], [ 62 - html.text( 63 - ep.episode 64 - |> option.unwrap(""), 65 - ), 66 - html.text(" - "), 67 - html.text( 68 - ep.name 69 - |> option.unwrap("Unknown"), 70 - ), 71 - ]), 72 - html.text( 73 - " (Aired: " 74 - <> option.unwrap(ep.air_date, "Unknown") 75 - <> ")", 76 - ), 77 - ]), 78 - ]) 79 - }), 80 - ) 81 - } 82 - } 83 - } 84 - }
···
-80
examples/03-unstable-cache/src/components/location_list.gleam
··· 1 - import generated/queries/get_locations 2 - import gleam/json 3 - import gleam/list 4 - import gleam/option 5 - import lustre/attribute 6 - import lustre/element.{type Element} 7 - import lustre/element/html 8 - import squall_cache.{type Cache} 9 - 10 - /// Fetch all locations from Rick and Morty API 11 - /// 12 - /// ```graphql 13 - /// query GetLocations { 14 - /// locations { 15 - /// results { 16 - /// id 17 - /// name 18 - /// type 19 - /// dimension 20 - /// } 21 - /// } 22 - /// } 23 - /// ``` 24 - pub fn view(cache: Cache) -> Element(msg) { 25 - let #(_cache, result) = 26 - squall_cache.lookup( 27 - cache, 28 - "GetLocations", 29 - json.object([]), 30 - get_locations.parse_get_locations_response, 31 - ) 32 - 33 - html.div([], [ 34 - html.h2([], [html.text("Locations")]), 35 - case result { 36 - squall_cache.Loading -> html.p([], [html.text("Loading locations...")]) 37 - 38 - squall_cache.Failed(message) -> 39 - html.p([attribute.style("color", "red")], [ 40 - html.text("Error: " <> message), 41 - ]) 42 - 43 - squall_cache.Data(data) -> render_locations(data) 44 - }, 45 - ]) 46 - } 47 - 48 - fn render_locations(data: get_locations.GetLocationsResponse) -> Element(msg) { 49 - case data.locations { 50 - option.None -> html.p([], [html.text("No locations data")]) 51 - option.Some(locations) -> 52 - case locations.results { 53 - option.None -> html.p([], [html.text("No results")]) 54 - option.Some(results) -> { 55 - html.ul( 56 - [], 57 - results 58 - |> list.map(fn(loc) { 59 - html.li([attribute.style("margin", "10px 0")], [ 60 - html.div([], [ 61 - html.strong([], [ 62 - html.text( 63 - loc.name 64 - |> option.unwrap("Unknown"), 65 - ), 66 - ]), 67 - html.text( 68 - " - Type: " <> option.unwrap(loc.type_, "Unknown"), 69 - ), 70 - html.text( 71 - ", Dimension: " <> option.unwrap(loc.dimension, "Unknown"), 72 - ), 73 - ]), 74 - ]) 75 - }), 76 - ) 77 - } 78 - } 79 - } 80 - }
···
-32
examples/03-unstable-cache/src/generated/queries.gleam
··· 1 - import squall/registry 2 - 3 - /// Initialize the query registry with all extracted queries 4 - /// This function is auto-generated by Squall 5 - pub fn init_registry() -> registry.Registry { 6 - let reg = registry.new() 7 - let reg = registry.register( 8 - reg, 9 - "GetCharacter", 10 - "query GetCharacter($id: ID!) {\n character(id: $id) {\n __typename\n id\n name\n status\n species\n type\n gender\n image\n }\n}", 11 - "generated/queries/get_character", 12 - ) 13 - let reg = registry.register( 14 - reg, 15 - "GetCharacters", 16 - "query GetCharacters {\n characters {\n __typename\n results {\n __typename\n id\n name\n status\n species\n }\n }\n}", 17 - "generated/queries/get_characters", 18 - ) 19 - let reg = registry.register( 20 - reg, 21 - "GetLocations", 22 - "query GetLocations {\n locations {\n __typename\n results {\n __typename\n id\n name\n type\n dimension\n }\n }\n}", 23 - "generated/queries/get_locations", 24 - ) 25 - let reg = registry.register( 26 - reg, 27 - "GetEpisodes", 28 - "query GetEpisodes {\n episodes {\n __typename\n results {\n __typename\n id\n name\n air_date\n episode\n }\n }\n}", 29 - "generated/queries/get_episodes", 30 - ) 31 - reg 32 - }
···
-83
examples/03-unstable-cache/src/generated/queries/get_character.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/http/request.{type Request} 3 - import gleam/json 4 - import squall 5 - import gleam/option.{type Option} 6 - 7 - pub type Character { 8 - Character( 9 - typename: Option(String), 10 - id: Option(String), 11 - name: Option(String), 12 - status: Option(String), 13 - species: Option(String), 14 - type_: Option(String), 15 - gender: Option(String), 16 - image: Option(String), 17 - ) 18 - } 19 - 20 - pub fn character_decoder() -> decode.Decoder(Character) { 21 - use typename <- decode.field("__typename", decode.optional(decode.string)) 22 - use id <- decode.field("id", decode.optional(decode.string)) 23 - use name <- decode.field("name", decode.optional(decode.string)) 24 - use status <- decode.field("status", decode.optional(decode.string)) 25 - use species <- decode.field("species", decode.optional(decode.string)) 26 - use type_ <- decode.field("type", decode.optional(decode.string)) 27 - use gender <- decode.field("gender", decode.optional(decode.string)) 28 - use image <- decode.field("image", decode.optional(decode.string)) 29 - decode.success(Character( 30 - typename: typename, 31 - id: id, 32 - name: name, 33 - status: status, 34 - species: species, 35 - type_: type_, 36 - gender: gender, 37 - image: image, 38 - )) 39 - } 40 - 41 - pub fn character_to_json(input: Character) -> json.Json { 42 - json.object( 43 - [ 44 - #("__typename", json.nullable(input.typename, json.string)), 45 - #("id", json.nullable(input.id, json.string)), 46 - #("name", json.nullable(input.name, json.string)), 47 - #("status", json.nullable(input.status, json.string)), 48 - #("species", json.nullable(input.species, json.string)), 49 - #("type", json.nullable(input.type_, json.string)), 50 - #("gender", json.nullable(input.gender, json.string)), 51 - #("image", json.nullable(input.image, json.string)), 52 - ], 53 - ) 54 - } 55 - 56 - pub type GetCharacterResponse { 57 - GetCharacterResponse(character: Option(Character)) 58 - } 59 - 60 - pub fn get_character_response_decoder() -> decode.Decoder(GetCharacterResponse) { 61 - use character <- decode.field("character", decode.optional(character_decoder())) 62 - decode.success(GetCharacterResponse(character: character)) 63 - } 64 - 65 - pub fn get_character_response_to_json(input: GetCharacterResponse) -> json.Json { 66 - json.object( 67 - [ 68 - #("character", json.nullable(input.character, character_to_json)), 69 - ], 70 - ) 71 - } 72 - 73 - pub fn get_character(client: squall.Client, id: String) -> Result(Request(String), String) { 74 - squall.prepare_request( 75 - client, 76 - "query GetCharacter($id: ID!) {\n character(id: $id) {\n __typename\n id\n name\n status\n species\n type\n gender\n image\n }\n}", 77 - json.object([#("id", json.string(id))]), 78 - ) 79 - } 80 - 81 - pub fn parse_get_character_response(body: String) -> Result(GetCharacterResponse, String) { 82 - squall.parse_response(body, get_character_response_decoder()) 83 - }
···
-93
examples/03-unstable-cache/src/generated/queries/get_characters.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/http/request.{type Request} 3 - import gleam/json 4 - import squall 5 - import gleam/option.{type Option} 6 - 7 - pub type Characters { 8 - Characters(typename: Option(String), results: Option(List(Character))) 9 - } 10 - 11 - pub fn characters_decoder() -> decode.Decoder(Characters) { 12 - use typename <- decode.field("__typename", decode.optional(decode.string)) 13 - use results <- decode.field("results", decode.optional(decode.list(character_decoder()))) 14 - decode.success(Characters(typename: typename, results: results)) 15 - } 16 - 17 - pub type Character { 18 - Character( 19 - typename: Option(String), 20 - id: Option(String), 21 - name: Option(String), 22 - status: Option(String), 23 - species: Option(String), 24 - ) 25 - } 26 - 27 - pub fn character_decoder() -> decode.Decoder(Character) { 28 - use typename <- decode.field("__typename", decode.optional(decode.string)) 29 - use id <- decode.field("id", decode.optional(decode.string)) 30 - use name <- decode.field("name", decode.optional(decode.string)) 31 - use status <- decode.field("status", decode.optional(decode.string)) 32 - use species <- decode.field("species", decode.optional(decode.string)) 33 - decode.success(Character( 34 - typename: typename, 35 - id: id, 36 - name: name, 37 - status: status, 38 - species: species, 39 - )) 40 - } 41 - 42 - pub fn characters_to_json(input: Characters) -> json.Json { 43 - json.object( 44 - [ 45 - #("__typename", json.nullable(input.typename, json.string)), 46 - #("results", json.nullable( 47 - input.results, 48 - fn(list) { json.array(from: list, of: character_to_json) }, 49 - )), 50 - ], 51 - ) 52 - } 53 - 54 - pub fn character_to_json(input: Character) -> json.Json { 55 - json.object( 56 - [ 57 - #("__typename", json.nullable(input.typename, json.string)), 58 - #("id", json.nullable(input.id, json.string)), 59 - #("name", json.nullable(input.name, json.string)), 60 - #("status", json.nullable(input.status, json.string)), 61 - #("species", json.nullable(input.species, json.string)), 62 - ], 63 - ) 64 - } 65 - 66 - pub type GetCharactersResponse { 67 - GetCharactersResponse(characters: Option(Characters)) 68 - } 69 - 70 - pub fn get_characters_response_decoder() -> decode.Decoder(GetCharactersResponse) { 71 - use characters <- decode.field("characters", decode.optional(characters_decoder())) 72 - decode.success(GetCharactersResponse(characters: characters)) 73 - } 74 - 75 - pub fn get_characters_response_to_json(input: GetCharactersResponse) -> json.Json { 76 - json.object( 77 - [ 78 - #("characters", json.nullable(input.characters, characters_to_json)), 79 - ], 80 - ) 81 - } 82 - 83 - pub fn get_characters(client: squall.Client) -> Result(Request(String), String) { 84 - squall.prepare_request( 85 - client, 86 - "query GetCharacters {\n characters {\n __typename\n results {\n __typename\n id\n name\n status\n species\n }\n }\n}", 87 - json.object([]), 88 - ) 89 - } 90 - 91 - pub fn parse_get_characters_response(body: String) -> Result(GetCharactersResponse, String) { 92 - squall.parse_response(body, get_characters_response_decoder()) 93 - }
···
-89
examples/03-unstable-cache/src/generated/queries/get_episodes.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/http/request.{type Request} 3 - import gleam/json 4 - import squall 5 - import gleam/option.{type Option} 6 - 7 - pub type Episodes { 8 - Episodes(typename: Option(String), results: Option(List(Episode))) 9 - } 10 - 11 - pub fn episodes_decoder() -> decode.Decoder(Episodes) { 12 - use typename <- decode.field("__typename", decode.optional(decode.string)) 13 - use results <- decode.field("results", decode.optional(decode.list(episode_decoder()))) 14 - decode.success(Episodes(typename: typename, results: results)) 15 - } 16 - 17 - pub type Episode { 18 - Episode( 19 - typename: Option(String), 20 - id: Option(String), 21 - name: Option(String), 22 - air_date: Option(String), 23 - episode: Option(String), 24 - ) 25 - } 26 - 27 - pub fn episode_decoder() -> decode.Decoder(Episode) { 28 - use typename <- decode.field("__typename", decode.optional(decode.string)) 29 - use id <- decode.field("id", decode.optional(decode.string)) 30 - use name <- decode.field("name", decode.optional(decode.string)) 31 - use air_date <- decode.field("air_date", decode.optional(decode.string)) 32 - use episode <- decode.field("episode", decode.optional(decode.string)) 33 - decode.success(Episode( 34 - typename: typename, 35 - id: id, 36 - name: name, 37 - air_date: air_date, 38 - episode: episode, 39 - )) 40 - } 41 - 42 - pub fn episodes_to_json(input: Episodes) -> json.Json { 43 - json.object( 44 - [ 45 - #("__typename", json.nullable(input.typename, json.string)), 46 - #("results", json.nullable( 47 - input.results, 48 - fn(list) { json.array(from: list, of: episode_to_json) }, 49 - )), 50 - ], 51 - ) 52 - } 53 - 54 - pub fn episode_to_json(input: Episode) -> json.Json { 55 - json.object( 56 - [ 57 - #("__typename", json.nullable(input.typename, json.string)), 58 - #("id", json.nullable(input.id, json.string)), 59 - #("name", json.nullable(input.name, json.string)), 60 - #("air_date", json.nullable(input.air_date, json.string)), 61 - #("episode", json.nullable(input.episode, json.string)), 62 - ], 63 - ) 64 - } 65 - 66 - pub type GetEpisodesResponse { 67 - GetEpisodesResponse(episodes: Option(Episodes)) 68 - } 69 - 70 - pub fn get_episodes_response_decoder() -> decode.Decoder(GetEpisodesResponse) { 71 - use episodes <- decode.field("episodes", decode.optional(episodes_decoder())) 72 - decode.success(GetEpisodesResponse(episodes: episodes)) 73 - } 74 - 75 - pub fn get_episodes_response_to_json(input: GetEpisodesResponse) -> json.Json { 76 - json.object([#("episodes", json.nullable(input.episodes, episodes_to_json))]) 77 - } 78 - 79 - pub fn get_episodes(client: squall.Client) -> Result(Request(String), String) { 80 - squall.prepare_request( 81 - client, 82 - "query GetEpisodes {\n episodes {\n __typename\n results {\n __typename\n id\n name\n air_date\n episode\n }\n }\n}", 83 - json.object([]), 84 - ) 85 - } 86 - 87 - pub fn parse_get_episodes_response(body: String) -> Result(GetEpisodesResponse, String) { 88 - squall.parse_response(body, get_episodes_response_decoder()) 89 - }
···
-93
examples/03-unstable-cache/src/generated/queries/get_locations.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/http/request.{type Request} 3 - import gleam/json 4 - import squall 5 - import gleam/option.{type Option} 6 - 7 - pub type Locations { 8 - Locations(typename: Option(String), results: Option(List(Location))) 9 - } 10 - 11 - pub fn locations_decoder() -> decode.Decoder(Locations) { 12 - use typename <- decode.field("__typename", decode.optional(decode.string)) 13 - use results <- decode.field("results", decode.optional(decode.list(location_decoder()))) 14 - decode.success(Locations(typename: typename, results: results)) 15 - } 16 - 17 - pub type Location { 18 - Location( 19 - typename: Option(String), 20 - id: Option(String), 21 - name: Option(String), 22 - type_: Option(String), 23 - dimension: Option(String), 24 - ) 25 - } 26 - 27 - pub fn location_decoder() -> decode.Decoder(Location) { 28 - use typename <- decode.field("__typename", decode.optional(decode.string)) 29 - use id <- decode.field("id", decode.optional(decode.string)) 30 - use name <- decode.field("name", decode.optional(decode.string)) 31 - use type_ <- decode.field("type", decode.optional(decode.string)) 32 - use dimension <- decode.field("dimension", decode.optional(decode.string)) 33 - decode.success(Location( 34 - typename: typename, 35 - id: id, 36 - name: name, 37 - type_: type_, 38 - dimension: dimension, 39 - )) 40 - } 41 - 42 - pub fn locations_to_json(input: Locations) -> json.Json { 43 - json.object( 44 - [ 45 - #("__typename", json.nullable(input.typename, json.string)), 46 - #("results", json.nullable( 47 - input.results, 48 - fn(list) { json.array(from: list, of: location_to_json) }, 49 - )), 50 - ], 51 - ) 52 - } 53 - 54 - pub fn location_to_json(input: Location) -> json.Json { 55 - json.object( 56 - [ 57 - #("__typename", json.nullable(input.typename, json.string)), 58 - #("id", json.nullable(input.id, json.string)), 59 - #("name", json.nullable(input.name, json.string)), 60 - #("type", json.nullable(input.type_, json.string)), 61 - #("dimension", json.nullable(input.dimension, json.string)), 62 - ], 63 - ) 64 - } 65 - 66 - pub type GetLocationsResponse { 67 - GetLocationsResponse(locations: Option(Locations)) 68 - } 69 - 70 - pub fn get_locations_response_decoder() -> decode.Decoder(GetLocationsResponse) { 71 - use locations <- decode.field("locations", decode.optional(locations_decoder())) 72 - decode.success(GetLocationsResponse(locations: locations)) 73 - } 74 - 75 - pub fn get_locations_response_to_json(input: GetLocationsResponse) -> json.Json { 76 - json.object( 77 - [ 78 - #("locations", json.nullable(input.locations, locations_to_json)), 79 - ], 80 - ) 81 - } 82 - 83 - pub fn get_locations(client: squall.Client) -> Result(Request(String), String) { 84 - squall.prepare_request( 85 - client, 86 - "query GetLocations {\n locations {\n __typename\n results {\n __typename\n id\n name\n type\n dimension\n }\n }\n}", 87 - json.object([]), 88 - ) 89 - } 90 - 91 - pub fn parse_get_locations_response(body: String) -> Result(GetLocationsResponse, String) { 92 - squall.parse_response(body, get_locations_response_decoder()) 93 - }
···
-79
examples/03-unstable-cache/src/graphql/get_character.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/http/request.{type Request} 3 - import gleam/json 4 - import squall 5 - import gleam/option.{type Option} 6 - 7 - pub type Character { 8 - Character( 9 - id: Option(String), 10 - name: Option(String), 11 - status: Option(String), 12 - species: Option(String), 13 - type_: Option(String), 14 - gender: Option(String), 15 - image: Option(String), 16 - ) 17 - } 18 - 19 - pub fn character_decoder() -> decode.Decoder(Character) { 20 - use id <- decode.field("id", decode.optional(decode.string)) 21 - use name <- decode.field("name", decode.optional(decode.string)) 22 - use status <- decode.field("status", decode.optional(decode.string)) 23 - use species <- decode.field("species", decode.optional(decode.string)) 24 - use type_ <- decode.field("type", decode.optional(decode.string)) 25 - use gender <- decode.field("gender", decode.optional(decode.string)) 26 - use image <- decode.field("image", decode.optional(decode.string)) 27 - decode.success(Character( 28 - id: id, 29 - name: name, 30 - status: status, 31 - species: species, 32 - type_: type_, 33 - gender: gender, 34 - image: image, 35 - )) 36 - } 37 - 38 - pub fn character_to_json(input: Character) -> json.Json { 39 - json.object( 40 - [ 41 - #("id", json.nullable(input.id, json.string)), 42 - #("name", json.nullable(input.name, json.string)), 43 - #("status", json.nullable(input.status, json.string)), 44 - #("species", json.nullable(input.species, json.string)), 45 - #("type", json.nullable(input.type_, json.string)), 46 - #("gender", json.nullable(input.gender, json.string)), 47 - #("image", json.nullable(input.image, json.string)), 48 - ], 49 - ) 50 - } 51 - 52 - pub type GetCharacterResponse { 53 - GetCharacterResponse(character: Option(Character)) 54 - } 55 - 56 - pub fn get_character_response_decoder() -> decode.Decoder(GetCharacterResponse) { 57 - use character <- decode.field("character", decode.optional(character_decoder())) 58 - decode.success(GetCharacterResponse(character: character)) 59 - } 60 - 61 - pub fn get_character_response_to_json(input: GetCharacterResponse) -> json.Json { 62 - json.object( 63 - [ 64 - #("character", json.nullable(input.character, character_to_json)), 65 - ], 66 - ) 67 - } 68 - 69 - pub fn get_character(client: squall.Client, id: String) -> Result(Request(String), String) { 70 - squall.prepare_request( 71 - client, 72 - "query GetCharacter($id: ID!) {\n character(id: $id) {\n id\n name\n status\n species\n type\n gender\n image\n }\n}\n", 73 - json.object([#("id", json.string(id))]), 74 - ) 75 - } 76 - 77 - pub fn parse_get_character_response(body: String) -> Result(GetCharacterResponse, String) { 78 - squall.parse_response(body, get_character_response_decoder()) 79 - }
···
-11
examples/03-unstable-cache/src/graphql/get_character.gql
··· 1 - query GetCharacter($id: ID!) { 2 - character(id: $id) { 3 - id 4 - name 5 - status 6 - species 7 - type 8 - gender 9 - image 10 - } 11 - }
···
-82
examples/03-unstable-cache/src/graphql/get_characters.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/http/request.{type Request} 3 - import gleam/json 4 - import squall 5 - import gleam/option.{type Option} 6 - 7 - pub type Characters { 8 - Characters(results: Option(List(Character))) 9 - } 10 - 11 - pub fn characters_decoder() -> decode.Decoder(Characters) { 12 - use results <- decode.field("results", decode.optional(decode.list(character_decoder()))) 13 - decode.success(Characters(results: results)) 14 - } 15 - 16 - pub type Character { 17 - Character( 18 - id: Option(String), 19 - name: Option(String), 20 - status: Option(String), 21 - species: Option(String), 22 - ) 23 - } 24 - 25 - pub fn character_decoder() -> decode.Decoder(Character) { 26 - use id <- decode.field("id", decode.optional(decode.string)) 27 - use name <- decode.field("name", decode.optional(decode.string)) 28 - use status <- decode.field("status", decode.optional(decode.string)) 29 - use species <- decode.field("species", decode.optional(decode.string)) 30 - decode.success(Character(id: id, name: name, status: status, species: species)) 31 - } 32 - 33 - pub fn characters_to_json(input: Characters) -> json.Json { 34 - json.object( 35 - [ 36 - #("results", json.nullable( 37 - input.results, 38 - fn(list) { json.array(from: list, of: character_to_json) }, 39 - )), 40 - ], 41 - ) 42 - } 43 - 44 - pub fn character_to_json(input: Character) -> json.Json { 45 - json.object( 46 - [ 47 - #("id", json.nullable(input.id, json.string)), 48 - #("name", json.nullable(input.name, json.string)), 49 - #("status", json.nullable(input.status, json.string)), 50 - #("species", json.nullable(input.species, json.string)), 51 - ], 52 - ) 53 - } 54 - 55 - pub type GetCharactersResponse { 56 - GetCharactersResponse(characters: Option(Characters)) 57 - } 58 - 59 - pub fn get_characters_response_decoder() -> decode.Decoder(GetCharactersResponse) { 60 - use characters <- decode.field("characters", decode.optional(characters_decoder())) 61 - decode.success(GetCharactersResponse(characters: characters)) 62 - } 63 - 64 - pub fn get_characters_response_to_json(input: GetCharactersResponse) -> json.Json { 65 - json.object( 66 - [ 67 - #("characters", json.nullable(input.characters, characters_to_json)), 68 - ], 69 - ) 70 - } 71 - 72 - pub fn get_characters(client: squall.Client) -> Result(Request(String), String) { 73 - squall.prepare_request( 74 - client, 75 - "query GetCharacters {\n characters {\n results {\n id\n name\n status\n species\n }\n }\n}\n", 76 - json.object([]), 77 - ) 78 - } 79 - 80 - pub fn parse_get_characters_response(body: String) -> Result(GetCharactersResponse, String) { 81 - squall.parse_response(body, get_characters_response_decoder()) 82 - }
···
-10
examples/03-unstable-cache/src/graphql/get_characters.gql
··· 1 - query GetCharacters { 2 - characters { 3 - results { 4 - id 5 - name 6 - status 7 - species 8 - } 9 - } 10 - }
···
-83
examples/03-unstable-cache/src/graphql/get_episodes.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/http/request.{type Request} 3 - import gleam/json 4 - import squall 5 - import gleam/option.{type Option} 6 - 7 - pub type Episodes { 8 - Episodes(results: Option(List(Episode))) 9 - } 10 - 11 - pub fn episodes_decoder() -> decode.Decoder(Episodes) { 12 - use results <- decode.field("results", decode.optional(decode.list(episode_decoder()))) 13 - decode.success(Episodes(results: results)) 14 - } 15 - 16 - pub type Episode { 17 - Episode( 18 - id: Option(String), 19 - name: Option(String), 20 - air_date: Option(String), 21 - episode: Option(String), 22 - ) 23 - } 24 - 25 - pub fn episode_decoder() -> decode.Decoder(Episode) { 26 - use id <- decode.field("id", decode.optional(decode.string)) 27 - use name <- decode.field("name", decode.optional(decode.string)) 28 - use air_date <- decode.field("air_date", decode.optional(decode.string)) 29 - use episode <- decode.field("episode", decode.optional(decode.string)) 30 - decode.success(Episode( 31 - id: id, 32 - name: name, 33 - air_date: air_date, 34 - episode: episode, 35 - )) 36 - } 37 - 38 - pub fn episodes_to_json(input: Episodes) -> json.Json { 39 - json.object( 40 - [ 41 - #("results", json.nullable( 42 - input.results, 43 - fn(list) { json.array(from: list, of: episode_to_json) }, 44 - )), 45 - ], 46 - ) 47 - } 48 - 49 - pub fn episode_to_json(input: Episode) -> json.Json { 50 - json.object( 51 - [ 52 - #("id", json.nullable(input.id, json.string)), 53 - #("name", json.nullable(input.name, json.string)), 54 - #("air_date", json.nullable(input.air_date, json.string)), 55 - #("episode", json.nullable(input.episode, json.string)), 56 - ], 57 - ) 58 - } 59 - 60 - pub type GetEpisodesResponse { 61 - GetEpisodesResponse(episodes: Option(Episodes)) 62 - } 63 - 64 - pub fn get_episodes_response_decoder() -> decode.Decoder(GetEpisodesResponse) { 65 - use episodes <- decode.field("episodes", decode.optional(episodes_decoder())) 66 - decode.success(GetEpisodesResponse(episodes: episodes)) 67 - } 68 - 69 - pub fn get_episodes_response_to_json(input: GetEpisodesResponse) -> json.Json { 70 - json.object([#("episodes", json.nullable(input.episodes, episodes_to_json))]) 71 - } 72 - 73 - pub fn get_episodes(client: squall.Client) -> Result(Request(String), String) { 74 - squall.prepare_request( 75 - client, 76 - "query GetEpisodes {\n episodes {\n results {\n id\n name\n air_date\n episode\n }\n }\n}\n", 77 - json.object([]), 78 - ) 79 - } 80 - 81 - pub fn parse_get_episodes_response(body: String) -> Result(GetEpisodesResponse, String) { 82 - squall.parse_response(body, get_episodes_response_decoder()) 83 - }
···
-10
examples/03-unstable-cache/src/graphql/get_episodes.gql
··· 1 - query GetEpisodes { 2 - episodes { 3 - results { 4 - id 5 - name 6 - air_date 7 - episode 8 - } 9 - } 10 - }
···
-87
examples/03-unstable-cache/src/graphql/get_locations.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/http/request.{type Request} 3 - import gleam/json 4 - import squall 5 - import gleam/option.{type Option} 6 - 7 - pub type Locations { 8 - Locations(results: Option(List(Location))) 9 - } 10 - 11 - pub fn locations_decoder() -> decode.Decoder(Locations) { 12 - use results <- decode.field("results", decode.optional(decode.list(location_decoder()))) 13 - decode.success(Locations(results: results)) 14 - } 15 - 16 - pub type Location { 17 - Location( 18 - id: Option(String), 19 - name: Option(String), 20 - type_: Option(String), 21 - dimension: Option(String), 22 - ) 23 - } 24 - 25 - pub fn location_decoder() -> decode.Decoder(Location) { 26 - use id <- decode.field("id", decode.optional(decode.string)) 27 - use name <- decode.field("name", decode.optional(decode.string)) 28 - use type_ <- decode.field("type", decode.optional(decode.string)) 29 - use dimension <- decode.field("dimension", decode.optional(decode.string)) 30 - decode.success(Location( 31 - id: id, 32 - name: name, 33 - type_: type_, 34 - dimension: dimension, 35 - )) 36 - } 37 - 38 - pub fn locations_to_json(input: Locations) -> json.Json { 39 - json.object( 40 - [ 41 - #("results", json.nullable( 42 - input.results, 43 - fn(list) { json.array(from: list, of: location_to_json) }, 44 - )), 45 - ], 46 - ) 47 - } 48 - 49 - pub fn location_to_json(input: Location) -> json.Json { 50 - json.object( 51 - [ 52 - #("id", json.nullable(input.id, json.string)), 53 - #("name", json.nullable(input.name, json.string)), 54 - #("type", json.nullable(input.type_, json.string)), 55 - #("dimension", json.nullable(input.dimension, json.string)), 56 - ], 57 - ) 58 - } 59 - 60 - pub type GetLocationsResponse { 61 - GetLocationsResponse(locations: Option(Locations)) 62 - } 63 - 64 - pub fn get_locations_response_decoder() -> decode.Decoder(GetLocationsResponse) { 65 - use locations <- decode.field("locations", decode.optional(locations_decoder())) 66 - decode.success(GetLocationsResponse(locations: locations)) 67 - } 68 - 69 - pub fn get_locations_response_to_json(input: GetLocationsResponse) -> json.Json { 70 - json.object( 71 - [ 72 - #("locations", json.nullable(input.locations, locations_to_json)), 73 - ], 74 - ) 75 - } 76 - 77 - pub fn get_locations(client: squall.Client) -> Result(Request(String), String) { 78 - squall.prepare_request( 79 - client, 80 - "query GetLocations {\n locations {\n results {\n id\n name\n type\n dimension\n }\n }\n}\n", 81 - json.object([]), 82 - ) 83 - } 84 - 85 - pub fn parse_get_locations_response(body: String) -> Result(GetLocationsResponse, String) { 86 - squall.parse_response(body, get_locations_response_decoder()) 87 - }
···
-10
examples/03-unstable-cache/src/graphql/get_locations.gql
··· 1 - query GetLocations { 2 - locations { 3 - results { 4 - id 5 - name 6 - type 7 - dimension 8 - } 9 - } 10 - }
···
-13
examples/03-unstable-cache/test/cache_example_test.gleam
··· 1 - import gleeunit 2 - 3 - pub fn main() -> Nil { 4 - gleeunit.main() 5 - } 6 - 7 - // gleeunit test functions end in `_test` 8 - pub fn hello_world_test() { 9 - let name = "Joe" 10 - let greeting = "Hello, " <> name <> "!" 11 - 12 - assert greeting == "Hello, Joe!" 13 - }
···
+1 -1
gleam.toml
··· 1 name = "squall" 2 - version = "1.1.0" 3 description = "☁️ Type-safe GraphQL client generator for Gleam" 4 licences = ["Apache-2.0"] 5 repository = { type = "github", user = "bigmoves", repo = "squall" }
··· 1 name = "squall" 2 + version = "1.1.1" 3 description = "☁️ Type-safe GraphQL client generator for Gleam" 4 licences = ["Apache-2.0"] 5 repository = { type = "github", user = "bigmoves", repo = "squall" }
+4 -4
src/squall/internal/registry_codegen.gleam
··· 11 } 12 13 fn generate_imports() -> String { 14 - "import squall/registry" 15 } 16 17 fn generate_init_function(queries: List(QueryDefinition)) -> String { ··· 20 21 "/// Initialize the query registry with all extracted queries 22 /// This function is auto-generated by Squall 23 - pub fn init_registry() -> registry.Registry { 24 - let reg = registry.new() 25 " <> registrations_code <> " 26 reg 27 }" ··· 31 let module_path = "generated/queries/" <> to_snake_case(query.name) 32 let escaped_query = escape_string(query.query) 33 34 - "let reg = registry.register( 35 reg, 36 \"" <> query.name <> "\", 37 \"" <> escaped_query <> "\",
··· 11 } 12 13 fn generate_imports() -> String { 14 + "import squall/unstable_registry" 15 } 16 17 fn generate_init_function(queries: List(QueryDefinition)) -> String { ··· 20 21 "/// Initialize the query registry with all extracted queries 22 /// This function is auto-generated by Squall 23 + pub fn init_registry() -> unstable_registry.Registry { 24 + let reg = unstable_registry.new() 25 " <> registrations_code <> " 26 reg 27 }" ··· 31 let module_path = "generated/queries/" <> to_snake_case(query.name) 32 let escaped_query = escape_string(query.query) 33 34 + "let reg = unstable_registry.register( 35 reg, 36 \"" <> query.name <> "\", 37 \"" <> escaped_query <> "\",
src/squall/registry.gleam src/squall/unstable_registry.gleam