elm-pages 3.0.0-beta.3 → 3.0.0-beta.30
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.
- package/README.md +10 -1
- package/codegen/{elm-pages-codegen.js → elm-pages-codegen.cjs} +2864 -2589
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1327 -122
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +15295 -13271
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +4 -4
- package/generator/dead-code-review/elm.json +8 -6
- package/generator/dead-code-review/src/Pages/Review/DeadCodeEliminateData.elm +59 -10
- package/generator/dead-code-review/tests/Pages/Review/DeadCodeEliminateDataTest.elm +52 -36
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1327 -122
- package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +14621 -12637
- package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +4 -4
- package/generator/review/elm.json +8 -8
- package/generator/src/RouteBuilder.elm +113 -107
- package/generator/src/SharedTemplate.elm +3 -2
- package/generator/src/SiteConfig.elm +3 -2
- package/generator/src/basepath-middleware.js +3 -3
- package/generator/src/build.js +123 -87
- package/generator/src/cli.js +256 -77
- package/generator/src/codegen.js +29 -27
- package/generator/src/compatibility-key.js +3 -0
- package/generator/src/compile-elm.js +25 -25
- package/generator/src/config.js +39 -0
- package/generator/src/copy-dir.js +2 -2
- package/generator/src/dev-server.js +150 -133
- package/generator/src/dir-helpers.js +9 -26
- package/generator/src/elm-codegen.js +5 -4
- package/generator/src/elm-file-constants.js +2 -3
- package/generator/src/error-formatter.js +12 -11
- package/generator/src/file-helpers.js +3 -4
- package/generator/src/generate-template-module-connector.js +23 -22
- package/generator/src/init.js +9 -8
- package/generator/src/pre-render-html.js +39 -28
- package/generator/src/render-test.js +109 -0
- package/generator/src/render-worker.js +25 -28
- package/generator/src/render.js +322 -142
- package/generator/src/request-cache.js +252 -163
- package/generator/src/rewrite-client-elm-json.js +5 -5
- package/generator/src/rewrite-elm-json.js +7 -7
- package/generator/src/route-codegen-helpers.js +16 -31
- package/generator/src/seo-renderer.js +12 -7
- package/generator/src/vite-utils.js +77 -0
- package/generator/static-code/hmr.js +79 -13
- package/generator/template/app/Api.elm +6 -5
- package/generator/template/app/Effect.elm +123 -0
- package/generator/template/app/ErrorPage.elm +37 -6
- package/generator/template/app/Route/Index.elm +17 -10
- package/generator/template/app/Shared.elm +24 -47
- package/generator/template/app/Site.elm +19 -6
- package/generator/template/app/View.elm +1 -8
- package/generator/template/elm-tooling.json +0 -3
- package/generator/template/elm.json +34 -25
- package/generator/template/package.json +10 -4
- package/package.json +23 -22
- package/src/ApiRoute.elm +199 -61
- package/src/BackendTask/Custom.elm +325 -0
- package/src/BackendTask/Env.elm +90 -0
- package/src/{DataSource → BackendTask}/File.elm +128 -43
- package/src/{DataSource → BackendTask}/Glob.elm +136 -125
- package/src/BackendTask/Http.elm +673 -0
- package/src/{DataSource → BackendTask}/Internal/Glob.elm +1 -1
- package/src/BackendTask/Internal/Request.elm +28 -0
- package/src/BackendTask/Random.elm +79 -0
- package/src/BackendTask/Time.elm +47 -0
- package/src/BackendTask.elm +537 -0
- package/src/FatalError.elm +89 -0
- package/src/Form/Field.elm +21 -9
- package/src/Form/FieldView.elm +94 -14
- package/src/Form.elm +275 -400
- package/src/Head.elm +237 -7
- package/src/HtmlPrinter.elm +7 -3
- package/src/Internal/ApiRoute.elm +7 -5
- package/src/PageServerResponse.elm +6 -1
- package/src/Pages/FormState.elm +6 -5
- package/src/Pages/GeneratorProgramConfig.elm +15 -0
- package/src/Pages/Internal/FatalError.elm +5 -0
- package/src/Pages/Internal/Form.elm +21 -1
- package/src/Pages/{Msg.elm → Internal/Msg.elm} +26 -16
- package/src/Pages/Internal/Platform/Cli.elm +507 -763
- package/src/Pages/Internal/Platform/CompatibilityKey.elm +6 -0
- package/src/Pages/Internal/Platform/Effect.elm +1 -2
- package/src/Pages/Internal/Platform/GeneratorApplication.elm +373 -0
- package/src/Pages/Internal/Platform/StaticResponses.elm +73 -270
- package/src/Pages/Internal/Platform/ToJsPayload.elm +4 -7
- package/src/Pages/Internal/Platform.elm +215 -102
- package/src/Pages/Internal/Script.elm +17 -0
- package/src/Pages/Internal/StaticHttpBody.elm +35 -1
- package/src/Pages/Manifest.elm +29 -4
- package/src/Pages/PageUrl.elm +23 -9
- package/src/Pages/ProgramConfig.elm +14 -10
- package/src/Pages/Script.elm +109 -0
- package/src/Pages/SiteConfig.elm +3 -2
- package/src/Pages/StaticHttp/Request.elm +2 -2
- package/src/Pages/StaticHttpRequest.elm +23 -98
- package/src/PagesMsg.elm +92 -0
- package/src/Path.elm +16 -19
- package/src/QueryParams.elm +21 -172
- package/src/RequestsAndPending.elm +8 -19
- package/src/Result/Extra.elm +26 -0
- package/src/Scaffold/Form.elm +484 -0
- package/src/Scaffold/Route.elm +1376 -0
- package/src/Server/Request.elm +43 -37
- package/src/Server/Session.elm +34 -34
- package/src/Server/SetCookie.elm +1 -1
- package/src/Test/Html/Internal/ElmHtml/ToString.elm +8 -9
- package/src/DataSource/Env.elm +0 -38
- package/src/DataSource/Http.elm +0 -446
- package/src/DataSource/Internal/Request.elm +0 -20
- package/src/DataSource/Port.elm +0 -90
- package/src/DataSource.elm +0 -538
- package/src/Pages/Generate.elm +0 -800
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module BackendTask.Internal.Request exposing (request)
|
|
2
|
+
|
|
3
|
+
import BackendTask exposing (BackendTask)
|
|
4
|
+
import BackendTask.Http exposing (Body, Expect)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
request :
|
|
8
|
+
{ name : String
|
|
9
|
+
, body : Body
|
|
10
|
+
, expect : Expect a
|
|
11
|
+
}
|
|
12
|
+
-> BackendTask error a
|
|
13
|
+
request ({ name, body, expect } as params) =
|
|
14
|
+
-- elm-review: known-unoptimized-recursion
|
|
15
|
+
BackendTask.Http.request
|
|
16
|
+
{ url = "elm-pages-internal://" ++ name
|
|
17
|
+
, method = "GET"
|
|
18
|
+
, headers = []
|
|
19
|
+
, body = body
|
|
20
|
+
, timeoutInMs = Nothing
|
|
21
|
+
, retries = Nothing
|
|
22
|
+
}
|
|
23
|
+
expect
|
|
24
|
+
|> BackendTask.onError
|
|
25
|
+
(\_ ->
|
|
26
|
+
-- TODO avoid crash here, this should be handled as an internal error
|
|
27
|
+
request params
|
|
28
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module BackendTask.Random exposing
|
|
2
|
+
( generate
|
|
3
|
+
, int32
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
{-|
|
|
7
|
+
|
|
8
|
+
@docs generate
|
|
9
|
+
|
|
10
|
+
@docs int32
|
|
11
|
+
|
|
12
|
+
-}
|
|
13
|
+
|
|
14
|
+
import BackendTask exposing (BackendTask)
|
|
15
|
+
import BackendTask.Http
|
|
16
|
+
import BackendTask.Internal.Request
|
|
17
|
+
import Json.Decode as Decode
|
|
18
|
+
import Json.Encode as Encode
|
|
19
|
+
import Random
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
{-| Takes an `elm/random` `Random.Generator` and runs it using a randomly generated initial seed.
|
|
23
|
+
|
|
24
|
+
type alias Data =
|
|
25
|
+
{ randomData : ( Int, Float )
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
data : BackendTask FatalError Data
|
|
29
|
+
data =
|
|
30
|
+
BackendTask.map Data
|
|
31
|
+
(BackendTask.Random.generate generator)
|
|
32
|
+
|
|
33
|
+
generator : Random.Generator ( Int, Float )
|
|
34
|
+
generator =
|
|
35
|
+
Random.map2 Tuple.pair (Random.int 0 100) (Random.float 0 100)
|
|
36
|
+
|
|
37
|
+
The random initial seed is generated using <https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>
|
|
38
|
+
to generate a single 32-bit Integer. That 32-bit Integer is then used with `Random.initialSeed` to create an Elm Random.Seed value.
|
|
39
|
+
Then that `Seed` used to run the `Generator`.
|
|
40
|
+
|
|
41
|
+
Note that this is different than `elm/random`'s `Random.generate`. This difference shouldn't be problematic, and in fact the `BackendTask`
|
|
42
|
+
random seed generation is more cryptographically independent because you can't determine the
|
|
43
|
+
random seed based solely on the time at which it is run. Each time you call `BackendTask.generate` it uses a newly
|
|
44
|
+
generated random seed to run the `Random.Generator` that is passed in. In contrast, `elm/random`'s `Random.generate`
|
|
45
|
+
generates an initial seed using `Time.now`, and then continues with that same seed using using [`Random.step`](https://package.elm-lang.org/packages/elm/random/latest/Random#step)
|
|
46
|
+
to get new random values after that. You can [see the implementation here](https://github.com/elm/random/blob/c1c9da4d861363cee1c93382d2687880279ed0dd/src/Random.elm#L865-L896).
|
|
47
|
+
However, `elm/random` is still not suitable in general for cryptographic uses of random because it uses 32-bits for when it
|
|
48
|
+
steps through new seeds while running a single `Random.Generator`.
|
|
49
|
+
|
|
50
|
+
-}
|
|
51
|
+
generate : Random.Generator value -> BackendTask error value
|
|
52
|
+
generate generator =
|
|
53
|
+
int32
|
|
54
|
+
|> BackendTask.map
|
|
55
|
+
(Random.initialSeed
|
|
56
|
+
>> Random.step generator
|
|
57
|
+
>> Tuple.first
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
{-| Gives a random 32-bit Int. This can be useful if you want to do low-level things with a cryptographically sound
|
|
62
|
+
random 32-bit integer.
|
|
63
|
+
|
|
64
|
+
The value comes from running this code in Node using <https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import * as crypto from "node:crypto";
|
|
68
|
+
|
|
69
|
+
crypto.getRandomValues(new Uint32Array(1))[0]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
-}
|
|
73
|
+
int32 : BackendTask error Int
|
|
74
|
+
int32 =
|
|
75
|
+
BackendTask.Internal.Request.request
|
|
76
|
+
{ name = "randomSeed"
|
|
77
|
+
, body = BackendTask.Http.jsonBody Encode.null
|
|
78
|
+
, expect = BackendTask.Http.expectJson Decode.int
|
|
79
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module BackendTask.Time exposing (now)
|
|
2
|
+
|
|
3
|
+
{-|
|
|
4
|
+
|
|
5
|
+
@docs now
|
|
6
|
+
|
|
7
|
+
-}
|
|
8
|
+
|
|
9
|
+
import BackendTask exposing (BackendTask)
|
|
10
|
+
import BackendTask.Http
|
|
11
|
+
import BackendTask.Internal.Request
|
|
12
|
+
import Json.Decode as Decode
|
|
13
|
+
import Json.Encode as Encode
|
|
14
|
+
import Time
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
{-| Gives a `Time.Posix` of when the `BackendTask` executes.
|
|
18
|
+
|
|
19
|
+
type alias Data =
|
|
20
|
+
{ time : Time.Posix
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
data : BackendTask FatalError Data
|
|
24
|
+
data =
|
|
25
|
+
BackendTask.map Data
|
|
26
|
+
BackendTask.Time.now
|
|
27
|
+
|
|
28
|
+
It's better to use [`Server.Request.requestTime`](Server-Request#requestTime) or `Pages.builtAt` when those are the semantics
|
|
29
|
+
you are looking for. `requestTime` gives you a single reliable and consistent time for when the incoming HTTP request was received in
|
|
30
|
+
a server-rendered Route or server-rendered API Route. `Pages.builtAt` gives a single reliable and consistent time when the
|
|
31
|
+
site was built.
|
|
32
|
+
|
|
33
|
+
`BackendTask.Time.now` gives you the time that it happened to execute, which might give you what you need, but be
|
|
34
|
+
aware that the time you get is dependent on how BackendTask's are scheduled and executed internally in elm-pages, and
|
|
35
|
+
its best to avoid depending on that variation when possible.
|
|
36
|
+
|
|
37
|
+
-}
|
|
38
|
+
now : BackendTask error Time.Posix
|
|
39
|
+
now =
|
|
40
|
+
BackendTask.Internal.Request.request
|
|
41
|
+
{ name = "now"
|
|
42
|
+
, body =
|
|
43
|
+
BackendTask.Http.jsonBody Encode.null
|
|
44
|
+
, expect =
|
|
45
|
+
BackendTask.Http.expectJson
|
|
46
|
+
(Decode.int |> Decode.map Time.millisToPosix)
|
|
47
|
+
}
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
module BackendTask exposing
|
|
2
|
+
( BackendTask
|
|
3
|
+
, map, succeed, fail
|
|
4
|
+
, fromResult
|
|
5
|
+
, andThen, resolve, combine
|
|
6
|
+
, andMap
|
|
7
|
+
, map2, map3, map4, map5, map6, map7, map8, map9
|
|
8
|
+
, allowFatal, mapError, onError, toResult
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
{-| In an `elm-pages` app, each Route Module can define a value `data` which is a `BackendTask` 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 `BackendTask` lets you pull in data from:
|
|
15
|
+
|
|
16
|
+
- Local files ([`BackendTask.File`](BackendTask-File))
|
|
17
|
+
- HTTP requests ([`BackendTask.Http`](BackendTask-Http))
|
|
18
|
+
- Globs, i.e. listing out local files based on a pattern like `content/*.txt` ([`BackendTask.Glob`](BackendTask-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 ([`BackendTask.Custom`](BackendTask-Custom))
|
|
20
|
+
- Hardcoded data (`BackendTask.succeed "Hello!"`)
|
|
21
|
+
- Or any combination of the above, using `BackendTask.map2`, `BackendTask.andThen`, or other combining/continuing helpers from this module
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## BackendTask's vs. Effect's/Cmd's
|
|
25
|
+
|
|
26
|
+
BackendTask's are always resolved before the page is rendered and sent to the browser. A BackendTask is never executed
|
|
27
|
+
in the Browser. Instead, the resolved data from the BackendTask is passed down to the Browser - it has been resolved
|
|
28
|
+
before any client-side JavaScript ever executes. In the case of a pre-rendered route, this is during the CLI build phase,
|
|
29
|
+
and for server-rendered routes its BackendTask is resolved on the server.
|
|
30
|
+
|
|
31
|
+
Effect's/Cmd's are never executed on the CLI or server, they are only executed in the Browser. The data from a Route Module's
|
|
32
|
+
`init` function is used to render the initial HTML on the server or build step, but the Effect isn't executed and `update` is never called
|
|
33
|
+
before the page is hydrated in the Browser. This gives a deterministic mental model of what the first render will look like,
|
|
34
|
+
and a nicely typed way to define the initial `Data` you have to render your initial view.
|
|
35
|
+
|
|
36
|
+
Because `elm-pages` hydrates into a full Elm single-page app, it does need the data in order to initialize the Elm app.
|
|
37
|
+
So why not just get the data the old-fashioned way, with `elm/http`, for example?
|
|
38
|
+
|
|
39
|
+
A few reasons:
|
|
40
|
+
|
|
41
|
+
1. BackendTask'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 BackendTask and see the page hot reload as you save!
|
|
42
|
+
2. You can pre-render HTML for your 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.
|
|
43
|
+
3. Because `elm-pages` has a build step, you know that your `BackendTask.Http` requests succeeded, your decoders succeeded, your custom BackendTask 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. In the case of server-rendered routes, a BackendTask failure will render a 500 page, so more care needs to be taken to make sure all common errors are handled properly, but the tradeoff is that you can use BackendTask's to pull in highly dynamic data and even render user-specific pages.
|
|
44
|
+
4. For static routes, 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 optimized binary-encoded data 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 `BackendTask` 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!
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## Mental Model
|
|
48
|
+
|
|
49
|
+
You can think of a BackendTask 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 BackendTasks, etc.).
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## How do I actually use a BackendTask?
|
|
53
|
+
|
|
54
|
+
This is very similar to Cmd's in Elm. You don't perform a Cmd just by running that code, as you might in a language like JavaScript. Instead, a Cmd _will not do anything_ unless you pass it to The Elm Architecture to have it perform it for you.
|
|
55
|
+
You pass a Cmd to The Elm Architecture by returning it in `init` or `update`. So actually a `Cmd` is just data describing a side-effect that the Elm runtime can perform, and how to build a `Msg` once it's done.
|
|
56
|
+
|
|
57
|
+
`BackendTask`'s are very similar. A `BackendTask` doesn't do anything just by "running" it. Just like a `Cmd`, it's only data that describes a side-effect to perform. Specifically, it describes a side-effect that the _elm-pages runtime_ can perform.
|
|
58
|
+
There are a few places where we can pass a `BackendTask` to the `elm-pages` runtime so it can perform it. Most commonly, you give a field called `data` in your Route Module's definition. Instead of giving a `Msg` when the side-effects are complete,
|
|
59
|
+
the page will render once all of the side-effects have run and all the data is resolved. `elm-pages` makes the resolved data available your Route Module's `init`, `view`, `update`, and `head` functions, similar to how a regular Elm app passes `Msg`'s in
|
|
60
|
+
to `update`.
|
|
61
|
+
|
|
62
|
+
Any place in your `elm-pages` app where the framework lets you pass in a value of type `BackendTask` is a place where you can give `elm-pages` a BackendTask to perform (for example, `Site.head` where you define global head tags for your site).
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Basics
|
|
66
|
+
|
|
67
|
+
@docs BackendTask
|
|
68
|
+
|
|
69
|
+
@docs map, succeed, fail
|
|
70
|
+
|
|
71
|
+
@docs fromResult
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Chaining Requests
|
|
75
|
+
|
|
76
|
+
@docs andThen, resolve, combine
|
|
77
|
+
|
|
78
|
+
@docs andMap
|
|
79
|
+
|
|
80
|
+
@docs map2, map3, map4, map5, map6, map7, map8, map9
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## FatalError Handling
|
|
84
|
+
|
|
85
|
+
@docs allowFatal, mapError, onError, toResult
|
|
86
|
+
|
|
87
|
+
-}
|
|
88
|
+
|
|
89
|
+
import FatalError exposing (FatalError)
|
|
90
|
+
import Json.Encode
|
|
91
|
+
import Pages.StaticHttpRequest exposing (RawRequest(..))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
{-| A BackendTask represents data that will be gathered at build time. Multiple `BackendTask`s can be combined together using the `mapN` functions,
|
|
95
|
+
very similar to how you can manipulate values with Json Decoders in Elm.
|
|
96
|
+
-}
|
|
97
|
+
type alias BackendTask error value =
|
|
98
|
+
RawRequest error value
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
{-| Transform a request into an arbitrary value. The same underlying HTTP requests will be performed during the build
|
|
102
|
+
step, but mapping allows you to change the resulting values by applying functions to the results.
|
|
103
|
+
|
|
104
|
+
A common use for this is to map your data into your elm-pages view:
|
|
105
|
+
|
|
106
|
+
import BackendTask
|
|
107
|
+
import Json.Decode as Decode exposing (Decoder)
|
|
108
|
+
|
|
109
|
+
view =
|
|
110
|
+
BackendTask.Http.get
|
|
111
|
+
(Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
|
|
112
|
+
(Decode.field "stargazers_count" Decode.int)
|
|
113
|
+
|> BackendTask.map
|
|
114
|
+
(\stars ->
|
|
115
|
+
{ view =
|
|
116
|
+
\model viewForPage ->
|
|
117
|
+
{ title = "Current stars: " ++ String.fromInt stars
|
|
118
|
+
, body = Html.text <| "⭐️ " ++ String.fromInt stars
|
|
119
|
+
, head = []
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
-}
|
|
125
|
+
map : (a -> b) -> BackendTask error a -> BackendTask error b
|
|
126
|
+
map fn requestInfo =
|
|
127
|
+
case requestInfo of
|
|
128
|
+
ApiRoute value ->
|
|
129
|
+
ApiRoute (Result.map fn value)
|
|
130
|
+
|
|
131
|
+
Request urls lookupFn ->
|
|
132
|
+
Request
|
|
133
|
+
urls
|
|
134
|
+
(mapLookupFn fn lookupFn)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
mapLookupFn : (a -> b) -> (d -> c -> BackendTask error a) -> d -> c -> BackendTask error b
|
|
138
|
+
mapLookupFn fn lookupFn maybeMock requests =
|
|
139
|
+
map fn (lookupFn maybeMock requests)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
{-| Helper to remove an inner layer of Request wrapping.
|
|
143
|
+
-}
|
|
144
|
+
resolve : BackendTask error (List (BackendTask error value)) -> BackendTask error (List value)
|
|
145
|
+
resolve =
|
|
146
|
+
andThen combine
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
{-| Turn a list of `StaticHttp.Request`s into a single one.
|
|
150
|
+
|
|
151
|
+
import BackendTask
|
|
152
|
+
import Json.Decode as Decode exposing (Decoder)
|
|
153
|
+
|
|
154
|
+
type alias Pokemon =
|
|
155
|
+
{ name : String
|
|
156
|
+
, sprite : String
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
pokemonDetailRequest : StaticHttp.Request (List Pokemon)
|
|
160
|
+
pokemonDetailRequest =
|
|
161
|
+
StaticHttp.get
|
|
162
|
+
(Secrets.succeed "https://pokeapi.co/api/v2/pokemon/?limit=3")
|
|
163
|
+
(Decode.field "results"
|
|
164
|
+
(Decode.list
|
|
165
|
+
(Decode.map2 Tuple.pair
|
|
166
|
+
(Decode.field "name" Decode.string)
|
|
167
|
+
(Decode.field "url" Decode.string)
|
|
168
|
+
|> Decode.map
|
|
169
|
+
(\( name, url ) ->
|
|
170
|
+
StaticHttp.get (Secrets.succeed url)
|
|
171
|
+
(Decode.at
|
|
172
|
+
[ "sprites", "front_default" ]
|
|
173
|
+
Decode.string
|
|
174
|
+
|> Decode.map (Pokemon name)
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|> StaticHttp.andThen StaticHttp.combine
|
|
181
|
+
|
|
182
|
+
-}
|
|
183
|
+
combine : List (BackendTask error value) -> BackendTask error (List value)
|
|
184
|
+
combine items =
|
|
185
|
+
List.foldl (map2 (::)) (succeed []) items |> map List.reverse
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
{-| Like map, but it takes in two `Request`s.
|
|
189
|
+
|
|
190
|
+
view siteMetadata page =
|
|
191
|
+
StaticHttp.map2
|
|
192
|
+
(\elmPagesStars elmMarkdownStars ->
|
|
193
|
+
{ view =
|
|
194
|
+
\model viewForPage ->
|
|
195
|
+
{ title = "Repo Stargazers"
|
|
196
|
+
, body = starsView elmPagesStars elmMarkdownStars
|
|
197
|
+
}
|
|
198
|
+
, head = head elmPagesStars elmMarkdownStars
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
(get
|
|
202
|
+
(Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
|
|
203
|
+
(Decode.field "stargazers_count" Decode.int)
|
|
204
|
+
)
|
|
205
|
+
(get
|
|
206
|
+
(Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-markdown")
|
|
207
|
+
(Decode.field "stargazers_count" Decode.int)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
-}
|
|
211
|
+
map2 : (a -> b -> c) -> BackendTask error a -> BackendTask error b -> BackendTask error c
|
|
212
|
+
map2 fn request1 request2 =
|
|
213
|
+
-- elm-review: known-unoptimized-recursion
|
|
214
|
+
-- TODO try to find a way to optimize tail-call recursion here
|
|
215
|
+
case ( request1, request2 ) of
|
|
216
|
+
( ApiRoute value1, ApiRoute value2 ) ->
|
|
217
|
+
ApiRoute (Result.map2 fn value1 value2)
|
|
218
|
+
|
|
219
|
+
( Request urls1 lookupFn1, Request urls2 lookupFn2 ) ->
|
|
220
|
+
Request
|
|
221
|
+
(urls1 ++ urls2)
|
|
222
|
+
(\resolver responses ->
|
|
223
|
+
map2 fn
|
|
224
|
+
(lookupFn1 resolver responses)
|
|
225
|
+
(lookupFn2 resolver responses)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
( Request urls1 lookupFn1, ApiRoute value2 ) ->
|
|
229
|
+
Request
|
|
230
|
+
urls1
|
|
231
|
+
(\resolver responses ->
|
|
232
|
+
map2 fn
|
|
233
|
+
(lookupFn1 resolver responses)
|
|
234
|
+
(ApiRoute value2)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
( ApiRoute value2, Request urls1 lookupFn1 ) ->
|
|
238
|
+
Request
|
|
239
|
+
urls1
|
|
240
|
+
(\resolver responses ->
|
|
241
|
+
map2 fn
|
|
242
|
+
(ApiRoute value2)
|
|
243
|
+
(lookupFn1 resolver responses)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
{-| Build off of the response from a previous `BackendTask` request to build a follow-up request. You can use the data
|
|
248
|
+
from the previous response to build up the URL, headers, etc. that you send to the subsequent request.
|
|
249
|
+
|
|
250
|
+
import BackendTask
|
|
251
|
+
import Json.Decode as Decode exposing (Decoder)
|
|
252
|
+
|
|
253
|
+
licenseData : BackendTask String
|
|
254
|
+
licenseData =
|
|
255
|
+
BackendTask.Http.get
|
|
256
|
+
(Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
|
|
257
|
+
(Decode.at [ "license", "url" ] Decode.string)
|
|
258
|
+
|> BackendTask.andThen
|
|
259
|
+
(\licenseUrl ->
|
|
260
|
+
BackendTask.Http.get (Secrets.succeed licenseUrl) (Decode.field "description" Decode.string)
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
-}
|
|
264
|
+
andThen : (a -> BackendTask error b) -> BackendTask error a -> BackendTask error b
|
|
265
|
+
andThen fn requestInfo =
|
|
266
|
+
-- elm-review: known-unoptimized-recursion
|
|
267
|
+
-- TODO try to find a way to optimize recursion here
|
|
268
|
+
case requestInfo of
|
|
269
|
+
ApiRoute a ->
|
|
270
|
+
case a of
|
|
271
|
+
Ok okA ->
|
|
272
|
+
fn okA
|
|
273
|
+
|
|
274
|
+
Err errA ->
|
|
275
|
+
fail errA
|
|
276
|
+
|
|
277
|
+
Request urls lookupFn ->
|
|
278
|
+
if List.isEmpty urls then
|
|
279
|
+
andThen fn (lookupFn Nothing (Json.Encode.object []))
|
|
280
|
+
|
|
281
|
+
else
|
|
282
|
+
Request urls
|
|
283
|
+
(\maybeMockResolver responses ->
|
|
284
|
+
lookupFn maybeMockResolver responses
|
|
285
|
+
|> andThen fn
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
{-| -}
|
|
290
|
+
onError : (error -> BackendTask mappedError value) -> BackendTask error value -> BackendTask mappedError value
|
|
291
|
+
onError fromError backendTask =
|
|
292
|
+
-- elm-review: known-unoptimized-recursion
|
|
293
|
+
case backendTask of
|
|
294
|
+
ApiRoute a ->
|
|
295
|
+
case a of
|
|
296
|
+
Ok okA ->
|
|
297
|
+
succeed okA
|
|
298
|
+
|
|
299
|
+
Err errA ->
|
|
300
|
+
fromError errA
|
|
301
|
+
|
|
302
|
+
Request urls lookupFn ->
|
|
303
|
+
if List.isEmpty urls then
|
|
304
|
+
onError fromError (lookupFn Nothing (Json.Encode.object []))
|
|
305
|
+
|
|
306
|
+
else
|
|
307
|
+
Request urls
|
|
308
|
+
(\maybeMockResolver responses ->
|
|
309
|
+
lookupFn maybeMockResolver responses
|
|
310
|
+
|> onError fromError
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
{-| A helper for combining `BackendTask`s in pipelines.
|
|
315
|
+
-}
|
|
316
|
+
andMap : BackendTask error a -> BackendTask error (a -> b) -> BackendTask error b
|
|
317
|
+
andMap =
|
|
318
|
+
map2 (|>)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
{-| This is useful for prototyping with some hardcoded data, or for having a view that doesn't have any StaticHttp data.
|
|
322
|
+
|
|
323
|
+
import BackendTask
|
|
324
|
+
|
|
325
|
+
view :
|
|
326
|
+
List ( PagePath, Metadata )
|
|
327
|
+
->
|
|
328
|
+
{ path : PagePath
|
|
329
|
+
, frontmatter : Metadata
|
|
330
|
+
}
|
|
331
|
+
->
|
|
332
|
+
StaticHttp.Request
|
|
333
|
+
{ view : Model -> View -> { title : String, body : Html Msg }
|
|
334
|
+
, head : List (Head.Tag Pages.PathKey)
|
|
335
|
+
}
|
|
336
|
+
view siteMetadata page =
|
|
337
|
+
StaticHttp.succeed
|
|
338
|
+
{ view =
|
|
339
|
+
\model viewForPage ->
|
|
340
|
+
mainView model viewForPage
|
|
341
|
+
, head = head page.frontmatter
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
-}
|
|
345
|
+
succeed : a -> BackendTask error a
|
|
346
|
+
succeed value =
|
|
347
|
+
ApiRoute (Ok value)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
{-| -}
|
|
351
|
+
fail : error -> BackendTask error a
|
|
352
|
+
fail error =
|
|
353
|
+
ApiRoute (Err error)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
{-| Turn an Err into a BackendTask failure.
|
|
357
|
+
-}
|
|
358
|
+
fromResult : Result error value -> BackendTask error value
|
|
359
|
+
fromResult result =
|
|
360
|
+
case result of
|
|
361
|
+
Ok okValue ->
|
|
362
|
+
succeed okValue
|
|
363
|
+
|
|
364
|
+
Err error ->
|
|
365
|
+
fail error
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
{-| -}
|
|
369
|
+
mapError : (error -> errorMapped) -> BackendTask error value -> BackendTask errorMapped value
|
|
370
|
+
mapError mapFn requestInfo =
|
|
371
|
+
case requestInfo of
|
|
372
|
+
ApiRoute value ->
|
|
373
|
+
ApiRoute (Result.mapError mapFn value)
|
|
374
|
+
|
|
375
|
+
Request urls lookupFn ->
|
|
376
|
+
Request
|
|
377
|
+
urls
|
|
378
|
+
(mapLookupFnError mapFn lookupFn)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
mapLookupFnError : (error -> errorMapped) -> (d -> c -> BackendTask error a) -> d -> c -> BackendTask errorMapped a
|
|
382
|
+
mapLookupFnError fn lookupFn maybeMock requests =
|
|
383
|
+
mapError fn (lookupFn maybeMock requests)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
{-| -}
|
|
387
|
+
map3 :
|
|
388
|
+
(value1 -> value2 -> value3 -> valueCombined)
|
|
389
|
+
-> BackendTask error value1
|
|
390
|
+
-> BackendTask error value2
|
|
391
|
+
-> BackendTask error value3
|
|
392
|
+
-> BackendTask error valueCombined
|
|
393
|
+
map3 combineFn request1 request2 request3 =
|
|
394
|
+
succeed combineFn
|
|
395
|
+
|> map2 (|>) request1
|
|
396
|
+
|> map2 (|>) request2
|
|
397
|
+
|> map2 (|>) request3
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
{-| -}
|
|
401
|
+
map4 :
|
|
402
|
+
(value1 -> value2 -> value3 -> value4 -> valueCombined)
|
|
403
|
+
-> BackendTask error value1
|
|
404
|
+
-> BackendTask error value2
|
|
405
|
+
-> BackendTask error value3
|
|
406
|
+
-> BackendTask error value4
|
|
407
|
+
-> BackendTask error valueCombined
|
|
408
|
+
map4 combineFn request1 request2 request3 request4 =
|
|
409
|
+
succeed combineFn
|
|
410
|
+
|> map2 (|>) request1
|
|
411
|
+
|> map2 (|>) request2
|
|
412
|
+
|> map2 (|>) request3
|
|
413
|
+
|> map2 (|>) request4
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
{-| -}
|
|
417
|
+
map5 :
|
|
418
|
+
(value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined)
|
|
419
|
+
-> BackendTask error value1
|
|
420
|
+
-> BackendTask error value2
|
|
421
|
+
-> BackendTask error value3
|
|
422
|
+
-> BackendTask error value4
|
|
423
|
+
-> BackendTask error value5
|
|
424
|
+
-> BackendTask error valueCombined
|
|
425
|
+
map5 combineFn request1 request2 request3 request4 request5 =
|
|
426
|
+
succeed combineFn
|
|
427
|
+
|> map2 (|>) request1
|
|
428
|
+
|> map2 (|>) request2
|
|
429
|
+
|> map2 (|>) request3
|
|
430
|
+
|> map2 (|>) request4
|
|
431
|
+
|> map2 (|>) request5
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
{-| -}
|
|
435
|
+
map6 :
|
|
436
|
+
(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined)
|
|
437
|
+
-> BackendTask error value1
|
|
438
|
+
-> BackendTask error value2
|
|
439
|
+
-> BackendTask error value3
|
|
440
|
+
-> BackendTask error value4
|
|
441
|
+
-> BackendTask error value5
|
|
442
|
+
-> BackendTask error value6
|
|
443
|
+
-> BackendTask error valueCombined
|
|
444
|
+
map6 combineFn request1 request2 request3 request4 request5 request6 =
|
|
445
|
+
succeed combineFn
|
|
446
|
+
|> map2 (|>) request1
|
|
447
|
+
|> map2 (|>) request2
|
|
448
|
+
|> map2 (|>) request3
|
|
449
|
+
|> map2 (|>) request4
|
|
450
|
+
|> map2 (|>) request5
|
|
451
|
+
|> map2 (|>) request6
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
{-| -}
|
|
455
|
+
map7 :
|
|
456
|
+
(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> valueCombined)
|
|
457
|
+
-> BackendTask error value1
|
|
458
|
+
-> BackendTask error value2
|
|
459
|
+
-> BackendTask error value3
|
|
460
|
+
-> BackendTask error value4
|
|
461
|
+
-> BackendTask error value5
|
|
462
|
+
-> BackendTask error value6
|
|
463
|
+
-> BackendTask error value7
|
|
464
|
+
-> BackendTask error valueCombined
|
|
465
|
+
map7 combineFn request1 request2 request3 request4 request5 request6 request7 =
|
|
466
|
+
succeed combineFn
|
|
467
|
+
|> map2 (|>) request1
|
|
468
|
+
|> map2 (|>) request2
|
|
469
|
+
|> map2 (|>) request3
|
|
470
|
+
|> map2 (|>) request4
|
|
471
|
+
|> map2 (|>) request5
|
|
472
|
+
|> map2 (|>) request6
|
|
473
|
+
|> map2 (|>) request7
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
{-| -}
|
|
477
|
+
map8 :
|
|
478
|
+
(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> valueCombined)
|
|
479
|
+
-> BackendTask error value1
|
|
480
|
+
-> BackendTask error value2
|
|
481
|
+
-> BackendTask error value3
|
|
482
|
+
-> BackendTask error value4
|
|
483
|
+
-> BackendTask error value5
|
|
484
|
+
-> BackendTask error value6
|
|
485
|
+
-> BackendTask error value7
|
|
486
|
+
-> BackendTask error value8
|
|
487
|
+
-> BackendTask error valueCombined
|
|
488
|
+
map8 combineFn request1 request2 request3 request4 request5 request6 request7 request8 =
|
|
489
|
+
succeed combineFn
|
|
490
|
+
|> map2 (|>) request1
|
|
491
|
+
|> map2 (|>) request2
|
|
492
|
+
|> map2 (|>) request3
|
|
493
|
+
|> map2 (|>) request4
|
|
494
|
+
|> map2 (|>) request5
|
|
495
|
+
|> map2 (|>) request6
|
|
496
|
+
|> map2 (|>) request7
|
|
497
|
+
|> map2 (|>) request8
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
{-| -}
|
|
501
|
+
map9 :
|
|
502
|
+
(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> value9 -> valueCombined)
|
|
503
|
+
-> BackendTask error value1
|
|
504
|
+
-> BackendTask error value2
|
|
505
|
+
-> BackendTask error value3
|
|
506
|
+
-> BackendTask error value4
|
|
507
|
+
-> BackendTask error value5
|
|
508
|
+
-> BackendTask error value6
|
|
509
|
+
-> BackendTask error value7
|
|
510
|
+
-> BackendTask error value8
|
|
511
|
+
-> BackendTask error value9
|
|
512
|
+
-> BackendTask error valueCombined
|
|
513
|
+
map9 combineFn request1 request2 request3 request4 request5 request6 request7 request8 request9 =
|
|
514
|
+
succeed combineFn
|
|
515
|
+
|> map2 (|>) request1
|
|
516
|
+
|> map2 (|>) request2
|
|
517
|
+
|> map2 (|>) request3
|
|
518
|
+
|> map2 (|>) request4
|
|
519
|
+
|> map2 (|>) request5
|
|
520
|
+
|> map2 (|>) request6
|
|
521
|
+
|> map2 (|>) request7
|
|
522
|
+
|> map2 (|>) request8
|
|
523
|
+
|> map2 (|>) request9
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
{-| -}
|
|
527
|
+
allowFatal : BackendTask { error | fatal : FatalError } data -> BackendTask FatalError data
|
|
528
|
+
allowFatal backendTask =
|
|
529
|
+
mapError .fatal backendTask
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
{-| -}
|
|
533
|
+
toResult : BackendTask error data -> BackendTask noError (Result error data)
|
|
534
|
+
toResult backendTask =
|
|
535
|
+
backendTask
|
|
536
|
+
|> andThen (Ok >> succeed)
|
|
537
|
+
|> onError (Err >> succeed)
|