+6
CHANGELOG.md
+6
CHANGELOG.md
···
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
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
+
8
14
## [1.1.0] - 2025-11-12
9
15
10
16
### Added
-24
examples/03-unstable-cache/README.md
-24
examples/03-unstable-cache/README.md
···
1
-
# cache_example
2
-
3
-
[](https://hex.pm/packages/cache_example)
4
-
[](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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-11
examples/03-unstable-cache/src/graphql/get_character.gql
-82
examples/03-unstable-cache/src/graphql/get_characters.gleam
-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
-10
examples/03-unstable-cache/src/graphql/get_characters.gql
-83
examples/03-unstable-cache/src/graphql/get_episodes.gleam
-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
-10
examples/03-unstable-cache/src/graphql/get_episodes.gql
-87
examples/03-unstable-cache/src/graphql/get_locations.gleam
-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
-10
examples/03-unstable-cache/src/graphql/get_locations.gql
-13
examples/03-unstable-cache/test/cache_example_test.gleam
-13
examples/03-unstable-cache/test/cache_example_test.gleam
+1
-1
gleam.toml
+1
-1
gleam.toml
+4
-4
src/squall/internal/registry_codegen.gleam
+4
-4
src/squall/internal/registry_codegen.gleam
···
11
11
}
12
12
13
13
fn generate_imports() -> String {
14
-
"import squall/registry"
14
+
"import squall/unstable_registry"
15
15
}
16
16
17
17
fn generate_init_function(queries: List(QueryDefinition)) -> String {
···
20
20
21
21
"/// Initialize the query registry with all extracted queries
22
22
/// This function is auto-generated by Squall
23
-
pub fn init_registry() -> registry.Registry {
24
-
let reg = registry.new()
23
+
pub fn init_registry() -> unstable_registry.Registry {
24
+
let reg = unstable_registry.new()
25
25
" <> registrations_code <> "
26
26
reg
27
27
}"
···
31
31
let module_path = "generated/queries/" <> to_snake_case(query.name)
32
32
let escaped_query = escape_string(query.query)
33
33
34
-
"let reg = registry.register(
34
+
"let reg = unstable_registry.register(
35
35
reg,
36
36
\"" <> query.name <> "\",
37
37
\"" <> escaped_query <> "\",
src/squall/registry.gleam
src/squall/unstable_registry.gleam
src/squall/registry.gleam
src/squall/unstable_registry.gleam