elm-pages 2.1.7 → 2.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/generator/review/elm.json +34 -0
  2. package/generator/review/src/ReviewConfig.elm +10 -0
  3. package/generator/src/basepath-middleware.js +15 -9
  4. package/generator/src/build.js +77 -4
  5. package/generator/src/cli.js +13 -9
  6. package/generator/src/compile-elm.js +43 -0
  7. package/generator/src/dev-server.js +63 -11
  8. package/generator/src/error-formatter.js +62 -9
  9. package/generator/src/generate-template-module-connector.js +17 -4
  10. package/generator/src/init.js +4 -0
  11. package/generator/src/pre-render-html.js +19 -12
  12. package/generator/src/render-worker.js +0 -1
  13. package/generator/src/render.js +1 -2
  14. package/generator/src/seo-renderer.js +21 -2
  15. package/generator/static-code/hmr.js +43 -6
  16. package/generator/template/elm.json +13 -5
  17. package/generator/template/package.json +3 -2
  18. package/package.json +14 -8
  19. package/src/ApiRoute.elm +178 -0
  20. package/src/AriaLiveAnnouncer.elm +36 -0
  21. package/src/BuildError.elm +60 -0
  22. package/src/DataSource/File.elm +288 -0
  23. package/src/DataSource/Glob.elm +1050 -0
  24. package/src/DataSource/Http.elm +467 -0
  25. package/src/DataSource/Internal/Glob.elm +74 -0
  26. package/src/DataSource/Port.elm +87 -0
  27. package/src/DataSource/ServerRequest.elm +60 -0
  28. package/src/DataSource.elm +801 -0
  29. package/src/Head/Seo.elm +516 -0
  30. package/src/Head/Twitter.elm +109 -0
  31. package/src/Head.elm +452 -0
  32. package/src/HtmlPrinter.elm +27 -0
  33. package/src/Internal/ApiRoute.elm +89 -0
  34. package/src/Internal/OptimizedDecoder.elm +18 -0
  35. package/src/KeepOrDiscard.elm +6 -0
  36. package/src/OptimizedDecoder/Pipeline.elm +335 -0
  37. package/src/OptimizedDecoder.elm +818 -0
  38. package/src/Pages/ContentCache.elm +248 -0
  39. package/src/Pages/Flags.elm +26 -0
  40. package/src/Pages/Http.elm +10 -0
  41. package/src/Pages/Internal/ApplicationType.elm +6 -0
  42. package/src/Pages/Internal/NotFoundReason.elm +256 -0
  43. package/src/Pages/Internal/Platform/Cli.elm +1015 -0
  44. package/src/Pages/Internal/Platform/Effect.elm +14 -0
  45. package/src/Pages/Internal/Platform/StaticResponses.elm +540 -0
  46. package/src/Pages/Internal/Platform/ToJsPayload.elm +138 -0
  47. package/src/Pages/Internal/Platform.elm +745 -0
  48. package/src/Pages/Internal/RoutePattern.elm +122 -0
  49. package/src/Pages/Internal/Router.elm +116 -0
  50. package/src/Pages/Internal/StaticHttpBody.elm +54 -0
  51. package/src/Pages/Internal/String.elm +39 -0
  52. package/src/Pages/Manifest/Category.elm +240 -0
  53. package/src/Pages/Manifest.elm +412 -0
  54. package/src/Pages/PageUrl.elm +38 -0
  55. package/src/Pages/ProgramConfig.elm +73 -0
  56. package/src/Pages/Review/NoContractViolations.elm +397 -0
  57. package/src/Pages/Secrets.elm +83 -0
  58. package/src/Pages/SiteConfig.elm +13 -0
  59. package/src/Pages/StaticHttp/Request.elm +42 -0
  60. package/src/Pages/StaticHttpRequest.elm +320 -0
  61. package/src/Pages/Url.elm +60 -0
  62. package/src/Path.elm +96 -0
  63. package/src/QueryParams.elm +216 -0
  64. package/src/RenderRequest.elm +163 -0
  65. package/src/RequestsAndPending.elm +20 -0
  66. package/src/Secrets.elm +111 -0
  67. package/src/SecretsDict.elm +45 -0
  68. package/src/StructuredData.elm +236 -0
  69. package/src/TerminalText.elm +242 -0
  70. package/src/Test/Html/Internal/ElmHtml/Constants.elm +53 -0
  71. package/src/Test/Html/Internal/ElmHtml/Helpers.elm +17 -0
  72. package/src/Test/Html/Internal/ElmHtml/InternalTypes.elm +529 -0
  73. package/src/Test/Html/Internal/ElmHtml/Markdown.elm +56 -0
  74. package/src/Test/Html/Internal/ElmHtml/ToString.elm +197 -0
  75. package/src/Test/Internal/KernelConstants.elm +34 -0
@@ -0,0 +1,801 @@
1
+ module DataSource exposing
2
+ ( DataSource
3
+ , map, succeed, fail
4
+ , fromResult
5
+ , andThen, resolve, combine
6
+ , andMap
7
+ , map2, map3, map4, map5, map6, map7, map8, map9
8
+ , distill, validate, distillCodec, distillSerializeCodec
9
+ )
10
+
11
+ {-| In an `elm-pages` app, each page can define a value `data` which is a `DataSource` that will be resolved **before** `init` is called. That means it is also available
12
+ when the page's HTML is pre-rendered during the build step. You can also access the resolved data in `head` to use it for the page's SEO meta tags.
13
+
14
+ A `DataSource` lets you pull in data from:
15
+
16
+ - Local files ([`DataSource.File`](DataSource-File))
17
+ - HTTP requests ([`DataSource.Http`](DataSource-Http))
18
+ - Globs, i.e. listing out local files based on a pattern like `content/*.txt` ([`DataSource.Glob`](DataSource-Glob))
19
+ - Ports, i.e. getting JSON data from running custom NodeJS, similar to a port in a vanilla Elm app except run at build-time in NodeJS, rather than at run-time in the browser ([`DataSource.Port`](DataSource-Port))
20
+ - Hardcoded data (`DataSource.succeed "Hello!"`)
21
+ - Or any combination of the above, using `DataSource.map2`, `DataSource.andThen`, or other combining/continuing helpers from this module
22
+
23
+
24
+ ## Where Does DataSource Data Come From?
25
+
26
+ Data from a `DataSource` is resolved when you load a page in the `elm-pages` dev server, or when you run `elm-pages build`.
27
+
28
+ Because `elm-pages` hydrates into a full Elm single-page app, it does need the data in order to initialize the Elm app.
29
+ So why not just get the data the old-fashioned way, with `elm/http`, for example?
30
+
31
+ A few reasons:
32
+
33
+ 1. DataSource's allow you to pull in data that you wouldn't normally be able to access from an Elm app, like local files, or listings of files in a folder. Not only that, but the dev server knows to automatically hot reload the data when the files it depends on change, so you can edit the files you used in your DataSource and see the page hot reload as you save!
34
+ 2. Because `elm-pages` has a build step, you know that your `DataSource.Http` requests succeeded, your decoders succeeded, your custom DataSource validations succeeded, and everything went smoothly. If something went wrong, you get a build failure and can deal with the issues before the site goes live. That means your users won't see those errors, and as a developer you don't need to handle those error cases in your code! Think of it as "parse, don't validate", but for your entire build.
35
+ 3. You don't have to worry about an API being down, or hitting it repeatedly. You can build in data and it will end up as JSON files served up with all the other assets of your site. If your CDN (static site host) is down, then the rest of your site is probably down anyway. If your site host is up, then so is all of your `DataSource` data. Also, it will be served up extremely quickly without needing to wait for any database queries to be performed, `andThen` requests to be resolved, etc., because all of that work and waiting was done at build-time!
36
+ 4. You can pre-render pages, including the SEO meta tags, with all that rich, well-typed Elm data available! That's something you can't accomplish with a vanilla Elm app, and it's one of the main use cases for elm-pages.
37
+
38
+
39
+ ## Mental Model
40
+
41
+ You can think of a DataSource as a declarative (not imperative) definition of data. It represents where to get the data from, and how to transform it (map, combine with other DataSources, etc.).
42
+
43
+ Even though an HTTP request is non-deterministic, you should think of it that way as much as possible with a DataSource because elm-pages will only perform a given DataSource.Http request once, and
44
+ it will share the result between any other DataSource.Http requests that have the exact same URL, Method, Body, and Headers.
45
+
46
+ So calling a function to increment a counter on a server through an HTTP request would not be a good fit for a `DataSource`. Let's imagine we have an HTTP endpoint that gives these stateful results when called repeatedly:
47
+
48
+ <https://my-api.example.com/increment-counter>
49
+ -> Returns 1
50
+ <https://my-api.example.com/increment-counter>
51
+ -> Returns 2
52
+ <https://my-api.example.com/increment-counter>
53
+ -> Returns 3
54
+
55
+ If we define a `DataSource` that hits that endpoint:
56
+
57
+ data =
58
+ DataSource.Http.get
59
+ (Secrets.succeed "https://my-api.example.com/increment-counter")
60
+ Decode.int
61
+
62
+ No matter how many places we use that `DataSource`, its response will be "locked in" (let's say the response was `3`, then every page would have the same value of `3` for that request).
63
+
64
+ So even though HTTP requests, JavaScript code, etc. can be non-deterministic, a `DataSource` always represents a single snapshot of a resource, and those values will be re-used as if they were a deterministic, declarative resource.
65
+ So it's best to use that mental model to avoid confusion.
66
+
67
+
68
+ ## Basics
69
+
70
+ @docs DataSource
71
+
72
+ @docs map, succeed, fail
73
+
74
+ @docs fromResult
75
+
76
+
77
+ ## Chaining Requests
78
+
79
+ @docs andThen, resolve, combine
80
+
81
+ @docs andMap
82
+
83
+ @docs map2, map3, map4, map5, map6, map7, map8, map9
84
+
85
+
86
+ ## Optimizing Page Data
87
+
88
+ Distilling data lets you reduce the amount of data loaded on the client. You can also use it to perform computations at
89
+ build-time or server-request-time, store the result of the computation and then simply load that result on the client
90
+ without needing redo the computation again on the client.
91
+
92
+ @docs distill, validate, distillCodec, distillSerializeCodec
93
+
94
+
95
+ ### Ensuring Unique Distill Keys
96
+
97
+ If you use the same string key for two different distilled values that have differing encoded JSON, then you
98
+ will get a build error (and an error in the dev server for that page). That means you can safely distill values
99
+ and let the build command tell you about these issues if they arise.
100
+
101
+ -}
102
+
103
+ import Codec
104
+ import Dict exposing (Dict)
105
+ import Dict.Extra
106
+ import Json.Decode as Decode
107
+ import Json.Encode as Encode
108
+ import KeepOrDiscard exposing (KeepOrDiscard)
109
+ import Pages.Internal.ApplicationType as ApplicationType exposing (ApplicationType)
110
+ import Pages.Internal.StaticHttpBody as Body
111
+ import Pages.Secrets
112
+ import Pages.StaticHttp.Request as HashRequest
113
+ import Pages.StaticHttpRequest exposing (RawRequest(..), WhatToDo)
114
+ import RequestsAndPending exposing (RequestsAndPending)
115
+ import Serialize
116
+
117
+
118
+ {-| A DataSource represents data that will be gathered at build time. Multiple `DataSource`s can be combined together using the `mapN` functions,
119
+ very similar to how you can manipulate values with Json Decoders in Elm.
120
+ -}
121
+ type alias DataSource value =
122
+ RawRequest value
123
+
124
+
125
+ {-| Transform a request into an arbitrary value. The same underlying HTTP requests will be performed during the build
126
+ step, but mapping allows you to change the resulting values by applying functions to the results.
127
+
128
+ A common use for this is to map your data into your elm-pages view:
129
+
130
+ import DataSource
131
+ import Json.Decode as Decode exposing (Decoder)
132
+
133
+ view =
134
+ DataSource.Http.get
135
+ (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
136
+ (Decode.field "stargazers_count" Decode.int)
137
+ |> DataSource.map
138
+ (\stars ->
139
+ { view =
140
+ \model viewForPage ->
141
+ { title = "Current stars: " ++ String.fromInt stars
142
+ , body = Html.text <| "⭐️ " ++ String.fromInt stars
143
+ , head = []
144
+ }
145
+ }
146
+ )
147
+
148
+ -}
149
+ map : (a -> b) -> DataSource a -> DataSource b
150
+ map fn requestInfo =
151
+ -- elm-review: known-unoptimized-recursion
152
+ case requestInfo of
153
+ RequestError error ->
154
+ RequestError error
155
+
156
+ Request partiallyStripped ( urls, lookupFn ) ->
157
+ Request partiallyStripped
158
+ ( urls
159
+ , \keepOrDiscard appType rawResponses ->
160
+ map fn (lookupFn keepOrDiscard appType rawResponses)
161
+ )
162
+
163
+ ApiRoute stripped value ->
164
+ ApiRoute stripped (fn value)
165
+
166
+
167
+ dontSaveData : DataSource a -> DataSource a
168
+ dontSaveData requestInfo =
169
+ case requestInfo of
170
+ RequestError _ ->
171
+ requestInfo
172
+
173
+ Request partiallyStripped ( urls, lookupFn ) ->
174
+ Request partiallyStripped
175
+ ( urls
176
+ , \_ appType rawResponses ->
177
+ lookupFn KeepOrDiscard.Discard appType rawResponses
178
+ )
179
+
180
+ ApiRoute _ _ ->
181
+ requestInfo
182
+
183
+
184
+ {-| This is the low-level `distill` function. In most cases, you'll want to use `distill` with a `Codec` from either
185
+ [`miniBill/elm-codec`](https://package.elm-lang.org/packages/miniBill/elm-codec/latest/) or
186
+ [`MartinSStewart/elm-serialize`](https://package.elm-lang.org/packages/MartinSStewart/elm-serialize/latest/)
187
+ -}
188
+ distill :
189
+ String
190
+ -> (raw -> Encode.Value)
191
+ -> (Decode.Value -> Result String distilled)
192
+ -> DataSource raw
193
+ -> DataSource distilled
194
+ distill uniqueKey encode decode dataSource =
195
+ -- elm-review: known-unoptimized-recursion
196
+ case dataSource of
197
+ RequestError error ->
198
+ RequestError error
199
+
200
+ Request partiallyStripped ( urls, lookupFn ) ->
201
+ Request partiallyStripped
202
+ ( urls
203
+ , \_ appType rawResponses ->
204
+ case appType of
205
+ ApplicationType.Browser ->
206
+ rawResponses
207
+ |> RequestsAndPending.get uniqueKey
208
+ |> (\maybeResponse ->
209
+ case maybeResponse of
210
+ Just rawResponse ->
211
+ rawResponse
212
+ |> Decode.decodeString Decode.value
213
+ |> Result.mapError Decode.errorToString
214
+ |> Result.andThen decode
215
+ |> Result.mapError Pages.StaticHttpRequest.DecoderError
216
+ |> Result.map (Tuple.pair Dict.empty)
217
+
218
+ Nothing ->
219
+ Err (Pages.StaticHttpRequest.MissingHttpResponse ("distill://" ++ uniqueKey) [])
220
+ )
221
+ |> toResult
222
+
223
+ ApplicationType.Cli ->
224
+ lookupFn KeepOrDiscard.Discard appType rawResponses
225
+ |> distill uniqueKey encode decode
226
+ )
227
+
228
+ ApiRoute strippedResponses value ->
229
+ Request
230
+ (strippedResponses
231
+ |> Dict.insert
232
+ -- TODO should this include a prefix? Probably.
233
+ uniqueKey
234
+ (Pages.StaticHttpRequest.DistilledResponse (encode value))
235
+ )
236
+ ( []
237
+ , \_ _ _ ->
238
+ value
239
+ |> encode
240
+ |> decode
241
+ |> fromResult
242
+ )
243
+
244
+
245
+ {-| [`distill`](#distill) with a `Serialize.Codec` from [`MartinSStewart/elm-serialize`](https://package.elm-lang.org/packages/MartinSStewart/elm-serialize/latest).
246
+
247
+ import DataSource
248
+ import DataSource.Http
249
+ import Secrets
250
+ import Serialize
251
+
252
+ millionRandomSum : DataSource Int
253
+ millionRandomSum =
254
+ DataSource.Http.get
255
+ (Secrets.succeed "https://example.com/api/one-million-random-numbers.json")
256
+ (Decode.list Decode.int)
257
+ |> DataSource.map List.sum
258
+ -- all of this expensive computation and data will happen before it hits the client!
259
+ -- the user's browser simply loads up a single Int and runs an Int decoder to get it
260
+ |> DataSource.distillSerializeCodec "million-random-sum" Serialize.int
261
+
262
+ If we didn't distill the data here, then all million Ints would have to be loaded in order to load the page.
263
+ The reason the data for these `DataSource`s needs to be loaded is that `elm-pages` hydrates into an Elm app. If it
264
+ output only HTML then we could build the HTML and throw away the data. But we need to ensure that the hydrated Elm app
265
+ has all the data that a page depends on, even if it the HTML for the page is also pre-rendered.
266
+
267
+ Using a `Codec` makes it safer to distill data because you know it is reversible.
268
+
269
+ -}
270
+ distillSerializeCodec :
271
+ String
272
+ -> Serialize.Codec error value
273
+ -> DataSource value
274
+ -> DataSource value
275
+ distillSerializeCodec uniqueKey serializeCodec =
276
+ distill uniqueKey
277
+ (Serialize.encodeToJson serializeCodec)
278
+ (Serialize.decodeFromJson serializeCodec
279
+ >> Result.mapError
280
+ (\error ->
281
+ case error of
282
+ Serialize.DataCorrupted ->
283
+ "DataCorrupted"
284
+
285
+ Serialize.CustomError _ ->
286
+ "CustomError"
287
+
288
+ Serialize.SerializerOutOfDate ->
289
+ "SerializerOutOfDate"
290
+ )
291
+ )
292
+
293
+
294
+ {-| [`distill`](#distill) with a `Codec` from [`miniBill/elm-codec`](https://package.elm-lang.org/packages/miniBill/elm-codec/latest/).
295
+
296
+ import Codec
297
+ import DataSource
298
+ import DataSource.Http
299
+ import Secrets
300
+
301
+ millionRandomSum : DataSource Int
302
+ millionRandomSum =
303
+ DataSource.Http.get
304
+ (Secrets.succeed "https://example.com/api/one-million-random-numbers.json")
305
+ (Decode.list Decode.int)
306
+ |> DataSource.map List.sum
307
+ -- all of this expensive computation and data will happen before it hits the client!
308
+ -- the user's browser simply loads up a single Int and runs an Int decoder to get it
309
+ |> DataSource.distillCodec "million-random-sum" Codec.int
310
+
311
+ If we didn't distill the data here, then all million Ints would have to be loaded in order to load the page.
312
+ The reason the data for these `DataSource`s needs to be loaded is that `elm-pages` hydrates into an Elm app. If it
313
+ output only HTML then we could build the HTML and throw away the data. But we need to ensure that the hydrated Elm app
314
+ has all the data that a page depends on, even if it the HTML for the page is also pre-rendered.
315
+
316
+ Using a `Codec` makes it safer to distill data because you know it is reversible.
317
+
318
+ -}
319
+ distillCodec :
320
+ String
321
+ -> Codec.Codec value
322
+ -> DataSource value
323
+ -> DataSource value
324
+ distillCodec uniqueKey codec =
325
+ distill uniqueKey
326
+ (Codec.encodeToValue codec)
327
+ (Codec.decodeValue codec >> Result.mapError Decode.errorToString)
328
+
329
+
330
+ toResult : Result Pages.StaticHttpRequest.Error ( Dict String WhatToDo, b ) -> RawRequest b
331
+ toResult result =
332
+ case result of
333
+ Err error ->
334
+ RequestError error
335
+
336
+ Ok ( stripped, okValue ) ->
337
+ ApiRoute stripped okValue
338
+
339
+
340
+ {-| -}
341
+ validate :
342
+ (unvalidated -> validated)
343
+ -> (unvalidated -> DataSource (Result String ()))
344
+ -> DataSource unvalidated
345
+ -> DataSource validated
346
+ validate markValidated validateDataSource unvalidatedDataSource =
347
+ unvalidatedDataSource
348
+ |> andThen
349
+ (\unvalidated ->
350
+ unvalidated
351
+ |> validateDataSource
352
+ |> andThen
353
+ (\result ->
354
+ case result of
355
+ Ok () ->
356
+ succeed <| markValidated unvalidated
357
+
358
+ Err error ->
359
+ fail error
360
+ )
361
+ |> dontSaveData
362
+ )
363
+ |> dontSaveData
364
+
365
+
366
+ {-| Helper to remove an inner layer of Request wrapping.
367
+ -}
368
+ resolve : DataSource (List (DataSource value)) -> DataSource (List value)
369
+ resolve =
370
+ andThen combine
371
+
372
+
373
+ {-| Turn a list of `StaticHttp.Request`s into a single one.
374
+
375
+ import DataSource
376
+ import Json.Decode as Decode exposing (Decoder)
377
+
378
+ type alias Pokemon =
379
+ { name : String
380
+ , sprite : String
381
+ }
382
+
383
+ pokemonDetailRequest : StaticHttp.Request (List Pokemon)
384
+ pokemonDetailRequest =
385
+ StaticHttp.get
386
+ (Secrets.succeed "https://pokeapi.co/api/v2/pokemon/?limit=3")
387
+ (Decode.field "results"
388
+ (Decode.list
389
+ (Decode.map2 Tuple.pair
390
+ (Decode.field "name" Decode.string)
391
+ (Decode.field "url" Decode.string)
392
+ |> Decode.map
393
+ (\( name, url ) ->
394
+ StaticHttp.get (Secrets.succeed url)
395
+ (Decode.at
396
+ [ "sprites", "front_default" ]
397
+ Decode.string
398
+ |> Decode.map (Pokemon name)
399
+ )
400
+ )
401
+ )
402
+ )
403
+ )
404
+ |> StaticHttp.andThen StaticHttp.combine
405
+
406
+ -}
407
+ combine : List (DataSource value) -> DataSource (List value)
408
+ combine =
409
+ List.foldr (map2 (::)) (succeed [])
410
+
411
+
412
+ {-| Like map, but it takes in two `Request`s.
413
+
414
+ view siteMetadata page =
415
+ StaticHttp.map2
416
+ (\elmPagesStars elmMarkdownStars ->
417
+ { view =
418
+ \model viewForPage ->
419
+ { title = "Repo Stargazers"
420
+ , body = starsView elmPagesStars elmMarkdownStars
421
+ }
422
+ , head = head elmPagesStars elmMarkdownStars
423
+ }
424
+ )
425
+ (get
426
+ (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
427
+ (Decode.field "stargazers_count" Decode.int)
428
+ )
429
+ (get
430
+ (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-markdown")
431
+ (Decode.field "stargazers_count" Decode.int)
432
+ )
433
+
434
+ -}
435
+ map2 : (a -> b -> c) -> DataSource a -> DataSource b -> DataSource c
436
+ map2 fn request1 request2 =
437
+ -- elm-review: known-unoptimized-recursion
438
+ case ( request1, request2 ) of
439
+ ( RequestError error, _ ) ->
440
+ RequestError error
441
+
442
+ ( _, RequestError error ) ->
443
+ RequestError error
444
+
445
+ ( Request newDict1 ( urls1, lookupFn1 ), Request newDict2 ( urls2, lookupFn2 ) ) ->
446
+ Request (combineReducedDicts newDict1 newDict2)
447
+ ( urls1 ++ urls2
448
+ , \keepOrDiscard appType rawResponses ->
449
+ map2 fn
450
+ (lookupFn1 keepOrDiscard appType rawResponses)
451
+ (lookupFn2 keepOrDiscard appType rawResponses)
452
+ )
453
+
454
+ ( Request dict1 ( urls1, lookupFn1 ), ApiRoute stripped2 value2 ) ->
455
+ Request dict1
456
+ ( urls1
457
+ , \keepOrDiscard appType rawResponses ->
458
+ map2 fn
459
+ (lookupFn1 keepOrDiscard appType rawResponses)
460
+ (ApiRoute stripped2 value2)
461
+ )
462
+
463
+ ( ApiRoute stripped2 value2, Request dict1 ( urls1, lookupFn1 ) ) ->
464
+ Request dict1
465
+ ( urls1
466
+ , \keepOrDiscard appType rawResponses ->
467
+ map2 fn
468
+ (ApiRoute stripped2 value2)
469
+ (lookupFn1 keepOrDiscard appType rawResponses)
470
+ )
471
+
472
+ ( ApiRoute stripped1 value1, ApiRoute stripped2 value2 ) ->
473
+ ApiRoute
474
+ (combineReducedDicts stripped1 stripped2)
475
+ (fn value1 value2)
476
+
477
+
478
+ {-| Takes two dicts representing responses, some of which have been reduced, and picks the shorter of the two.
479
+ This is assuming that there are no duplicate URLs, so it can safely choose between either a raw or a reduced response.
480
+ It would not work correctly if it chose between two responses that were reduced with different `Json.Decode.Exploration.Decoder`s.
481
+ -}
482
+ combineReducedDicts : Dict String WhatToDo -> Dict String WhatToDo -> Dict String WhatToDo
483
+ combineReducedDicts dict1 dict2 =
484
+ if Dict.size dict1 > Dict.size dict2 then
485
+ uniqueInsertAll dict2 dict1
486
+
487
+ else
488
+ uniqueInsertAll dict1 dict2
489
+
490
+
491
+ uniqueInsertAll : Dict String WhatToDo -> Dict String WhatToDo -> Dict String WhatToDo
492
+ uniqueInsertAll dictToDedupeMerge startingDict =
493
+ Dict.foldl
494
+ (\key value acc -> Dict.Extra.insertDedupe (Pages.StaticHttpRequest.merge key) key value acc)
495
+ startingDict
496
+ dictToDedupeMerge
497
+
498
+
499
+ lookup : KeepOrDiscard -> ApplicationType -> DataSource value -> RequestsAndPending -> Result Pages.StaticHttpRequest.Error ( Dict String WhatToDo, value )
500
+ lookup =
501
+ lookupHelp Dict.empty
502
+
503
+
504
+ lookupHelp : Dict String WhatToDo -> KeepOrDiscard -> ApplicationType -> DataSource value -> RequestsAndPending -> Result Pages.StaticHttpRequest.Error ( Dict String WhatToDo, value )
505
+ lookupHelp strippedSoFar keepOrDiscard appType requestInfo rawResponses =
506
+ case requestInfo of
507
+ RequestError error ->
508
+ Err error
509
+
510
+ Request strippedResponses ( urls, lookupFn ) ->
511
+ lookupHelp (combineReducedDicts strippedResponses strippedSoFar)
512
+ keepOrDiscard
513
+ appType
514
+ (addUrls urls (lookupFn keepOrDiscard appType rawResponses))
515
+ rawResponses
516
+
517
+ ApiRoute stripped value ->
518
+ Ok ( combineReducedDicts stripped strippedSoFar, value )
519
+
520
+
521
+ addUrls : List (Pages.Secrets.Value HashRequest.Request) -> DataSource value -> DataSource value
522
+ addUrls urlsToAdd requestInfo =
523
+ case requestInfo of
524
+ RequestError error ->
525
+ RequestError error
526
+
527
+ Request stripped ( initialUrls, function ) ->
528
+ Request stripped ( initialUrls ++ urlsToAdd, function )
529
+
530
+ ApiRoute stripped value ->
531
+ ApiRoute stripped value
532
+
533
+
534
+ {-| The full details to perform a StaticHttp request.
535
+ -}
536
+ type alias RequestDetails =
537
+ { url : String
538
+ , method : String
539
+ , headers : List ( String, String )
540
+ , body : Body.Body
541
+ }
542
+
543
+
544
+ lookupUrls : DataSource value -> List (Pages.Secrets.Value RequestDetails)
545
+ lookupUrls requestInfo =
546
+ case requestInfo of
547
+ RequestError _ ->
548
+ -- TODO should this have URLs passed through?
549
+ []
550
+
551
+ Request _ ( urls, _ ) ->
552
+ urls
553
+
554
+ ApiRoute _ _ ->
555
+ []
556
+
557
+
558
+ {-| Build off of the response from a previous `DataSource` request to build a follow-up request. You can use the data
559
+ from the previous response to build up the URL, headers, etc. that you send to the subsequent request.
560
+
561
+ import DataSource
562
+ import Json.Decode as Decode exposing (Decoder)
563
+
564
+ licenseData : DataSource String
565
+ licenseData =
566
+ DataSource.Http.get
567
+ (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
568
+ (Decode.at [ "license", "url" ] Decode.string)
569
+ |> DataSource.andThen
570
+ (\licenseUrl ->
571
+ DataSource.Http.get (Secrets.succeed licenseUrl) (Decode.field "description" Decode.string)
572
+ )
573
+
574
+ -}
575
+ andThen : (a -> DataSource b) -> DataSource a -> DataSource b
576
+ andThen fn requestInfo =
577
+ -- TODO should this be non-empty Dict? Or should it be passed down some other way?
578
+ Request Dict.empty
579
+ ( lookupUrls requestInfo
580
+ , \keepOrDiscard appType rawResponses ->
581
+ lookup
582
+ keepOrDiscard
583
+ appType
584
+ requestInfo
585
+ rawResponses
586
+ |> (\result ->
587
+ case result of
588
+ Err error ->
589
+ -- TODO should I pass through strippedResponses here?
590
+ --( strippedResponses, fn value )
591
+ RequestError error
592
+
593
+ Ok ( strippedResponses, value ) ->
594
+ case fn value of
595
+ Request dict ( values, function ) ->
596
+ Request (combineReducedDicts strippedResponses dict) ( values, function )
597
+
598
+ RequestError error ->
599
+ RequestError error
600
+
601
+ ApiRoute dict finalValue ->
602
+ ApiRoute (combineReducedDicts strippedResponses dict) finalValue
603
+ )
604
+ )
605
+
606
+
607
+ {-| A helper for combining `DataSource`s in pipelines.
608
+ -}
609
+ andMap : DataSource a -> DataSource (a -> b) -> DataSource b
610
+ andMap =
611
+ map2 (|>)
612
+
613
+
614
+ {-| This is useful for prototyping with some hardcoded data, or for having a view that doesn't have any StaticHttp data.
615
+
616
+ import DataSource
617
+
618
+ view :
619
+ List ( PagePath, Metadata )
620
+ ->
621
+ { path : PagePath
622
+ , frontmatter : Metadata
623
+ }
624
+ ->
625
+ StaticHttp.Request
626
+ { view : Model -> View -> { title : String, body : Html Msg }
627
+ , head : List (Head.Tag Pages.PathKey)
628
+ }
629
+ view siteMetadata page =
630
+ StaticHttp.succeed
631
+ { view =
632
+ \model viewForPage ->
633
+ mainView model viewForPage
634
+ , head = head page.frontmatter
635
+ }
636
+
637
+ -}
638
+ succeed : a -> DataSource a
639
+ succeed value =
640
+ ApiRoute Dict.empty value
641
+
642
+
643
+ {-| Stop the StaticHttp chain with the given error message. If you reach a `fail` in your request,
644
+ you will get a build error. Or in the dev server, you will see the error message in an overlay in your browser (and in
645
+ the terminal).
646
+ -}
647
+ fail : String -> DataSource a
648
+ fail errorMessage =
649
+ RequestError (Pages.StaticHttpRequest.UserCalledStaticHttpFail errorMessage)
650
+
651
+
652
+ {-| Turn an Err into a DataSource failure.
653
+ -}
654
+ fromResult : Result String value -> DataSource value
655
+ fromResult result =
656
+ case result of
657
+ Ok okValue ->
658
+ succeed okValue
659
+
660
+ Err error ->
661
+ fail error
662
+
663
+
664
+ {-| -}
665
+ map3 :
666
+ (value1 -> value2 -> value3 -> valueCombined)
667
+ -> DataSource value1
668
+ -> DataSource value2
669
+ -> DataSource value3
670
+ -> DataSource valueCombined
671
+ map3 combineFn request1 request2 request3 =
672
+ succeed combineFn
673
+ |> map2 (|>) request1
674
+ |> map2 (|>) request2
675
+ |> map2 (|>) request3
676
+
677
+
678
+ {-| -}
679
+ map4 :
680
+ (value1 -> value2 -> value3 -> value4 -> valueCombined)
681
+ -> DataSource value1
682
+ -> DataSource value2
683
+ -> DataSource value3
684
+ -> DataSource value4
685
+ -> DataSource valueCombined
686
+ map4 combineFn request1 request2 request3 request4 =
687
+ succeed combineFn
688
+ |> map2 (|>) request1
689
+ |> map2 (|>) request2
690
+ |> map2 (|>) request3
691
+ |> map2 (|>) request4
692
+
693
+
694
+ {-| -}
695
+ map5 :
696
+ (value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined)
697
+ -> DataSource value1
698
+ -> DataSource value2
699
+ -> DataSource value3
700
+ -> DataSource value4
701
+ -> DataSource value5
702
+ -> DataSource valueCombined
703
+ map5 combineFn request1 request2 request3 request4 request5 =
704
+ succeed combineFn
705
+ |> map2 (|>) request1
706
+ |> map2 (|>) request2
707
+ |> map2 (|>) request3
708
+ |> map2 (|>) request4
709
+ |> map2 (|>) request5
710
+
711
+
712
+ {-| -}
713
+ map6 :
714
+ (value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined)
715
+ -> DataSource value1
716
+ -> DataSource value2
717
+ -> DataSource value3
718
+ -> DataSource value4
719
+ -> DataSource value5
720
+ -> DataSource value6
721
+ -> DataSource valueCombined
722
+ map6 combineFn request1 request2 request3 request4 request5 request6 =
723
+ succeed combineFn
724
+ |> map2 (|>) request1
725
+ |> map2 (|>) request2
726
+ |> map2 (|>) request3
727
+ |> map2 (|>) request4
728
+ |> map2 (|>) request5
729
+ |> map2 (|>) request6
730
+
731
+
732
+ {-| -}
733
+ map7 :
734
+ (value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> valueCombined)
735
+ -> DataSource value1
736
+ -> DataSource value2
737
+ -> DataSource value3
738
+ -> DataSource value4
739
+ -> DataSource value5
740
+ -> DataSource value6
741
+ -> DataSource value7
742
+ -> DataSource valueCombined
743
+ map7 combineFn request1 request2 request3 request4 request5 request6 request7 =
744
+ succeed combineFn
745
+ |> map2 (|>) request1
746
+ |> map2 (|>) request2
747
+ |> map2 (|>) request3
748
+ |> map2 (|>) request4
749
+ |> map2 (|>) request5
750
+ |> map2 (|>) request6
751
+ |> map2 (|>) request7
752
+
753
+
754
+ {-| -}
755
+ map8 :
756
+ (value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> valueCombined)
757
+ -> DataSource value1
758
+ -> DataSource value2
759
+ -> DataSource value3
760
+ -> DataSource value4
761
+ -> DataSource value5
762
+ -> DataSource value6
763
+ -> DataSource value7
764
+ -> DataSource value8
765
+ -> DataSource valueCombined
766
+ map8 combineFn request1 request2 request3 request4 request5 request6 request7 request8 =
767
+ succeed combineFn
768
+ |> map2 (|>) request1
769
+ |> map2 (|>) request2
770
+ |> map2 (|>) request3
771
+ |> map2 (|>) request4
772
+ |> map2 (|>) request5
773
+ |> map2 (|>) request6
774
+ |> map2 (|>) request7
775
+ |> map2 (|>) request8
776
+
777
+
778
+ {-| -}
779
+ map9 :
780
+ (value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> value9 -> valueCombined)
781
+ -> DataSource value1
782
+ -> DataSource value2
783
+ -> DataSource value3
784
+ -> DataSource value4
785
+ -> DataSource value5
786
+ -> DataSource value6
787
+ -> DataSource value7
788
+ -> DataSource value8
789
+ -> DataSource value9
790
+ -> DataSource valueCombined
791
+ map9 combineFn request1 request2 request3 request4 request5 request6 request7 request8 request9 =
792
+ succeed combineFn
793
+ |> map2 (|>) request1
794
+ |> map2 (|>) request2
795
+ |> map2 (|>) request3
796
+ |> map2 (|>) request4
797
+ |> map2 (|>) request5
798
+ |> map2 (|>) request6
799
+ |> map2 (|>) request7
800
+ |> map2 (|>) request8
801
+ |> map2 (|>) request9