diff --git a/docs.json b/docs.json index de52555d..5b60ac9e 100644 --- a/docs.json +++ b/docs.json @@ -1 +1 @@ -[{"name":"ApiRoute","comment":" ApiRoute's are defined in `src/Api.elm` and are a way to generate files, like RSS feeds, sitemaps, or any text-based file that you output with an Elm function! You get access\nto a BackendTask so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in\nthe BackendTask for your ApiRoutes, and it won't effect the payload size. Instead, the size of an ApiRoute is just the content you output for that route.\n\nSimilar to your elm-pages Route Modules, ApiRoute's can be either server-rendered or pre-rendered. Let's compare the differences between pre-rendered and server-rendered ApiRoutes, and the different\nuse cases they support.\n\n\n## Pre-Rendering\n\nA pre-rendered ApiRoute is just a generated file. For example:\n\n - [An RSS feed](https://github.com/dillonkearns/elm-pages/blob/131f7b750cdefb2ba7a34a06be06dfbfafc79a86/examples/docs/app/Api.elm#L77-L84) ([Output file](https://elm-pages.com/blog/feed.xml))\n - [A calendar feed in the ical format](https://github.com/dillonkearns/incrementalelm.com/blob/d4934d899d06232dc66dcf9f4b5eccc74bbc60d3/src/Api.elm#L51-L60) ([Output file](https://incrementalelm.com/live.ics))\n - A redirect file for a hosting provider like Netlify\n\nYou could even generate a JavaScript file, an Elm file, or any file with a String body! It's really just a way to generate files, which are typically used to serve files to a user or Browser, but you execute them, copy them, etc. The only limit is your imagination!\nThe beauty is that you have a way to 1) pull in type-safe data using BackendTask's, and 2) write those files, and all in pure Elm!\n\n@docs single, preRender\n\n\n## Server Rendering\n\nYou could use server-rendered ApiRoutes to do a lot of similar things, the main difference being that it will be served up through a URL and generated on-demand when that URL is requested.\nSo for example, for an RSS feed or ical calendar feed like in the pre-rendered examples, you could build the same routes, but you would be pulling in the list of posts or calendar events on-demand rather\nthan upfront at build-time. That means you can hit your database and serve up always-up-to-date data.\n\nNot only that, but your server-rendered ApiRoutes have access to the incoming HTTP request payload just like your server-rendered Route Modules do. Just as with server-rendered Route Modules,\na server-rendered ApiRoute accesses the incoming HTTP request through a [Server.Request.Parser](Server-Request). Consider the use cases that this opens up:\n\n - Serve up protected assets. For example, gated content, like a paid subscriber feed for a podcast that checks authentication information in a query parameter to authenticate that a user has an active paid subscription before serving up the Pro RSS feed.\n - Serve up user-specific content, either through a cookie or other means of authentication\n - Look at the [accepted content-type in the request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) and use that to choose a response format, like XML or JSON ([full example](https://github.com/dillonkearns/elm-pages/blob/131f7b750cdefb2ba7a34a06be06dfbfafc79a86/examples/end-to-end/app/Api.elm#L76-L107)).\n - Look at the [accepted language in the request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language) and use that to choose a language for the response data.\n\n@docs serverRender\n\nYou can also do a hybrid approach using `preRenderWithFallback`. This allows you to pre-render a set of routes at build-time, but build additional routes that weren't rendered at build-time on the fly on the server.\nConceptually, this is just a delayed version of a pre-rendered route. Because of that, you _do not_ have access to the incoming HTTP request (no `Server.Request.Parser` like in server-rendered ApiRoute's).\nThe strategy used to build these routes will differ depending on your hosting provider and the elm-pages adapter you have setup, but generally ApiRoute's that use `preRenderWithFallback` will be cached on the server\nso within a certain time interval (or in the case of [Netlify's DPR](https://www.netlify.com/blog/2021/04/14/distributed-persistent-rendering-a-new-jamstack-approach-for-faster-builds/), until a new build is done)\nthat asset will be served up if that URL was already served up by the server.\n\n@docs preRenderWithFallback\n\n\n## Defining ApiRoute's\n\nYou define your ApiRoute's in `app/Api.elm`. Here's a simple example:\n\n module Api exposing (routes)\n\n import ApiRoute\n import BackendTask exposing (BackendTask)\n import Server.Request\n\n routes :\n BackendTask (List Route)\n -> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)\n -> List (ApiRoute.ApiRoute ApiRoute.Response)\n routes getStaticRoutes htmlToString =\n [ preRenderedExample\n , requestPrinterExample\n ]\n\n {-| Generates the following files when you\n run `elm-pages build`:\n\n - `dist/users/1.json`\n - `dist/users/2.json`\n - `dist/users/3.json`\n\n When you host it, these static assets will\n be served at `/users/1.json`, etc.\n\n -}\n preRenderedExample : ApiRoute.ApiRoute ApiRoute.Response\n preRenderedExample =\n ApiRoute.succeed\n (\\userId ->\n BackendTask.succeed\n (Json.Encode.object\n [ ( \"id\", Json.Encode.string userId )\n , ( \"name\", \"Data for user \" ++ userId |> Json.Encode.string )\n ]\n |> Json.Encode.encode 2\n )\n )\n |> ApiRoute.literal \"users\"\n |> ApiRoute.slash\n |> ApiRoute.capture\n |> ApiRoute.literal \".json\"\n |> ApiRoute.preRender\n (\\route ->\n BackendTask.succeed\n [ route \"1\"\n , route \"2\"\n , route \"3\"\n ]\n )\n\n {-| This returns a JSON response that prints information about the incoming\n HTTP request. In practice you'd want to do something useful with that data,\n and use more of the high-level helpers from the Server.Request API.\n -}\n requestPrinterExample : ApiRoute ApiRoute.Response\n requestPrinterExample =\n ApiRoute.succeed\n (Server.Request.map4\n (\\rawBody method cookies queryParams ->\n Encode.object\n [ ( \"rawBody\"\n , rawBody\n |> Maybe.map Encode.string\n |> Maybe.withDefault Encode.null\n )\n , ( \"method\"\n , method\n |> Server.Request.methodToString\n |> Encode.string\n )\n , ( \"cookies\"\n , cookies\n |> Encode.dict\n identity\n Encode.string\n )\n , ( \"queryParams\"\n , queryParams\n |> Encode.dict\n identity\n (Encode.list Encode.string)\n )\n ]\n |> Response.json\n |> BackendTask.succeed\n )\n Server.Request.rawBody\n Server.Request.method\n Server.Request.allCookies\n Server.Request.queryParams\n )\n |> ApiRoute.literal \"api\"\n |> ApiRoute.slash\n |> ApiRoute.literal \"request-test\"\n |> ApiRoute.serverRender\n\n@docs ApiRoute, ApiRouteBuilder, Response\n\n@docs capture, literal, slash, succeed\n\n\n## Including Head Tags\n\n@docs withGlobalHeadTags\n\n\n## Internals\n\n@docs toJson, getBuildTimeRoutes, getGlobalHeadTagsBackendTask\n\n","unions":[],"aliases":[{"name":"ApiRoute","comment":" ","args":["response"],"type":"Internal.ApiRoute.ApiRoute response"},{"name":"ApiRouteBuilder","comment":" ","args":["a","constructor"],"type":"Internal.ApiRoute.ApiRouteBuilder a constructor"},{"name":"Response","comment":" ","args":[],"type":"Json.Encode.Value"}],"values":[{"name":"capture","comment":" ","type":"ApiRoute.ApiRouteBuilder (String.String -> a) constructor -> ApiRoute.ApiRouteBuilder a (String.String -> constructor)"},{"name":"getBuildTimeRoutes","comment":" For internal use by generated code. Not so useful in user-land.\n","type":"ApiRoute.ApiRoute response -> BackendTask.BackendTask FatalError.FatalError (List.List String.String)"},{"name":"getGlobalHeadTagsBackendTask","comment":" ","type":"ApiRoute.ApiRoute response -> Maybe.Maybe (BackendTask.BackendTask FatalError.FatalError (List.List Head.Tag))"},{"name":"literal","comment":" A literal String segment of a route.\n","type":"String.String -> ApiRoute.ApiRouteBuilder a constructor -> ApiRoute.ApiRouteBuilder a constructor"},{"name":"preRender","comment":" ","type":"(constructor -> BackendTask.BackendTask FatalError.FatalError (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError String.String) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"preRenderWithFallback","comment":" ","type":"(constructor -> BackendTask.BackendTask FatalError.FatalError (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Basics.Never Basics.Never)) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"serverRender","comment":" ","type":"ApiRoute.ApiRouteBuilder (Server.Request.Parser (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Basics.Never Basics.Never))) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"single","comment":" Same as [`preRender`](#preRender), but for an ApiRoute that has no dynamic segments. This is just a bit simpler because\nsince there are no dynamic segments, you don't need to provide a BackendTask with the list of dynamic segments to pre-render because there is only a single possible route.\n","type":"ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError String.String) (List.List String.String) -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"slash","comment":" ","type":"ApiRoute.ApiRouteBuilder a constructor -> ApiRoute.ApiRouteBuilder a constructor"},{"name":"succeed","comment":" ","type":"a -> ApiRoute.ApiRouteBuilder a (List.List String.String)"},{"name":"toJson","comment":" Turn the route into a pattern in JSON format. For internal uses.\n","type":"ApiRoute.ApiRoute response -> Json.Encode.Value"},{"name":"withGlobalHeadTags","comment":" Include head tags on every page's HTML.\n","type":"BackendTask.BackendTask FatalError.FatalError (List.List Head.Tag) -> ApiRoute.ApiRoute response -> ApiRoute.ApiRoute response"}],"binops":[]},{"name":"BackendTask","comment":" 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\nwhen 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.\n\nA `BackendTask` lets you pull in data from:\n\n - Local files ([`BackendTask.File`](BackendTask-File))\n - HTTP requests ([`BackendTask.Http`](BackendTask-Http))\n - Globs, i.e. listing out local files based on a pattern like `content/*.txt` ([`BackendTask.Glob`](BackendTask-Glob))\n - 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))\n - Hardcoded data (`BackendTask.succeed \"Hello!\"`)\n - Or any combination of the above, using `BackendTask.map2`, `BackendTask.andThen`, or other combining/continuing helpers from this module\n\n\n## BackendTask's vs. Effect's/Cmd's\n\nBackendTask's are always resolved before the page is rendered and sent to the browser. A BackendTask is never executed\nin the Browser. Instead, the resolved data from the BackendTask is passed down to the Browser - it has been resolved\nbefore any client-side JavaScript ever executes. In the case of a pre-rendered route, this is during the CLI build phase,\nand for server-rendered routes its BackendTask is resolved on the server.\n\nEffect'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\n`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\nbefore the page is hydrated in the Browser. This gives a deterministic mental model of what the first render will look like,\nand a nicely typed way to define the initial `Data` you have to render your initial view.\n\nBecause `elm-pages` hydrates into a full Elm single-page app, it does need the data in order to initialize the Elm app.\nSo why not just get the data the old-fashioned way, with `elm/http`, for example?\n\nA few reasons:\n\n1. 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!\n2. 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.\n3. 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.\n4. 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!\n\n\n## Mental Model\n\nYou 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.).\n\n\n## How do I actually use a BackendTask?\n\nThis 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.\nYou 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.\n\n`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.\nThere 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,\nthe 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\nto `update`.\n\nAny 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).\n\n\n## Basics\n\n@docs BackendTask\n\n@docs map, succeed, fail\n\n@docs fromResult\n\n\n## Chaining Requests\n\n@docs andThen, resolve, combine\n\n@docs andMap\n\n@docs map2, map3, map4, map5, map6, map7, map8, map9\n\n\n## FatalError Handling\n\n@docs allowFatal, mapError, onError, toResult\n\n","unions":[],"aliases":[{"name":"BackendTask","comment":" A BackendTask represents data that will be gathered at build time. Multiple `BackendTask`s can be combined together using the `mapN` functions,\nvery similar to how you can manipulate values with Json Decoders in Elm.\n","args":["error","value"],"type":"Pages.StaticHttpRequest.RawRequest error value"}],"values":[{"name":"allowFatal","comment":" Ignore any recoverable error data and propagate the `FatalError`. Similar to a `Cmd` in The Elm Architecture,\na `FatalError` will not do anything except if it is returned at the top-level of your application. Read more\nin the [`FatalError` docs](FatalError).\n","type":"BackendTask.BackendTask { error | fatal : FatalError.FatalError } data -> BackendTask.BackendTask FatalError.FatalError data"},{"name":"andMap","comment":" A helper for combining `BackendTask`s in pipelines.\n","type":"BackendTask.BackendTask error a -> BackendTask.BackendTask error (a -> b) -> BackendTask.BackendTask error b"},{"name":"andThen","comment":" Build off of the response from a previous `BackendTask` request to build a follow-up request. You can use the data\nfrom the previous response to build up the URL, headers, etc. that you send to the subsequent request.\n\n import BackendTask\n import Json.Decode as Decode exposing (Decoder)\n\n licenseData : BackendTask String\n licenseData =\n BackendTask.Http.get\n (Secrets.succeed \"https://api.github.com/repos/dillonkearns/elm-pages\")\n (Decode.at [ \"license\", \"url\" ] Decode.string)\n |> BackendTask.andThen\n (\\licenseUrl ->\n BackendTask.Http.get (Secrets.succeed licenseUrl) (Decode.field \"description\" Decode.string)\n )\n\n","type":"(a -> BackendTask.BackendTask error b) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b"},{"name":"combine","comment":" Turn a list of `BackendTask`s into a single one.\n\n import BackendTask\n import Json.Decode as Decode exposing (Decoder)\n\n type alias Pokemon =\n { name : String\n , sprite : String\n }\n\n pokemonDetailRequest : BackendTask (List Pokemon)\n pokemonDetailRequest =\n BackendTask.Http.getJson\n \"https://pokeapi.co/api/v2/pokemon/?limit=3\"\n (Decode.field \"results\"\n (Decode.list\n (Decode.map2 Tuple.pair\n (Decode.field \"name\" Decode.string)\n (Decode.field \"url\" Decode.string)\n |> Decode.map\n (\\( name, url ) ->\n BackendTask.Http.getJson url\n (Decode.at\n [ \"sprites\", \"front_default\" ]\n Decode.string\n |> Decode.map (Pokemon name)\n )\n )\n )\n )\n )\n |> BackendTask.andThen BackendTask.combine\n\n","type":"List.List (BackendTask.BackendTask error value) -> BackendTask.BackendTask error (List.List value)"},{"name":"fail","comment":" ","type":"error -> BackendTask.BackendTask error a"},{"name":"fromResult","comment":" Turn `Ok` into `BackendTask.succeed` and `Err` into `BackendTask.fail`.\n","type":"Result.Result error value -> BackendTask.BackendTask error value"},{"name":"map","comment":" Transform a request into an arbitrary value. The same underlying task will be performed,\nbut mapping allows you to change the resulting values by applying functions to the results.\n\n import BackendTask\n import BackendTask.Http\n import Json.Decode as Decode exposing (Decoder)\n\n starsMessage =\n BackendTask.Http.getJson\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"stargazers_count\" Decode.int)\n |> BackendTask.map\n (\\stars -> \"⭐️ \" ++ String.fromInt stars)\n\n","type":"(a -> b) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b"},{"name":"map2","comment":" Like map, but it takes in two `Request`s.\n\n view siteMetadata page =\n StaticHttp.map2\n (\\elmPagesStars elmMarkdownStars ->\n { view =\n \\model viewForPage ->\n { title = \"Repo Stargazers\"\n , body = starsView elmPagesStars elmMarkdownStars\n }\n , head = head elmPagesStars elmMarkdownStars\n }\n )\n (get\n (Secrets.succeed \"https://api.github.com/repos/dillonkearns/elm-pages\")\n (Decode.field \"stargazers_count\" Decode.int)\n )\n (get\n (Secrets.succeed \"https://api.github.com/repos/dillonkearns/elm-markdown\")\n (Decode.field \"stargazers_count\" Decode.int)\n )\n\n","type":"(a -> b -> c) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b -> BackendTask.BackendTask error c"},{"name":"map3","comment":" ","type":"(value1 -> value2 -> value3 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error valueCombined"},{"name":"map4","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error valueCombined"},{"name":"map5","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error valueCombined"},{"name":"map6","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error valueCombined"},{"name":"map7","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error valueCombined"},{"name":"map8","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error value8 -> BackendTask.BackendTask error valueCombined"},{"name":"map9","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> value9 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error value8 -> BackendTask.BackendTask error value9 -> BackendTask.BackendTask error valueCombined"},{"name":"mapError","comment":" ","type":"(error -> errorMapped) -> BackendTask.BackendTask error value -> BackendTask.BackendTask errorMapped value"},{"name":"onError","comment":" ","type":"(error -> BackendTask.BackendTask mappedError value) -> BackendTask.BackendTask error value -> BackendTask.BackendTask mappedError value"},{"name":"resolve","comment":" Helper to remove an inner layer of Request wrapping.\n","type":"BackendTask.BackendTask error (List.List (BackendTask.BackendTask error value)) -> BackendTask.BackendTask error (List.List value)"},{"name":"succeed","comment":" This is useful for prototyping with some hardcoded data, or for having a view that doesn't have any StaticHttp data.\n\n import BackendTask\n\n view :\n List ( PagePath, Metadata )\n ->\n { path : PagePath\n , frontmatter : Metadata\n }\n ->\n StaticHttp.Request\n { view : Model -> View -> { title : String, body : Html Msg }\n , head : List (Head.Tag Pages.PathKey)\n }\n view siteMetadata page =\n StaticHttp.succeed\n { view =\n \\model viewForPage ->\n mainView model viewForPage\n , head = head page.frontmatter\n }\n\n","type":"a -> BackendTask.BackendTask error a"},{"name":"toResult","comment":" ","type":"BackendTask.BackendTask error data -> BackendTask.BackendTask noError (Result.Result error data)"}],"binops":[]},{"name":"BackendTask.Custom","comment":" In a vanilla Elm application, ports let you either send or receive JSON data between your Elm application and the JavaScript context in the user's browser at runtime.\n\nWith `BackendTask.Custom`, you send and receive JSON to JavaScript running in NodeJS. As with any `BackendTask`, Custom BackendTask's are either run at build-time (for pre-rendered routes) or at request-time (for server-rendered routes). See [`BackendTask`](BackendTask) for more about the\nlifecycle of `BackendTask`'s.\n\nThis means that you can call shell scripts, run NPM packages that are installed, or anything else you could do with NodeJS to perform custom side-effects, get some data, or both.\n\nA `BackendTask.Custom` will call an async JavaScript function with the given name from the definition in a file called `custom-backend-task.js` in your project's root directory. The function receives the input JSON value, and the Decoder is used to decode the return value of the async function.\n\n@docs run\n\nHere is the Elm code and corresponding JavaScript definition for getting an environment variable (or an `FatalError BackendTask.Custom.Error` if it isn't found). In this example,\nwe're using `BackendTask.allowFatal` to let the framework treat that as an unexpected exception, but we could also handle the possible failures of the `FatalError` (see [`FatalError`](FatalError)).\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Custom\n import Json.Encode\n import OptimizedDecoder as Decode\n\n data : BackendTask FatalError String\n data =\n BackendTask.Custom.run \"environmentVariable\"\n (Json.Encode.string \"EDITOR\")\n Decode.string\n |> BackendTask.allowFatal\n\n -- will resolve to \"VIM\" if you run `EDITOR=vim elm-pages dev`\n\n```javascript\n// custom-backend-task.js\n\nmodule.exports =\n /**\n * @param { unknown } fromElm\n * @returns { Promise }\n */\n {\n environmentVariable: async function (name) {\n const result = process.env[name];\n if (result) {\n return result;\n } else {\n throw `No environment variable called ${name}\n\nAvailable:\n\n${Object.keys(process.env).join(\"\\n\")}\n`;\n }\n },\n }\n```\n\n\n## Performance\n\nAs with any JavaScript or NodeJS code, avoid doing blocking IO operations. For example, avoid using `fs.readFileSync`, because blocking IO can slow down your elm-pages builds and dev server. `elm-pages` performs all `BackendTask`'s in parallel whenever possible.\nSo if you do `BackendTask.map2 Tuple.pair myHttpBackendTask myCustomBackendTask`, it will resolve those two in parallel. NodeJS performs best when you take advantage of its ability to do non-blocking I/O (file reads, HTTP requests, etc.). If you use `BackendTask.andThen`,\nit will need to resolve them in sequence rather than in parallel, but it's still best to avoid blocking IO operations in your Custom BackendTask definitions.\n\n\n## Error Handling\n\nThere are a few different things that can go wrong when running a custom-backend-task. These possible errors are captured in the `BackendTask.Custom.Error` type.\n\n@docs Error\n\nAny time you throw a JavaScript exception from a BackendTask.Custom definition, it will give you a `CustomBackendTaskException`. It's usually easier to add a `try`/`catch` in your JavaScript code in `custom-backend-task.js`\nto handle possible errors, but you can throw a JSON value and handle it in Elm in the `CustomBackendTaskException` call error.\n\n\n## Decoding JS Date Objects\n\nThese decoders are for use with decoding JS values of type `Date`. If you have control over the format, it may be better to\nbe more explicit with a [Rata Die](https://en.wikipedia.org/wiki/Rata_Die) number value or an ISO-8601 formatted date string instead.\nBut often JavaScript libraries and core APIs will give you JS Date objects, so this can be useful for working with those.\n\n@docs timeDecoder, dateDecoder\n\n","unions":[{"name":"Error","comment":" ","args":[],"cases":[["Error",[]],["ErrorInCustomBackendTaskFile",[]],["MissingCustomBackendTaskFile",[]],["CustomBackendTaskNotDefined",["{ name : String.String }"]],["CustomBackendTaskException",["Json.Decode.Value"]],["NonJsonException",["String.String"]],["ExportIsNotFunction",[]],["DecodeError",["Json.Decode.Error"]]]}],"aliases":[],"values":[{"name":"dateDecoder","comment":" The same as `timeDecoder`, but it converts the decoded `Time.Posix` value into a `Date` with `Date.fromPosix Time.utc`.\n\nJavaScript `Date` objects don't distinguish between values with only a date vs. values with both a date and a time. So be sure\nto use this decoder when you know the semantics represent a date with no associated time (or you're sure you don't care about the time).\n\n","type":"Json.Decode.Decoder Date.Date"},{"name":"run","comment":" ","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder b -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Custom.Error } b"},{"name":"timeDecoder","comment":" ","type":"Json.Decode.Decoder Time.Posix"}],"binops":[]},{"name":"BackendTask.Env","comment":" Because BackendTask's in `elm-pages` never run in the browser (see [the BackendTask docs](BackendTask)), you can access environment variables securely. As long as the environment variable isn't sent\ndown into the final `Data` value, it won't end up in the client!\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Env\n import FatalError exposing (FatalError)\n\n type alias EnvVariables =\n { sendGridKey : String\n , siteUrl : String\n }\n\n sendEmail : Email -> BackendTask FatalError ()\n sendEmail email =\n BackendTask.map2 EnvVariables\n (BackendTask.Env.expect \"SEND_GRID_KEY\" |> BackendTask.allowFatal)\n (BackendTask.Env.get \"BASE_URL\"\n |> BackendTask.map (Maybe.withDefault \"http://localhost:1234\")\n )\n |> BackendTask.andThen (sendEmailBackendTask email)\n\n sendEmailBackendTask : Email -> EnvVariables -> BackendTask FatalError ()\n sendEmailBackendTask email envVariables =\n Debug.todo \"Not defined here\"\n\n@docs get, expect\n\n\n## Errors\n\n@docs Error\n\n","unions":[{"name":"Error","comment":" ","args":[],"cases":[["MissingEnvVariable",["String.String"]]]}],"aliases":[],"values":[{"name":"expect","comment":" Get an environment variable, or a BackendTask FatalError if there is no environment variable matching that name.\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Env.Error } String.String"},{"name":"get","comment":" Get an environment variable, or Nothing if there is no environment variable matching that name. This `BackendTask`\nwill never fail, but instead will return `Nothing` if the environment variable is missing.\n","type":"String.String -> BackendTask.BackendTask error (Maybe.Maybe String.String)"}],"binops":[]},{"name":"BackendTask.File","comment":" This module lets you read files from the local filesystem as a [`BackendTask`](BackendTask#BackendTask).\nFile paths are relative to the root of your `elm-pages` project (next to the `elm.json` file and `src/` directory).\n\n\n## Files With Frontmatter\n\nFrontmatter is a convention used to keep metadata at the top of a file between `---`'s.\n\nFor example, you might have a file called `blog/hello-world.md` with this content:\n\n```markdown\n---\ntitle: Hello, World!\ntags: elm\n---\nHey there! This is my first post :)\n```\n\nThe frontmatter is in the [YAML format](https://en.wikipedia.org/wiki/YAML) here. You can also use JSON in your elm-pages frontmatter.\n\n```markdown\n---\n{\"title\": \"Hello, World!\", \"tags\": \"elm\"}\n---\nHey there! This is my first post :)\n```\n\nWhether it's YAML or JSON, you use an `Decode` to decode your frontmatter, so it feels just like using\nplain old JSON in Elm.\n\n@docs bodyWithFrontmatter, bodyWithoutFrontmatter, onlyFrontmatter\n\n\n## Reading Files Without Frontmatter\n\n@docs jsonFile, rawFile\n\n\n## FatalErrors\n\n@docs FileReadError\n\n","unions":[{"name":"FileReadError","comment":" ","args":["decoding"],"cases":[["FileDoesntExist",[]],["FileReadError",["String.String"]],["DecodingError",["decoding"]]]}],"aliases":[],"values":[{"name":"bodyWithFrontmatter","comment":"\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import Decode exposing (Decoder)\n\n blogPost : BackendTask BlogPostMetadata\n blogPost =\n File.bodyWithFrontmatter blogPostDecoder\n \"blog/hello-world.md\"\n\n type alias BlogPostMetadata =\n { body : String\n , title : String\n , tags : List String\n }\n\n blogPostDecoder : String -> Decoder BlogPostMetadata\n blogPostDecoder body =\n Decode.map2 (BlogPostMetadata body)\n (Decode.field \"title\" Decode.string)\n (Decode.field \"tags\" tagsDecoder)\n\n tagsDecoder : Decoder (List String)\n tagsDecoder =\n Decode.map (String.split \" \")\n Decode.string\n\nThis will give us a BackendTask that results in the following value:\n\n value =\n { body = \"Hey there! This is my first post :)\"\n , title = \"Hello, World!\"\n , tags = [ \"elm\" ]\n }\n\nIt's common to parse the body with a markdown parser or other format.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import Decode exposing (Decoder)\n import Html exposing (Html)\n\n example :\n BackendTask\n { title : String\n , body : List (Html msg)\n }\n example =\n File.bodyWithFrontmatter\n (\\markdownString ->\n Decode.map2\n (\\title renderedMarkdown ->\n { title = title\n , body = renderedMarkdown\n }\n )\n (Decode.field \"title\" Decode.string)\n (markdownString\n |> markdownToView\n |> Decode.fromResult\n )\n )\n \"foo.md\"\n\n markdownToView :\n String\n -> Result String (List (Html msg))\n markdownToView markdownString =\n markdownString\n |> Markdown.Parser.parse\n |> Result.mapError (\\_ -> \"Markdown error.\")\n |> Result.andThen\n (\\blocks ->\n Markdown.Renderer.render\n Markdown.Renderer.defaultHtmlRenderer\n blocks\n )\n\n","type":"(String.String -> Json.Decode.Decoder frontmatter) -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } frontmatter"},{"name":"bodyWithoutFrontmatter","comment":" Same as `bodyWithFrontmatter` except it doesn't include the frontmatter.\n\nFor example, if you have a file called `blog/hello-world.md` with\n\n```markdown\n---\ntitle: Hello, World!\ntags: elm\n---\nHey there! This is my first post :)\n```\n\n import BackendTask exposing (BackendTask)\n\n data : BackendTask String\n data =\n bodyWithoutFrontmatter \"blog/hello-world.md\"\n\nThen data will yield the value `\"Hey there! This is my first post :)\"`.\n\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } String.String"},{"name":"jsonFile","comment":" Read a file as JSON.\n\nThe Decode will strip off any unused JSON data.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n\n sourceDirectories : BackendTask (List String)\n sourceDirectories =\n File.jsonFile\n (Decode.field\n \"source-directories\"\n (Decode.list Decode.string)\n )\n \"elm.json\"\n\n","type":"Json.Decode.Decoder a -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } a"},{"name":"onlyFrontmatter","comment":" Same as `bodyWithFrontmatter` except it doesn't include the body.\n\nThis is often useful when you're aggregating data, for example getting a listing of blog posts and need to extract\njust the metadata.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import Decode exposing (Decoder)\n\n blogPost : BackendTask BlogPostMetadata\n blogPost =\n File.onlyFrontmatter\n blogPostDecoder\n \"blog/hello-world.md\"\n\n type alias BlogPostMetadata =\n { title : String\n , tags : List String\n }\n\n blogPostDecoder : Decoder BlogPostMetadata\n blogPostDecoder =\n Decode.map2 BlogPostMetadata\n (Decode.field \"title\" Decode.string)\n (Decode.field \"tags\" (Decode.list Decode.string))\n\nIf you wanted to use this to get this metadata for all blog posts in a folder, you could use\nthe [`BackendTask`](BackendTask) API along with [`BackendTask.Glob`](BackendTask-Glob).\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import Decode exposing (Decoder)\n\n blogPostFiles : BackendTask (List String)\n blogPostFiles =\n Glob.succeed identity\n |> Glob.captureFilePath\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.match Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\n allMetadata : BackendTask (List BlogPostMetadata)\n allMetadata =\n blogPostFiles\n |> BackendTask.map\n (List.map\n (File.onlyFrontmatter\n blogPostDecoder\n )\n )\n |> BackendTask.resolve\n\n","type":"Json.Decode.Decoder frontmatter -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } frontmatter"},{"name":"rawFile","comment":" Get the raw file content. Unlike the frontmatter helpers in this module, this function will not strip off frontmatter if there is any.\n\nThis is the function you want if you are reading in a file directly. For example, if you read in a CSV file, a raw text file, or any other file that doesn't\nhave frontmatter.\n\nThere's a special function for reading in JSON files, [`jsonFile`](#jsonFile). If you're reading a JSON file then be sure to\nuse `jsonFile` to get the benefits of the `Decode` here.\n\nYou could read a file called `hello.txt` in your root project directory like this:\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n\n elmJsonFile : BackendTask String\n elmJsonFile =\n File.rawFile \"hello.txt\"\n\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } String.String"}],"binops":[]},{"name":"BackendTask.Glob","comment":"\n\n@docs Glob\n\nThis module helps you get a List of matching file paths from your local file system as a [`BackendTask`](BackendTask#BackendTask). See the [`BackendTask`](BackendTask) module documentation\nfor ways you can combine and map `BackendTask`s.\n\nA common example would be to find all the markdown files of your blog posts. If you have all your blog posts in `content/blog/*.md`\n, then you could use that glob pattern in most shells to refer to each of those files.\n\nWith the `BackendTask.Glob` API, you could get all of those files like so:\n\n import BackendTask exposing (BackendTask)\n\n blogPostsGlob : BackendTask (List String)\n blogPostsGlob =\n Glob.succeed (\\slug -> slug)\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nLet's say you have these files locally:\n\n```shell\n- elm.json\n- src/\n- content/\n - blog/\n - first-post.md\n - second-post.md\n```\n\nWe would end up with a `BackendTask` like this:\n\n BackendTask.succeed [ \"first-post\", \"second-post\" ]\n\nOf course, if you add or remove matching files, the BackendTask will get those new files (unlike `BackendTask.succeed`). That's why we have Glob!\n\nYou can even see the `elm-pages dev` server will automatically flow through any added/removed matching files with its hot module reloading.\n\nBut why did we get `\"first-post\"` instead of a full file path, like `\"content/blog/first-post.md\"`? That's the difference between\n`capture` and `match`.\n\n\n## Capture and Match\n\nThere are two functions for building up a Glob pattern: `capture` and `match`.\n\n`capture` and `match` both build up a `Glob` pattern that will match 0 or more files on your local file system.\nThere will be one argument for every `capture` in your pipeline, whereas `match` does not apply any arguments.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n blogPostsGlob : BackendTask (List String)\n blogPostsGlob =\n Glob.succeed (\\slug -> slug)\n -- no argument from this, but we will only\n -- match files that begin with `content/blog/`\n |> Glob.match (Glob.literal \"content/blog/\")\n -- we get the value of the `wildcard`\n -- as the slug argument\n |> Glob.capture Glob.wildcard\n -- no argument from this, but we will only\n -- match files that end with `.md`\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nSo to understand _which_ files will match, you can ignore whether you are using `capture` or `match` and just read\nthe patterns you're using in order to understand what will match. To understand what Elm data type you will get\n_for each matching file_, you need to see which parts are being captured and how each of those captured values are being\nused in the function you use in `Glob.succeed`.\n\n@docs capture, match\n\n`capture` is a lot like building up a JSON decoder with a pipeline.\n\nLet's try our blogPostsGlob from before, but change every `match` to `capture`.\n\n import BackendTask exposing (BackendTask)\n\n blogPostsGlob :\n BackendTask\n (List\n { filePath : String\n , slug : String\n }\n )\n blogPostsGlob =\n Glob.succeed\n (\\capture1 capture2 capture3 ->\n { filePath = capture1 ++ capture2 ++ capture3\n , slug = capture2\n }\n )\n |> Glob.capture (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.capture (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nNotice that we now need 3 arguments at the start of our pipeline instead of 1. That's because\nwe apply 1 more argument every time we do a `Glob.capture`, much like `Json.Decode.Pipeline.required`, or other pipeline APIs.\n\nNow we actually have the full file path of our files. But having that slug (like `first-post`) is also very helpful sometimes, so\nwe kept that in our record as well. So we'll now have the equivalent of this `BackendTask` with the current `.md` files in our `blog` folder:\n\n BackendTask.succeed\n [ { filePath = \"content/blog/first-post.md\"\n , slug = \"first-post\"\n }\n , { filePath = \"content/blog/second-post.md\"\n , slug = \"second-post\"\n }\n ]\n\nHaving the full file path lets us read in files. But concatenating it manually is tedious\nand error prone. That's what the [`captureFilePath`](#captureFilePath) helper is for.\n\n\n## Reading matching files\n\n@docs captureFilePath\n\nIn many cases you will want to take the matching files from a `Glob` and then read the body or frontmatter from matching files.\n\n\n## Reading Metadata for each Glob Match\n\nFor example, if we had files like this:\n\n```markdown\n---\ntitle: My First Post\n---\nThis is my first post!\n```\n\nThen we could read that title for our blog post list page using our `blogPosts` `BackendTask` that we defined above.\n\n import BackendTask.File\n import Json.Decode as Decode exposing (Decoder)\n\n titles : BackendTask (List BlogPost)\n titles =\n blogPosts\n |> BackendTask.map\n (List.map\n (\\blogPost ->\n BackendTask.File.request\n blogPost.filePath\n (BackendTask.File.frontmatter blogFrontmatterDecoder)\n )\n )\n |> BackendTask.resolve\n\n type alias BlogPost =\n { title : String }\n\n blogFrontmatterDecoder : Decoder BlogPost\n blogFrontmatterDecoder =\n Decode.map BlogPost\n (Decode.field \"title\" Decode.string)\n\nThat will give us\n\n BackendTask.succeed\n [ { title = \"My First Post\" }\n , { title = \"My Second Post\" }\n ]\n\n\n## Capturing Patterns\n\n@docs wildcard, recursiveWildcard\n\n\n## Capturing Specific Characters\n\n@docs int, digits\n\n\n## Matching a Specific Number of Files\n\n@docs expectUniqueMatch, expectUniqueMatchFromList\n\n\n## Glob Patterns\n\n@docs literal\n\n@docs map, succeed\n\n@docs oneOf\n\n@docs zeroOrMore, atLeastOne\n\n\n## Getting Glob Data from a BackendTask\n\n@docs toBackendTask\n\n\n### With Custom Options\n\n@docs toBackendTaskWithOptions\n\n@docs defaultOptions, Options, Include\n\n","unions":[{"name":"Include","comment":" \n\n\n\n","args":[],"cases":[["OnlyFiles",[]],["OnlyFolders",[]],["FilesAndFolders",[]]]}],"aliases":[{"name":"Glob","comment":" A pattern to match local files and capture parts of the path into a nice Elm data type.\n","args":["a"],"type":"BackendTask.Internal.Glob.Glob a"},{"name":"Options","comment":" Custom options you can pass in to run the glob with [`toBackendTaskWithOptions`](#toBackendTaskWithOptions).\n\n { includeDotFiles = Bool -- https://github.com/mrmlnc/fast-glob#dot\n , include = Include -- return results that are `OnlyFiles`, `OnlyFolders`, or both `FilesAndFolders` (default is `OnlyFiles`)\n , followSymbolicLinks = Bool -- https://github.com/mrmlnc/fast-glob#followsymboliclinks\n , caseSensitiveMatch = Bool -- https://github.com/mrmlnc/fast-glob#casesensitivematch\n , gitignore = Bool -- https://www.npmjs.com/package/globby#gitignore\n , maxDepth = Maybe Int -- https://github.com/mrmlnc/fast-glob#deep\n }\n\n","args":[],"type":"{ includeDotFiles : Basics.Bool, include : BackendTask.Glob.Include, followSymbolicLinks : Basics.Bool, caseSensitiveMatch : Basics.Bool, gitignore : Basics.Bool, maxDepth : Maybe.Maybe Basics.Int }"}],"values":[{"name":"atLeastOne","comment":" ","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> BackendTask.Glob.Glob ( a, List.List a )"},{"name":"capture","comment":" Adds on to the glob pattern, and captures it in the resulting Elm match value. That means this both changes which\nfiles will match, and gives you the sub-match as Elm data for each matching file.\n\nExactly the same as `match` except it also captures the matched sub-pattern.\n\n type alias ArchivesArticle =\n { year : String\n , month : String\n , day : String\n , slug : String\n }\n\n archives : BackendTask ArchivesArticle\n archives =\n Glob.succeed ArchivesArticle\n |> Glob.match (Glob.literal \"archive/\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nThe file `archive/1977/06/10/apple-2-released.md` will give us this match:\n\n matches : List ArchivesArticle\n matches =\n BackendTask.succeed\n [ { year = 1977\n , month = 6\n , day = 10\n , slug = \"apple-2-released\"\n }\n ]\n\nWhen possible, it's best to grab data and turn it into structured Elm data when you have it. That way,\nyou don't end up with duplicate validation logic and data normalization, and your code will be more robust.\n\nIf you only care about getting the full matched file paths, you can use `match`. `capture` is very useful because\nyou can pick apart structured data as you build up your glob pattern. This follows the principle of\n[Parse, Don't Validate](https://elm-radio.com/episode/parse-dont-validate/).\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.Glob.Glob (a -> value) -> BackendTask.Glob.Glob value"},{"name":"captureFilePath","comment":"\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n blogPosts :\n BackendTask\n (List\n { filePath : String\n , slug : String\n }\n )\n blogPosts =\n Glob.succeed\n (\\filePath slug ->\n { filePath = filePath\n , slug = slug\n }\n )\n |> Glob.captureFilePath\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nThis function does not change which files will or will not match. It just gives you the full matching\nfile path in your `Glob` pipeline.\n\nWhenever possible, it's a good idea to use function to make sure you have an accurate file path when you need to read a file.\n\n","type":"BackendTask.Glob.Glob (String.String -> value) -> BackendTask.Glob.Glob value"},{"name":"defaultOptions","comment":" The default options used in [`toBackendTask`](#toBackendTask). To use a custom set of options, use [`toBackendTaskWithOptions`](#toBackendTaskWithOptions).\n","type":"BackendTask.Glob.Options"},{"name":"digits","comment":" This is similar to [`wildcard`](#wildcard), but it will only match 1 or more digits (i.e. `[0-9]+`).\n\nSee [`int`](#int) for a convenience function to get an Int value instead of a String of digits.\n\n","type":"BackendTask.Glob.Glob String.String"},{"name":"expectUniqueMatch","comment":" Sometimes you want to make sure there is a unique file matching a particular pattern.\nThis is a simple helper that will give you a `BackendTask` error if there isn't exactly 1 matching file.\nIf there is exactly 1, then you successfully get back that single match.\n\nFor example, maybe you can have\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n findBlogBySlug : String -> BackendTask String\n findBlogBySlug slug =\n Glob.succeed identity\n |> Glob.captureFilePath\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.capture (Glob.literal slug)\n |> Glob.match\n (Glob.oneOf\n ( ( \"\", () )\n , [ ( \"/index\", () ) ]\n )\n )\n |> Glob.match (Glob.literal \".md\")\n |> Glob.expectUniqueMatch\n\nIf we used `findBlogBySlug \"first-post\"` with these files:\n\n```markdown\n- blog/\n - first-post/\n - index.md\n```\n\nThis would give us:\n\n results : BackendTask String\n results =\n BackendTask.succeed \"blog/first-post/index.md\"\n\nIf we used `findBlogBySlug \"first-post\"` with these files:\n\n```markdown\n- blog/\n - first-post.md\n - first-post/\n - index.md\n```\n\nThen we will get a `BackendTask` error saying `More than one file matched.` Keep in mind that `BackendTask` failures\nin build-time routes will cause a build failure, giving you the opportunity to fix the problem before users see the issue,\nso it's ideal to make this kind of assertion rather than having fallback behavior that could silently cover up\nissues (like if we had instead ignored the case where there are two or more matching blog post files).\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : String.String } a"},{"name":"expectUniqueMatchFromList","comment":" ","type":"List.List (BackendTask.Glob.Glob a) -> BackendTask.BackendTask String.String a"},{"name":"int","comment":" Same as [`digits`](#digits), but it safely turns the digits String into an `Int`.\n\nLeading 0's are ignored.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n slides : BackendTask (List Int)\n slides =\n Glob.succeed identity\n |> Glob.match (Glob.literal \"slide-\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nWith files\n\n```shell\n- slide-no-match.md\n- slide-.md\n- slide-1.md\n- slide-01.md\n- slide-2.md\n- slide-03.md\n- slide-4.md\n- slide-05.md\n- slide-06.md\n- slide-007.md\n- slide-08.md\n- slide-09.md\n- slide-10.md\n- slide-11.md\n```\n\nYields\n\n matches : BackendTask (List Int)\n matches =\n BackendTask.succeed\n [ 1\n , 1\n , 2\n , 3\n , 4\n , 5\n , 6\n , 7\n , 8\n , 9\n , 10\n , 11\n ]\n\nNote that neither `slide-no-match.md` nor `slide-.md` match.\nAnd both `slide-1.md` and `slide-01.md` match and turn into `1`.\n\n","type":"BackendTask.Glob.Glob Basics.Int"},{"name":"literal","comment":" Match a literal part of a path. Can include `/`s.\n\nSome common uses include\n\n - The leading part of a pattern, to say \"starts with `content/blog/`\"\n - The ending part of a pattern, to say \"ends with `.md`\"\n - In-between wildcards, to say \"these dynamic parts are separated by `/`\"\n\n```elm\nimport BackendTask exposing (BackendTask)\nimport BackendTask.Glob as Glob\n\nblogPosts =\n Glob.succeed\n (\\section slug ->\n { section = section, slug = slug }\n )\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n```\n\n","type":"String.String -> BackendTask.Glob.Glob String.String"},{"name":"map","comment":" A `Glob` can be mapped. This can be useful for transforming a sub-match in-place.\n\nFor example, if you wanted to take the slugs for a blog post and make sure they are normalized to be all lowercase, you\ncould use\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n blogPostsGlob : BackendTask (List String)\n blogPostsGlob =\n Glob.succeed (\\slug -> slug)\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture (Glob.wildcard |> Glob.map String.toLower)\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nIf you want to validate file formats, you can combine that with some `BackendTask` helpers to turn a `Glob (Result String value)` into\na `BackendTask (List value)`.\n\nFor example, you could take a date and parse it.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n example : BackendTask (List ( String, String ))\n example =\n Glob.succeed\n (\\dateResult slug ->\n dateResult\n |> Result.map (\\okDate -> ( okDate, slug ))\n )\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.capture (Glob.recursiveWildcard |> Glob.map expectDateFormat)\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n |> BackendTask.map (List.map BackendTask.fromResult)\n |> BackendTask.resolve\n\n expectDateFormat : List String -> Result String String\n expectDateFormat dateParts =\n case dateParts of\n [ year, month, date ] ->\n Ok (String.join \"-\" [ year, month, date ])\n\n _ ->\n Err \"Unexpected date format, expected yyyy/mm/dd folder structure.\"\n\n","type":"(a -> b) -> BackendTask.Glob.Glob a -> BackendTask.Glob.Glob b"},{"name":"match","comment":" Adds on to the glob pattern, but does not capture it in the resulting Elm match value. That means this changes which\nfiles will match, but does not change the Elm data type you get for each matching file.\n\nExactly the same as `capture` except it doesn't capture the matched sub-pattern.\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.Glob.Glob value -> BackendTask.Glob.Glob value"},{"name":"oneOf","comment":"\n\n import BackendTask.Glob as Glob\n\n type Extension\n = Json\n | Yml\n\n type alias DataFile =\n { name : String\n , extension : String\n }\n\n dataFiles : BackendTask (List DataFile)\n dataFiles =\n Glob.succeed DataFile\n |> Glob.match (Glob.literal \"my-data/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".\")\n |> Glob.capture\n (Glob.oneOf\n ( ( \"yml\", Yml )\n , [ ( \"json\", Json )\n ]\n )\n )\n\nIf we have the following files\n\n```shell\n- my-data/\n - authors.yml\n - events.json\n```\n\nThat gives us\n\n results : BackendTask (List DataFile)\n results =\n BackendTask.succeed\n [ { name = \"authors\"\n , extension = Yml\n }\n , { name = \"events\"\n , extension = Json\n }\n ]\n\nYou could also match an optional file path segment using `oneOf`.\n\n rootFilesMd : BackendTask (List String)\n rootFilesMd =\n Glob.succeed (\\slug -> slug)\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match\n (Glob.oneOf\n ( ( \"\", () )\n , [ ( \"/index\", () ) ]\n )\n )\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nWith these files:\n\n```markdown\n- blog/\n - first-post.md\n - second-post/\n - index.md\n```\n\nThis would give us:\n\n results : BackendTask (List String)\n results =\n BackendTask.succeed\n [ \"first-post\"\n , \"second-post\"\n ]\n\n","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> BackendTask.Glob.Glob a"},{"name":"recursiveWildcard","comment":" Matches any number of characters, including `/`, as long as it's the only thing in a path part.\n\nIn contrast, `wildcard` will never match `/`, so it only matches within a single path part.\n\nThis is the elm-pages equivalent of `**/*.txt` in standard shell syntax:\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n example : BackendTask (List ( List String, String ))\n example =\n Glob.succeed Tuple.pair\n |> Glob.match (Glob.literal \"articles/\")\n |> Glob.capture Glob.recursiveWildcard\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".txt\")\n |> Glob.toBackendTask\n\nWith these files:\n\n```shell\n- articles/\n - google-io-2021-recap.txt\n - archive/\n - 1977/\n - 06/\n - 10/\n - apple-2-announced.txt\n```\n\nWe would get the following matches:\n\n matches : BackendTask (List ( List String, String ))\n matches =\n BackendTask.succeed\n [ ( [ \"archive\", \"1977\", \"06\", \"10\" ], \"apple-2-announced\" )\n , ( [], \"google-io-2021-recap\" )\n ]\n\nNote that the recursive wildcard conveniently gives us a `List String`, where\neach String is a path part with no slashes (like `archive`).\n\nAnd also note that it matches 0 path parts into an empty list.\n\nIf we didn't include the `wildcard` after the `recursiveWildcard`, then we would only get\na single level of matches because it is followed by a file extension.\n\n example : BackendTask (List String)\n example =\n Glob.succeed identity\n |> Glob.match (Glob.literal \"articles/\")\n |> Glob.capture Glob.recursiveWildcard\n |> Glob.match (Glob.literal \".txt\")\n\n matches : BackendTask (List String)\n matches =\n BackendTask.succeed\n [ \"google-io-2021-recap\"\n ]\n\nThis is usually not what is intended. Using `recursiveWildcard` is usually followed by a `wildcard` for this reason.\n\n","type":"BackendTask.Glob.Glob (List.List String.String)"},{"name":"succeed","comment":" `succeed` is how you start a pipeline for a `Glob`. You will need one argument for each `capture` in your `Glob`.\n","type":"constructor -> BackendTask.Glob.Glob constructor"},{"name":"toBackendTask","comment":" In order to get match data from your glob, turn it into a `BackendTask` with this function.\n","type":"BackendTask.Glob.Glob a -> BackendTask.BackendTask error (List.List a)"},{"name":"toBackendTaskWithOptions","comment":" Same as toBackendTask, but lets you set custom glob options. For example, to list folders instead of files,\n\n import BackendTask.Glob as Glob exposing (OnlyFolders, defaultOptions)\n\n matchingFiles : Glob a -> BackendTask (List a)\n matchingFiles glob =\n glob\n |> Glob.toBackendTaskWithOptions { defaultOptions | include = OnlyFolders }\n\n","type":"BackendTask.Glob.Options -> BackendTask.Glob.Glob a -> BackendTask.BackendTask error (List.List a)"},{"name":"wildcard","comment":" Matches anything except for a `/` in a file path. You may be familiar with this syntax from shells like bash\nwhere you can run commands like `rm client/*.js` to remove all `.js` files in the `client` directory.\n\nJust like a `*` glob pattern in bash, this `Glob.wildcard` function will only match within a path part. If you need to\nmatch 0 or more path parts like, see `recursiveWildcard`.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n type alias BlogPost =\n { year : String\n , month : String\n , day : String\n , slug : String\n }\n\n example : BackendTask (List BlogPost)\n example =\n Glob.succeed BlogPost\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.match Glob.wildcard\n |> Glob.match (Glob.literal \"-\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \"-\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\n```shell\n\n- blog/\n - 2021-05-27/\n - first-post.md\n```\n\nThat will match to:\n\n results : BackendTask (List BlogPost)\n results =\n BackendTask.succeed\n [ { year = \"2021\"\n , month = \"05\"\n , day = \"27\"\n , slug = \"first-post\"\n }\n ]\n\nNote that we can \"destructure\" the date part of this file path in the format `yyyy-mm-dd`. The `wildcard` matches\nwill match _within_ a path part (think between the slashes of a file path). `recursiveWildcard` can match across path parts.\n\n","type":"BackendTask.Glob.Glob String.String"},{"name":"zeroOrMore","comment":" ","type":"List.List String.String -> BackendTask.Glob.Glob (Maybe.Maybe String.String)"}],"binops":[]},{"name":"BackendTask.Http","comment":" `BackendTask.Http` requests are an alternative to doing Elm HTTP requests the traditional way using the `elm/http` package.\n\nThe key differences are:\n\n - `BackendTask.Http.Request`s are performed once at build time (`Http.Request`s are performed at runtime, at whenever point you perform them)\n - `BackendTask.Http.Request`s have a built-in `BackendTask.andThen` that allows you to perform follow-up requests without using tasks\n\n\n## Scenarios where BackendTask.Http is a good fit\n\nIf you need data that is refreshed often you may want to do a traditional HTTP request with the `elm/http` package.\nThe kinds of situations that are served well by static HTTP are with data that updates moderately frequently or infrequently (or never).\nA common pattern is to trigger a new build when data changes. Many JAMstack services\nallow you to send a WebHook to your host (for example, Netlify is a good static file host that supports triggering builds with webhooks). So\nyou may want to have your site rebuild everytime your calendar feed has an event added, or whenever a page or article is added\nor updated on a CMS service like Contentful.\n\nIn scenarios like this, you can serve data that is just as up-to-date as it would be using `elm/http`, but you get the performance\ngains of using `BackendTask.Http.Request`s as well as the simplicity and robustness that comes with it. Read more about these benefits\nin [this article introducing BackendTask.Http requests and some concepts around it](https://elm-pages.com/blog/static-http).\n\n\n## Scenarios where BackendTask.Http is not a good fit\n\n - Data that is specific to the logged-in user\n - Data that needs to be the very latest and changes often (for example, sports scores)\n\n\n## Making a Request\n\n@docs get, getJson\n\n@docs post\n\n\n## Decoding Request Body\n\n@docs Expect, expectString, expectJson, expectBytes, expectWhatever\n\n\n## Error Handling\n\n@docs Error\n\n\n## General Requests\n\n@docs request\n\n\n## Building a BackendTask.Http Request Body\n\nThe way you build a body is analogous to the `elm/http` package. Currently, only `emptyBody` and\n`stringBody` are supported. If you have a use case that calls for a different body type, please open a Github issue\nand describe your use case!\n\n@docs Body, emptyBody, stringBody, jsonBody, bytesBody\n\n\n## Caching Options\n\n`elm-pages` performs GET requests using a local HTTP cache by default. These requests are not performed using Elm's `elm/http`,\nbut rather are performed in NodeJS. Under the hood it uses [the NPM package `make-fetch-happen`](https://github.com/npm/make-fetch-happen).\nOnly GET requests made with `get`, `getJson`, or `getWithOptions` use local caching. Requests made with [`BackendTask.Http.request`](#request)\nare not cached, even if the method is set to `GET`.\n\nIn dev mode, assets are cached more aggressively by default, whereas for a production build assets use a default to revalidate each cached response's freshness before using it (the `ForceRevalidate` [`CacheStrategy`](#CacheStrategy)).\n\nThe default caching behavior for GET requests is to use a local cache in `.elm-pages/http-cache`. This uses the same caching behavior\nthat browsers use to avoid re-downloading content when it hasn't changed. Servers can set HTTP response headers to explicitly control\nthis caching behavior.\n\n - [`cache-control` HTTP response headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) let you set a length of time before considering an asset stale. This could mean that the server considers it acceptable for an asset to be somewhat outdated, or this could mean that the asset is guaranteed to be up-to-date until it is stale - those semantics are up to the server.\n - `Last-Modified` and `ETag` HTTP response headers can be returned by the server allow [Conditional Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests). Conditional Requests let us send back the `Last-Modified` timestamp or `etag` hash for assets that are in our local cache to the server to check if the asset is fresh, and skip re-downloading it if it is unchanged (or download a fresh one otherwise).\n\nIt's important to note that depending on how the server sets these HTTP response headers, we may have outdated data - either because the server explicitly allows assets to become outdated with their cache-control headers, OR because cache-control headers are not set. When these headers aren't explicitly set, [clients are allowed to cache assets for 10% of the amount of time since it was last modified](https://httpwg.org/specs/rfc7234.html#heuristic.freshness).\nFor production builds, the default caching will ignore both the implicit and explicit information about an asset's freshness and _always_ revalidate it before using a locally cached response.\n\n@docs getWithOptions\n\n@docs CacheStrategy\n\n\n## Including HTTP Metadata\n\n@docs withMetadata, Metadata\n\n","unions":[{"name":"CacheStrategy","comment":" ","args":[],"cases":[["IgnoreCache",[]],["ForceRevalidate",[]],["ForceReload",[]],["ForceCache",[]],["ErrorUnlessCached",[]]]},{"name":"Error","comment":" ","args":[],"cases":[["BadUrl",["String.String"]],["Timeout",[]],["NetworkError",[]],["BadStatus",["BackendTask.Http.Metadata","String.String"]],["BadBody",["Maybe.Maybe Json.Decode.Error","String.String"]]]},{"name":"Expect","comment":" Analogous to the `Expect` type in the `elm/http` package. This represents how you will process the data that comes\nback in your BackendTask.Http request.\n\nYou can derive `ExpectJson` from `ExpectString`. Or you could build your own helper to process the String\nas XML, for example, or give an `elm-pages` build error if the response can't be parsed as XML.\n\n","args":["value"],"cases":[]}],"aliases":[{"name":"Body","comment":" A body for a BackendTask.Http request.\n","args":[],"type":"Pages.Internal.StaticHttpBody.Body"},{"name":"Metadata","comment":" ","args":[],"type":"{ url : String.String, statusCode : Basics.Int, statusText : String.String, headers : Dict.Dict String.String String.String }"}],"values":[{"name":"bytesBody","comment":" Build a body from `Bytes` for a BackendTask.Http request. See [elm/http's `Http.bytesBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#bytesBody).\n","type":"String.String -> Bytes.Bytes -> BackendTask.Http.Body"},{"name":"emptyBody","comment":" Build an empty body for a BackendTask.Http request. See [elm/http's `Http.emptyBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#emptyBody).\n","type":"BackendTask.Http.Body"},{"name":"expectBytes","comment":" ","type":"Bytes.Decode.Decoder value -> BackendTask.Http.Expect value"},{"name":"expectJson","comment":" Handle the incoming response as JSON and don't optimize the asset and strip out unused values.\nBe sure to use the `BackendTask.Http.request` function if you want an optimized request that\nstrips out unused JSON to optimize your asset size. This function makes sense to use for things like a GraphQL request\nwhere the JSON payload is already trimmed down to the data you explicitly requested.\n\nIf the function you pass to `expectString` yields an `Err`, then you will get a build error that will\nfail your `elm-pages` build and print out the String from the `Err`.\n\n","type":"Json.Decode.Decoder value -> BackendTask.Http.Expect value"},{"name":"expectString","comment":" Gives the HTTP response body as a raw String.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Http\n\n request : BackendTask String\n request =\n BackendTask.Http.request\n { url = \"https://example.com/file.txt\"\n , method = \"GET\"\n , headers = []\n , body = BackendTask.Http.emptyBody\n }\n BackendTask.Http.expectString\n\n","type":"BackendTask.Http.Expect String.String"},{"name":"expectWhatever","comment":" ","type":"value -> BackendTask.Http.Expect value"},{"name":"get","comment":" A simplified helper around [`BackendTask.Http.getWithOptions`](#getWithOptions), which builds up a GET request with\nthe default retries, timeout, and HTTP caching options. If you need to configure those options or include HTTP request headers,\nuse the more flexible `getWithOptions`.\n\n import BackendTask\n import BackendTask.Http\n import FatalError exposing (FatalError)\n\n getRequest : BackendTask (FatalError Error) String\n getRequest =\n BackendTask.Http.get\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n BackendTask.Http.expectString\n\n","type":"String.String -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"getJson","comment":" A simplified helper around [`BackendTask.Http.get`](#get), which builds up a BackendTask.Http GET request with `expectJson`.\n\n import BackendTask\n import BackendTask.Http\n import FatalError exposing (FatalError)\n import Json.Decode as Decode exposing (Decoder)\n\n getRequest : BackendTask (FatalError Error) Int\n getRequest =\n BackendTask.Http.getJson\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"stargazers_count\" Decode.int)\n\n","type":"String.String -> Json.Decode.Decoder a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"getWithOptions","comment":" Perform a GET request, with some additional options for the HTTP request, including options for caching behavior.\n\n - `retries` - Default is 0. Will try performing request again if set to a number greater than 0.\n - `timeoutInMs` - Default is no timeout.\n - `cacheStrategy` - The [caching options are passed to the NPM package `make-fetch-happen`](https://github.com/npm/make-fetch-happen#opts-cache)\n - `cachePath` - override the default directory for the local HTTP cache. This can be helpful if you want more granular control to clear some HTTP caches more or less frequently than others. Or you may want to preserve the local cache for some requests in your build server, but not store the cache for other requests.\n\n","type":"{ url : String.String, expect : BackendTask.Http.Expect a, headers : List.List ( String.String, String.String ), cacheStrategy : Maybe.Maybe BackendTask.Http.CacheStrategy, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int, cachePath : Maybe.Maybe String.String } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"jsonBody","comment":" Builds a JSON body for a BackendTask.Http request. See [elm/http's `Http.jsonBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#jsonBody).\n","type":"Json.Encode.Value -> BackendTask.Http.Body"},{"name":"post","comment":" ","type":"String.String -> BackendTask.Http.Body -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"request","comment":" ","type":"{ url : String.String, method : String.String, headers : List.List ( String.String, String.String ), body : BackendTask.Http.Body, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int } -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"stringBody","comment":" Builds a string body for a BackendTask.Http request. See [elm/http's `Http.stringBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#stringBody).\n\nNote from the `elm/http` docs:\n\n> The first argument is a [MIME type](https://en.wikipedia.org/wiki/Media_type) of the body. Some servers are strict about this!\n\n","type":"String.String -> String.String -> BackendTask.Http.Body"},{"name":"withMetadata","comment":" ","type":"(BackendTask.Http.Metadata -> value -> combined) -> BackendTask.Http.Expect value -> BackendTask.Http.Expect combined"}],"binops":[]},{"name":"BackendTask.Random","comment":"\n\n@docs generate\n\n@docs int32\n\n","unions":[],"aliases":[],"values":[{"name":"generate","comment":" Takes an `elm/random` `Random.Generator` and runs it using a randomly generated initial seed.\n\n type alias Data =\n { randomData : ( Int, Float )\n }\n\n data : BackendTask FatalError Data\n data =\n BackendTask.map Data\n (BackendTask.Random.generate generator)\n\n generator : Random.Generator ( Int, Float )\n generator =\n Random.map2 Tuple.pair (Random.int 0 100) (Random.float 0 100)\n\nThe random initial seed is generated using \nto generate a single 32-bit Integer. That 32-bit Integer is then used with `Random.initialSeed` to create an Elm Random.Seed value.\nThen that `Seed` used to run the `Generator`.\n\nNote that this is different than `elm/random`'s `Random.generate`. This difference shouldn't be problematic, and in fact the `BackendTask`\nrandom seed generation is more cryptographically independent because you can't determine the\nrandom seed based solely on the time at which it is run. Each time you call `BackendTask.generate` it uses a newly\ngenerated random seed to run the `Random.Generator` that is passed in. In contrast, `elm/random`'s `Random.generate`\ngenerates 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)\nto get new random values after that. You can [see the implementation here](https://github.com/elm/random/blob/c1c9da4d861363cee1c93382d2687880279ed0dd/src/Random.elm#L865-L896).\nHowever, `elm/random` is still not suitable in general for cryptographic uses of random because it uses 32-bits for when it\nsteps through new seeds while running a single `Random.Generator`.\n\n","type":"Random.Generator value -> BackendTask.BackendTask error value"},{"name":"int32","comment":" Gives a random 32-bit Int. This can be useful if you want to do low-level things with a cryptographically sound\nrandom 32-bit integer.\n\nThe value comes from running this code in Node using :\n\n```js\nimport * as crypto from \"node:crypto\";\n\ncrypto.getRandomValues(new Uint32Array(1))[0]\n```\n\n","type":"BackendTask.BackendTask error Basics.Int"}],"binops":[]},{"name":"BackendTask.Time","comment":"\n\n@docs now\n\n","unions":[],"aliases":[],"values":[{"name":"now","comment":" Gives a `Time.Posix` of when the `BackendTask` executes.\n\n type alias Data =\n { time : Time.Posix\n }\n\n data : BackendTask FatalError Data\n data =\n BackendTask.map Data\n BackendTask.Time.now\n\nIt's better to use [`Server.Request.requestTime`](Server-Request#requestTime) or `Pages.builtAt` when those are the semantics\nyou are looking for. `requestTime` gives you a single reliable and consistent time for when the incoming HTTP request was received in\na server-rendered Route or server-rendered API Route. `Pages.builtAt` gives a single reliable and consistent time when the\nsite was built.\n\n`BackendTask.Time.now` gives you the time that it happened to execute, which might give you what you need, but be\naware that the time you get is dependent on how BackendTask's are scheduled and executed internally in elm-pages, and\nits best to avoid depending on that variation when possible.\n\n","type":"BackendTask.BackendTask error Time.Posix"}],"binops":[]},{"name":"FatalError","comment":" The Elm language doesn't have the concept of exceptions or special control flow for errors. It just has\nCustom Types, and by convention types like `Result` and the `Err` variant are used to represent possible failure states\nand combine together different error states.\n\n`elm-pages` doesn't change that, Elm still doesn't have special exception control flow at the language level. It does have\na type, which is just a regular old Elm type, called `FatalError`. Why? Because this plain old Elm type does have one\nspecial characteristic - the `elm-pages` framework knows how to turn it into an error message. This becomes interesting\nbecause an `elm-pages` app has several places that accept a value of type `BackendTask FatalError.FatalError value`.\nThis design lets the `elm-pages` framework do some of the work for you.\n\nFor example, if you wanted to handle possible errors to present them to the user\n\n type alias Data =\n String\n\n data : RouteParams -> BackendTask FatalError Data\n data routeParams =\n BackendTask.Http.getJson \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"description\" Decode.string)\n |> BackendTask.onError\n (\\error ->\n case FatalError.unwrap error of\n BackendTask.Http.BadStatus metadata string ->\n if metadata.statusCode == 401 || metadata.statusCode == 403 || metadata.statusCode == 404 then\n BackendTask.succeed \"Either this repo doesn't exist or you don't have access to it.\"\n\n else\n -- we're only handling these expected error cases. In the case of an HTTP timeout,\n -- we'll let the error propagate as a FatalError\n BackendTask.fail error |> BackendTask.allowFatal\n\n _ ->\n BackendTask.fail error |> BackendTask.allowFatal\n )\n\nThis can be a lot of work for all possible errors, though. If you don't expect this kind of error (it's an _exceptional_ case),\nyou can let the framework handle it if the error ever does unexpectedly occur.\n\n data : RouteParams -> BackendTask FatalError Data\n data routeParams =\n BackendTask.Http.getJson \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"description\" Decode.string)\n |> BackendTask.allowFatal\n\nThis is especially useful for pages generated at build-time (`RouteBuilder.preRender`) where you want the build\nto fail if anything unexpected happens. With pre-rendered routes, you know that these error cases won't\nbe seen by users, so it's often a great idea to just let the framework handle these unexpected errors so a developer can\ndebug them and see what went wrong. In the example above, maybe we are only pre-rendering pages for a set of known\nGitHub Repositories, so a Not Found or Unauthorized HTTP error would be unexpected and should stop the build so we can fix the\nissue.\n\nIn the case of server-rendered Routes (`RouteBuilder.serverRender`), `elm-pages` will show your 500 error page\nwhen these errors occur.\n\n@docs FatalError, build, fromString, recoverable\n\n","unions":[],"aliases":[{"name":"FatalError","comment":" ","args":[],"type":"Pages.Internal.FatalError.FatalError"}],"values":[{"name":"build","comment":" Create a FatalError with a title and body.\n","type":"{ title : String.String, body : String.String } -> FatalError.FatalError"},{"name":"fromString","comment":" ","type":"String.String -> FatalError.FatalError"},{"name":"recoverable","comment":" ","type":"{ title : String.String, body : String.String } -> error -> { fatal : FatalError.FatalError, recoverable : error }"}],"binops":[]},{"name":"Head","comment":" This module contains functions for building up\ntags with metadata that will be rendered into the page's `` tag\nwhen your page is pre-rendered (or server-rendered, in the case of your server-rendered Route Modules). See also [`Head.Seo`](Head-Seo),\nwhich has some helper functions for defining OpenGraph and Twitter tags.\n\nOne of the unique benefits of using `elm-pages` is that all of your routes (both pre-rendered and server-rendered) fully\nrender the HTML of your page. That includes the full initial `view` (with the BackendTask resolved, and the `Model` from `init`).\nThe HTML response also includes all of the `Head` tags, which are defined in two places:\n\n1. `app/Site.elm` - there is a `head` definition in `Site.elm` where you define global head tags that will be included on every rendered page.\n\n2. In each Route Module - there is a `head` function where you have access to both the resolved `BackendTask` and the `RouteParams` for the page and can return head tags based on that.\n\nHere is a common set of global head tags that we can define in `Site.elm`:\n\n module Site exposing (canonicalUrl, config)\n\n import BackendTask exposing (BackendTask)\n import Head\n import MimeType\n import SiteConfig exposing (SiteConfig)\n\n config : SiteConfig\n config =\n { canonicalUrl = \"\n , head = head\n }\n\n head : BackendTask (List Head.Tag)\n head =\n [ Head.metaName \"viewport\" (Head.raw \"width=device-width,initial-scale=1\")\n , Head.metaName \"mobile-web-app-capable\" (Head.raw \"yes\")\n , Head.metaName \"theme-color\" (Head.raw \"#ffffff\")\n , Head.metaName \"apple-mobile-web-app-capable\" (Head.raw \"yes\")\n , Head.metaName \"apple-mobile-web-app-status-bar-style\" (Head.raw \"black-translucent\")\n , Head.icon [ ( 32, 32 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 32)\n , Head.icon [ ( 16, 16 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 16)\n , Head.appleTouchIcon (Just 180) (cloudinaryIcon MimeType.Png 180)\n , Head.appleTouchIcon (Just 192) (cloudinaryIcon MimeType.Png 192)\n ]\n |> BackendTask.succeed\n\nAnd here is a `head` function for a Route Module for a blog post. Note that we have access to our `BackendTask` Data and\nare using it to populate article metadata like the article's image, publish date, etc.\n\n import Article\n import BackendTask\n import Date\n import Head\n import Head.Seo\n import Path\n import Route exposing (Route)\n import RouteBuilder exposing (App, StatelessRoute)\n\n type alias RouteParams =\n { slug : String }\n\n type alias Data =\n { metadata : ArticleMetadata\n , body : List Markdown.Block.Block\n }\n\n route : StatelessRoute RouteParams Data ActionData\n route =\n RouteBuilder.preRender\n { data = data\n , head = head\n , pages = pages\n }\n |> RouteBuilder.buildNoState { view = view }\n\n head :\n App Data ActionData RouteParams\n -> List Head.Tag\n head static =\n let\n metadata =\n static.data.metadata\n in\n Head.Seo.summaryLarge\n { canonicalUrlOverride = Nothing\n , siteName = \"elm-pages\"\n , image =\n { url = metadata.image\n , alt = metadata.description\n , dimensions = Nothing\n , mimeType = Nothing\n }\n , description = metadata.description\n , locale = Nothing\n , title = metadata.title\n }\n |> Head.Seo.article\n { tags = []\n , section = Nothing\n , publishedTime = Just (DateOrDateTime.Date metadata.published)\n , modifiedTime = Nothing\n , expirationTime = Nothing\n }\n\n\n## Why is pre-rendered HTML important? Does it still matter for SEO?\n\nMany search engines are able to execute JavaScript now. However, not all are, and even with crawlers like Google, there\nis a longer lead time for your pages to be indexed when you have HTML with a blank page that is only visible after the JavaScript executes.\n\nBut most importantly, many tools that unfurl links will not execute JavaScript at all, but rather simply do a simple pass to parse your `` tags.\nIt is not viable or reliable to add `` tags for metadata on the client-side, it must be present in the initial HTML payload. Otherwise you may not\nget unfurling preview content when you share a link to your site on Slack, Twitter, etc.\n\n\n## Building up Head Tags\n\n@docs Tag, metaName, metaProperty, metaRedirect\n@docs rssLink, sitemapLink, rootLanguage, manifestLink\n\n@docs nonLoadingNode\n\n\n## Structured Data\n\n@docs structuredData\n\n\n## `AttributeValue`s\n\n@docs AttributeValue\n@docs currentPageFullUrl, urlAttribute, raw\n\n\n## Icons\n\n@docs appleTouchIcon, icon\n\n\n## Functions for use by generated code\n\n@docs toJson, canonicalLink\n\n","unions":[{"name":"AttributeValue","comment":" Values, such as between the `<>`'s here:\n\n```html\n\" content=\"\" />\n```\n\n","args":[],"cases":[]},{"name":"Tag","comment":" Values that can be passed to the generated `Pages.application` config\nthrough the `head` function.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"appleTouchIcon","comment":" Note: the type must be png.\nSee .\n\nIf a size is provided, it will be turned into square dimensions as per the recommendations here: \n\nImages must be png's, and non-transparent images are recommended. Current recommended dimensions are 180px and 192px.\n\n","type":"Maybe.Maybe Basics.Int -> Pages.Url.Url -> Head.Tag"},{"name":"canonicalLink","comment":" It's recommended that you use the `Seo` module helpers, which will provide this\nfor you, rather than directly using this.\n\nExample:\n\n Head.canonicalLink \"https://elm-pages.com\"\n\n","type":"Maybe.Maybe String.String -> Head.Tag"},{"name":"currentPageFullUrl","comment":" Create an `AttributeValue` representing the current page's full url.\n","type":"Head.AttributeValue"},{"name":"icon","comment":" ","type":"List.List ( Basics.Int, Basics.Int ) -> MimeType.MimeImage -> Pages.Url.Url -> Head.Tag"},{"name":"manifestLink","comment":" Let's you link to your manifest.json file, see .\n","type":"String.String -> Head.Tag"},{"name":"metaName","comment":" Example:\n\n Head.metaName \"twitter:card\" (Head.raw \"summary_large_image\")\n\nResults in ``\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaProperty","comment":" Example:\n\n Head.metaProperty \"fb:app_id\" (Head.raw \"123456789\")\n\nResults in ``\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaRedirect","comment":" Example:\n\n metaRedirect (Raw \"0; url=https://google.com\")\n\nResults in ``\n\n","type":"Head.AttributeValue -> Head.Tag"},{"name":"nonLoadingNode","comment":" Escape hatch for any head tags that don't have high-level helpers. This lets you build arbitrary head nodes as long as they\nare not loading or preloading directives.\n\nTags that do loading/pre-loading will not work from this function. `elm-pages` uses ViteJS for loading assets like\nscript tags, stylesheets, fonts, etc., and allows you to customize which assets to preload and how through the elm-pages.config.mjs file.\nSee the full discussion of the design in [#339](https://github.com/dillonkearns/elm-pages/discussions/339).\n\nSo for example the following tags would _not_ load if defined through `nonLoadingNode`, and would instead need to be registered through Vite:\n\n - `\n```\n\nTo get that data, you would write this in your `elm-pages` head tags:\n\n import Json.Encode as Encode\n\n {-| \n -}\n encodeArticle :\n { title : String\n , description : String\n , author : StructuredDataHelper { authorMemberOf | personOrOrganization : () } authorPossibleFields\n , publisher : StructuredDataHelper { publisherMemberOf | personOrOrganization : () } publisherPossibleFields\n , url : String\n , imageUrl : String\n , datePublished : String\n , mainEntityOfPage : Encode.Value\n }\n -> Head.Tag\n encodeArticle info =\n Encode.object\n [ ( \"@context\", Encode.string \"http://schema.org/\" )\n , ( \"@type\", Encode.string \"Article\" )\n , ( \"headline\", Encode.string info.title )\n , ( \"description\", Encode.string info.description )\n , ( \"image\", Encode.string info.imageUrl )\n , ( \"author\", encode info.author )\n , ( \"publisher\", encode info.publisher )\n , ( \"url\", Encode.string info.url )\n , ( \"datePublished\", Encode.string info.datePublished )\n , ( \"mainEntityOfPage\", info.mainEntityOfPage )\n ]\n |> Head.structuredData\n\nTake a look at this [Google Search Gallery](https://developers.google.com/search/docs/guides/search-gallery)\nto see some examples of how structured data can be used by search engines to give rich search results. It can help boost\nyour rankings, get better engagement for your content, and also make your content more accessible. For example,\nvoice assistant devices can make use of structured data. If you're hosting a conference and want to make the event\ndate and location easy for attendees to find, this can make that information more accessible.\n\nFor the current version of API, you'll need to make sure that the format is correct and contains the required and recommended\nstructure.\n\nCheck out for a comprehensive listing of possible data types and fields. And take a look at\nGoogle's [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool)\ntoo make sure that your structured data is valid and includes the recommended values.\n\nIn the future, `elm-pages` will likely support a typed API, but schema.org is a massive spec, and changes frequently.\nAnd there are multiple sources of information on the possible and recommended structure. So it will take some time\nfor the right API design to evolve. In the meantime, this allows you to make use of this for SEO purposes.\n\n","type":"Json.Encode.Value -> Head.Tag"},{"name":"toJson","comment":" Feel free to use this, but in 99% of cases you won't need it. The generated\ncode will run this for you to generate your `manifest.json` file automatically!\n","type":"String.String -> String.String -> Head.Tag -> Json.Encode.Value"},{"name":"urlAttribute","comment":" Create an `AttributeValue` from an `ImagePath`.\n","type":"Pages.Url.Url -> Head.AttributeValue"}],"binops":[]},{"name":"Head.Seo","comment":" \n\nThis module encapsulates some of the best practices for SEO for your site.\n\n`elm-pages` pre-renders the HTML for your pages (either at build-time or server-render time) so that\nweb crawlers can efficiently and accurately process it. The functions in this module are for use\nwith the `head` function in your `Route` modules to help you build up a set of `` tags that\nincludes common meta tags used for rich link previews, namely [OpenGraph tags](https://ogp.me/) and [Twitter card tags](https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards).\n\n import Date\n import Head\n import Head.Seo as Seo\n\n\n -- justinmimbs/date package\n type alias ArticleMetadata =\n { title : String\n , description : String\n , published : Date\n , author : Data.Author.Author\n }\n\n head : ArticleMetadata -> List Head.Tag\n head articleMetadata =\n Seo.summaryLarge\n { canonicalUrlOverride = Nothing\n , siteName = \"elm-pages\"\n , image =\n { url = Pages.images.icon\n , alt = articleMetadata.description\n , dimensions = Nothing\n , mimeType = Nothing\n }\n , description = articleMetadata.description\n , locale = Nothing\n , title = articleMetadata.title\n }\n |> Seo.article\n { tags = []\n , section = Nothing\n , publishedTime = Just (Date.toIsoString articleMetadata.published)\n , modifiedTime = Nothing\n , expirationTime = Nothing\n }\n\n@docs Common, Image, article, audioPlayer, book, profile, song, summary, summaryLarge, videoPlayer, website\n\n","unions":[],"aliases":[{"name":"Common","comment":" These fields apply to any type in the og object types\nSee and \n\nSkipping this for now, if there's a use case I can add it in:\n\n - og:determiner - The word that appears before this object's title in a sentence. An enum of (a, an, the, \"\", auto). If auto is chosen, the consumer of your data should chose between \"a\" or \"an\". Default is \"\" (blank).\n\n","args":[],"type":"{ title : String.String, image : Head.Seo.Image, canonicalUrlOverride : Maybe.Maybe String.String, description : String.String, siteName : String.String, audio : Maybe.Maybe Head.Seo.Audio, video : Maybe.Maybe Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale, alternateLocales : List.List Head.Seo.Locale, twitterCard : Head.Twitter.TwitterCard }"},{"name":"Image","comment":" See \n","args":[],"type":"{ url : Pages.Url.Url, alt : String.String, dimensions : Maybe.Maybe { width : Basics.Int, height : Basics.Int }, mimeType : Maybe.Maybe MimeType.MimeType }"}],"values":[{"name":"article","comment":" See \n","type":"{ tags : List.List String.String, section : Maybe.Maybe String.String, publishedTime : Maybe.Maybe DateOrDateTime.DateOrDateTime, modifiedTime : Maybe.Maybe DateOrDateTime.DateOrDateTime, expirationTime : Maybe.Maybe DateOrDateTime.DateOrDateTime } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"audioPlayer","comment":" Will be displayed as a Player card in twitter\nSee: \n\nOpenGraph audio will also be included.\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, audio : Head.Seo.Audio, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"book","comment":" See \n","type":"Head.Seo.Common -> { tags : List.List String.String, isbn : Maybe.Maybe String.String, releaseDate : Maybe.Maybe DateOrDateTime.DateOrDateTime } -> List.List Head.Tag"},{"name":"profile","comment":" See \n","type":"{ firstName : String.String, lastName : String.String, username : Maybe.Maybe String.String } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"song","comment":" See \n","type":"Head.Seo.Common -> { duration : Maybe.Maybe Basics.Int, album : Maybe.Maybe Basics.Int, disc : Maybe.Maybe Basics.Int, track : Maybe.Maybe Basics.Int } -> List.List Head.Tag"},{"name":"summary","comment":" Will be displayed as a large card in twitter\nSee: \n\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"summaryLarge","comment":" Will be displayed as a large card in twitter\nSee: \n\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"videoPlayer","comment":" Will be displayed as a Player card in twitter\nSee: \n\nOpenGraph video will also be included.\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, video : Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"website","comment":" \n","type":"Head.Seo.Common -> List.List Head.Tag"}],"binops":[]},{"name":"Pages.ConcurrentSubmission","comment":" When you render a `Form` with the [`Pages.Form.withConcurrent`](Pages-Form#withConcurrent) `Option`, the state of in-flight and completed submissions will be available\nfrom your `Route` module through `app.concurrentSubmissions` as a `Dict String (ConcurrentSubmission (Maybe Action))`.\n\nYou can use this state to declaratively derive Pending UI or Optimistic UI from your pending submissions (without managing the state in your `Model`, since `elm-pages`\nmanages form submission state for you).\n\nYou can [see the full-stack TodoMVC example](https://github.com/dillonkearns/elm-pages-v3-beta/blob/master/examples/todos/app/Route/Visibility__.elm) for a complete example of deriving Pending UI state from `app.concurrentSubmissions`.\n\nFor example, this how the TodoMVC example derives the list of new items that are being created (but are still pending).\n\n view :\n App Data ActionData RouteParams\n -> Shared.Model\n -> Model\n -> View (PagesMsg Msg)\n view app shared model =\n let\n pendingActions : List Action\n pendingActions =\n app.concurrentSubmissions\n |> Dict.values\n |> List.filterMap\n (\\{ status, payload } ->\n case status of\n Pages.ConcurrentSubmission.Complete _ ->\n Nothing\n\n _ ->\n allForms\n |> Form.Handler.run payload.fields\n |> Form.toResult\n |> Result.toMaybe\n )\n\n newPendingItems : List Entry\n newPendingItems =\n pendingActions\n |> List.filterMap\n (\\submission ->\n case submission of\n Add description ->\n Just\n { description = description\n , completed = False\n , id = \"\"\n , isPending = True\n }\n\n _ ->\n -- `newPendingItems` only cares about pending Add actions. Other values will use\n -- pending submissions for other types of Actions.\n Nothing\n )\n in\n itemsView app newPendingItems\n\n allForms : Form.Handler.Handler String Action\n allForms =\n |> Form.Handler.init Add addItemForm\n -- |> Form.Handler.with ...\n\n\n type Action\n = UpdateEntry ( String, String )\n | Add String\n | Delete String\n | DeleteComplete\n | Check ( Bool, String )\n | CheckAll Bool\n\n@docs ConcurrentSubmission, Status\n\n@docs map\n\n","unions":[{"name":"Status","comment":" The status of a `ConcurrentSubmission`.\n\n - `Submitting` - The submission is in-flight.\n - `Reloading` - The submission has completed, and the page is now reloading the `Route`'s `data` to reflect the new state. The `actionData` holds any data returned from the `Route`'s `action`.\n - `Complete` - The submission has completed, and the `Route`'s `data` has since reloaded so the state reflects the refreshed state after completing this specific form submission. The `actionData` holds any data returned from the `Route`'s `action`.\n\n","args":["actionData"],"cases":[["Submitting",[]],["Reloading",["actionData"]],["Complete",["actionData"]]]}],"aliases":[{"name":"ConcurrentSubmission","comment":" ","args":["actionData"],"type":"{ status : Pages.ConcurrentSubmission.Status actionData, payload : Pages.FormData.FormData, initiatedAt : Time.Posix }"}],"values":[{"name":"map","comment":" `map` a `ConcurrentSubmission`. Not needed for most high-level cases since this state is managed by the `elm-pages` framework for you.\n","type":"(a -> b) -> Pages.ConcurrentSubmission.ConcurrentSubmission a -> Pages.ConcurrentSubmission.ConcurrentSubmission b"}],"binops":[]},{"name":"Pages.Fetcher","comment":"\n\n@docs Fetcher, FetcherInfo, submit, map\n\n","unions":[{"name":"Fetcher","comment":" ","args":["decoded"],"cases":[["Fetcher",["Pages.Fetcher.FetcherInfo decoded"]]]}],"aliases":[{"name":"FetcherInfo","comment":" ","args":["decoded"],"type":"{ decoder : Result.Result Http.Error Bytes.Bytes -> decoded, fields : List.List ( String.String, String.String ), headers : List.List ( String.String, String.String ), url : Maybe.Maybe String.String }"}],"values":[{"name":"map","comment":" ","type":"(a -> b) -> Pages.Fetcher.Fetcher a -> Pages.Fetcher.Fetcher b"},{"name":"submit","comment":" ","type":"Bytes.Decode.Decoder decoded -> { fields : List.List ( String.String, String.String ), headers : List.List ( String.String, String.String ) } -> Pages.Fetcher.Fetcher (Result.Result Http.Error decoded)"}],"binops":[]},{"name":"Pages.Flags","comment":"\n\n@docs Flags\n\n","unions":[{"name":"Flags","comment":" elm-pages apps run in two different contexts\n\n1. In the browser (like a regular Elm app)\n2. In pre-render mode. For example when you run `elm-pages build`, there is no browser involved, it just runs Elm directly.\n\nYou can pass in Flags and use them in your `Shared.init` function. You can store data in your `Shared.Model` from these flags and then access it across any page.\n\nYou will need to handle the `PreRender` case with no flags value because there is no browser to get flags from. For example, say you wanted to get the\ncurrent user's Browser window size and pass it in as a flag. When that page is pre-rendered, you need to decide on a value to use for the window size\nsince there is no window (the user hasn't requested the page yet, and the page isn't even loaded in a Browser window yet).\n\n","args":[],"cases":[["BrowserFlags",["Json.Decode.Value"]],["PreRenderFlags",[]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.Form","comment":" `elm-pages` has a built-in integration with [`dillonkearns/elm-form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/). See the `dillonkearns/elm-form`\ndocs and examples for more information on how to define your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form). This module is the interface for rendering your `Form` in your `elm-pages` app.\n\nBy rendering your `Form` with this module,\nyou get all of the boilerplate managed for you automatically by the `elm-pages` framework. That means you do not need to use `Form.init`, `Form.update`, `Form.Model` since these are all\nabstracted away. In addition to that, in-flight form state is automatically managed for you and exposed through the `app` argument in your Route modules.\n\nThis means that you can declaratively derive Pending UI or Optimistic UI state from `app.navigation` or `app.concurrentSubmissions` in your Route modules, and even build a\nrich dynamic page that shows pending submissions in the UI without using your Route module's `Model`! This is the power of this abstraction - it's less error-prone to\ndeclaratively derive state rather than imperatively managing your `Model`.\n\n\n## Rendering Forms\n\n@docs renderHtml, renderStyledHtml\n\n@docs Options\n\n\n## Form Submission Strategies\n\nWhen you render with [`Pages.Form.renderHtml`](#renderHtml) or [`Pages.Form.renderStyledHtml`](#renderStyledHtml),\n`elm-pages` progressively enhances form submissions to manage the requests through Elm (instead of as a vanilla HTML form submission, which performs a full page reload).\n\nBy default, `elm-pages` Forms will use the same mental model as the browser's default form submission behavior. That is, the form submission state will be tied to the page's navigation state.\nIf you click a link while a form is submitting, the form submission will be cancelled and the page will navigate to the new page. Conceptually, you can think of this as being tied to the navigation state.\nA form submission is part of the page's navigation state, and so is a page navigation. So if you have a page with an edit form, a delete form (no inputs but only a delete button), and a link to a new page,\nyou can interact with any of these and it will cancel the previous interactions.\n\nYou can access this state through `app.navigation` in your `Route` module, which is a value of type [`Pages.Navigation`](Pages-Navigation).\n\nThis default form submission strategy is a good fit for more linear actions. This is more traditional server submission behavior that you might be familiar with from Rails or other server frameworks without JavaScript enhancement.\n\n@docs withConcurrent\n\n\n## Server-Side Validation\n\n@docs FormWithServerValidations, Handler\n\n","unions":[],"aliases":[{"name":"FormWithServerValidations","comment":" ","args":["error","combined","input","view"],"type":"Form.Form error { combine : Form.Validation.Validation error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never)) Basics.Never Basics.Never, view : Form.Context error input -> view } (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never)) input"},{"name":"Handler","comment":" ","args":["error","combined"],"type":"Form.Handler.Handler error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never))"},{"name":"Options","comment":" A replacement for [`Form.Options`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Options)\nwith some extra configuration for the `elm-pages` integration. You can use this type to annotate your form's options.\n","args":["error","parsed","input","msg"],"type":"Form.Options error parsed input msg { concurrent : Basics.Bool }"}],"values":[{"name":"renderHtml","comment":" A replacement for `Form.renderHtml` from `dillonkearns/elm-form` that integrates with `elm-pages`. Use this to render your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form)\nas `elm/html` `Html`.\n","type":"List.List (Html.Attribute (PagesMsg.PagesMsg userMsg)) -> Pages.Form.Options error parsed input userMsg -> { app | pageFormState : Form.Model, navigation : Maybe.Maybe Pages.Navigation.Navigation, concurrentSubmissions : Dict.Dict String.String (Pages.ConcurrentSubmission.ConcurrentSubmission (Maybe.Maybe action)) } -> Form.Form error { combine : Form.Validation.Validation error parsed named constraints, view : Form.Context error input -> List.List (Html.Html (PagesMsg.PagesMsg userMsg)) } parsed input -> Html.Html (PagesMsg.PagesMsg userMsg)"},{"name":"renderStyledHtml","comment":" A replacement for `Form.renderStyledHtml` from `dillonkearns/elm-form` that integrates with `elm-pages`. Use this to render your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form)\nas `rtfeldman/elm-css` `Html.Styled.Html`.\n","type":"List.List (Html.Styled.Attribute (PagesMsg.PagesMsg userMsg)) -> Pages.Form.Options error parsed input userMsg -> { app | pageFormState : Form.Model, navigation : Maybe.Maybe Pages.Navigation.Navigation, concurrentSubmissions : Dict.Dict String.String (Pages.ConcurrentSubmission.ConcurrentSubmission (Maybe.Maybe action)) } -> Form.Form error { combine : Form.Validation.Validation error parsed named constraints, view : Form.Context error input -> List.List (Html.Styled.Html (PagesMsg.PagesMsg userMsg)) } parsed input -> Html.Styled.Html (PagesMsg.PagesMsg userMsg)"},{"name":"withConcurrent","comment":" Instead of using the default submission strategy (tied to the page's navigation state), you can use `withConcurrent`.\n`withConcurrent` allows multiple form submissions to be in flight at the same time. It is useful for more dynamic applications. A good rule of thumb\nis if you could have multiple pending spinners on the page at the same time, you should use `withConcurrent`. For example, if you have a page with a list of items,\nsay a Twitter clone. If you click the like button on a Tweet, it won't result in a page navigation. You can click the like button on multiple Tweets at the same time\nand they will all submit independently.\n\nIn the case of Twitter, there isn't an indication of a loading spinner on the like button because it is expected that it will succeed. This is an example of a User Experience (UX) pattern\ncalled Optimistic UI. Since it is very likely that liking a Tweet will be successful, the UI will update the UI as if it has immediately succeeded even though the request is still in flight.\nIf the request fails, the UI will be updated to reflect the failure with an animation to show that something went wrong.\n\nThe `withConcurrent` is a good fit for either of these UX patterns (Optimistic UI or Pending UI, i.e. showing a loading spinner). You can derive either of these\nvisual states from the `app.concurrentSubmissions` field in your `Route` module.\n\nYou can call `withConcurrent` on your `Form.Options`. Note that while `withConcurrent` will allow multiple form submissions to be in flight at the same time independently,\nthe ID of the Form will still have a unique submission. For example, if you click submit on a form with the ID `\"edit-123\"` and then submit it again before the first submission has completed,\nthe second submission will cancel the first submission. So it is important to use unique IDs for forms that represent unique operations.\n\n import Form\n import Pages.Form\n\n todoItemView app todo =\n deleteItemForm\n |> Pages.Form.renderHtml []\n (Form.options (\"delete-\" ++ todo.id)\n |> Form.withInput todo\n |> Pages.Form.withConcurrent\n )\n app\n\n","type":"Pages.Form.Options error parsed input msg -> Pages.Form.Options error parsed input msg"}],"binops":[]},{"name":"Pages.FormData","comment":"\n\n@docs FormData\n\n","unions":[],"aliases":[{"name":"FormData","comment":" The payload for form submissions.\n","args":[],"type":"{ fields : List.List ( String.String, String.String ), method : Form.Method, action : String.String, id : Maybe.Maybe String.String }"}],"values":[],"binops":[]},{"name":"Pages.Internal.NotFoundReason","comment":" Exposed for internal use only (used in generated code).\n\n@docs ModuleContext, NotFoundReason, Payload, Record, document\n\n","unions":[{"name":"NotFoundReason","comment":" ","args":[],"cases":[["NoMatchingRoute",[]],["NotPrerendered",["Pages.Internal.NotFoundReason.ModuleContext","List.List Pages.Internal.NotFoundReason.Record"]],["NotPrerenderedOrHandledByFallback",["Pages.Internal.NotFoundReason.ModuleContext","List.List Pages.Internal.NotFoundReason.Record"]],["UnhandledServerRoute",["Pages.Internal.NotFoundReason.ModuleContext"]]]}],"aliases":[{"name":"ModuleContext","comment":" ","args":[],"type":"{ moduleName : List.List String.String, routePattern : Pages.Internal.RoutePattern.RoutePattern, matchedRouteParams : Pages.Internal.NotFoundReason.Record }"},{"name":"Payload","comment":" ","args":[],"type":"{ path : UrlPath.UrlPath, reason : Pages.Internal.NotFoundReason.NotFoundReason }"},{"name":"Record","comment":" ","args":[],"type":"List.List ( String.String, String.String )"}],"values":[{"name":"document","comment":" ","type":"List.List Pages.Internal.RoutePattern.RoutePattern -> Pages.Internal.NotFoundReason.Payload -> { title : String.String, body : List.List (Html.Html msg) }"}],"binops":[]},{"name":"Pages.Internal.Platform","comment":" Exposed for internal use only (used in generated code).\n\n@docs Flags, Model, Msg, Program, application, init, update\n\n@docs Effect, RequestInfo, view\n\n","unions":[{"name":"Effect","comment":" ","args":["userMsg","pageData","actionData","sharedData","userEffect","errorPage"],"cases":[["ScrollToTop",[]],["NoEffect",[]],["BrowserLoadUrl",["String.String"]],["BrowserPushUrl",["String.String"]],["BrowserReplaceUrl",["String.String"]],["FetchPageData",["Basics.Int","Maybe.Maybe Pages.Internal.Platform.FormData","Url.Url","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData ) -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage"]],["Submit",["Pages.Internal.Platform.FormData"]],["SubmitFetcher",["String.String","Basics.Int","Pages.Internal.Platform.FormData"]],["Batch",["List.List (Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage)"]],["UserCmd",["userEffect"]],["CancelRequest",["Basics.Int"]],["RunCmd",["Platform.Cmd.Cmd (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"]]]},{"name":"Msg","comment":" ","args":["userMsg","pageData","actionData","sharedData","errorPage"],"cases":[["LinkClicked",["Browser.UrlRequest"]],["UrlChanged",["Url.Url"]],["UserMsg",["PagesMsg.PagesMsg userMsg"]],["FormMsg",["Form.Msg (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"]],["UpdateCacheAndUrlNew",["Basics.Bool","Url.Url","Maybe.Maybe userMsg","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData )"]],["FetcherComplete",["Basics.Bool","String.String","Basics.Int","Result.Result Http.Error ( Maybe.Maybe userMsg, Pages.Internal.Platform.ActionDataOrRedirect actionData )"]],["FetcherStarted",["String.String","Basics.Int","Pages.Internal.Platform.FormData","Time.Posix"]],["PageScrollComplete",[]],["HotReloadCompleteNew",["Bytes.Bytes"]],["ProcessFetchResponse",["Basics.Int","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData )","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData ) -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":["userModel","pageData","actionData","sharedData"],"type":"{ key : Maybe.Maybe Browser.Navigation.Key, url : Url.Url, currentPath : String.String, ariaNavigationAnnouncement : String.String, pageData : Result.Result String.String { userModel : userModel, pageData : pageData, sharedData : sharedData, actionData : Maybe.Maybe actionData }, notFound : Maybe.Maybe { reason : Pages.Internal.NotFoundReason.NotFoundReason, path : UrlPath.UrlPath }, userFlags : Json.Decode.Value, transition : Maybe.Maybe ( Basics.Int, Pages.Navigation.Navigation ), nextTransitionKey : Basics.Int, inFlightFetchers : Dict.Dict String.String ( Basics.Int, Pages.ConcurrentSubmission.ConcurrentSubmission actionData ), pageFormState : Form.Model, pendingRedirect : Basics.Bool, pendingData : Maybe.Maybe ( pageData, sharedData, Maybe.Maybe actionData ) }"},{"name":"Program","comment":" ","args":["userModel","userMsg","pageData","actionData","sharedData","errorPage"],"type":"Platform.Program Pages.Internal.Platform.Flags (Pages.Internal.Platform.Model userModel pageData actionData sharedData) (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"},{"name":"RequestInfo","comment":" ","args":[],"type":"{ contentType : String.String, body : String.String }"}],"values":[{"name":"application","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Platform.Program Pages.Internal.Platform.Flags (Pages.Internal.Platform.Model userModel pageData actionData sharedData) (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"},{"name":"init","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Flags -> Url.Url -> Maybe.Maybe Browser.Navigation.Key -> ( Pages.Internal.Platform.Model userModel pageData actionData sharedData, Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage )"},{"name":"update","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage -> Pages.Internal.Platform.Model userModel pageData actionData sharedData -> ( Pages.Internal.Platform.Model userModel pageData actionData sharedData, Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage )"},{"name":"view","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Model userModel pageData actionData sharedData -> Browser.Document (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"}],"binops":[]},{"name":"Pages.Internal.Platform.Cli","comment":" Exposed for internal use only (used in generated code).\n\n@docs Flags, Model, Msg, Program, cliApplication, init, requestDecoder, update, currentCompatibilityKey\n\n","unions":[{"name":"Msg","comment":" ","args":[],"cases":[["GotDataBatch",["Json.Decode.Value"]],["GotBuildError",["BuildError.BuildError"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":["route"],"type":"{ staticResponses : BackendTask.BackendTask FatalError.FatalError Pages.Internal.Platform.Effect.Effect, errors : List.List BuildError.BuildError, maybeRequestJson : RenderRequest.RenderRequest route, isDevServer : Basics.Bool }"},{"name":"Program","comment":" ","args":["route"],"type":"Platform.Program Pages.Internal.Platform.Cli.Flags (Pages.Internal.Platform.Cli.Model route) Pages.Internal.Platform.Cli.Msg"}],"values":[{"name":"cliApplication","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel (Maybe.Maybe route) pageData actionData sharedData effect mappedMsg errorPage -> Pages.Internal.Platform.Cli.Program (Maybe.Maybe route)"},{"name":"currentCompatibilityKey","comment":" ","type":"Basics.Int"},{"name":"init","comment":" ","type":"Pages.SiteConfig.SiteConfig -> RenderRequest.RenderRequest route -> Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect mappedMsg errorPage -> Json.Decode.Value -> ( Pages.Internal.Platform.Cli.Model route, Pages.Internal.Platform.Effect.Effect )"},{"name":"requestDecoder","comment":" ","type":"Json.Decode.Decoder Pages.StaticHttp.Request.Request"},{"name":"update","comment":" ","type":"Pages.Internal.Platform.Cli.Msg -> Pages.Internal.Platform.Cli.Model route -> ( Pages.Internal.Platform.Cli.Model route, Pages.Internal.Platform.Effect.Effect )"}],"binops":[]},{"name":"Pages.Internal.Platform.GeneratorApplication","comment":" Exposed for internal use only (used in generated code).\n\n@docs Program, Flags, Model, Msg, init, requestDecoder, update, app, JsonValue\n\n","unions":[{"name":"Msg","comment":" ","args":[],"cases":[["GotDataBatch",["Json.Decode.Value"]],["GotBuildError",["BuildError.BuildError"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"{ compatibilityKey : Basics.Int }"},{"name":"JsonValue","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":[],"type":"{ staticResponses : BackendTask.BackendTask FatalError.FatalError (), errors : List.List BuildError.BuildError }"},{"name":"Program","comment":" ","args":[],"type":"Cli.Program.StatefulProgram Pages.Internal.Platform.GeneratorApplication.Model Pages.Internal.Platform.GeneratorApplication.Msg (BackendTask.BackendTask FatalError.FatalError ()) Pages.Internal.Platform.GeneratorApplication.Flags"}],"values":[{"name":"app","comment":" ","type":"Pages.GeneratorProgramConfig.GeneratorProgramConfig -> Pages.Internal.Platform.GeneratorApplication.Program"},{"name":"init","comment":" ","type":"BackendTask.BackendTask FatalError.FatalError () -> Cli.Program.FlagsIncludingArgv Pages.Internal.Platform.GeneratorApplication.Flags -> ( Pages.Internal.Platform.GeneratorApplication.Model, Pages.Internal.Platform.Effect.Effect )"},{"name":"requestDecoder","comment":" ","type":"Json.Decode.Decoder Pages.StaticHttp.Request.Request"},{"name":"update","comment":" ","type":"Pages.Internal.Platform.GeneratorApplication.Msg -> Pages.Internal.Platform.GeneratorApplication.Model -> ( Pages.Internal.Platform.GeneratorApplication.Model, Pages.Internal.Platform.Effect.Effect )"}],"binops":[]},{"name":"Pages.Internal.ResponseSketch","comment":"\n\n@docs ResponseSketch\n\n","unions":[{"name":"ResponseSketch","comment":" ","args":["data","action","shared"],"cases":[["RenderPage",["data","Maybe.Maybe action"]],["HotUpdate",["data","shared","Maybe.Maybe action"]],["Redirect",["String.String"]],["NotFound",["{ reason : Pages.Internal.NotFoundReason.NotFoundReason, path : UrlPath.UrlPath }"]],["Action",["action"]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.Internal.RoutePattern","comment":" Exposed for internal use only (used in generated code).\n\n@docs Ending, RoutePattern, Segment, view, toVariant, routeToBranch\n\n@docs Param, RouteParam, fromModuleName, hasRouteParams, repeatWithoutOptionalEnding, toModuleName, toRouteParamTypes, toRouteParamsRecord, toVariantName\n\n","unions":[{"name":"Ending","comment":" ","args":[],"cases":[["Optional",["String.String"]],["RequiredSplat",[]],["OptionalSplat",[]]]},{"name":"Param","comment":" ","args":[],"cases":[["RequiredParam",[]],["OptionalParam",[]],["RequiredSplatParam",[]],["OptionalSplatParam",[]]]},{"name":"RouteParam","comment":" ","args":[],"cases":[["StaticParam",["String.String"]],["DynamicParam",["String.String"]],["OptionalParam2",["String.String"]],["RequiredSplatParam2",[]],["OptionalSplatParam2",[]]]},{"name":"Segment","comment":" ","args":[],"cases":[["StaticSegment",["String.String"]],["DynamicSegment",["String.String"]]]}],"aliases":[{"name":"RoutePattern","comment":" ","args":[],"type":"{ segments : List.List Pages.Internal.RoutePattern.Segment, ending : Maybe.Maybe Pages.Internal.RoutePattern.Ending }"}],"values":[{"name":"fromModuleName","comment":" ","type":"List.List String.String -> Maybe.Maybe Pages.Internal.RoutePattern.RoutePattern"},{"name":"hasRouteParams","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Basics.Bool"},{"name":"repeatWithoutOptionalEnding","comment":" ","type":"List.List Pages.Internal.RoutePattern.RouteParam -> Maybe.Maybe (List.List Pages.Internal.RoutePattern.RouteParam)"},{"name":"routeToBranch","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( Elm.CodeGen.Pattern, Elm.CodeGen.Expression )"},{"name":"toModuleName","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List String.String"},{"name":"toRouteParamTypes","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( String.String, Pages.Internal.RoutePattern.Param )"},{"name":"toRouteParamsRecord","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( String.String, Elm.Annotation.Annotation )"},{"name":"toVariant","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Elm.Variant"},{"name":"toVariantName","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> { variantName : String.String, params : List.List Pages.Internal.RoutePattern.RouteParam }"},{"name":"view","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Html.Html msg"}],"binops":[]},{"name":"Pages.Internal.Router","comment":" Exposed for internal use only (used in generated code).\n\n@docs Matcher, firstMatch, fromOptionalSplat, maybeToList, nonEmptyToList, toNonEmpty\n\n","unions":[],"aliases":[{"name":"Matcher","comment":" ","args":["route"],"type":"{ pattern : String.String, toRoute : List.List (Maybe.Maybe String.String) -> Maybe.Maybe route }"}],"values":[{"name":"firstMatch","comment":" ","type":"List.List (Pages.Internal.Router.Matcher route) -> String.String -> Maybe.Maybe route"},{"name":"fromOptionalSplat","comment":" ","type":"Maybe.Maybe String.String -> List.List String.String"},{"name":"maybeToList","comment":" ","type":"Maybe.Maybe String.String -> List.List String.String"},{"name":"nonEmptyToList","comment":" ","type":"( String.String, List.List String.String ) -> List.List String.String"},{"name":"toNonEmpty","comment":" ","type":"String.String -> ( String.String, List.List String.String )"}],"binops":[]},{"name":"Pages.Manifest","comment":" Represents the configuration of a\n[web manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest).\n\nYou pass your `Pages.Manifest.Config` record into the `Pages.Manifest.generator`\nin your `app/Api.elm` module to define a file generator that will build a `manifest.json` file as part of your build.\n\n import Pages.Manifest as Manifest\n import Pages.Manifest.Category\n\n manifest : Manifest.Config\n manifest =\n Manifest.init\n { name = static.siteName\n , description = \"elm-pages - \" ++ tagline\n , startUrl = Route.Index {} |> Route.toPath\n , icons =\n [ icon webp 192\n , icon webp 512\n , icon MimeType.Png 192\n , icon MimeType.Png 512\n ]\n }\n |> Manifest.withShortName \"elm-pages\"\n\n@docs Config, Icon\n\n\n## Builder options\n\n@docs init\n\n@docs withBackgroundColor, withCategories, withDisplayMode, withIarcRatingId, withLang, withOrientation, withShortName, withThemeColor\n\n\n## Arbitrary Fields Escape Hatch\n\n@docs withField\n\n\n## Config options\n\n@docs DisplayMode, Orientation, IconPurpose\n\n\n## Generating a Manifest.json\n\n@docs generator\n\n\n## Functions for use by the generated code (`Pages.elm`)\n\n@docs toJson\n\n","unions":[{"name":"DisplayMode","comment":" See \n","args":[],"cases":[["Fullscreen",[]],["Standalone",[]],["MinimalUi",[]],["Browser",[]]]},{"name":"IconPurpose","comment":" \n","args":[],"cases":[["IconPurposeMonochrome",[]],["IconPurposeMaskable",[]],["IconPurposeAny",[]]]},{"name":"Orientation","comment":" \n","args":[],"cases":[["Any",[]],["Natural",[]],["Landscape",[]],["LandscapePrimary",[]],["LandscapeSecondary",[]],["Portrait",[]],["PortraitPrimary",[]],["PortraitSecondary",[]]]}],"aliases":[{"name":"Config","comment":" Represents a [web app manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest)\n(see above for how to use it).\n","args":[],"type":"{ backgroundColor : Maybe.Maybe Color.Color, categories : List.List Pages.Manifest.Category.Category, displayMode : Pages.Manifest.DisplayMode, orientation : Pages.Manifest.Orientation, description : String.String, iarcRatingId : Maybe.Maybe String.String, name : String.String, themeColor : Maybe.Maybe Color.Color, startUrl : UrlPath.UrlPath, shortName : Maybe.Maybe String.String, icons : List.List Pages.Manifest.Icon, lang : LanguageTag.LanguageTag, otherFields : Dict.Dict String.String Json.Encode.Value }"},{"name":"Icon","comment":" \n","args":[],"type":"{ src : Pages.Url.Url, sizes : List.List ( Basics.Int, Basics.Int ), mimeType : Maybe.Maybe MimeType.MimeImage, purposes : List.List Pages.Manifest.IconPurpose }"}],"values":[{"name":"generator","comment":" A generator for `Api.elm` to include a manifest.json. The String argument is the canonical URL of the site.\n\n module Api exposing (routes)\n\n import ApiRoute\n import Pages.Manifest\n\n routes :\n BackendTask FatalError (List Route)\n -> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)\n -> List (ApiRoute.ApiRoute ApiRoute.Response)\n routes getStaticRoutes htmlToString =\n [ Pages.Manifest.generator\n Site.canonicalUrl\n Manifest.config\n ]\n\n","type":"String.String -> BackendTask.BackendTask FatalError.FatalError Pages.Manifest.Config -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"init","comment":" Setup a minimal Manifest.Config. You can then use the `with...` builder functions to set additional options.\n","type":"{ description : String.String, name : String.String, startUrl : UrlPath.UrlPath, icons : List.List Pages.Manifest.Icon } -> Pages.Manifest.Config"},{"name":"toJson","comment":" Feel free to use this, but in 99% of cases you won't need it. The generated\ncode will run this for you to generate your `manifest.json` file automatically!\n","type":"String.String -> Pages.Manifest.Config -> Json.Encode.Value"},{"name":"withBackgroundColor","comment":" Set .\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withCategories","comment":" Set .\n","type":"List.List Pages.Manifest.Category.Category -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withDisplayMode","comment":" Set .\n","type":"Pages.Manifest.DisplayMode -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withField","comment":" Escape hatch for specifying fields that aren't exposed through this module otherwise. The possible supported properties\nin a manifest file can change over time, so see [MDN manifest.json docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json)\nfor a full listing of the current supported properties.\n","type":"String.String -> Json.Encode.Value -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withIarcRatingId","comment":" Set .\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withLang","comment":" Set .\n","type":"LanguageTag.LanguageTag -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withOrientation","comment":" Set .\n","type":"Pages.Manifest.Orientation -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withShortName","comment":" Set .\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withThemeColor","comment":" Set .\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"}],"binops":[]},{"name":"Pages.Manifest.Category","comment":" See and\n\n\n@docs toString, Category\n\n@docs books, business, education, entertainment, finance, fitness, food, games, government, health, kids, lifestyle, magazines, medical, music, navigation, news, personalization, photo, politics, productivity, security, shopping, social, sports, travel, utilities, weather\n\n\n## Custom categories\n\n@docs custom\n\n","unions":[{"name":"Category","comment":" Represents a known, valid category, as specified by\n. If this document is updated\nand I don't add it, please open an issue or pull request to let me know!\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"books","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"business","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"custom","comment":" It's best to use the pre-defined categories to ensure that clients (Android, iOS,\nChrome, Windows app store, etc.) are aware of it and can handle it appropriately.\nBut, if you're confident about using a custom one, you can do so with `Pages.Manifest.custom`.\n","type":"String.String -> Pages.Manifest.Category.Category"},{"name":"education","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"entertainment","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"finance","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"fitness","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"food","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"games","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"government","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"health","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"kids","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"lifestyle","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"magazines","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"medical","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"music","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"navigation","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"news","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"personalization","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"photo","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"politics","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"productivity","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"security","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"shopping","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"social","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"sports","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"toString","comment":" Turn a category into its official String representation, as seen\nhere: .\n","type":"Pages.Manifest.Category.Category -> String.String"},{"name":"travel","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"utilities","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"weather","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"}],"binops":[]},{"name":"Pages.Navigation","comment":" `elm-pages` maintains a single `Maybe Navigation` state which is accessible from your `Route` modules through `app.navigation`.\n\nYou can use it to show a loading indicator while a page is loading:\n\n import Pages.Navigation as Navigation\n\n pageLoadingIndicator app =\n case app.navigation of\n Just (Navigation.Loading path _) ->\n Spinner.view\n\n Nothing ->\n emptyView\n\n emptyView : Html msg\n emptyView =\n Html.text \"\"\n\nYou can also use it to derive Pending UI or Optimistic UI from a pending form submission:\n\n import Form\n import Form.Handler\n import Pages.Navigation as Navigation\n\n view app =\n let\n optimisticProduct : Maybe Product\n optimisticProduct =\n case app.navigation of\n Just (Navigation.Submitting formData) ->\n formHandler\n |> Form.Handler.run formData\n |> Form.toResult\n |> Result.toMaybe\n\n Just (Navigation.LoadAfterSubmit formData path _) ->\n formHandler\n |> Form.Handler.run formData\n |> Form.toResult\n |> Result.toMaybe\n\n Nothing ->\n Nothing\n in\n -- our `productsView` function could show a loading spinner (Pending UI),\n -- or it could assume the product will be created successfully (Optimistic UI) and\n -- display it as a regular product in the list\n productsView optimisticProduct app.data.products\n\n allForms : Form.Handler.Handler String Product\n allForms =\n Form.Handler.init identity productForm\n\n editItemForm : Form.HtmlForm String Product input msg\n editItemForm =\n Debug.todo \"Form definition here\"\n\n@docs Navigation, LoadingState\n\n","unions":[{"name":"LoadingState","comment":" ","args":[],"cases":[["Redirecting",[]],["Load",[]],["ActionRedirect",[]]]},{"name":"Navigation","comment":" Represents the global page navigation state of the app.\n\n - `Loading` - navigating to a page, for example from a link click, or from a programmatic navigation with `Browser.Navigation.pushUrl`.\n - `Submitting` - submitting a form using the default submission strategy (note that Forms rendered with the [`Pages.Form.withConcurrent`](Pages-Form#withConcurrent) Option have their state managed in `app.concurrentSubmissions` instead of `app.navigation`).\n - `LoadAfterSubmit` - the state immediately after `Submitting` - allows you to continue using the `FormData` from a submission while a data reload or redirect is occurring.\n\n","args":[],"cases":[["Submitting",["Pages.FormData.FormData"]],["LoadAfterSubmit",["Pages.FormData.FormData","UrlPath.UrlPath","Pages.Navigation.LoadingState"]],["Loading",["UrlPath.UrlPath","Pages.Navigation.LoadingState"]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.PageUrl","comment":" Same as a Url in `elm/url`, but slightly more structured. The path portion of the URL is parsed into a `List String` representing each segment, and\nthe query params are parsed into a `Dict String (List String)`.\n\n@docs PageUrl, toUrl\n\n@docs parseQueryParams\n\n","unions":[],"aliases":[{"name":"PageUrl","comment":" ","args":[],"type":"{ protocol : Url.Protocol, host : String.String, port_ : Maybe.Maybe Basics.Int, path : UrlPath.UrlPath, query : Dict.Dict String.String (List.List String.String), fragment : Maybe.Maybe String.String }"}],"values":[{"name":"parseQueryParams","comment":" ","type":"String.String -> Dict.Dict String.String (List.List String.String)"},{"name":"toUrl","comment":" ","type":"Pages.PageUrl.PageUrl -> Url.Url"}],"binops":[]},{"name":"Pages.Script","comment":" An elm-pages Script is a way to execute an `elm-pages` `BackendTask`.\n\nRead more about using the `elm-pages` CLI to run (or bundle) scripts, plus a brief tutorial, at .\n\n@docs Script\n\n\n## Defining Scripts\n\n@docs withCliOptions, withoutCliOptions\n\n\n## File System Utilities\n\n@docs writeFile\n\n\n## Utilities\n\n@docs log\n\n\n## Errors\n\n@docs Error\n\n","unions":[{"name":"Error","comment":" The recoverable error type for file writes. You can use `BackendTask.allowFatal` if you want to allow the program to crash\nwith an error message if a file write is unsuccessful.\n","args":[],"cases":[["FileWriteError",[]]]}],"aliases":[{"name":"Script","comment":" The type for your `run` function that can be executed by `elm-pages run`.\n","args":[],"type":"Pages.Internal.Script.Script"}],"values":[{"name":"log","comment":" Log to stdout.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.log \"Hello!\"\n |> BackendTask.allowFatal\n )\n\n","type":"String.String -> BackendTask.BackendTask error ()"},{"name":"withCliOptions","comment":" Same as [`withoutCliOptions`](#withoutCliOptions), but allows you to define a CLI Options Parser so the user can\npass in additional options for the script.\n\nUses .\n\nRead more at .\n\n","type":"Cli.Program.Config cliOptions -> (cliOptions -> BackendTask.BackendTask FatalError.FatalError ()) -> Pages.Script.Script"},{"name":"withoutCliOptions","comment":" Define a simple Script (no CLI Options).\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.log \"Hello!\"\n |> BackendTask.allowFatal\n )\n\n","type":"BackendTask.BackendTask FatalError.FatalError () -> Pages.Script.Script"},{"name":"writeFile","comment":" Write a file to the file system.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.writeFile\n { path = \"hello.json\"\n , body = \"\"\"{ \"message\": \"Hello, World!\" }\"\"\"\n }\n |> BackendTask.allowFatal\n )\n\n","type":"{ path : String.String, body : String.String } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : Pages.Script.Error } ()"}],"binops":[]},{"name":"Pages.Url","comment":" Some of the `elm-pages` APIs will take internal URLs and ensure that they have the `canonicalSiteUrl` prepended.\n\nThat's the purpose for this type. If you have an external URL, like `Pages.Url.external \"https://google.com\"`,\nthen the canonicalUrl will not be prepended when it is used in a head tag.\n\nIf you refer to a local page, like `Route.Index |> Route.toPath |> Pages.Url.fromPath`, or `Pages.Url.fromPath`\n\n@docs Url, external, fromPath, toAbsoluteUrl, toString\n\n","unions":[{"name":"Url","comment":" ","args":[],"cases":[]}],"aliases":[],"values":[{"name":"external","comment":" ","type":"String.String -> Pages.Url.Url"},{"name":"fromPath","comment":" ","type":"UrlPath.UrlPath -> Pages.Url.Url"},{"name":"toAbsoluteUrl","comment":" ","type":"String.String -> Pages.Url.Url -> String.String"},{"name":"toString","comment":" ","type":"Pages.Url.Url -> String.String"}],"binops":[]},{"name":"PagesMsg","comment":" In `elm-pages`, Route modules have their own `Msg` type which can be used like a normal TEA (The Elm Architecture) app.\nBut the `Msg` defined in a `Route` module is wrapped in the `PagesMsg` type.\n\n@docs PagesMsg\n\nYou can wrap your Route Module's `Msg` using `fromMsg`.\n\n@docs fromMsg\n\n@docs map, noOp\n\n","unions":[],"aliases":[{"name":"PagesMsg","comment":" ","args":["userMsg"],"type":"Pages.Internal.Msg.Msg userMsg"}],"values":[{"name":"fromMsg","comment":"\n\n import Form\n import Pages.Form\n import PagesMsg exposing (PagesMsg)\n\n type Msg\n = ToggleMenu\n\n view :\n Maybe PageUrl\n -> Shared.Model\n -> Model\n -> App Data ActionData RouteParams\n -> View (PagesMsg Msg)\n view maybeUrl sharedModel model app =\n { title = \"My Page\"\n , view =\n [ button\n -- we need to wrap our Route module's `Msg` here so we have a `PagesMsg Msg`\n [ onClick (PagesMsg.fromMsg ToggleMenu) ]\n []\n\n -- `Pages.Form.renderHtml` gives us `Html (PagesMsg msg)`, so we don't need to wrap its Msg type\n , logoutForm\n |> Pages.Form.renderHtml []\n Pages.Form.Serial\n (Form.options \"logout\"\n |> Form.withOnSubmit (\\_ -> NewItemSubmitted)\n )\n app\n ]\n }\n\n","type":"userMsg -> PagesMsg.PagesMsg userMsg"},{"name":"map","comment":" ","type":"(a -> b) -> PagesMsg.PagesMsg a -> PagesMsg.PagesMsg b"},{"name":"noOp","comment":" A Msg that is handled by the elm-pages framework and does nothing. Helpful for when you don't want to register a callback.\n\n import Browser.Dom as Dom\n import PagesMsg exposing (PagesMsg)\n import Task\n\n resetViewport : Cmd (PagesMsg msg)\n resetViewport =\n Dom.setViewport 0 0\n |> Task.perform (\\() -> PagesMsg.noOp)\n\n","type":"PagesMsg.PagesMsg userMsg"}],"binops":[]},{"name":"Scaffold.Form","comment":" This module helps you with scaffolding a form in `elm-pages`, similar to how rails generators are used to scaffold out forms to\nget up and running quickly with the starting point for a form with different field types. See also [`Scaffold.Route`](Scaffold-Route).\n\nSee the `AddRoute` script in the starter template for an example. It's usually easiest to modify that script as a starting\npoint rather than using this API from scratch.\n\nUsing the `AddRoute` script from the default starter template, you can run a command like this:\n\n`npx elm-pages run AddRoute Profile.Username_.Edit first last bio:textarea dob:date` to generate a Route module `app/Route/Profile/Username_/Edit.elm`\nwith the wiring form a `Form`.\n\n[Learn more about writing and running elm-pages Scripts for scaffolding](https://elm-pages-v3.netlify.app/docs/elm-pages-scripts#scaffolding-a-route-module).\n\n@docs Kind, provide, restArgsParser\n\n@docs Context\n\n@docs recordEncoder, fieldEncoder\n\n","unions":[{"name":"Kind","comment":" ","args":[],"cases":[["FieldInt",[]],["FieldText",[]],["FieldTextarea",[]],["FieldFloat",[]],["FieldTime",[]],["FieldDate",[]],["FieldCheckbox",[]]]}],"aliases":[{"name":"Context","comment":" ","args":[],"type":"{ errors : Elm.Expression, submitting : Elm.Expression, submitAttempted : Elm.Expression, data : Elm.Expression, expression : Elm.Expression }"}],"values":[{"name":"fieldEncoder","comment":" A lower-level, more granular version of `recordEncoder` - lets you generate a JSON Encoder `Expression` for an individual Field rather than a group of Fields.\n","type":"Elm.Expression -> String.String -> Scaffold.Form.Kind -> Elm.Expression"},{"name":"provide","comment":" ","type":"{ fields : List.List ( String.String, Scaffold.Form.Kind ), elmCssView : Basics.Bool, view : { formState : Scaffold.Form.Context, params : List.List { name : String.String, kind : Scaffold.Form.Kind, param : Elm.Expression } } -> Elm.Expression } -> Maybe.Maybe { formHandlers : Elm.Expression, form : Elm.Expression, declarations : List.List Elm.Declaration }"},{"name":"recordEncoder","comment":" Generate a JSON Encoder for the form fields. This can be helpful for sending the validated form data through a\nBackendTask.Custom or to an external API from your scaffolded Route Module code.\n","type":"Elm.Expression -> List.List ( String.String, Scaffold.Form.Kind ) -> Elm.Expression"},{"name":"restArgsParser","comment":" This parser handles the following field types (or `text` if none is provided):\n\n - `text`\n - `textarea`\n - `checkbox`\n - `time`\n - `date`\n\nThe naming convention follows the same naming as the HTML form field elements or attributes that are used to represent them.\nIn addition to using the appropriate field type, this will also give you an Elm type with the corresponding base type (like `Date` for `date` or `Bool` for `checkbox`).\n\n","type":"Cli.Option.Option (List.List String.String) (List.List ( String.String, Scaffold.Form.Kind )) Cli.Option.RestArgsOption"}],"binops":[]},{"name":"Scaffold.Route","comment":" This module provides some functions for scaffolding code for a new Route Module. It uses [`elm-codegen`'s API](https://package.elm-lang.org/packages/mdgriffith/elm-codegen/latest/) for generating code.\n\nTypically you'll want to use this via the `elm-pages run` CLI command. The default starter template includes a Script that uses these functions, which you can tweak to customize your scaffolding commands.\n[Learn more about writing and running elm-pages Scripts for scaffolding](https://elm-pages-v3.netlify.app/docs/elm-pages-scripts#scaffolding-a-route-module).\n\nIt's typically easiest to modify the `AddRoute` script from the starter template and adjust it to your needs rather than writing one from scratch.\n\n\n## Initializing the Generator Builder\n\nThese functions mirror the `RouteBuilder` API that you use in your Route modules to define your route. The difference is that\ninstead of defining a route, this is defining a code generator for a Route module.\n\n@docs buildWithLocalState, buildWithSharedState, buildNoState, Builder\n\n@docs Type\n\n\n## Generating Server-Rendered Pages\n\n@docs serverRender\n\n\n## Generating pre-rendered pages\n\n@docs preRender, single\n\n\n## Including Additional elm-codegen Declarations\n\n@docs addDeclarations\n\n\n## CLI Options Parsing Helpers\n\n@docs moduleNameCliArg\n\n","unions":[{"name":"Builder","comment":" ","args":[],"cases":[]},{"name":"Type","comment":" ","args":[],"cases":[["Alias",["Elm.Annotation.Annotation"]],["Custom",["List.List Elm.Variant"]]]}],"aliases":[],"values":[{"name":"addDeclarations","comment":" The helpers in this module help you generate a Route module file with the core boilerplate abstracted away.\n\nYou can also define additional top-level declarations in the generated Route module using this helper.\n\n","type":"List.List Elm.Declaration -> Scaffold.Route.Builder -> Scaffold.Route.Builder"},{"name":"buildNoState","comment":" ","type":"{ view : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"buildWithLocalState","comment":" ","type":"{ view : { shared : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { shared : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { routeParams : Elm.Expression, path : Elm.Expression, shared : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Scaffold.Route.Type, model : Scaffold.Route.Type } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"buildWithSharedState","comment":" ","type":"{ view : { shared : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { shared : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { routeParams : Elm.Expression, path : Elm.Expression, shared : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Scaffold.Route.Type, model : Scaffold.Route.Type } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"moduleNameCliArg","comment":" A positional argument for elm-cli-options-parser that does a Regex validation to check that the module name is a valid Elm Route module name.\n","type":"Cli.Option.Option from String.String builderState -> Cli.Option.Option from (List.List String.String) builderState"},{"name":"preRender","comment":" ","type":"{ data : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression ), pages : Elm.Expression, head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"},{"name":"serverRender","comment":" ","type":"{ data : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression ), action : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"},{"name":"single","comment":" ","type":"{ data : ( Scaffold.Route.Type, Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"}],"binops":[]},{"name":"Server.Request","comment":"\n\n@docs Parser\n\n@docs succeed, fromResult, skip\n\n\n## Forms\n\n@docs formData, formDataWithServerValidation\n\n@docs rawFormData\n\n\n## Direct Values\n\n@docs rawUrl\n\n@docs method, rawBody, allCookies, rawHeaders\n\n@docs requestTime, optionalHeader, expectContentType, expectJsonBody\n\n@docs acceptMethod, acceptContentTypes\n\n\n## Transforming\n\n@docs map, map2, oneOf, andMap, andThen\n\n\n## Query Parameters\n\n@docs queryParam, expectQueryParam, queryParams\n\n\n## Cookies\n\n@docs cookie, expectCookie\n\n\n## Headers\n\n@docs expectHeader\n\n\n## Multi-part forms and file uploads\n\n@docs File, expectMultiPartFormPost\n\n\n## Request Parsers That Can Fail\n\n@docs expectBody\n\n\n## Map Functions\n\n@docs map3, map4, map5, map6, map7, map8, map9\n\n\n## Method Type\n\n@docs Method, methodToString\n\n\n## Internals\n\n@docs errorsToString, errorToString, getDecoder, ValidationError\n\n","unions":[{"name":"Method","comment":" ","args":[],"cases":[["Connect",[]],["Delete",[]],["Get",[]],["Head",[]],["Options",[]],["Patch",[]],["Post",[]],["Put",[]],["Trace",[]],["NonStandard",["String.String"]]]},{"name":"ValidationError","comment":" ","args":[],"cases":[]}],"aliases":[{"name":"File","comment":" ","args":[],"type":"{ name : String.String, mimeType : String.String, body : String.String }"},{"name":"Parser","comment":" A `Server.Request.Parser` lets you send a `Server.Response.Response` based on an incoming HTTP request. For example,\nusing a `Server.Request.Parser`, you could check a session cookie to decide whether to respond by rendering a page\nfor the logged-in user, or else respond with an HTTP redirect response (see the [`Server.Response` docs](Server-Response)).\n\nYou can access the incoming HTTP request's:\n\n - Headers\n - Cookies\n - [`method`](#method)\n - URL query parameters\n - [`requestTime`](#requestTime) (as a `Time.Posix`)\n\nNote that this data is not available for pre-rendered pages or pre-rendered API Routes, only for server-rendered pages.\nThis is because when a page is pre-rendered, there _is_ no incoming HTTP request to respond to, it is rendered before a user\nrequests the page and then the pre-rendered page is served as a plain file (without running your Route Module).\n\nThat's why `RouteBuilder.preRender` has `data : RouteParams -> BackendTask Data`:\n\n import BackendTask exposing (BackendTask)\n import RouteBuilder exposing (StatelessRoute)\n\n type alias Data =\n {}\n\n data : RouteParams -> BackendTask Data\n data routeParams =\n BackendTask.succeed Data\n\n route : StatelessRoute RouteParams Data ActionData\n route =\n RouteBuilder.preRender\n { data = data\n , head = head\n , pages = pages\n }\n |> RouteBuilder.buildNoState { view = view }\n\nA server-rendered Route Module _does_ have access to a user's incoming HTTP request because it runs every time the page\nis loaded. That's why `data` is a `Request.Parser` in server-rendered Route Modules. Since you have an incoming HTTP request for server-rendered routes,\n`RouteBuilder.serverRender` has `data : RouteParams -> Request.Parser (BackendTask (Response Data))`. That means that you\ncan use the incoming HTTP request data to choose how to respond. For example, you could check for a dark-mode preference\ncookie and render a light- or dark-themed page and render a different page.\n\n\n## Building a Request Parser\n\n`Request.Parser` means you can pull out data from the incoming HTTP request.\n\nWith a pre-rendered Route, we wouldn't be able to access the query param before rendering the page, but for a server-rendered Route\nwe can dynamically respond to the incoming HTTP request (including its query params).\n\nFor example, we might send the user an email with a link like `https://example.com/products/123?coupon=xmas22`.\nWhen the user clicks the link, we want to respond based on that `coupon` query parameter and render the page with the coupon data\n(either by directly checking the coupon with our DB through [`BackendTask.Custom`](BackendTask#Custom), or through an API request with [`BackendTask.Http`](BackendTask#Http)).\n\n module Route.Products.Id_ exposing (..)\n\n import BackendTask exposing (BackendTask)\n import RouteBuilder exposing (StatelessRoute)\n import Server.Request as Request exposing (Request)\n import Server.Response as Response exposing (Response)\n\n type alias Data =\n { coupon : Maybe Coupon }\n\n data :\n RouteParams\n -> Request.Parser (BackendTask (Response Data))\n data routeParams =\n Request.queryParam \"coupon\"\n |> Request.map\n (\\maybeCouponCode ->\n maybeCouponCode\n |> lookupCoupon\n |> BackendTask.map (\\maybeCoupon ->\n (Response.render { coupon = maybeCoupon })\n )\n\n route : StatelessRoute RouteParams Data ActionData\n route =\n RouteBuilder.serverRender\n { head = head\n , data = data\n }\n |> RouteBuilder.buildNoState { view = view }\n\n","args":["decodesTo"],"type":"Internal.Request.Parser decodesTo Server.Request.ValidationError"}],"values":[{"name":"acceptContentTypes","comment":" ","type":"( String.String, List.List String.String ) -> Server.Request.Parser value -> Server.Request.Parser value"},{"name":"acceptMethod","comment":" ","type":"( Server.Request.Method, List.List Server.Request.Method ) -> Server.Request.Parser value -> Server.Request.Parser value"},{"name":"allCookies","comment":" ","type":"Server.Request.Parser (Dict.Dict String.String String.String)"},{"name":"andMap","comment":" Decode an argument and provide it to a function in a decoder.\n\n decoder : Decoder String\n decoder =\n succeed (String.repeat)\n |> andMap (field \"count\" int)\n |> andMap (field \"val\" string)\n\n\n \"\"\" { \"val\": \"hi\", \"count\": 3 } \"\"\"\n |> decodeString decoder\n --> Success \"hihihi\"\n\n","type":"Server.Request.Parser a -> Server.Request.Parser (a -> b) -> Server.Request.Parser b"},{"name":"andThen","comment":" ","type":"(a -> Server.Request.Parser b) -> Server.Request.Parser a -> Server.Request.Parser b"},{"name":"cookie","comment":" ","type":"String.String -> Server.Request.Parser (Maybe.Maybe String.String)"},{"name":"errorToString","comment":" TODO internal only\n","type":"Server.Request.ValidationError -> String.String"},{"name":"errorsToString","comment":" ","type":"( Server.Request.ValidationError, List.List Server.Request.ValidationError ) -> String.String"},{"name":"expectBody","comment":" Same as [`rawBody`](#rawBody), but will only match when a body is present in the HTTP request.\n","type":"Server.Request.Parser String.String"},{"name":"expectContentType","comment":" ","type":"String.String -> Server.Request.Parser ()"},{"name":"expectCookie","comment":" ","type":"String.String -> Server.Request.Parser String.String"},{"name":"expectHeader","comment":" ","type":"String.String -> Server.Request.Parser String.String"},{"name":"expectJsonBody","comment":" ","type":"Json.Decode.Decoder value -> Server.Request.Parser value"},{"name":"expectMultiPartFormPost","comment":" ","type":"({ field : String.String -> Server.Request.Parser String.String, optionalField : String.String -> Server.Request.Parser (Maybe.Maybe String.String), fileField : String.String -> Server.Request.Parser Server.Request.File } -> Server.Request.Parser decodedForm) -> Server.Request.Parser decodedForm"},{"name":"expectQueryParam","comment":" Same as [`queryParam`](#queryParam), but instead of returning `Nothing` if the query param is missing, it will result\na non-matching Parser (will try a subsequent parser if there is a oneOf).\n\n import Server.Request as Request\n\n data =\n Request.oneOf\n [ Request.expectQueryParam \"token\"\n |> Request.map (\\token -> showProtectedPageOrRedirect token)\n , Request.succeed guestPageView\n ]\n\n","type":"String.String -> Server.Request.Parser String.String"},{"name":"formData","comment":" Takes a [`Form.Handler.Handler`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form-Handler) and\nparses the raw form data into a [`Form.Validated`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Validated) value.\n\nThis is the standard pattern for dealing with form data in `elm-pages`. You can share your code for your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Form)\ndefinitions between your client and server code, using this function to parse the raw form data into a `Form.Validated` value for the backend,\nand [`Pages.Form`](Pages-Form) to render the `Form` on the client.\n\nSince we are sharing the `Form` definition between frontend and backend, we get to re-use the same validation logic so we gain confidence that\nthe validation errors that the user sees on the client are protected on our backend, and vice versa.\n\n import BackendTask\n import Form\n import Server.Request\n\n type Action\n = Delete\n | CreateOrUpdate Post\n\n formHandlers : Form.Handler.Handler String Action\n formHandlers =\n deleteForm\n |> Form.Handler.init (\\() -> Delete)\n |> Form.Handler.with CreateOrUpdate createOrUpdateForm\n\n deleteForm : Form.HtmlForm String () input msg\n\n createOrUpdateForm : Form.HtmlForm String Post Post msg\n\n action :\n RouteParams\n -> Server.Request.Parser (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response ActionData ErrorPage.ErrorPage))\n action routeParams =\n Server.Request.map\n (\\( formResponse, parsedForm ) ->\n case parsedForm of\n Form.Valid Delete ->\n deletePostBySlug routeParams.slug\n |> BackendTask.map\n (\\() -> Route.redirectTo Route.Index)\n\n Form.Valid (CreateOrUpdate post) ->\n let\n createPost : Bool\n createPost =\n okForm.slug == \"new\"\n in\n createOrUpdatePost post\n |> BackendTask.map\n (\\() ->\n Route.redirectTo\n (Route.Admin__Slug_ { slug = okForm.slug })\n )\n\n Form.Invalid _ invalidForm ->\n BackendTask.succeed\n (Server.Response.render\n { errors = formResponse }\n )\n )\n (Server.Request.formData formHandlers)\n\nYou can handle form submissions as either GET or POST requests. Note that for security reasons, it's important to performing mutations with care from GET requests,\nsince a GET request can be performed from an outside origin by embedding an image that points to the given URL. So a logout submission should be protected by\nusing `POST` to ensure that you can't log users out by embedding an image with a logout URL in it.\n\nIf the request has HTTP method `GET`, the form data will come from the query parameters.\n\nIf the request has the HTTP method `POST` _and_ the `Content-Type` is `application/x-www-form-urlencoded`, it will return the\ndecoded form data from the body of the request.\n\nOtherwise, this `Parser` will not match.\n\nNote that in server-rendered Route modules, your `data` function will handle `GET` requests (and will _not_ receive any `POST` requests),\nwhile your `action` will receive POST (and other non-GET) requests.\n\nBy default, [`Form`]'s are rendered with a `POST` method, and you can configure them to submit `GET` requests using [`withGetMethod`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#withGetMethod).\nSo you will want to handle any `Form`'s rendered using `withGetMethod` in your Route's `data` function, or otherwise handle forms in `action`.\n\n","type":"Form.Handler.Handler error combined -> Server.Request.Parser ( Form.ServerResponse error, Form.Validated error combined )"},{"name":"formDataWithServerValidation","comment":" ","type":"Pages.Form.Handler error combined -> Server.Request.Parser (BackendTask.BackendTask FatalError.FatalError (Result.Result (Form.ServerResponse error) ( Form.ServerResponse error, combined )))"},{"name":"fromResult","comment":" Turn a Result into a Request. Useful with `andThen`. Turns `Err` into a skipped request handler (non-matching request),\nand `Ok` values into a `succeed` (matching request).\n","type":"Result.Result String.String value -> Server.Request.Parser value"},{"name":"getDecoder","comment":" TODO internal only\n","type":"Server.Request.Parser (BackendTask.BackendTask error response) -> Json.Decode.Decoder (Result.Result ( Server.Request.ValidationError, List.List Server.Request.ValidationError ) (BackendTask.BackendTask error response))"},{"name":"map","comment":" ","type":"(a -> b) -> Server.Request.Parser a -> Server.Request.Parser b"},{"name":"map2","comment":" ","type":"(a -> b -> c) -> Server.Request.Parser a -> Server.Request.Parser b -> Server.Request.Parser c"},{"name":"map3","comment":" ","type":"(value1 -> value2 -> value3 -> valueCombined) -> Server.Request.Parser value1 -> Server.Request.Parser value2 -> Server.Request.Parser value3 -> Server.Request.Parser valueCombined"},{"name":"map4","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> valueCombined) -> Server.Request.Parser value1 -> Server.Request.Parser value2 -> Server.Request.Parser value3 -> Server.Request.Parser value4 -> Server.Request.Parser valueCombined"},{"name":"map5","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined) -> Server.Request.Parser value1 -> Server.Request.Parser value2 -> Server.Request.Parser value3 -> Server.Request.Parser value4 -> Server.Request.Parser value5 -> Server.Request.Parser valueCombined"},{"name":"map6","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined) -> Server.Request.Parser value1 -> Server.Request.Parser value2 -> Server.Request.Parser value3 -> Server.Request.Parser value4 -> Server.Request.Parser value5 -> Server.Request.Parser value6 -> Server.Request.Parser valueCombined"},{"name":"map7","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> valueCombined) -> Server.Request.Parser value1 -> Server.Request.Parser value2 -> Server.Request.Parser value3 -> Server.Request.Parser value4 -> Server.Request.Parser value5 -> Server.Request.Parser value6 -> Server.Request.Parser value7 -> Server.Request.Parser valueCombined"},{"name":"map8","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> valueCombined) -> Server.Request.Parser value1 -> Server.Request.Parser value2 -> Server.Request.Parser value3 -> Server.Request.Parser value4 -> Server.Request.Parser value5 -> Server.Request.Parser value6 -> Server.Request.Parser value7 -> Server.Request.Parser value8 -> Server.Request.Parser valueCombined"},{"name":"map9","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> value9 -> valueCombined) -> Server.Request.Parser value1 -> Server.Request.Parser value2 -> Server.Request.Parser value3 -> Server.Request.Parser value4 -> Server.Request.Parser value5 -> Server.Request.Parser value6 -> Server.Request.Parser value7 -> Server.Request.Parser value8 -> Server.Request.Parser value9 -> Server.Request.Parser valueCombined"},{"name":"method","comment":" ","type":"Server.Request.Parser Server.Request.Method"},{"name":"methodToString","comment":" Gets the HTTP Method as a String, like 'GET', 'PUT', etc.\n","type":"Server.Request.Method -> String.String"},{"name":"oneOf","comment":" ","type":"List.List (Server.Request.Parser a) -> Server.Request.Parser a"},{"name":"optionalHeader","comment":" ","type":"String.String -> Server.Request.Parser (Maybe.Maybe String.String)"},{"name":"queryParam","comment":" Get `Nothing` if the query param with the given name is missing, or `Just` the value if it is present.\n\nIf there are multiple query params with the same name, the first one is returned.\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc\n -- parses into: Just \"abc\"\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc&coupon=xyz\n -- parses into: Just \"abc\"\n\n queryParam \"coupon\"\n\n -- url: http://example.com\n -- parses into: Nothing\n\nSee also [`expectQueryParam`](#expectQueryParam) and [`queryParams`](#queryParams), or [`rawUrl`](#rawUrl) if you need something more low-level.\n\n","type":"String.String -> Server.Request.Parser (Maybe.Maybe String.String)"},{"name":"queryParams","comment":" Gives all query params from the URL.\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc\n -- parses into: Dict.fromList [(\"coupon\", [\"abc\"])]\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc&coupon=xyz\n -- parses into: Dict.fromList [(\"coupon\", [\"abc\", \"xyz\"])]\n\n","type":"Server.Request.Parser (Dict.Dict String.String (List.List String.String))"},{"name":"rawBody","comment":" ","type":"Server.Request.Parser (Maybe.Maybe String.String)"},{"name":"rawFormData","comment":" Get the raw key-value pairs from a form submission.\n\nIf the request has the HTTP method `GET`, it will return the query parameters.\n\nIf the request has the HTTP method `POST` _and_ the `Content-Type` is `application/x-www-form-urlencoded`, it will return the\ndecoded form data from the body of the request.\n\nOtherwise, this `Parser` will not match.\n\nNote that in server-rendered Route modules, your `data` function will handle `GET` requests (and will _not_ receive any `POST` requests),\nwhile your `action` will receive POST (and other non-GET) requests.\n\nBy default, [`Form`]'s are rendered with a `POST` method, and you can configure them to submit `GET` requests using [`withGetMethod`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#withGetMethod).\nSo you will want to handle any `Form`'s rendered using `withGetMethod` in your Route's `data` function, or otherwise handle forms in `action`.\n\n","type":"Server.Request.Parser (List.List ( String.String, String.String ))"},{"name":"rawHeaders","comment":" ","type":"Server.Request.Parser (Dict.Dict String.String String.String)"},{"name":"rawUrl","comment":" ","type":"Server.Request.Parser String.String"},{"name":"requestTime","comment":" ","type":"Server.Request.Parser Time.Posix"},{"name":"skip","comment":" This is a Request.Parser that will never match an HTTP request. Similar to `Json.Decode.fail`.\n\nWhy would you want it to always fail? It's helpful for building custom `Server.Request.Parser`. For example, let's say\nyou wanted to define a custom `Server.Request.Parser` to use an XML Decoding package on the request body.\nYou could define a custom function like this\n\n import Server.Request as Request\n\n expectXmlBody : XmlDecoder value -> Request.Parser value\n expectXmlBody xmlDecoder =\n Request.expectBody\n |> Request.andThen\n (\\bodyAsString ->\n case runXmlDecoder xmlDecoder bodyAsString of\n Ok decodedXml ->\n Request.succeed decodedXml\n\n Err error ->\n Request.skip (\"XML could not be decoded \" ++ xmlErrorToString error)\n )\n\nNote that when we said `Request.skip`, remaining Request Parsers will run (for example if you use [`Server.Request.oneOf`](#oneOf)).\nYou could build this with different semantics if you wanted to handle _any_ valid XML body. This Request Parser will _not_\nhandle any valid XML body. It will only handle requests that can match the XmlDecoder that is passed in.\n\nSo when you define your `Server.Request.Parser`s, think carefully about whether you want to handle invalid cases and give an\nerror, or fall through to other Parsers. There's no universal right answer, it's just something to decide for your use case.\n\n expectXmlBody : Request.Parser value\n expectXmlBody =\n Request.map2\n acceptContentTypes\n Request.expectBody\n |> Request.andThen\n (\\bodyAsString ->\n case runXmlDecoder xmlDecoder bodyAsString of\n Ok decodedXml ->\n Request.succeed decodedXml\n\n Err error ->\n Request.skip (\"XML could not be decoded \" ++ xmlErrorToString error)\n )\n\n","type":"String.String -> Server.Request.Parser value"},{"name":"succeed","comment":" A Parser that always matches and parsers into the given value.\n\n import Server.Request as Request exposing (Request)\n\n data routeParams =\n Request.succeed\n (BackendTask.succeed\n (Response.render { data = {} })\n )\n\n","type":"value -> Server.Request.Parser value"}],"binops":[]},{"name":"Server.Response","comment":"\n\n\n## Responses\n\n@docs Response\n\nThere are two top-level response types:\n\n1. Server Responses\n2. Render Responses\n\nA Server Response is a way to directly send a low-level server response, with no additional magic. You can set a String body,\na list of headers, the status code, etc. The Server Response helpers like `json` and `temporaryRedirect` are just helpers for\nbuilding up those low-level Server Responses.\n\nRender Responses are a little more special in the way they are connected to your elm-pages app. They allow you to render\nthe current Route Module. To do that, you'll need to pass along the `data` for your Route Module.\n\nYou can use `withHeader` and `withStatusCode` to customize either type of Response (Server Responses or Render Responses).\n\n\n## Server Responses\n\n@docs json, plainText, temporaryRedirect, permanentRedirect\n\n\n## Custom Responses\n\n@docs emptyBody, body, bytesBody, base64Body\n\n\n## Render Responses\n\n@docs render\n\n\n## Rendering Error Pages\n\n@docs errorPage, mapError\n\n@docs map\n\n\n## Amending Responses\n\n@docs withHeader, withHeaders, withStatusCode, withSetCookieHeader\n\n\n## Internals\n\n@docs toJson\n\n","unions":[],"aliases":[{"name":"Response","comment":" ","args":["data","error"],"type":"PageServerResponse.PageServerResponse data error"}],"values":[{"name":"base64Body","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"body","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"bytesBody","comment":" ","type":"Bytes.Bytes -> Server.Response.Response data error"},{"name":"emptyBody","comment":" ","type":"Server.Response.Response data error"},{"name":"errorPage","comment":" ","type":"errorPage -> Server.Response.Response data errorPage"},{"name":"json","comment":" ","type":"Json.Encode.Value -> Server.Response.Response data error"},{"name":"map","comment":" ","type":"(data -> mappedData) -> Server.Response.Response data error -> Server.Response.Response mappedData error"},{"name":"mapError","comment":" ","type":"(errorPage -> mappedErrorPage) -> Server.Response.Response data errorPage -> Server.Response.Response data mappedErrorPage"},{"name":"permanentRedirect","comment":" Build a 308 permanent redirect response.\n\nPermanent redirects tell the browser that a resource has permanently moved. If you redirect because a user is not logged in,\nthen you **do not** want to use a permanent redirect because the page they are looking for hasn't changed, you are just\ntemporarily pointing them to a new page since they need to authenticate.\n\nPermanent redirects are aggressively cached so be careful not to use them when you mean to use temporary redirects instead.\n\nIf you need to specifically rely on a 301 permanent redirect (see on the difference between 301 and 308),\nuse `customResponse` instead.\n\n","type":"String.String -> Server.Response.Response data error"},{"name":"plainText","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"render","comment":" ","type":"data -> Server.Response.Response data error"},{"name":"temporaryRedirect","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"toJson","comment":" ","type":"Server.Response.Response Basics.Never Basics.Never -> Json.Encode.Value"},{"name":"withHeader","comment":" ","type":"String.String -> String.String -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withHeaders","comment":" ","type":"List.List ( String.String, String.String ) -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withSetCookieHeader","comment":" ","type":"Server.SetCookie.SetCookie -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withStatusCode","comment":" ","type":"Basics.Int -> Server.Response.Response data Basics.Never -> Server.Response.Response data Basics.Never"}],"binops":[]},{"name":"Server.Session","comment":" You can manage server state with HTTP cookies using this Server.Session API. Server-rendered pages define a `Server.Request.Parser`\nto choose which requests to respond to and how to extract structured data from the incoming request.\n\n\n## Using Sessions in a Request.Parser\n\nUsing these functions, you can store and read session data in cookies to maintain state between requests.\nFor example, TODO:\n\n action : RouteParams -> Request.Parser (BackendTask (Response ActionData ErrorPage))\n action routeParams =\n MySession.withSession\n (Request.formDataWithServerValidation (form |> Form.initCombinedServer identity))\n (\\nameResultData session ->\n nameResultData\n |> BackendTask.map\n (\\nameResult ->\n case nameResult of\n Err errors ->\n ( session\n |> Result.withDefault Nothing\n |> Maybe.withDefault Session.empty\n , Response.render\n { errors = errors\n }\n )\n\n Ok ( _, name ) ->\n ( session\n |> Result.withDefault Nothing\n |> Maybe.withDefault Session.empty\n |> Session.insert \"name\" name\n |> Session.withFlash \"message\" (\"Welcome \" ++ name ++ \"!\")\n , Route.redirectTo Route.Greet\n )\n )\n )\n\nThe elm-pages framework will manage signing these cookies using the `secrets : BackendTask (List String)` you pass in.\nThat means that the values you set in your session will be directly visible to anyone who has access to the cookie\n(so don't directly store sensitive data in your session). Since the session cookie is signed using the secret you provide,\nthe cookie will be invalidated if it is tampered with because it won't match when elm-pages verifies that it has been\nsigned with your secrets. Of course you need to provide secure secrets and treat your secrets with care.\n\n\n### Rotating Secrets\n\nThe first String in `secrets : BackendTask (List String)` will be used to sign sessions, while the remaining String's will\nstill be used to attempt to \"unsign\" the cookies. So if you have a single secret:\n\n Session.withSession\n { name = \"mysession\"\n , secrets =\n BackendTask.map List.singleton\n (Env.expect \"SESSION_SECRET2022-09-01\")\n , options = Nothing\n }\n\nThen you add a second secret\n\n Session.withSession\n { name = \"mysession\"\n , secrets =\n BackendTask.map2\n (\\newSecret oldSecret -> [ newSecret, oldSecret ])\n (Env.expect \"SESSION_SECRET2022-12-01\")\n (Env.expect \"SESSION_SECRET2022-09-01\")\n , options = Nothing\n }\n\nThe new secret (`2022-12-01`) will be used to sign all requests. This API always re-signs using the newest secret in the list\nwhenever a new request comes in (even if the Session key-value pairs are unchanged), so these cookies get \"refreshed\" with the latest\nsigning secret when a new request comes in.\n\nHowever, incoming requests with a cookie signed using the old secret (`2022-09-01`) will still successfully be unsigned\nbecause they are still in the rotation (and then subsequently \"refreshed\" and signed using the new secret).\n\nThis allows you to rotate your session secrets (for security purposes). When a secret goes out of the rotation,\nit will invalidate all cookies signed with that. For example, if we remove our old secret from the rotation:\n\n Session.withSession\n { name = \"mysession\"\n , secrets =\n BackendTask.map List.singleton\n (Env.expect \"SESSION_SECRET2022-12-01\")\n , options = Nothing\n }\n\nAnd then a user makes a request but had a session signed with our old secret (`2022-09-01`), the session will be invalid\n(so `withSession` would parse the session for that request as `Nothing`). It's standard for cookies to have an expiration date,\nso there's nothing wrong with an old session expiring (and the browser will eventually delete old cookies), just be aware of that when rotating secrets.\n\n@docs withSession, withSessionResult\n\n@docs NotLoadedReason\n\n\n## Creating and Updating Sessions\n\n@docs Session, empty, get, insert, remove, update, withFlash\n\n","unions":[{"name":"NotLoadedReason","comment":" ","args":[],"cases":[["NoSessionCookie",[]],["InvalidSessionCookie",[]]]},{"name":"Session","comment":" ","args":[],"cases":[]}],"aliases":[],"values":[{"name":"empty","comment":" ","type":"Server.Session.Session"},{"name":"get","comment":" ","type":"String.String -> Server.Session.Session -> Maybe.Maybe String.String"},{"name":"insert","comment":" ","type":"String.String -> String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"remove","comment":" ","type":"String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"update","comment":" ","type":"String.String -> (Maybe.Maybe String.String -> Maybe.Maybe String.String) -> Server.Session.Session -> Server.Session.Session"},{"name":"withFlash","comment":" ","type":"String.String -> String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"withSession","comment":" ","type":"{ name : String.String, secrets : BackendTask.BackendTask error (List.List String.String), options : Maybe.Maybe Server.SetCookie.Options } -> (request -> Server.Session.Session -> BackendTask.BackendTask error ( Server.Session.Session, Server.Response.Response data errorPage )) -> Server.Request.Parser request -> Server.Request.Parser (BackendTask.BackendTask error (Server.Response.Response data errorPage))"},{"name":"withSessionResult","comment":" ","type":"{ name : String.String, secrets : BackendTask.BackendTask error (List.List String.String), options : Maybe.Maybe Server.SetCookie.Options } -> (request -> Result.Result Server.Session.NotLoadedReason Server.Session.Session -> BackendTask.BackendTask error ( Server.Session.Session, Server.Response.Response data errorPage )) -> Server.Request.Parser request -> Server.Request.Parser (BackendTask.BackendTask error (Server.Response.Response data errorPage))"}],"binops":[]},{"name":"Server.SetCookie","comment":" Server-rendered pages in your `elm-pages` can set cookies. `elm-pages` provides two high-level ways to work with cookies:\n\n - [`Server.Session.withSession`](Server-Session#withSession)\n - [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader)\n\n[`Server.Session.withSession`](Server-Session#withSession) provides a high-level way to manage key-value pairs of data using cookie storage,\nwhereas `Server.Response.withSetCookieHeader` gives a more low-level tool for setting cookies. It's often best to use the\nmost high-level tool that will fit your use case.\n\nYou can learn more about the basics of cookies in the Web Platform in these helpful MDN documentation pages:\n\n - \n - \n\n@docs SetCookie, setCookie\n\n\n## Building Options\n\nUsually you'll want to start by creating default `Options` with `options` and then overriding defaults using the `with...` helpers.\n\n import Server.SetCookie as SetCookie\n\n options : SetCookie.Options\n options =\n SetCookie.options\n |> SetCookie.nonSecure\n |> SetCookie.withMaxAge 123\n |> SetCookie.makeVisibleToJavaScript\n |> SetCookie.withoutPath\n |> SetCookie.setCookie \"id\" \"a3fWa\"\n\n@docs Options, options\n\n@docs SameSite, withSameSite\n\n@docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, withDomain, withExpiration, withMaxAge, withPath, withoutPath\n\n\n## Internal\n\n@docs toString\n\n","unions":[{"name":"SameSite","comment":" Possible values for [the cookie's same-site value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value).\n\nThe default option is [`Lax`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#lax) (Lax does not send\ncookies in cross-origin requests so it is a good default for most cases, but [`Strict`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#strict)\nis even more restrictive).\n\nOverride the default option using [`withSameSite`](#withSameSite).\n\n","args":[],"cases":[["Strict",[]],["Lax",[]],["None",[]]]}],"aliases":[{"name":"Options","comment":" The set of possible configuration options. You can configure this record directly, or use the `with...` helpers.\n","args":[],"type":"{ expiration : Maybe.Maybe Time.Posix, visibleToJavaScript : Basics.Bool, maxAge : Maybe.Maybe Basics.Int, path : Maybe.Maybe String.String, domain : Maybe.Maybe String.String, secure : Basics.Bool, sameSite : Maybe.Maybe Server.SetCookie.SameSite }"},{"name":"SetCookie","comment":" ","args":[],"type":"{ name : String.String, value : String.String, options : Server.SetCookie.Options }"}],"values":[{"name":"makeVisibleToJavaScript","comment":" The default option in this API is for HttpOnly cookies .\n\nCookies can be exposed so you can read them from JavaScript using `Document.cookie`. When this is intended and understood\nthen there's nothing unsafe about that (for example, if you are setting a `darkMode` cookie and what to access that\ndynamically). In this API you opt into exposing a cookie you set to JavaScript to ensure cookies aren't exposed to JS unintentionally.\n\nIn general if you can accomplish your goal using HttpOnly cookies (i.e. not using `makeVisibleToJavaScript`) then\nit's a good practice. With server-rendered `elm-pages` applications you can often manage your session state by pulling\nin session data from cookies in a `BackendTask` (which is resolved server-side before it ever reaches the browser).\n\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"nonSecure","comment":" Secure (only sent over https, or localhost on http) is the default. This overrides that and\nremoves the `Secure` attribute from the cookie.\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"options","comment":" Initialize the default `SetCookie` `Options`. Can be configured directly through a record update, or with `withExpiration`, etc.\n","type":"Server.SetCookie.Options"},{"name":"setCookie","comment":" Create a `SetCookie` record with the given name, value, and [`Options`](Options]. To add a `Set-Cookie` header, you can\npass this value with [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader). Or for more low-level\nuses you can stringify the value manually with [`toString`](#toString).\n","type":"String.String -> String.String -> Server.SetCookie.Options -> Server.SetCookie.SetCookie"},{"name":"toString","comment":" Usually you'll want to use [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader) instead.\n\nThis is a low-level helper that's there in case you want it but most users will never need this.\n\n","type":"Server.SetCookie.SetCookie -> String.String"},{"name":"withDomain","comment":" Sets the `Set-Cookie`'s [`Domain`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value).\n","type":"String.String -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withExpiration","comment":" ","type":"Time.Posix -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withImmediateExpiration","comment":" Sets [`Expires`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#expiresdate) to `Time.millisToPosix 0`,\nwhich effectively tells the browser to delete the cookie immediately (by giving it an expiration date in the past).\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withMaxAge","comment":" Sets the `Set-Cookie`'s [`Max-Age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#max-agenumber).\n","type":"Basics.Int -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withPath","comment":" Sets the `Set-Cookie`'s [`Path`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value).\n\nThe default value is `/`, which will match any sub-directories or the root directory. See also [\\`withoutPath](#withoutPath)\n\n","type":"String.String -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withSameSite","comment":" The default SameSite policy is Lax if one is not explicitly set. See the SameSite section in .\n","type":"Server.SetCookie.SameSite -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withoutPath","comment":"\n\n> If the server omits the Path attribute, the user agent will use the \"directory\" of the request-uri's path component as the default value.\n\nSource: . See .\n\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"}],"binops":[]},{"name":"UrlPath","comment":" Represents the path portion of a URL (not query parameters, fragment, protocol, port, etc.).\n\nThis helper lets you combine together path parts without worrying about having too many or too few slashes.\nThese two examples will result in the same URL, even though the first example has trailing and leading slashes, and the\nsecond does not.\n\n UrlPath.join [ \"/blog/\", \"/post-1/\" ]\n |> UrlPath.toAbsolute\n --> \"/blog/post-1\"\n\n UrlPath.join [ \"blog\", \"post-1\" ]\n |> UrlPath.toAbsolute\n --> \"/blog/post-1\"\n\nWe can also safely join Strings that include multiple path parts, a single path part per string, or a mix of the two:\n\n UrlPath.join [ \"/articles/archive/\", \"1977\", \"06\", \"10\", \"post-1\" ]\n |> UrlPath.toAbsolute\n --> \"/articles/archive/1977/06/10/post-1\"\n\n\n## Creating UrlPaths\n\n@docs UrlPath, join, fromString\n\n\n## Turning UrlPaths to String\n\n@docs toAbsolute, toRelative, toSegments\n\n","unions":[],"aliases":[{"name":"UrlPath","comment":" The path portion of the URL, normalized to ensure that path segments are joined with `/`s in the right places (no doubled up or missing slashes).\n","args":[],"type":"List.List String.String"}],"values":[{"name":"fromString","comment":" Create a UrlPath from a path String.\n\n UrlPath.fromString \"blog/post-1/\"\n |> UrlPath.toAbsolute\n |> Expect.equal \"/blog/post-1\"\n\n","type":"String.String -> UrlPath.UrlPath"},{"name":"join","comment":" Turn a Path to a relative URL.\n","type":"UrlPath.UrlPath -> UrlPath.UrlPath"},{"name":"toAbsolute","comment":" Turn a UrlPath to an absolute URL (with no trailing slash).\n","type":"UrlPath.UrlPath -> String.String"},{"name":"toRelative","comment":" Turn a UrlPath to a relative URL.\n","type":"UrlPath.UrlPath -> String.String"},{"name":"toSegments","comment":" ","type":"String.String -> List.List String.String"}],"binops":[]}] \ No newline at end of file +[{"name":"ApiRoute","comment":" ApiRoute's are defined in `src/Api.elm` and are a way to generate files, like RSS feeds, sitemaps, or any text-based file that you output with an Elm function! You get access\nto a BackendTask so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in\nthe BackendTask for your ApiRoutes, and it won't effect the payload size. Instead, the size of an ApiRoute is just the content you output for that route.\n\nSimilar to your elm-pages Route Modules, ApiRoute's can be either server-rendered or pre-rendered. Let's compare the differences between pre-rendered and server-rendered ApiRoutes, and the different\nuse cases they support.\n\n\n## Pre-Rendering\n\nA pre-rendered ApiRoute is just a generated file. For example:\n\n - [An RSS feed](https://github.com/dillonkearns/elm-pages/blob/131f7b750cdefb2ba7a34a06be06dfbfafc79a86/examples/docs/app/Api.elm#L77-L84) ([Output file](https://elm-pages.com/blog/feed.xml))\n - [A calendar feed in the ical format](https://github.com/dillonkearns/incrementalelm.com/blob/d4934d899d06232dc66dcf9f4b5eccc74bbc60d3/src/Api.elm#L51-L60) ([Output file](https://incrementalelm.com/live.ics))\n - A redirect file for a hosting provider like Netlify\n\nYou could even generate a JavaScript file, an Elm file, or any file with a String body! It's really just a way to generate files, which are typically used to serve files to a user or Browser, but you execute them, copy them, etc. The only limit is your imagination!\nThe beauty is that you have a way to 1) pull in type-safe data using BackendTask's, and 2) write those files, and all in pure Elm!\n\n@docs single, preRender\n\n\n## Server Rendering\n\nYou could use server-rendered ApiRoutes to do a lot of similar things, the main difference being that it will be served up through a URL and generated on-demand when that URL is requested.\nSo for example, for an RSS feed or ical calendar feed like in the pre-rendered examples, you could build the same routes, but you would be pulling in the list of posts or calendar events on-demand rather\nthan upfront at build-time. That means you can hit your database and serve up always-up-to-date data.\n\nNot only that, but your server-rendered ApiRoutes have access to the incoming HTTP request payload just like your server-rendered Route Modules do. Just as with server-rendered Route Modules,\na server-rendered ApiRoute accesses the incoming HTTP request through a [Server.Request.Parser](Server-Request). Consider the use cases that this opens up:\n\n - Serve up protected assets. For example, gated content, like a paid subscriber feed for a podcast that checks authentication information in a query parameter to authenticate that a user has an active paid subscription before serving up the Pro RSS feed.\n - Serve up user-specific content, either through a cookie or other means of authentication\n - Look at the [accepted content-type in the request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) and use that to choose a response format, like XML or JSON ([full example](https://github.com/dillonkearns/elm-pages/blob/131f7b750cdefb2ba7a34a06be06dfbfafc79a86/examples/end-to-end/app/Api.elm#L76-L107)).\n - Look at the [accepted language in the request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language) and use that to choose a language for the response data.\n\n@docs serverRender\n\nYou can also do a hybrid approach using `preRenderWithFallback`. This allows you to pre-render a set of routes at build-time, but build additional routes that weren't rendered at build-time on the fly on the server.\nConceptually, this is just a delayed version of a pre-rendered route. Because of that, you _do not_ have access to the incoming HTTP request (no `Server.Request.Parser` like in server-rendered ApiRoute's).\nThe strategy used to build these routes will differ depending on your hosting provider and the elm-pages adapter you have setup, but generally ApiRoute's that use `preRenderWithFallback` will be cached on the server\nso within a certain time interval (or in the case of [Netlify's DPR](https://www.netlify.com/blog/2021/04/14/distributed-persistent-rendering-a-new-jamstack-approach-for-faster-builds/), until a new build is done)\nthat asset will be served up if that URL was already served up by the server.\n\n@docs preRenderWithFallback\n\n\n## Defining ApiRoute's\n\nYou define your ApiRoute's in `app/Api.elm`. Here's a simple example:\n\n module Api exposing (routes)\n\n import ApiRoute\n import BackendTask exposing (BackendTask)\n import Server.Request\n\n routes :\n BackendTask (List Route)\n -> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)\n -> List (ApiRoute.ApiRoute ApiRoute.Response)\n routes getStaticRoutes htmlToString =\n [ preRenderedExample\n , requestPrinterExample\n ]\n\n {-| Generates the following files when you\n run `elm-pages build`:\n\n - `dist/users/1.json`\n - `dist/users/2.json`\n - `dist/users/3.json`\n\n When you host it, these static assets will\n be served at `/users/1.json`, etc.\n\n -}\n preRenderedExample : ApiRoute.ApiRoute ApiRoute.Response\n preRenderedExample =\n ApiRoute.succeed\n (\\userId ->\n BackendTask.succeed\n (Json.Encode.object\n [ ( \"id\", Json.Encode.string userId )\n , ( \"name\", \"Data for user \" ++ userId |> Json.Encode.string )\n ]\n |> Json.Encode.encode 2\n )\n )\n |> ApiRoute.literal \"users\"\n |> ApiRoute.slash\n |> ApiRoute.capture\n |> ApiRoute.literal \".json\"\n |> ApiRoute.preRender\n (\\route ->\n BackendTask.succeed\n [ route \"1\"\n , route \"2\"\n , route \"3\"\n ]\n )\n\n {-| This returns a JSON response that prints information about the incoming\n HTTP request. In practice you'd want to do something useful with that data,\n and use more of the high-level helpers from the Server.Request API.\n -}\n requestPrinterExample : ApiRoute ApiRoute.Response\n requestPrinterExample =\n ApiRoute.succeed\n (Server.Request.map4\n (\\rawBody method cookies queryParams ->\n Encode.object\n [ ( \"rawBody\"\n , rawBody\n |> Maybe.map Encode.string\n |> Maybe.withDefault Encode.null\n )\n , ( \"method\"\n , method\n |> Server.Request.methodToString\n |> Encode.string\n )\n , ( \"cookies\"\n , cookies\n |> Encode.dict\n identity\n Encode.string\n )\n , ( \"queryParams\"\n , queryParams\n |> Encode.dict\n identity\n (Encode.list Encode.string)\n )\n ]\n |> Response.json\n |> BackendTask.succeed\n )\n Server.Request.rawBody\n Server.Request.method\n Server.Request.allCookies\n Server.Request.queryParams\n )\n |> ApiRoute.literal \"api\"\n |> ApiRoute.slash\n |> ApiRoute.literal \"request-test\"\n |> ApiRoute.serverRender\n\n@docs ApiRoute, ApiRouteBuilder, Response\n\n@docs capture, literal, slash, succeed\n\n\n## Including Head Tags\n\n@docs withGlobalHeadTags\n\n\n## Internals\n\n@docs toJson, getBuildTimeRoutes, getGlobalHeadTagsBackendTask\n\n","unions":[],"aliases":[{"name":"ApiRoute","comment":" ","args":["response"],"type":"Internal.ApiRoute.ApiRoute response"},{"name":"ApiRouteBuilder","comment":" ","args":["a","constructor"],"type":"Internal.ApiRoute.ApiRouteBuilder a constructor"},{"name":"Response","comment":" ","args":[],"type":"Json.Encode.Value"}],"values":[{"name":"capture","comment":" ","type":"ApiRoute.ApiRouteBuilder (String.String -> a) constructor -> ApiRoute.ApiRouteBuilder a (String.String -> constructor)"},{"name":"getBuildTimeRoutes","comment":" For internal use by generated code. Not so useful in user-land.\n","type":"ApiRoute.ApiRoute response -> BackendTask.BackendTask FatalError.FatalError (List.List String.String)"},{"name":"getGlobalHeadTagsBackendTask","comment":" ","type":"ApiRoute.ApiRoute response -> Maybe.Maybe (BackendTask.BackendTask FatalError.FatalError (List.List Head.Tag))"},{"name":"literal","comment":" A literal String segment of a route.\n","type":"String.String -> ApiRoute.ApiRouteBuilder a constructor -> ApiRoute.ApiRouteBuilder a constructor"},{"name":"preRender","comment":" ","type":"(constructor -> BackendTask.BackendTask FatalError.FatalError (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError String.String) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"preRenderWithFallback","comment":" ","type":"(constructor -> BackendTask.BackendTask FatalError.FatalError (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Basics.Never Basics.Never)) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"serverRender","comment":" ","type":"ApiRoute.ApiRouteBuilder (Server.Request.Request -> BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Basics.Never Basics.Never)) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"single","comment":" Same as [`preRender`](#preRender), but for an ApiRoute that has no dynamic segments. This is just a bit simpler because\nsince there are no dynamic segments, you don't need to provide a BackendTask with the list of dynamic segments to pre-render because there is only a single possible route.\n","type":"ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError String.String) (List.List String.String) -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"slash","comment":" ","type":"ApiRoute.ApiRouteBuilder a constructor -> ApiRoute.ApiRouteBuilder a constructor"},{"name":"succeed","comment":" ","type":"a -> ApiRoute.ApiRouteBuilder a (List.List String.String)"},{"name":"toJson","comment":" Turn the route into a pattern in JSON format. For internal uses.\n","type":"ApiRoute.ApiRoute response -> Json.Encode.Value"},{"name":"withGlobalHeadTags","comment":" Include head tags on every page's HTML.\n","type":"BackendTask.BackendTask FatalError.FatalError (List.List Head.Tag) -> ApiRoute.ApiRoute response -> ApiRoute.ApiRoute response"}],"binops":[]},{"name":"BackendTask","comment":" 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\nwhen 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.\n\nA `BackendTask` lets you pull in data from:\n\n - Local files ([`BackendTask.File`](BackendTask-File))\n - HTTP requests ([`BackendTask.Http`](BackendTask-Http))\n - Globs, i.e. listing out local files based on a pattern like `content/*.txt` ([`BackendTask.Glob`](BackendTask-Glob))\n - 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))\n - Hardcoded data (`BackendTask.succeed \"Hello!\"`)\n - Or any combination of the above, using `BackendTask.map2`, `BackendTask.andThen`, or other combining/continuing helpers from this module\n\n\n## BackendTask's vs. Effect's/Cmd's\n\nBackendTask's are always resolved before the page is rendered and sent to the browser. A BackendTask is never executed\nin the Browser. Instead, the resolved data from the BackendTask is passed down to the Browser - it has been resolved\nbefore any client-side JavaScript ever executes. In the case of a pre-rendered route, this is during the CLI build phase,\nand for server-rendered routes its BackendTask is resolved on the server.\n\nEffect'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\n`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\nbefore the page is hydrated in the Browser. This gives a deterministic mental model of what the first render will look like,\nand a nicely typed way to define the initial `Data` you have to render your initial view.\n\nBecause `elm-pages` hydrates into a full Elm single-page app, it does need the data in order to initialize the Elm app.\nSo why not just get the data the old-fashioned way, with `elm/http`, for example?\n\nA few reasons:\n\n1. 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!\n2. 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.\n3. 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.\n4. 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!\n\n\n## Mental Model\n\nYou 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.).\n\n\n## How do I actually use a BackendTask?\n\nThis 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.\nYou 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.\n\n`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.\nThere 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,\nthe 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\nto `update`.\n\nAny 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).\n\n\n## Basics\n\n@docs BackendTask\n\n@docs map, succeed, fail\n\n@docs fromResult\n\n\n## Chaining Requests\n\n@docs andThen, resolve, combine\n\n@docs andMap\n\n@docs map2, map3, map4, map5, map6, map7, map8, map9\n\n\n## FatalError Handling\n\n@docs allowFatal, mapError, onError, toResult\n\n","unions":[],"aliases":[{"name":"BackendTask","comment":" A BackendTask represents data that will be gathered at build time. Multiple `BackendTask`s can be combined together using the `mapN` functions,\nvery similar to how you can manipulate values with Json Decoders in Elm.\n","args":["error","value"],"type":"Pages.StaticHttpRequest.RawRequest error value"}],"values":[{"name":"allowFatal","comment":" Ignore any recoverable error data and propagate the `FatalError`. Similar to a `Cmd` in The Elm Architecture,\na `FatalError` will not do anything except if it is returned at the top-level of your application. Read more\nin the [`FatalError` docs](FatalError).\n","type":"BackendTask.BackendTask { error | fatal : FatalError.FatalError } data -> BackendTask.BackendTask FatalError.FatalError data"},{"name":"andMap","comment":" A helper for combining `BackendTask`s in pipelines.\n","type":"BackendTask.BackendTask error a -> BackendTask.BackendTask error (a -> b) -> BackendTask.BackendTask error b"},{"name":"andThen","comment":" Build off of the response from a previous `BackendTask` request to build a follow-up request. You can use the data\nfrom the previous response to build up the URL, headers, etc. that you send to the subsequent request.\n\n import BackendTask\n import Json.Decode as Decode exposing (Decoder)\n\n licenseData : BackendTask String\n licenseData =\n BackendTask.Http.get\n (Secrets.succeed \"https://api.github.com/repos/dillonkearns/elm-pages\")\n (Decode.at [ \"license\", \"url\" ] Decode.string)\n |> BackendTask.andThen\n (\\licenseUrl ->\n BackendTask.Http.get (Secrets.succeed licenseUrl) (Decode.field \"description\" Decode.string)\n )\n\n","type":"(a -> BackendTask.BackendTask error b) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b"},{"name":"combine","comment":" Turn a list of `BackendTask`s into a single one.\n\n import BackendTask\n import Json.Decode as Decode exposing (Decoder)\n\n type alias Pokemon =\n { name : String\n , sprite : String\n }\n\n pokemonDetailRequest : BackendTask (List Pokemon)\n pokemonDetailRequest =\n BackendTask.Http.getJson\n \"https://pokeapi.co/api/v2/pokemon/?limit=3\"\n (Decode.field \"results\"\n (Decode.list\n (Decode.map2 Tuple.pair\n (Decode.field \"name\" Decode.string)\n (Decode.field \"url\" Decode.string)\n |> Decode.map\n (\\( name, url ) ->\n BackendTask.Http.getJson url\n (Decode.at\n [ \"sprites\", \"front_default\" ]\n Decode.string\n |> Decode.map (Pokemon name)\n )\n )\n )\n )\n )\n |> BackendTask.andThen BackendTask.combine\n\n","type":"List.List (BackendTask.BackendTask error value) -> BackendTask.BackendTask error (List.List value)"},{"name":"fail","comment":" ","type":"error -> BackendTask.BackendTask error a"},{"name":"fromResult","comment":" Turn `Ok` into `BackendTask.succeed` and `Err` into `BackendTask.fail`.\n","type":"Result.Result error value -> BackendTask.BackendTask error value"},{"name":"map","comment":" Transform a request into an arbitrary value. The same underlying task will be performed,\nbut mapping allows you to change the resulting values by applying functions to the results.\n\n import BackendTask\n import BackendTask.Http\n import Json.Decode as Decode exposing (Decoder)\n\n starsMessage =\n BackendTask.Http.getJson\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"stargazers_count\" Decode.int)\n |> BackendTask.map\n (\\stars -> \"⭐️ \" ++ String.fromInt stars)\n\n","type":"(a -> b) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b"},{"name":"map2","comment":" Like map, but it takes in two `Request`s.\n\n view siteMetadata page =\n StaticHttp.map2\n (\\elmPagesStars elmMarkdownStars ->\n { view =\n \\model viewForPage ->\n { title = \"Repo Stargazers\"\n , body = starsView elmPagesStars elmMarkdownStars\n }\n , head = head elmPagesStars elmMarkdownStars\n }\n )\n (get\n (Secrets.succeed \"https://api.github.com/repos/dillonkearns/elm-pages\")\n (Decode.field \"stargazers_count\" Decode.int)\n )\n (get\n (Secrets.succeed \"https://api.github.com/repos/dillonkearns/elm-markdown\")\n (Decode.field \"stargazers_count\" Decode.int)\n )\n\n","type":"(a -> b -> c) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b -> BackendTask.BackendTask error c"},{"name":"map3","comment":" ","type":"(value1 -> value2 -> value3 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error valueCombined"},{"name":"map4","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error valueCombined"},{"name":"map5","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error valueCombined"},{"name":"map6","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error valueCombined"},{"name":"map7","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error valueCombined"},{"name":"map8","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error value8 -> BackendTask.BackendTask error valueCombined"},{"name":"map9","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> value9 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error value8 -> BackendTask.BackendTask error value9 -> BackendTask.BackendTask error valueCombined"},{"name":"mapError","comment":" ","type":"(error -> errorMapped) -> BackendTask.BackendTask error value -> BackendTask.BackendTask errorMapped value"},{"name":"onError","comment":" ","type":"(error -> BackendTask.BackendTask mappedError value) -> BackendTask.BackendTask error value -> BackendTask.BackendTask mappedError value"},{"name":"resolve","comment":" Helper to remove an inner layer of Request wrapping.\n","type":"BackendTask.BackendTask error (List.List (BackendTask.BackendTask error value)) -> BackendTask.BackendTask error (List.List value)"},{"name":"succeed","comment":" This is useful for prototyping with some hardcoded data, or for having a view that doesn't have any StaticHttp data.\n\n import BackendTask\n\n view :\n List ( PagePath, Metadata )\n ->\n { path : PagePath\n , frontmatter : Metadata\n }\n ->\n StaticHttp.Request\n { view : Model -> View -> { title : String, body : Html Msg }\n , head : List (Head.Tag Pages.PathKey)\n }\n view siteMetadata page =\n StaticHttp.succeed\n { view =\n \\model viewForPage ->\n mainView model viewForPage\n , head = head page.frontmatter\n }\n\n","type":"a -> BackendTask.BackendTask error a"},{"name":"toResult","comment":" ","type":"BackendTask.BackendTask error data -> BackendTask.BackendTask noError (Result.Result error data)"}],"binops":[]},{"name":"BackendTask.Custom","comment":" In a vanilla Elm application, ports let you either send or receive JSON data between your Elm application and the JavaScript context in the user's browser at runtime.\n\nWith `BackendTask.Custom`, you send and receive JSON to JavaScript running in NodeJS. As with any `BackendTask`, Custom BackendTask's are either run at build-time (for pre-rendered routes) or at request-time (for server-rendered routes). See [`BackendTask`](BackendTask) for more about the\nlifecycle of `BackendTask`'s.\n\nThis means that you can call shell scripts, run NPM packages that are installed, or anything else you could do with NodeJS to perform custom side-effects, get some data, or both.\n\nA `BackendTask.Custom` will call an async JavaScript function with the given name from the definition in a file called `custom-backend-task.js` in your project's root directory. The function receives the input JSON value, and the Decoder is used to decode the return value of the async function.\n\n@docs run\n\nHere is the Elm code and corresponding JavaScript definition for getting an environment variable (or an `FatalError BackendTask.Custom.Error` if it isn't found). In this example,\nwe're using `BackendTask.allowFatal` to let the framework treat that as an unexpected exception, but we could also handle the possible failures of the `FatalError` (see [`FatalError`](FatalError)).\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Custom\n import Json.Encode\n import OptimizedDecoder as Decode\n\n data : BackendTask FatalError String\n data =\n BackendTask.Custom.run \"environmentVariable\"\n (Json.Encode.string \"EDITOR\")\n Decode.string\n |> BackendTask.allowFatal\n\n -- will resolve to \"VIM\" if you run `EDITOR=vim elm-pages dev`\n\n```javascript\n// custom-backend-task.js\n\nmodule.exports =\n /**\n * @param { unknown } fromElm\n * @returns { Promise }\n */\n {\n environmentVariable: async function (name) {\n const result = process.env[name];\n if (result) {\n return result;\n } else {\n throw `No environment variable called ${name}\n\nAvailable:\n\n${Object.keys(process.env).join(\"\\n\")}\n`;\n }\n },\n }\n```\n\n\n## Performance\n\nAs with any JavaScript or NodeJS code, avoid doing blocking IO operations. For example, avoid using `fs.readFileSync`, because blocking IO can slow down your elm-pages builds and dev server. `elm-pages` performs all `BackendTask`'s in parallel whenever possible.\nSo if you do `BackendTask.map2 Tuple.pair myHttpBackendTask myCustomBackendTask`, it will resolve those two in parallel. NodeJS performs best when you take advantage of its ability to do non-blocking I/O (file reads, HTTP requests, etc.). If you use `BackendTask.andThen`,\nit will need to resolve them in sequence rather than in parallel, but it's still best to avoid blocking IO operations in your Custom BackendTask definitions.\n\n\n## Error Handling\n\nThere are a few different things that can go wrong when running a custom-backend-task. These possible errors are captured in the `BackendTask.Custom.Error` type.\n\n@docs Error\n\nAny time you throw a JavaScript exception from a BackendTask.Custom definition, it will give you a `CustomBackendTaskException`. It's usually easier to add a `try`/`catch` in your JavaScript code in `custom-backend-task.js`\nto handle possible errors, but you can throw a JSON value and handle it in Elm in the `CustomBackendTaskException` call error.\n\n\n## Decoding JS Date Objects\n\nThese decoders are for use with decoding JS values of type `Date`. If you have control over the format, it may be better to\nbe more explicit with a [Rata Die](https://en.wikipedia.org/wiki/Rata_Die) number value or an ISO-8601 formatted date string instead.\nBut often JavaScript libraries and core APIs will give you JS Date objects, so this can be useful for working with those.\n\n@docs timeDecoder, dateDecoder\n\n","unions":[{"name":"Error","comment":" ","args":[],"cases":[["Error",[]],["ErrorInCustomBackendTaskFile",[]],["MissingCustomBackendTaskFile",[]],["CustomBackendTaskNotDefined",["{ name : String.String }"]],["CustomBackendTaskException",["Json.Decode.Value"]],["NonJsonException",["String.String"]],["ExportIsNotFunction",[]],["DecodeError",["Json.Decode.Error"]]]}],"aliases":[],"values":[{"name":"dateDecoder","comment":" The same as `timeDecoder`, but it converts the decoded `Time.Posix` value into a `Date` with `Date.fromPosix Time.utc`.\n\nJavaScript `Date` objects don't distinguish between values with only a date vs. values with both a date and a time. So be sure\nto use this decoder when you know the semantics represent a date with no associated time (or you're sure you don't care about the time).\n\n","type":"Json.Decode.Decoder Date.Date"},{"name":"run","comment":" ","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder b -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Custom.Error } b"},{"name":"timeDecoder","comment":" ","type":"Json.Decode.Decoder Time.Posix"}],"binops":[]},{"name":"BackendTask.Env","comment":" Because BackendTask's in `elm-pages` never run in the browser (see [the BackendTask docs](BackendTask)), you can access environment variables securely. As long as the environment variable isn't sent\ndown into the final `Data` value, it won't end up in the client!\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Env\n import FatalError exposing (FatalError)\n\n type alias EnvVariables =\n { sendGridKey : String\n , siteUrl : String\n }\n\n sendEmail : Email -> BackendTask FatalError ()\n sendEmail email =\n BackendTask.map2 EnvVariables\n (BackendTask.Env.expect \"SEND_GRID_KEY\" |> BackendTask.allowFatal)\n (BackendTask.Env.get \"BASE_URL\"\n |> BackendTask.map (Maybe.withDefault \"http://localhost:1234\")\n )\n |> BackendTask.andThen (sendEmailBackendTask email)\n\n sendEmailBackendTask : Email -> EnvVariables -> BackendTask FatalError ()\n sendEmailBackendTask email envVariables =\n Debug.todo \"Not defined here\"\n\n@docs get, expect\n\n\n## Errors\n\n@docs Error\n\n","unions":[{"name":"Error","comment":" ","args":[],"cases":[["MissingEnvVariable",["String.String"]]]}],"aliases":[],"values":[{"name":"expect","comment":" Get an environment variable, or a BackendTask FatalError if there is no environment variable matching that name.\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Env.Error } String.String"},{"name":"get","comment":" Get an environment variable, or Nothing if there is no environment variable matching that name. This `BackendTask`\nwill never fail, but instead will return `Nothing` if the environment variable is missing.\n","type":"String.String -> BackendTask.BackendTask error (Maybe.Maybe String.String)"}],"binops":[]},{"name":"BackendTask.File","comment":" This module lets you read files from the local filesystem as a [`BackendTask`](BackendTask#BackendTask).\nFile paths are relative to the root of your `elm-pages` project (next to the `elm.json` file and `src/` directory).\n\n\n## Files With Frontmatter\n\nFrontmatter is a convention used to keep metadata at the top of a file between `---`'s.\n\nFor example, you might have a file called `blog/hello-world.md` with this content:\n\n```markdown\n---\ntitle: Hello, World!\ntags: elm\n---\nHey there! This is my first post :)\n```\n\nThe frontmatter is in the [YAML format](https://en.wikipedia.org/wiki/YAML) here. You can also use JSON in your elm-pages frontmatter.\n\n```markdown\n---\n{\"title\": \"Hello, World!\", \"tags\": \"elm\"}\n---\nHey there! This is my first post :)\n```\n\nWhether it's YAML or JSON, you use an `Decode` to decode your frontmatter, so it feels just like using\nplain old JSON in Elm.\n\n@docs bodyWithFrontmatter, bodyWithoutFrontmatter, onlyFrontmatter\n\n\n## Reading Files Without Frontmatter\n\n@docs jsonFile, rawFile\n\n\n## FatalErrors\n\n@docs FileReadError\n\n","unions":[{"name":"FileReadError","comment":" ","args":["decoding"],"cases":[["FileDoesntExist",[]],["FileReadError",["String.String"]],["DecodingError",["decoding"]]]}],"aliases":[],"values":[{"name":"bodyWithFrontmatter","comment":"\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import Decode exposing (Decoder)\n\n blogPost : BackendTask BlogPostMetadata\n blogPost =\n File.bodyWithFrontmatter blogPostDecoder\n \"blog/hello-world.md\"\n\n type alias BlogPostMetadata =\n { body : String\n , title : String\n , tags : List String\n }\n\n blogPostDecoder : String -> Decoder BlogPostMetadata\n blogPostDecoder body =\n Decode.map2 (BlogPostMetadata body)\n (Decode.field \"title\" Decode.string)\n (Decode.field \"tags\" tagsDecoder)\n\n tagsDecoder : Decoder (List String)\n tagsDecoder =\n Decode.map (String.split \" \")\n Decode.string\n\nThis will give us a BackendTask that results in the following value:\n\n value =\n { body = \"Hey there! This is my first post :)\"\n , title = \"Hello, World!\"\n , tags = [ \"elm\" ]\n }\n\nIt's common to parse the body with a markdown parser or other format.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import Decode exposing (Decoder)\n import Html exposing (Html)\n\n example :\n BackendTask\n { title : String\n , body : List (Html msg)\n }\n example =\n File.bodyWithFrontmatter\n (\\markdownString ->\n Decode.map2\n (\\title renderedMarkdown ->\n { title = title\n , body = renderedMarkdown\n }\n )\n (Decode.field \"title\" Decode.string)\n (markdownString\n |> markdownToView\n |> Decode.fromResult\n )\n )\n \"foo.md\"\n\n markdownToView :\n String\n -> Result String (List (Html msg))\n markdownToView markdownString =\n markdownString\n |> Markdown.Parser.parse\n |> Result.mapError (\\_ -> \"Markdown error.\")\n |> Result.andThen\n (\\blocks ->\n Markdown.Renderer.render\n Markdown.Renderer.defaultHtmlRenderer\n blocks\n )\n\n","type":"(String.String -> Json.Decode.Decoder frontmatter) -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } frontmatter"},{"name":"bodyWithoutFrontmatter","comment":" Same as `bodyWithFrontmatter` except it doesn't include the frontmatter.\n\nFor example, if you have a file called `blog/hello-world.md` with\n\n```markdown\n---\ntitle: Hello, World!\ntags: elm\n---\nHey there! This is my first post :)\n```\n\n import BackendTask exposing (BackendTask)\n\n data : BackendTask String\n data =\n bodyWithoutFrontmatter \"blog/hello-world.md\"\n\nThen data will yield the value `\"Hey there! This is my first post :)\"`.\n\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } String.String"},{"name":"jsonFile","comment":" Read a file as JSON.\n\nThe Decode will strip off any unused JSON data.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n\n sourceDirectories : BackendTask (List String)\n sourceDirectories =\n File.jsonFile\n (Decode.field\n \"source-directories\"\n (Decode.list Decode.string)\n )\n \"elm.json\"\n\n","type":"Json.Decode.Decoder a -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } a"},{"name":"onlyFrontmatter","comment":" Same as `bodyWithFrontmatter` except it doesn't include the body.\n\nThis is often useful when you're aggregating data, for example getting a listing of blog posts and need to extract\njust the metadata.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import Decode exposing (Decoder)\n\n blogPost : BackendTask BlogPostMetadata\n blogPost =\n File.onlyFrontmatter\n blogPostDecoder\n \"blog/hello-world.md\"\n\n type alias BlogPostMetadata =\n { title : String\n , tags : List String\n }\n\n blogPostDecoder : Decoder BlogPostMetadata\n blogPostDecoder =\n Decode.map2 BlogPostMetadata\n (Decode.field \"title\" Decode.string)\n (Decode.field \"tags\" (Decode.list Decode.string))\n\nIf you wanted to use this to get this metadata for all blog posts in a folder, you could use\nthe [`BackendTask`](BackendTask) API along with [`BackendTask.Glob`](BackendTask-Glob).\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import Decode exposing (Decoder)\n\n blogPostFiles : BackendTask (List String)\n blogPostFiles =\n Glob.succeed identity\n |> Glob.captureFilePath\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.match Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\n allMetadata : BackendTask (List BlogPostMetadata)\n allMetadata =\n blogPostFiles\n |> BackendTask.map\n (List.map\n (File.onlyFrontmatter\n blogPostDecoder\n )\n )\n |> BackendTask.resolve\n\n","type":"Json.Decode.Decoder frontmatter -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } frontmatter"},{"name":"rawFile","comment":" Get the raw file content. Unlike the frontmatter helpers in this module, this function will not strip off frontmatter if there is any.\n\nThis is the function you want if you are reading in a file directly. For example, if you read in a CSV file, a raw text file, or any other file that doesn't\nhave frontmatter.\n\nThere's a special function for reading in JSON files, [`jsonFile`](#jsonFile). If you're reading a JSON file then be sure to\nuse `jsonFile` to get the benefits of the `Decode` here.\n\nYou could read a file called `hello.txt` in your root project directory like this:\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n\n elmJsonFile : BackendTask String\n elmJsonFile =\n File.rawFile \"hello.txt\"\n\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } String.String"}],"binops":[]},{"name":"BackendTask.Glob","comment":"\n\n@docs Glob\n\nThis module helps you get a List of matching file paths from your local file system as a [`BackendTask`](BackendTask#BackendTask). See the [`BackendTask`](BackendTask) module documentation\nfor ways you can combine and map `BackendTask`s.\n\nA common example would be to find all the markdown files of your blog posts. If you have all your blog posts in `content/blog/*.md`\n, then you could use that glob pattern in most shells to refer to each of those files.\n\nWith the `BackendTask.Glob` API, you could get all of those files like so:\n\n import BackendTask exposing (BackendTask)\n\n blogPostsGlob : BackendTask (List String)\n blogPostsGlob =\n Glob.succeed (\\slug -> slug)\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nLet's say you have these files locally:\n\n```shell\n- elm.json\n- src/\n- content/\n - blog/\n - first-post.md\n - second-post.md\n```\n\nWe would end up with a `BackendTask` like this:\n\n BackendTask.succeed [ \"first-post\", \"second-post\" ]\n\nOf course, if you add or remove matching files, the BackendTask will get those new files (unlike `BackendTask.succeed`). That's why we have Glob!\n\nYou can even see the `elm-pages dev` server will automatically flow through any added/removed matching files with its hot module reloading.\n\nBut why did we get `\"first-post\"` instead of a full file path, like `\"content/blog/first-post.md\"`? That's the difference between\n`capture` and `match`.\n\n\n## Capture and Match\n\nThere are two functions for building up a Glob pattern: `capture` and `match`.\n\n`capture` and `match` both build up a `Glob` pattern that will match 0 or more files on your local file system.\nThere will be one argument for every `capture` in your pipeline, whereas `match` does not apply any arguments.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n blogPostsGlob : BackendTask (List String)\n blogPostsGlob =\n Glob.succeed (\\slug -> slug)\n -- no argument from this, but we will only\n -- match files that begin with `content/blog/`\n |> Glob.match (Glob.literal \"content/blog/\")\n -- we get the value of the `wildcard`\n -- as the slug argument\n |> Glob.capture Glob.wildcard\n -- no argument from this, but we will only\n -- match files that end with `.md`\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nSo to understand _which_ files will match, you can ignore whether you are using `capture` or `match` and just read\nthe patterns you're using in order to understand what will match. To understand what Elm data type you will get\n_for each matching file_, you need to see which parts are being captured and how each of those captured values are being\nused in the function you use in `Glob.succeed`.\n\n@docs capture, match\n\n`capture` is a lot like building up a JSON decoder with a pipeline.\n\nLet's try our blogPostsGlob from before, but change every `match` to `capture`.\n\n import BackendTask exposing (BackendTask)\n\n blogPostsGlob :\n BackendTask\n (List\n { filePath : String\n , slug : String\n }\n )\n blogPostsGlob =\n Glob.succeed\n (\\capture1 capture2 capture3 ->\n { filePath = capture1 ++ capture2 ++ capture3\n , slug = capture2\n }\n )\n |> Glob.capture (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.capture (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nNotice that we now need 3 arguments at the start of our pipeline instead of 1. That's because\nwe apply 1 more argument every time we do a `Glob.capture`, much like `Json.Decode.Pipeline.required`, or other pipeline APIs.\n\nNow we actually have the full file path of our files. But having that slug (like `first-post`) is also very helpful sometimes, so\nwe kept that in our record as well. So we'll now have the equivalent of this `BackendTask` with the current `.md` files in our `blog` folder:\n\n BackendTask.succeed\n [ { filePath = \"content/blog/first-post.md\"\n , slug = \"first-post\"\n }\n , { filePath = \"content/blog/second-post.md\"\n , slug = \"second-post\"\n }\n ]\n\nHaving the full file path lets us read in files. But concatenating it manually is tedious\nand error prone. That's what the [`captureFilePath`](#captureFilePath) helper is for.\n\n\n## Reading matching files\n\n@docs captureFilePath\n\nIn many cases you will want to take the matching files from a `Glob` and then read the body or frontmatter from matching files.\n\n\n## Reading Metadata for each Glob Match\n\nFor example, if we had files like this:\n\n```markdown\n---\ntitle: My First Post\n---\nThis is my first post!\n```\n\nThen we could read that title for our blog post list page using our `blogPosts` `BackendTask` that we defined above.\n\n import BackendTask.File\n import Json.Decode as Decode exposing (Decoder)\n\n titles : BackendTask (List BlogPost)\n titles =\n blogPosts\n |> BackendTask.map\n (List.map\n (\\blogPost ->\n BackendTask.File.request\n blogPost.filePath\n (BackendTask.File.frontmatter blogFrontmatterDecoder)\n )\n )\n |> BackendTask.resolve\n\n type alias BlogPost =\n { title : String }\n\n blogFrontmatterDecoder : Decoder BlogPost\n blogFrontmatterDecoder =\n Decode.map BlogPost\n (Decode.field \"title\" Decode.string)\n\nThat will give us\n\n BackendTask.succeed\n [ { title = \"My First Post\" }\n , { title = \"My Second Post\" }\n ]\n\n\n## Capturing Patterns\n\n@docs wildcard, recursiveWildcard\n\n\n## Capturing Specific Characters\n\n@docs int, digits\n\n\n## Matching a Specific Number of Files\n\n@docs expectUniqueMatch, expectUniqueMatchFromList\n\n\n## Glob Patterns\n\n@docs literal\n\n@docs map, succeed\n\n@docs oneOf\n\n@docs zeroOrMore, atLeastOne\n\n\n## Getting Glob Data from a BackendTask\n\n@docs toBackendTask\n\n\n### With Custom Options\n\n@docs toBackendTaskWithOptions\n\n@docs defaultOptions, Options, Include\n\n","unions":[{"name":"Include","comment":" \n\n\n\n","args":[],"cases":[["OnlyFiles",[]],["OnlyFolders",[]],["FilesAndFolders",[]]]}],"aliases":[{"name":"Glob","comment":" A pattern to match local files and capture parts of the path into a nice Elm data type.\n","args":["a"],"type":"BackendTask.Internal.Glob.Glob a"},{"name":"Options","comment":" Custom options you can pass in to run the glob with [`toBackendTaskWithOptions`](#toBackendTaskWithOptions).\n\n { includeDotFiles = Bool -- https://github.com/mrmlnc/fast-glob#dot\n , include = Include -- return results that are `OnlyFiles`, `OnlyFolders`, or both `FilesAndFolders` (default is `OnlyFiles`)\n , followSymbolicLinks = Bool -- https://github.com/mrmlnc/fast-glob#followsymboliclinks\n , caseSensitiveMatch = Bool -- https://github.com/mrmlnc/fast-glob#casesensitivematch\n , gitignore = Bool -- https://www.npmjs.com/package/globby#gitignore\n , maxDepth = Maybe Int -- https://github.com/mrmlnc/fast-glob#deep\n }\n\n","args":[],"type":"{ includeDotFiles : Basics.Bool, include : BackendTask.Glob.Include, followSymbolicLinks : Basics.Bool, caseSensitiveMatch : Basics.Bool, gitignore : Basics.Bool, maxDepth : Maybe.Maybe Basics.Int }"}],"values":[{"name":"atLeastOne","comment":" ","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> BackendTask.Glob.Glob ( a, List.List a )"},{"name":"capture","comment":" Adds on to the glob pattern, and captures it in the resulting Elm match value. That means this both changes which\nfiles will match, and gives you the sub-match as Elm data for each matching file.\n\nExactly the same as `match` except it also captures the matched sub-pattern.\n\n type alias ArchivesArticle =\n { year : String\n , month : String\n , day : String\n , slug : String\n }\n\n archives : BackendTask ArchivesArticle\n archives =\n Glob.succeed ArchivesArticle\n |> Glob.match (Glob.literal \"archive/\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nThe file `archive/1977/06/10/apple-2-released.md` will give us this match:\n\n matches : List ArchivesArticle\n matches =\n BackendTask.succeed\n [ { year = 1977\n , month = 6\n , day = 10\n , slug = \"apple-2-released\"\n }\n ]\n\nWhen possible, it's best to grab data and turn it into structured Elm data when you have it. That way,\nyou don't end up with duplicate validation logic and data normalization, and your code will be more robust.\n\nIf you only care about getting the full matched file paths, you can use `match`. `capture` is very useful because\nyou can pick apart structured data as you build up your glob pattern. This follows the principle of\n[Parse, Don't Validate](https://elm-radio.com/episode/parse-dont-validate/).\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.Glob.Glob (a -> value) -> BackendTask.Glob.Glob value"},{"name":"captureFilePath","comment":"\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n blogPosts :\n BackendTask\n (List\n { filePath : String\n , slug : String\n }\n )\n blogPosts =\n Glob.succeed\n (\\filePath slug ->\n { filePath = filePath\n , slug = slug\n }\n )\n |> Glob.captureFilePath\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nThis function does not change which files will or will not match. It just gives you the full matching\nfile path in your `Glob` pipeline.\n\nWhenever possible, it's a good idea to use function to make sure you have an accurate file path when you need to read a file.\n\n","type":"BackendTask.Glob.Glob (String.String -> value) -> BackendTask.Glob.Glob value"},{"name":"defaultOptions","comment":" The default options used in [`toBackendTask`](#toBackendTask). To use a custom set of options, use [`toBackendTaskWithOptions`](#toBackendTaskWithOptions).\n","type":"BackendTask.Glob.Options"},{"name":"digits","comment":" This is similar to [`wildcard`](#wildcard), but it will only match 1 or more digits (i.e. `[0-9]+`).\n\nSee [`int`](#int) for a convenience function to get an Int value instead of a String of digits.\n\n","type":"BackendTask.Glob.Glob String.String"},{"name":"expectUniqueMatch","comment":" Sometimes you want to make sure there is a unique file matching a particular pattern.\nThis is a simple helper that will give you a `BackendTask` error if there isn't exactly 1 matching file.\nIf there is exactly 1, then you successfully get back that single match.\n\nFor example, maybe you can have\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n findBlogBySlug : String -> BackendTask String\n findBlogBySlug slug =\n Glob.succeed identity\n |> Glob.captureFilePath\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.capture (Glob.literal slug)\n |> Glob.match\n (Glob.oneOf\n ( ( \"\", () )\n , [ ( \"/index\", () ) ]\n )\n )\n |> Glob.match (Glob.literal \".md\")\n |> Glob.expectUniqueMatch\n\nIf we used `findBlogBySlug \"first-post\"` with these files:\n\n```markdown\n- blog/\n - first-post/\n - index.md\n```\n\nThis would give us:\n\n results : BackendTask String\n results =\n BackendTask.succeed \"blog/first-post/index.md\"\n\nIf we used `findBlogBySlug \"first-post\"` with these files:\n\n```markdown\n- blog/\n - first-post.md\n - first-post/\n - index.md\n```\n\nThen we will get a `BackendTask` error saying `More than one file matched.` Keep in mind that `BackendTask` failures\nin build-time routes will cause a build failure, giving you the opportunity to fix the problem before users see the issue,\nso it's ideal to make this kind of assertion rather than having fallback behavior that could silently cover up\nissues (like if we had instead ignored the case where there are two or more matching blog post files).\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : String.String } a"},{"name":"expectUniqueMatchFromList","comment":" ","type":"List.List (BackendTask.Glob.Glob a) -> BackendTask.BackendTask String.String a"},{"name":"int","comment":" Same as [`digits`](#digits), but it safely turns the digits String into an `Int`.\n\nLeading 0's are ignored.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n slides : BackendTask (List Int)\n slides =\n Glob.succeed identity\n |> Glob.match (Glob.literal \"slide-\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nWith files\n\n```shell\n- slide-no-match.md\n- slide-.md\n- slide-1.md\n- slide-01.md\n- slide-2.md\n- slide-03.md\n- slide-4.md\n- slide-05.md\n- slide-06.md\n- slide-007.md\n- slide-08.md\n- slide-09.md\n- slide-10.md\n- slide-11.md\n```\n\nYields\n\n matches : BackendTask (List Int)\n matches =\n BackendTask.succeed\n [ 1\n , 1\n , 2\n , 3\n , 4\n , 5\n , 6\n , 7\n , 8\n , 9\n , 10\n , 11\n ]\n\nNote that neither `slide-no-match.md` nor `slide-.md` match.\nAnd both `slide-1.md` and `slide-01.md` match and turn into `1`.\n\n","type":"BackendTask.Glob.Glob Basics.Int"},{"name":"literal","comment":" Match a literal part of a path. Can include `/`s.\n\nSome common uses include\n\n - The leading part of a pattern, to say \"starts with `content/blog/`\"\n - The ending part of a pattern, to say \"ends with `.md`\"\n - In-between wildcards, to say \"these dynamic parts are separated by `/`\"\n\n```elm\nimport BackendTask exposing (BackendTask)\nimport BackendTask.Glob as Glob\n\nblogPosts =\n Glob.succeed\n (\\section slug ->\n { section = section, slug = slug }\n )\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n```\n\n","type":"String.String -> BackendTask.Glob.Glob String.String"},{"name":"map","comment":" A `Glob` can be mapped. This can be useful for transforming a sub-match in-place.\n\nFor example, if you wanted to take the slugs for a blog post and make sure they are normalized to be all lowercase, you\ncould use\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n blogPostsGlob : BackendTask (List String)\n blogPostsGlob =\n Glob.succeed (\\slug -> slug)\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture (Glob.wildcard |> Glob.map String.toLower)\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nIf you want to validate file formats, you can combine that with some `BackendTask` helpers to turn a `Glob (Result String value)` into\na `BackendTask (List value)`.\n\nFor example, you could take a date and parse it.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n example : BackendTask (List ( String, String ))\n example =\n Glob.succeed\n (\\dateResult slug ->\n dateResult\n |> Result.map (\\okDate -> ( okDate, slug ))\n )\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.capture (Glob.recursiveWildcard |> Glob.map expectDateFormat)\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n |> BackendTask.map (List.map BackendTask.fromResult)\n |> BackendTask.resolve\n\n expectDateFormat : List String -> Result String String\n expectDateFormat dateParts =\n case dateParts of\n [ year, month, date ] ->\n Ok (String.join \"-\" [ year, month, date ])\n\n _ ->\n Err \"Unexpected date format, expected yyyy/mm/dd folder structure.\"\n\n","type":"(a -> b) -> BackendTask.Glob.Glob a -> BackendTask.Glob.Glob b"},{"name":"match","comment":" Adds on to the glob pattern, but does not capture it in the resulting Elm match value. That means this changes which\nfiles will match, but does not change the Elm data type you get for each matching file.\n\nExactly the same as `capture` except it doesn't capture the matched sub-pattern.\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.Glob.Glob value -> BackendTask.Glob.Glob value"},{"name":"oneOf","comment":"\n\n import BackendTask.Glob as Glob\n\n type Extension\n = Json\n | Yml\n\n type alias DataFile =\n { name : String\n , extension : String\n }\n\n dataFiles : BackendTask (List DataFile)\n dataFiles =\n Glob.succeed DataFile\n |> Glob.match (Glob.literal \"my-data/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".\")\n |> Glob.capture\n (Glob.oneOf\n ( ( \"yml\", Yml )\n , [ ( \"json\", Json )\n ]\n )\n )\n\nIf we have the following files\n\n```shell\n- my-data/\n - authors.yml\n - events.json\n```\n\nThat gives us\n\n results : BackendTask (List DataFile)\n results =\n BackendTask.succeed\n [ { name = \"authors\"\n , extension = Yml\n }\n , { name = \"events\"\n , extension = Json\n }\n ]\n\nYou could also match an optional file path segment using `oneOf`.\n\n rootFilesMd : BackendTask (List String)\n rootFilesMd =\n Glob.succeed (\\slug -> slug)\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match\n (Glob.oneOf\n ( ( \"\", () )\n , [ ( \"/index\", () ) ]\n )\n )\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nWith these files:\n\n```markdown\n- blog/\n - first-post.md\n - second-post/\n - index.md\n```\n\nThis would give us:\n\n results : BackendTask (List String)\n results =\n BackendTask.succeed\n [ \"first-post\"\n , \"second-post\"\n ]\n\n","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> BackendTask.Glob.Glob a"},{"name":"recursiveWildcard","comment":" Matches any number of characters, including `/`, as long as it's the only thing in a path part.\n\nIn contrast, `wildcard` will never match `/`, so it only matches within a single path part.\n\nThis is the elm-pages equivalent of `**/*.txt` in standard shell syntax:\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n example : BackendTask (List ( List String, String ))\n example =\n Glob.succeed Tuple.pair\n |> Glob.match (Glob.literal \"articles/\")\n |> Glob.capture Glob.recursiveWildcard\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".txt\")\n |> Glob.toBackendTask\n\nWith these files:\n\n```shell\n- articles/\n - google-io-2021-recap.txt\n - archive/\n - 1977/\n - 06/\n - 10/\n - apple-2-announced.txt\n```\n\nWe would get the following matches:\n\n matches : BackendTask (List ( List String, String ))\n matches =\n BackendTask.succeed\n [ ( [ \"archive\", \"1977\", \"06\", \"10\" ], \"apple-2-announced\" )\n , ( [], \"google-io-2021-recap\" )\n ]\n\nNote that the recursive wildcard conveniently gives us a `List String`, where\neach String is a path part with no slashes (like `archive`).\n\nAnd also note that it matches 0 path parts into an empty list.\n\nIf we didn't include the `wildcard` after the `recursiveWildcard`, then we would only get\na single level of matches because it is followed by a file extension.\n\n example : BackendTask (List String)\n example =\n Glob.succeed identity\n |> Glob.match (Glob.literal \"articles/\")\n |> Glob.capture Glob.recursiveWildcard\n |> Glob.match (Glob.literal \".txt\")\n\n matches : BackendTask (List String)\n matches =\n BackendTask.succeed\n [ \"google-io-2021-recap\"\n ]\n\nThis is usually not what is intended. Using `recursiveWildcard` is usually followed by a `wildcard` for this reason.\n\n","type":"BackendTask.Glob.Glob (List.List String.String)"},{"name":"succeed","comment":" `succeed` is how you start a pipeline for a `Glob`. You will need one argument for each `capture` in your `Glob`.\n","type":"constructor -> BackendTask.Glob.Glob constructor"},{"name":"toBackendTask","comment":" In order to get match data from your glob, turn it into a `BackendTask` with this function.\n","type":"BackendTask.Glob.Glob a -> BackendTask.BackendTask error (List.List a)"},{"name":"toBackendTaskWithOptions","comment":" Same as toBackendTask, but lets you set custom glob options. For example, to list folders instead of files,\n\n import BackendTask.Glob as Glob exposing (OnlyFolders, defaultOptions)\n\n matchingFiles : Glob a -> BackendTask (List a)\n matchingFiles glob =\n glob\n |> Glob.toBackendTaskWithOptions { defaultOptions | include = OnlyFolders }\n\n","type":"BackendTask.Glob.Options -> BackendTask.Glob.Glob a -> BackendTask.BackendTask error (List.List a)"},{"name":"wildcard","comment":" Matches anything except for a `/` in a file path. You may be familiar with this syntax from shells like bash\nwhere you can run commands like `rm client/*.js` to remove all `.js` files in the `client` directory.\n\nJust like a `*` glob pattern in bash, this `Glob.wildcard` function will only match within a path part. If you need to\nmatch 0 or more path parts like, see `recursiveWildcard`.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n type alias BlogPost =\n { year : String\n , month : String\n , day : String\n , slug : String\n }\n\n example : BackendTask (List BlogPost)\n example =\n Glob.succeed BlogPost\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.match Glob.wildcard\n |> Glob.match (Glob.literal \"-\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \"-\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\n```shell\n\n- blog/\n - 2021-05-27/\n - first-post.md\n```\n\nThat will match to:\n\n results : BackendTask (List BlogPost)\n results =\n BackendTask.succeed\n [ { year = \"2021\"\n , month = \"05\"\n , day = \"27\"\n , slug = \"first-post\"\n }\n ]\n\nNote that we can \"destructure\" the date part of this file path in the format `yyyy-mm-dd`. The `wildcard` matches\nwill match _within_ a path part (think between the slashes of a file path). `recursiveWildcard` can match across path parts.\n\n","type":"BackendTask.Glob.Glob String.String"},{"name":"zeroOrMore","comment":" ","type":"List.List String.String -> BackendTask.Glob.Glob (Maybe.Maybe String.String)"}],"binops":[]},{"name":"BackendTask.Http","comment":" `BackendTask.Http` requests are an alternative to doing Elm HTTP requests the traditional way using the `elm/http` package.\n\nThe key differences are:\n\n - `BackendTask.Http.Request`s are performed once at build time (`Http.Request`s are performed at runtime, at whenever point you perform them)\n - `BackendTask.Http.Request`s have a built-in `BackendTask.andThen` that allows you to perform follow-up requests without using tasks\n\n\n## Scenarios where BackendTask.Http is a good fit\n\nIf you need data that is refreshed often you may want to do a traditional HTTP request with the `elm/http` package.\nThe kinds of situations that are served well by static HTTP are with data that updates moderately frequently or infrequently (or never).\nA common pattern is to trigger a new build when data changes. Many JAMstack services\nallow you to send a WebHook to your host (for example, Netlify is a good static file host that supports triggering builds with webhooks). So\nyou may want to have your site rebuild everytime your calendar feed has an event added, or whenever a page or article is added\nor updated on a CMS service like Contentful.\n\nIn scenarios like this, you can serve data that is just as up-to-date as it would be using `elm/http`, but you get the performance\ngains of using `BackendTask.Http.Request`s as well as the simplicity and robustness that comes with it. Read more about these benefits\nin [this article introducing BackendTask.Http requests and some concepts around it](https://elm-pages.com/blog/static-http).\n\n\n## Scenarios where BackendTask.Http is not a good fit\n\n - Data that is specific to the logged-in user\n - Data that needs to be the very latest and changes often (for example, sports scores)\n\n\n## Making a Request\n\n@docs get, getJson\n\n@docs post\n\n\n## Decoding Request Body\n\n@docs Expect, expectString, expectJson, expectBytes, expectWhatever\n\n\n## Error Handling\n\n@docs Error\n\n\n## General Requests\n\n@docs request\n\n\n## Building a BackendTask.Http Request Body\n\nThe way you build a body is analogous to the `elm/http` package. Currently, only `emptyBody` and\n`stringBody` are supported. If you have a use case that calls for a different body type, please open a Github issue\nand describe your use case!\n\n@docs Body, emptyBody, stringBody, jsonBody, bytesBody\n\n\n## Caching Options\n\n`elm-pages` performs GET requests using a local HTTP cache by default. These requests are not performed using Elm's `elm/http`,\nbut rather are performed in NodeJS. Under the hood it uses [the NPM package `make-fetch-happen`](https://github.com/npm/make-fetch-happen).\nOnly GET requests made with `get`, `getJson`, or `getWithOptions` use local caching. Requests made with [`BackendTask.Http.request`](#request)\nare not cached, even if the method is set to `GET`.\n\nIn dev mode, assets are cached more aggressively by default, whereas for a production build assets use a default to revalidate each cached response's freshness before using it (the `ForceRevalidate` [`CacheStrategy`](#CacheStrategy)).\n\nThe default caching behavior for GET requests is to use a local cache in `.elm-pages/http-cache`. This uses the same caching behavior\nthat browsers use to avoid re-downloading content when it hasn't changed. Servers can set HTTP response headers to explicitly control\nthis caching behavior.\n\n - [`cache-control` HTTP response headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) let you set a length of time before considering an asset stale. This could mean that the server considers it acceptable for an asset to be somewhat outdated, or this could mean that the asset is guaranteed to be up-to-date until it is stale - those semantics are up to the server.\n - `Last-Modified` and `ETag` HTTP response headers can be returned by the server allow [Conditional Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests). Conditional Requests let us send back the `Last-Modified` timestamp or `etag` hash for assets that are in our local cache to the server to check if the asset is fresh, and skip re-downloading it if it is unchanged (or download a fresh one otherwise).\n\nIt's important to note that depending on how the server sets these HTTP response headers, we may have outdated data - either because the server explicitly allows assets to become outdated with their cache-control headers, OR because cache-control headers are not set. When these headers aren't explicitly set, [clients are allowed to cache assets for 10% of the amount of time since it was last modified](https://httpwg.org/specs/rfc7234.html#heuristic.freshness).\nFor production builds, the default caching will ignore both the implicit and explicit information about an asset's freshness and _always_ revalidate it before using a locally cached response.\n\n@docs getWithOptions\n\n@docs CacheStrategy\n\n\n## Including HTTP Metadata\n\n@docs withMetadata, Metadata\n\n","unions":[{"name":"CacheStrategy","comment":" ","args":[],"cases":[["IgnoreCache",[]],["ForceRevalidate",[]],["ForceReload",[]],["ForceCache",[]],["ErrorUnlessCached",[]]]},{"name":"Error","comment":" ","args":[],"cases":[["BadUrl",["String.String"]],["Timeout",[]],["NetworkError",[]],["BadStatus",["BackendTask.Http.Metadata","String.String"]],["BadBody",["Maybe.Maybe Json.Decode.Error","String.String"]]]},{"name":"Expect","comment":" Analogous to the `Expect` type in the `elm/http` package. This represents how you will process the data that comes\nback in your BackendTask.Http request.\n\nYou can derive `ExpectJson` from `ExpectString`. Or you could build your own helper to process the String\nas XML, for example, or give an `elm-pages` build error if the response can't be parsed as XML.\n\n","args":["value"],"cases":[]}],"aliases":[{"name":"Body","comment":" A body for a BackendTask.Http request.\n","args":[],"type":"Pages.Internal.StaticHttpBody.Body"},{"name":"Metadata","comment":" ","args":[],"type":"{ url : String.String, statusCode : Basics.Int, statusText : String.String, headers : Dict.Dict String.String String.String }"}],"values":[{"name":"bytesBody","comment":" Build a body from `Bytes` for a BackendTask.Http request. See [elm/http's `Http.bytesBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#bytesBody).\n","type":"String.String -> Bytes.Bytes -> BackendTask.Http.Body"},{"name":"emptyBody","comment":" Build an empty body for a BackendTask.Http request. See [elm/http's `Http.emptyBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#emptyBody).\n","type":"BackendTask.Http.Body"},{"name":"expectBytes","comment":" ","type":"Bytes.Decode.Decoder value -> BackendTask.Http.Expect value"},{"name":"expectJson","comment":" Handle the incoming response as JSON and don't optimize the asset and strip out unused values.\nBe sure to use the `BackendTask.Http.request` function if you want an optimized request that\nstrips out unused JSON to optimize your asset size. This function makes sense to use for things like a GraphQL request\nwhere the JSON payload is already trimmed down to the data you explicitly requested.\n\nIf the function you pass to `expectString` yields an `Err`, then you will get a build error that will\nfail your `elm-pages` build and print out the String from the `Err`.\n\n","type":"Json.Decode.Decoder value -> BackendTask.Http.Expect value"},{"name":"expectString","comment":" Gives the HTTP response body as a raw String.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Http\n\n request : BackendTask String\n request =\n BackendTask.Http.request\n { url = \"https://example.com/file.txt\"\n , method = \"GET\"\n , headers = []\n , body = BackendTask.Http.emptyBody\n }\n BackendTask.Http.expectString\n\n","type":"BackendTask.Http.Expect String.String"},{"name":"expectWhatever","comment":" ","type":"value -> BackendTask.Http.Expect value"},{"name":"get","comment":" A simplified helper around [`BackendTask.Http.getWithOptions`](#getWithOptions), which builds up a GET request with\nthe default retries, timeout, and HTTP caching options. If you need to configure those options or include HTTP request headers,\nuse the more flexible `getWithOptions`.\n\n import BackendTask\n import BackendTask.Http\n import FatalError exposing (FatalError)\n\n getRequest : BackendTask (FatalError Error) String\n getRequest =\n BackendTask.Http.get\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n BackendTask.Http.expectString\n\n","type":"String.String -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"getJson","comment":" A simplified helper around [`BackendTask.Http.get`](#get), which builds up a BackendTask.Http GET request with `expectJson`.\n\n import BackendTask\n import BackendTask.Http\n import FatalError exposing (FatalError)\n import Json.Decode as Decode exposing (Decoder)\n\n getRequest : BackendTask (FatalError Error) Int\n getRequest =\n BackendTask.Http.getJson\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"stargazers_count\" Decode.int)\n\n","type":"String.String -> Json.Decode.Decoder a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"getWithOptions","comment":" Perform a GET request, with some additional options for the HTTP request, including options for caching behavior.\n\n - `retries` - Default is 0. Will try performing request again if set to a number greater than 0.\n - `timeoutInMs` - Default is no timeout.\n - `cacheStrategy` - The [caching options are passed to the NPM package `make-fetch-happen`](https://github.com/npm/make-fetch-happen#opts-cache)\n - `cachePath` - override the default directory for the local HTTP cache. This can be helpful if you want more granular control to clear some HTTP caches more or less frequently than others. Or you may want to preserve the local cache for some requests in your build server, but not store the cache for other requests.\n\n","type":"{ url : String.String, expect : BackendTask.Http.Expect a, headers : List.List ( String.String, String.String ), cacheStrategy : Maybe.Maybe BackendTask.Http.CacheStrategy, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int, cachePath : Maybe.Maybe String.String } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"jsonBody","comment":" Builds a JSON body for a BackendTask.Http request. See [elm/http's `Http.jsonBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#jsonBody).\n","type":"Json.Encode.Value -> BackendTask.Http.Body"},{"name":"post","comment":" ","type":"String.String -> BackendTask.Http.Body -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"request","comment":" ","type":"{ url : String.String, method : String.String, headers : List.List ( String.String, String.String ), body : BackendTask.Http.Body, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int } -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"stringBody","comment":" Builds a string body for a BackendTask.Http request. See [elm/http's `Http.stringBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#stringBody).\n\nNote from the `elm/http` docs:\n\n> The first argument is a [MIME type](https://en.wikipedia.org/wiki/Media_type) of the body. Some servers are strict about this!\n\n","type":"String.String -> String.String -> BackendTask.Http.Body"},{"name":"withMetadata","comment":" ","type":"(BackendTask.Http.Metadata -> value -> combined) -> BackendTask.Http.Expect value -> BackendTask.Http.Expect combined"}],"binops":[]},{"name":"BackendTask.Random","comment":"\n\n@docs generate\n\n@docs int32\n\n","unions":[],"aliases":[],"values":[{"name":"generate","comment":" Takes an `elm/random` `Random.Generator` and runs it using a randomly generated initial seed.\n\n type alias Data =\n { randomData : ( Int, Float )\n }\n\n data : BackendTask FatalError Data\n data =\n BackendTask.map Data\n (BackendTask.Random.generate generator)\n\n generator : Random.Generator ( Int, Float )\n generator =\n Random.map2 Tuple.pair (Random.int 0 100) (Random.float 0 100)\n\nThe random initial seed is generated using \nto generate a single 32-bit Integer. That 32-bit Integer is then used with `Random.initialSeed` to create an Elm Random.Seed value.\nThen that `Seed` used to run the `Generator`.\n\nNote that this is different than `elm/random`'s `Random.generate`. This difference shouldn't be problematic, and in fact the `BackendTask`\nrandom seed generation is more cryptographically independent because you can't determine the\nrandom seed based solely on the time at which it is run. Each time you call `BackendTask.generate` it uses a newly\ngenerated random seed to run the `Random.Generator` that is passed in. In contrast, `elm/random`'s `Random.generate`\ngenerates 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)\nto get new random values after that. You can [see the implementation here](https://github.com/elm/random/blob/c1c9da4d861363cee1c93382d2687880279ed0dd/src/Random.elm#L865-L896).\nHowever, `elm/random` is still not suitable in general for cryptographic uses of random because it uses 32-bits for when it\nsteps through new seeds while running a single `Random.Generator`.\n\n","type":"Random.Generator value -> BackendTask.BackendTask error value"},{"name":"int32","comment":" Gives a random 32-bit Int. This can be useful if you want to do low-level things with a cryptographically sound\nrandom 32-bit integer.\n\nThe value comes from running this code in Node using :\n\n```js\nimport * as crypto from \"node:crypto\";\n\ncrypto.getRandomValues(new Uint32Array(1))[0]\n```\n\n","type":"BackendTask.BackendTask error Basics.Int"}],"binops":[]},{"name":"BackendTask.Time","comment":"\n\n@docs now\n\n","unions":[],"aliases":[],"values":[{"name":"now","comment":" Gives a `Time.Posix` of when the `BackendTask` executes.\n\n type alias Data =\n { time : Time.Posix\n }\n\n data : BackendTask FatalError Data\n data =\n BackendTask.map Data\n BackendTask.Time.now\n\nIt's better to use [`Server.Request.requestTime`](Server-Request#requestTime) or `Pages.builtAt` when those are the semantics\nyou are looking for. `requestTime` gives you a single reliable and consistent time for when the incoming HTTP request was received in\na server-rendered Route or server-rendered API Route. `Pages.builtAt` gives a single reliable and consistent time when the\nsite was built.\n\n`BackendTask.Time.now` gives you the time that it happened to execute, which might give you what you need, but be\naware that the time you get is dependent on how BackendTask's are scheduled and executed internally in elm-pages, and\nits best to avoid depending on that variation when possible.\n\n","type":"BackendTask.BackendTask error Time.Posix"}],"binops":[]},{"name":"FatalError","comment":" The Elm language doesn't have the concept of exceptions or special control flow for errors. It just has\nCustom Types, and by convention types like `Result` and the `Err` variant are used to represent possible failure states\nand combine together different error states.\n\n`elm-pages` doesn't change that, Elm still doesn't have special exception control flow at the language level. It does have\na type, which is just a regular old Elm type, called `FatalError`. Why? Because this plain old Elm type does have one\nspecial characteristic - the `elm-pages` framework knows how to turn it into an error message. This becomes interesting\nbecause an `elm-pages` app has several places that accept a value of type `BackendTask FatalError.FatalError value`.\nThis design lets the `elm-pages` framework do some of the work for you.\n\nFor example, if you wanted to handle possible errors to present them to the user\n\n type alias Data =\n String\n\n data : RouteParams -> BackendTask FatalError Data\n data routeParams =\n BackendTask.Http.getJson \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"description\" Decode.string)\n |> BackendTask.onError\n (\\error ->\n case FatalError.unwrap error of\n BackendTask.Http.BadStatus metadata string ->\n if metadata.statusCode == 401 || metadata.statusCode == 403 || metadata.statusCode == 404 then\n BackendTask.succeed \"Either this repo doesn't exist or you don't have access to it.\"\n\n else\n -- we're only handling these expected error cases. In the case of an HTTP timeout,\n -- we'll let the error propagate as a FatalError\n BackendTask.fail error |> BackendTask.allowFatal\n\n _ ->\n BackendTask.fail error |> BackendTask.allowFatal\n )\n\nThis can be a lot of work for all possible errors, though. If you don't expect this kind of error (it's an _exceptional_ case),\nyou can let the framework handle it if the error ever does unexpectedly occur.\n\n data : RouteParams -> BackendTask FatalError Data\n data routeParams =\n BackendTask.Http.getJson \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"description\" Decode.string)\n |> BackendTask.allowFatal\n\nThis is especially useful for pages generated at build-time (`RouteBuilder.preRender`) where you want the build\nto fail if anything unexpected happens. With pre-rendered routes, you know that these error cases won't\nbe seen by users, so it's often a great idea to just let the framework handle these unexpected errors so a developer can\ndebug them and see what went wrong. In the example above, maybe we are only pre-rendering pages for a set of known\nGitHub Repositories, so a Not Found or Unauthorized HTTP error would be unexpected and should stop the build so we can fix the\nissue.\n\nIn the case of server-rendered Routes (`RouteBuilder.serverRender`), `elm-pages` will show your 500 error page\nwhen these errors occur.\n\n@docs FatalError, build, fromString, recoverable\n\n","unions":[],"aliases":[{"name":"FatalError","comment":" ","args":[],"type":"Pages.Internal.FatalError.FatalError"}],"values":[{"name":"build","comment":" Create a FatalError with a title and body.\n","type":"{ title : String.String, body : String.String } -> FatalError.FatalError"},{"name":"fromString","comment":" ","type":"String.String -> FatalError.FatalError"},{"name":"recoverable","comment":" ","type":"{ title : String.String, body : String.String } -> error -> { fatal : FatalError.FatalError, recoverable : error }"}],"binops":[]},{"name":"Head","comment":" This module contains functions for building up\ntags with metadata that will be rendered into the page's `` tag\nwhen your page is pre-rendered (or server-rendered, in the case of your server-rendered Route Modules). See also [`Head.Seo`](Head-Seo),\nwhich has some helper functions for defining OpenGraph and Twitter tags.\n\nOne of the unique benefits of using `elm-pages` is that all of your routes (both pre-rendered and server-rendered) fully\nrender the HTML of your page. That includes the full initial `view` (with the BackendTask resolved, and the `Model` from `init`).\nThe HTML response also includes all of the `Head` tags, which are defined in two places:\n\n1. `app/Site.elm` - there is a `head` definition in `Site.elm` where you define global head tags that will be included on every rendered page.\n\n2. In each Route Module - there is a `head` function where you have access to both the resolved `BackendTask` and the `RouteParams` for the page and can return head tags based on that.\n\nHere is a common set of global head tags that we can define in `Site.elm`:\n\n module Site exposing (canonicalUrl, config)\n\n import BackendTask exposing (BackendTask)\n import Head\n import MimeType\n import SiteConfig exposing (SiteConfig)\n\n config : SiteConfig\n config =\n { canonicalUrl = \"\n , head = head\n }\n\n head : BackendTask (List Head.Tag)\n head =\n [ Head.metaName \"viewport\" (Head.raw \"width=device-width,initial-scale=1\")\n , Head.metaName \"mobile-web-app-capable\" (Head.raw \"yes\")\n , Head.metaName \"theme-color\" (Head.raw \"#ffffff\")\n , Head.metaName \"apple-mobile-web-app-capable\" (Head.raw \"yes\")\n , Head.metaName \"apple-mobile-web-app-status-bar-style\" (Head.raw \"black-translucent\")\n , Head.icon [ ( 32, 32 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 32)\n , Head.icon [ ( 16, 16 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 16)\n , Head.appleTouchIcon (Just 180) (cloudinaryIcon MimeType.Png 180)\n , Head.appleTouchIcon (Just 192) (cloudinaryIcon MimeType.Png 192)\n ]\n |> BackendTask.succeed\n\nAnd here is a `head` function for a Route Module for a blog post. Note that we have access to our `BackendTask` Data and\nare using it to populate article metadata like the article's image, publish date, etc.\n\n import Article\n import BackendTask\n import Date\n import Head\n import Head.Seo\n import Path\n import Route exposing (Route)\n import RouteBuilder exposing (App, StatelessRoute)\n\n type alias RouteParams =\n { slug : String }\n\n type alias Data =\n { metadata : ArticleMetadata\n , body : List Markdown.Block.Block\n }\n\n route : StatelessRoute RouteParams Data ActionData\n route =\n RouteBuilder.preRender\n { data = data\n , head = head\n , pages = pages\n }\n |> RouteBuilder.buildNoState { view = view }\n\n head :\n App Data ActionData RouteParams\n -> List Head.Tag\n head static =\n let\n metadata =\n static.data.metadata\n in\n Head.Seo.summaryLarge\n { canonicalUrlOverride = Nothing\n , siteName = \"elm-pages\"\n , image =\n { url = metadata.image\n , alt = metadata.description\n , dimensions = Nothing\n , mimeType = Nothing\n }\n , description = metadata.description\n , locale = Nothing\n , title = metadata.title\n }\n |> Head.Seo.article\n { tags = []\n , section = Nothing\n , publishedTime = Just (DateOrDateTime.Date metadata.published)\n , modifiedTime = Nothing\n , expirationTime = Nothing\n }\n\n\n## Why is pre-rendered HTML important? Does it still matter for SEO?\n\nMany search engines are able to execute JavaScript now. However, not all are, and even with crawlers like Google, there\nis a longer lead time for your pages to be indexed when you have HTML with a blank page that is only visible after the JavaScript executes.\n\nBut most importantly, many tools that unfurl links will not execute JavaScript at all, but rather simply do a simple pass to parse your `` tags.\nIt is not viable or reliable to add `` tags for metadata on the client-side, it must be present in the initial HTML payload. Otherwise you may not\nget unfurling preview content when you share a link to your site on Slack, Twitter, etc.\n\n\n## Building up Head Tags\n\n@docs Tag, metaName, metaProperty, metaRedirect\n@docs rssLink, sitemapLink, rootLanguage, manifestLink\n\n@docs nonLoadingNode\n\n\n## Structured Data\n\n@docs structuredData\n\n\n## `AttributeValue`s\n\n@docs AttributeValue\n@docs currentPageFullUrl, urlAttribute, raw\n\n\n## Icons\n\n@docs appleTouchIcon, icon\n\n\n## Functions for use by generated code\n\n@docs toJson, canonicalLink\n\n","unions":[{"name":"AttributeValue","comment":" Values, such as between the `<>`'s here:\n\n```html\n\" content=\"\" />\n```\n\n","args":[],"cases":[]},{"name":"Tag","comment":" Values that can be passed to the generated `Pages.application` config\nthrough the `head` function.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"appleTouchIcon","comment":" Note: the type must be png.\nSee .\n\nIf a size is provided, it will be turned into square dimensions as per the recommendations here: \n\nImages must be png's, and non-transparent images are recommended. Current recommended dimensions are 180px and 192px.\n\n","type":"Maybe.Maybe Basics.Int -> Pages.Url.Url -> Head.Tag"},{"name":"canonicalLink","comment":" It's recommended that you use the `Seo` module helpers, which will provide this\nfor you, rather than directly using this.\n\nExample:\n\n Head.canonicalLink \"https://elm-pages.com\"\n\n","type":"Maybe.Maybe String.String -> Head.Tag"},{"name":"currentPageFullUrl","comment":" Create an `AttributeValue` representing the current page's full url.\n","type":"Head.AttributeValue"},{"name":"icon","comment":" ","type":"List.List ( Basics.Int, Basics.Int ) -> MimeType.MimeImage -> Pages.Url.Url -> Head.Tag"},{"name":"manifestLink","comment":" Let's you link to your manifest.json file, see .\n","type":"String.String -> Head.Tag"},{"name":"metaName","comment":" Example:\n\n Head.metaName \"twitter:card\" (Head.raw \"summary_large_image\")\n\nResults in ``\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaProperty","comment":" Example:\n\n Head.metaProperty \"fb:app_id\" (Head.raw \"123456789\")\n\nResults in ``\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaRedirect","comment":" Example:\n\n metaRedirect (Raw \"0; url=https://google.com\")\n\nResults in ``\n\n","type":"Head.AttributeValue -> Head.Tag"},{"name":"nonLoadingNode","comment":" Escape hatch for any head tags that don't have high-level helpers. This lets you build arbitrary head nodes as long as they\nare not loading or preloading directives.\n\nTags that do loading/pre-loading will not work from this function. `elm-pages` uses ViteJS for loading assets like\nscript tags, stylesheets, fonts, etc., and allows you to customize which assets to preload and how through the elm-pages.config.mjs file.\nSee the full discussion of the design in [#339](https://github.com/dillonkearns/elm-pages/discussions/339).\n\nSo for example the following tags would _not_ load if defined through `nonLoadingNode`, and would instead need to be registered through Vite:\n\n - `\n```\n\nTo get that data, you would write this in your `elm-pages` head tags:\n\n import Json.Encode as Encode\n\n {-| \n -}\n encodeArticle :\n { title : String\n , description : String\n , author : StructuredDataHelper { authorMemberOf | personOrOrganization : () } authorPossibleFields\n , publisher : StructuredDataHelper { publisherMemberOf | personOrOrganization : () } publisherPossibleFields\n , url : String\n , imageUrl : String\n , datePublished : String\n , mainEntityOfPage : Encode.Value\n }\n -> Head.Tag\n encodeArticle info =\n Encode.object\n [ ( \"@context\", Encode.string \"http://schema.org/\" )\n , ( \"@type\", Encode.string \"Article\" )\n , ( \"headline\", Encode.string info.title )\n , ( \"description\", Encode.string info.description )\n , ( \"image\", Encode.string info.imageUrl )\n , ( \"author\", encode info.author )\n , ( \"publisher\", encode info.publisher )\n , ( \"url\", Encode.string info.url )\n , ( \"datePublished\", Encode.string info.datePublished )\n , ( \"mainEntityOfPage\", info.mainEntityOfPage )\n ]\n |> Head.structuredData\n\nTake a look at this [Google Search Gallery](https://developers.google.com/search/docs/guides/search-gallery)\nto see some examples of how structured data can be used by search engines to give rich search results. It can help boost\nyour rankings, get better engagement for your content, and also make your content more accessible. For example,\nvoice assistant devices can make use of structured data. If you're hosting a conference and want to make the event\ndate and location easy for attendees to find, this can make that information more accessible.\n\nFor the current version of API, you'll need to make sure that the format is correct and contains the required and recommended\nstructure.\n\nCheck out for a comprehensive listing of possible data types and fields. And take a look at\nGoogle's [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool)\ntoo make sure that your structured data is valid and includes the recommended values.\n\nIn the future, `elm-pages` will likely support a typed API, but schema.org is a massive spec, and changes frequently.\nAnd there are multiple sources of information on the possible and recommended structure. So it will take some time\nfor the right API design to evolve. In the meantime, this allows you to make use of this for SEO purposes.\n\n","type":"Json.Encode.Value -> Head.Tag"},{"name":"toJson","comment":" Feel free to use this, but in 99% of cases you won't need it. The generated\ncode will run this for you to generate your `manifest.json` file automatically!\n","type":"String.String -> String.String -> Head.Tag -> Json.Encode.Value"},{"name":"urlAttribute","comment":" Create an `AttributeValue` from an `ImagePath`.\n","type":"Pages.Url.Url -> Head.AttributeValue"}],"binops":[]},{"name":"Head.Seo","comment":" \n\nThis module encapsulates some of the best practices for SEO for your site.\n\n`elm-pages` pre-renders the HTML for your pages (either at build-time or server-render time) so that\nweb crawlers can efficiently and accurately process it. The functions in this module are for use\nwith the `head` function in your `Route` modules to help you build up a set of `` tags that\nincludes common meta tags used for rich link previews, namely [OpenGraph tags](https://ogp.me/) and [Twitter card tags](https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards).\n\n import Date\n import Head\n import Head.Seo as Seo\n\n\n -- justinmimbs/date package\n type alias ArticleMetadata =\n { title : String\n , description : String\n , published : Date\n , author : Data.Author.Author\n }\n\n head : ArticleMetadata -> List Head.Tag\n head articleMetadata =\n Seo.summaryLarge\n { canonicalUrlOverride = Nothing\n , siteName = \"elm-pages\"\n , image =\n { url = Pages.images.icon\n , alt = articleMetadata.description\n , dimensions = Nothing\n , mimeType = Nothing\n }\n , description = articleMetadata.description\n , locale = Nothing\n , title = articleMetadata.title\n }\n |> Seo.article\n { tags = []\n , section = Nothing\n , publishedTime = Just (Date.toIsoString articleMetadata.published)\n , modifiedTime = Nothing\n , expirationTime = Nothing\n }\n\n@docs Common, Image, article, audioPlayer, book, profile, song, summary, summaryLarge, videoPlayer, website\n\n","unions":[],"aliases":[{"name":"Common","comment":" These fields apply to any type in the og object types\nSee and \n\nSkipping this for now, if there's a use case I can add it in:\n\n - og:determiner - The word that appears before this object's title in a sentence. An enum of (a, an, the, \"\", auto). If auto is chosen, the consumer of your data should chose between \"a\" or \"an\". Default is \"\" (blank).\n\n","args":[],"type":"{ title : String.String, image : Head.Seo.Image, canonicalUrlOverride : Maybe.Maybe String.String, description : String.String, siteName : String.String, audio : Maybe.Maybe Head.Seo.Audio, video : Maybe.Maybe Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale, alternateLocales : List.List Head.Seo.Locale, twitterCard : Head.Twitter.TwitterCard }"},{"name":"Image","comment":" See \n","args":[],"type":"{ url : Pages.Url.Url, alt : String.String, dimensions : Maybe.Maybe { width : Basics.Int, height : Basics.Int }, mimeType : Maybe.Maybe MimeType.MimeType }"}],"values":[{"name":"article","comment":" See \n","type":"{ tags : List.List String.String, section : Maybe.Maybe String.String, publishedTime : Maybe.Maybe DateOrDateTime.DateOrDateTime, modifiedTime : Maybe.Maybe DateOrDateTime.DateOrDateTime, expirationTime : Maybe.Maybe DateOrDateTime.DateOrDateTime } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"audioPlayer","comment":" Will be displayed as a Player card in twitter\nSee: \n\nOpenGraph audio will also be included.\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, audio : Head.Seo.Audio, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"book","comment":" See \n","type":"Head.Seo.Common -> { tags : List.List String.String, isbn : Maybe.Maybe String.String, releaseDate : Maybe.Maybe DateOrDateTime.DateOrDateTime } -> List.List Head.Tag"},{"name":"profile","comment":" See \n","type":"{ firstName : String.String, lastName : String.String, username : Maybe.Maybe String.String } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"song","comment":" See \n","type":"Head.Seo.Common -> { duration : Maybe.Maybe Basics.Int, album : Maybe.Maybe Basics.Int, disc : Maybe.Maybe Basics.Int, track : Maybe.Maybe Basics.Int } -> List.List Head.Tag"},{"name":"summary","comment":" Will be displayed as a large card in twitter\nSee: \n\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"summaryLarge","comment":" Will be displayed as a large card in twitter\nSee: \n\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"videoPlayer","comment":" Will be displayed as a Player card in twitter\nSee: \n\nOpenGraph video will also be included.\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, video : Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"website","comment":" \n","type":"Head.Seo.Common -> List.List Head.Tag"}],"binops":[]},{"name":"Pages.ConcurrentSubmission","comment":" When you render a `Form` with the [`Pages.Form.withConcurrent`](Pages-Form#withConcurrent) `Option`, the state of in-flight and completed submissions will be available\nfrom your `Route` module through `app.concurrentSubmissions` as a `Dict String (ConcurrentSubmission (Maybe Action))`.\n\nYou can use this state to declaratively derive Pending UI or Optimistic UI from your pending submissions (without managing the state in your `Model`, since `elm-pages`\nmanages form submission state for you).\n\nYou can [see the full-stack TodoMVC example](https://github.com/dillonkearns/elm-pages-v3-beta/blob/master/examples/todos/app/Route/Visibility__.elm) for a complete example of deriving Pending UI state from `app.concurrentSubmissions`.\n\nFor example, this how the TodoMVC example derives the list of new items that are being created (but are still pending).\n\n view :\n App Data ActionData RouteParams\n -> Shared.Model\n -> Model\n -> View (PagesMsg Msg)\n view app shared model =\n let\n pendingActions : List Action\n pendingActions =\n app.concurrentSubmissions\n |> Dict.values\n |> List.filterMap\n (\\{ status, payload } ->\n case status of\n Pages.ConcurrentSubmission.Complete _ ->\n Nothing\n\n _ ->\n allForms\n |> Form.Handler.run payload.fields\n |> Form.toResult\n |> Result.toMaybe\n )\n\n newPendingItems : List Entry\n newPendingItems =\n pendingActions\n |> List.filterMap\n (\\submission ->\n case submission of\n Add description ->\n Just\n { description = description\n , completed = False\n , id = \"\"\n , isPending = True\n }\n\n _ ->\n -- `newPendingItems` only cares about pending Add actions. Other values will use\n -- pending submissions for other types of Actions.\n Nothing\n )\n in\n itemsView app newPendingItems\n\n allForms : Form.Handler.Handler String Action\n allForms =\n |> Form.Handler.init Add addItemForm\n -- |> Form.Handler.with ...\n\n\n type Action\n = UpdateEntry ( String, String )\n | Add String\n | Delete String\n | DeleteComplete\n | Check ( Bool, String )\n | CheckAll Bool\n\n@docs ConcurrentSubmission, Status\n\n@docs map\n\n","unions":[{"name":"Status","comment":" The status of a `ConcurrentSubmission`.\n\n - `Submitting` - The submission is in-flight.\n - `Reloading` - The submission has completed, and the page is now reloading the `Route`'s `data` to reflect the new state. The `actionData` holds any data returned from the `Route`'s `action`.\n - `Complete` - The submission has completed, and the `Route`'s `data` has since reloaded so the state reflects the refreshed state after completing this specific form submission. The `actionData` holds any data returned from the `Route`'s `action`.\n\n","args":["actionData"],"cases":[["Submitting",[]],["Reloading",["actionData"]],["Complete",["actionData"]]]}],"aliases":[{"name":"ConcurrentSubmission","comment":" ","args":["actionData"],"type":"{ status : Pages.ConcurrentSubmission.Status actionData, payload : Pages.FormData.FormData, initiatedAt : Time.Posix }"}],"values":[{"name":"map","comment":" `map` a `ConcurrentSubmission`. Not needed for most high-level cases since this state is managed by the `elm-pages` framework for you.\n","type":"(a -> b) -> Pages.ConcurrentSubmission.ConcurrentSubmission a -> Pages.ConcurrentSubmission.ConcurrentSubmission b"}],"binops":[]},{"name":"Pages.Fetcher","comment":"\n\n@docs Fetcher, FetcherInfo, submit, map\n\n","unions":[{"name":"Fetcher","comment":" ","args":["decoded"],"cases":[["Fetcher",["Pages.Fetcher.FetcherInfo decoded"]]]}],"aliases":[{"name":"FetcherInfo","comment":" ","args":["decoded"],"type":"{ decoder : Result.Result Http.Error Bytes.Bytes -> decoded, fields : List.List ( String.String, String.String ), headers : List.List ( String.String, String.String ), url : Maybe.Maybe String.String }"}],"values":[{"name":"map","comment":" ","type":"(a -> b) -> Pages.Fetcher.Fetcher a -> Pages.Fetcher.Fetcher b"},{"name":"submit","comment":" ","type":"Bytes.Decode.Decoder decoded -> { fields : List.List ( String.String, String.String ), headers : List.List ( String.String, String.String ) } -> Pages.Fetcher.Fetcher (Result.Result Http.Error decoded)"}],"binops":[]},{"name":"Pages.Flags","comment":"\n\n@docs Flags\n\n","unions":[{"name":"Flags","comment":" elm-pages apps run in two different contexts\n\n1. In the browser (like a regular Elm app)\n2. In pre-render mode. For example when you run `elm-pages build`, there is no browser involved, it just runs Elm directly.\n\nYou can pass in Flags and use them in your `Shared.init` function. You can store data in your `Shared.Model` from these flags and then access it across any page.\n\nYou will need to handle the `PreRender` case with no flags value because there is no browser to get flags from. For example, say you wanted to get the\ncurrent user's Browser window size and pass it in as a flag. When that page is pre-rendered, you need to decide on a value to use for the window size\nsince there is no window (the user hasn't requested the page yet, and the page isn't even loaded in a Browser window yet).\n\n","args":[],"cases":[["BrowserFlags",["Json.Decode.Value"]],["PreRenderFlags",[]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.Form","comment":" `elm-pages` has a built-in integration with [`dillonkearns/elm-form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/). See the `dillonkearns/elm-form`\ndocs and examples for more information on how to define your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form). This module is the interface for rendering your `Form` in your `elm-pages` app.\n\nBy rendering your `Form` with this module,\nyou get all of the boilerplate managed for you automatically by the `elm-pages` framework. That means you do not need to use `Form.init`, `Form.update`, `Form.Model` since these are all\nabstracted away. In addition to that, in-flight form state is automatically managed for you and exposed through the `app` argument in your Route modules.\n\nThis means that you can declaratively derive Pending UI or Optimistic UI state from `app.navigation` or `app.concurrentSubmissions` in your Route modules, and even build a\nrich dynamic page that shows pending submissions in the UI without using your Route module's `Model`! This is the power of this abstraction - it's less error-prone to\ndeclaratively derive state rather than imperatively managing your `Model`.\n\n\n## Rendering Forms\n\n@docs renderHtml, renderStyledHtml\n\n@docs Options\n\n\n## Form Submission Strategies\n\nWhen you render with [`Pages.Form.renderHtml`](#renderHtml) or [`Pages.Form.renderStyledHtml`](#renderStyledHtml),\n`elm-pages` progressively enhances form submissions to manage the requests through Elm (instead of as a vanilla HTML form submission, which performs a full page reload).\n\nBy default, `elm-pages` Forms will use the same mental model as the browser's default form submission behavior. That is, the form submission state will be tied to the page's navigation state.\nIf you click a link while a form is submitting, the form submission will be cancelled and the page will navigate to the new page. Conceptually, you can think of this as being tied to the navigation state.\nA form submission is part of the page's navigation state, and so is a page navigation. So if you have a page with an edit form, a delete form (no inputs but only a delete button), and a link to a new page,\nyou can interact with any of these and it will cancel the previous interactions.\n\nYou can access this state through `app.navigation` in your `Route` module, which is a value of type [`Pages.Navigation`](Pages-Navigation).\n\nThis default form submission strategy is a good fit for more linear actions. This is more traditional server submission behavior that you might be familiar with from Rails or other server frameworks without JavaScript enhancement.\n\n@docs withConcurrent\n\n\n## Server-Side Validation\n\n@docs FormWithServerValidations, Handler\n\n","unions":[],"aliases":[{"name":"FormWithServerValidations","comment":" ","args":["error","combined","input","view"],"type":"Form.Form error { combine : Form.Validation.Validation error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never)) Basics.Never Basics.Never, view : Form.Context error input -> view } (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never)) input"},{"name":"Handler","comment":" ","args":["error","combined"],"type":"Form.Handler.Handler error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never))"},{"name":"Options","comment":" A replacement for [`Form.Options`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Options)\nwith some extra configuration for the `elm-pages` integration. You can use this type to annotate your form's options.\n","args":["error","parsed","input","msg"],"type":"Form.Options error parsed input msg { concurrent : Basics.Bool }"}],"values":[{"name":"renderHtml","comment":" A replacement for `Form.renderHtml` from `dillonkearns/elm-form` that integrates with `elm-pages`. Use this to render your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form)\nas `elm/html` `Html`.\n","type":"List.List (Html.Attribute (PagesMsg.PagesMsg userMsg)) -> Pages.Form.Options error parsed input userMsg -> { app | pageFormState : Form.Model, navigation : Maybe.Maybe Pages.Navigation.Navigation, concurrentSubmissions : Dict.Dict String.String (Pages.ConcurrentSubmission.ConcurrentSubmission (Maybe.Maybe action)) } -> Form.Form error { combine : Form.Validation.Validation error parsed named constraints, view : Form.Context error input -> List.List (Html.Html (PagesMsg.PagesMsg userMsg)) } parsed input -> Html.Html (PagesMsg.PagesMsg userMsg)"},{"name":"renderStyledHtml","comment":" A replacement for `Form.renderStyledHtml` from `dillonkearns/elm-form` that integrates with `elm-pages`. Use this to render your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form)\nas `rtfeldman/elm-css` `Html.Styled.Html`.\n","type":"List.List (Html.Styled.Attribute (PagesMsg.PagesMsg userMsg)) -> Pages.Form.Options error parsed input userMsg -> { app | pageFormState : Form.Model, navigation : Maybe.Maybe Pages.Navigation.Navigation, concurrentSubmissions : Dict.Dict String.String (Pages.ConcurrentSubmission.ConcurrentSubmission (Maybe.Maybe action)) } -> Form.Form error { combine : Form.Validation.Validation error parsed named constraints, view : Form.Context error input -> List.List (Html.Styled.Html (PagesMsg.PagesMsg userMsg)) } parsed input -> Html.Styled.Html (PagesMsg.PagesMsg userMsg)"},{"name":"withConcurrent","comment":" Instead of using the default submission strategy (tied to the page's navigation state), you can use `withConcurrent`.\n`withConcurrent` allows multiple form submissions to be in flight at the same time. It is useful for more dynamic applications. A good rule of thumb\nis if you could have multiple pending spinners on the page at the same time, you should use `withConcurrent`. For example, if you have a page with a list of items,\nsay a Twitter clone. If you click the like button on a Tweet, it won't result in a page navigation. You can click the like button on multiple Tweets at the same time\nand they will all submit independently.\n\nIn the case of Twitter, there isn't an indication of a loading spinner on the like button because it is expected that it will succeed. This is an example of a User Experience (UX) pattern\ncalled Optimistic UI. Since it is very likely that liking a Tweet will be successful, the UI will update the UI as if it has immediately succeeded even though the request is still in flight.\nIf the request fails, the UI will be updated to reflect the failure with an animation to show that something went wrong.\n\nThe `withConcurrent` is a good fit for either of these UX patterns (Optimistic UI or Pending UI, i.e. showing a loading spinner). You can derive either of these\nvisual states from the `app.concurrentSubmissions` field in your `Route` module.\n\nYou can call `withConcurrent` on your `Form.Options`. Note that while `withConcurrent` will allow multiple form submissions to be in flight at the same time independently,\nthe ID of the Form will still have a unique submission. For example, if you click submit on a form with the ID `\"edit-123\"` and then submit it again before the first submission has completed,\nthe second submission will cancel the first submission. So it is important to use unique IDs for forms that represent unique operations.\n\n import Form\n import Pages.Form\n\n todoItemView app todo =\n deleteItemForm\n |> Pages.Form.renderHtml []\n (Form.options (\"delete-\" ++ todo.id)\n |> Form.withInput todo\n |> Pages.Form.withConcurrent\n )\n app\n\n","type":"Pages.Form.Options error parsed input msg -> Pages.Form.Options error parsed input msg"}],"binops":[]},{"name":"Pages.FormData","comment":"\n\n@docs FormData\n\n","unions":[],"aliases":[{"name":"FormData","comment":" The payload for form submissions.\n","args":[],"type":"{ fields : List.List ( String.String, String.String ), method : Form.Method, action : String.String, id : Maybe.Maybe String.String }"}],"values":[],"binops":[]},{"name":"Pages.Internal.NotFoundReason","comment":" Exposed for internal use only (used in generated code).\n\n@docs ModuleContext, NotFoundReason, Payload, Record, document\n\n","unions":[{"name":"NotFoundReason","comment":" ","args":[],"cases":[["NoMatchingRoute",[]],["NotPrerendered",["Pages.Internal.NotFoundReason.ModuleContext","List.List Pages.Internal.NotFoundReason.Record"]],["NotPrerenderedOrHandledByFallback",["Pages.Internal.NotFoundReason.ModuleContext","List.List Pages.Internal.NotFoundReason.Record"]],["UnhandledServerRoute",["Pages.Internal.NotFoundReason.ModuleContext"]]]}],"aliases":[{"name":"ModuleContext","comment":" ","args":[],"type":"{ moduleName : List.List String.String, routePattern : Pages.Internal.RoutePattern.RoutePattern, matchedRouteParams : Pages.Internal.NotFoundReason.Record }"},{"name":"Payload","comment":" ","args":[],"type":"{ path : UrlPath.UrlPath, reason : Pages.Internal.NotFoundReason.NotFoundReason }"},{"name":"Record","comment":" ","args":[],"type":"List.List ( String.String, String.String )"}],"values":[{"name":"document","comment":" ","type":"List.List Pages.Internal.RoutePattern.RoutePattern -> Pages.Internal.NotFoundReason.Payload -> { title : String.String, body : List.List (Html.Html msg) }"}],"binops":[]},{"name":"Pages.Internal.Platform","comment":" Exposed for internal use only (used in generated code).\n\n@docs Flags, Model, Msg, Program, application, init, update\n\n@docs Effect, RequestInfo, view\n\n","unions":[{"name":"Effect","comment":" ","args":["userMsg","pageData","actionData","sharedData","userEffect","errorPage"],"cases":[["ScrollToTop",[]],["NoEffect",[]],["BrowserLoadUrl",["String.String"]],["BrowserPushUrl",["String.String"]],["BrowserReplaceUrl",["String.String"]],["FetchPageData",["Basics.Int","Maybe.Maybe Pages.Internal.Platform.FormData","Url.Url","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData ) -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage"]],["Submit",["Pages.Internal.Platform.FormData"]],["SubmitFetcher",["String.String","Basics.Int","Pages.Internal.Platform.FormData"]],["Batch",["List.List (Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage)"]],["UserCmd",["userEffect"]],["CancelRequest",["Basics.Int"]],["RunCmd",["Platform.Cmd.Cmd (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"]]]},{"name":"Msg","comment":" ","args":["userMsg","pageData","actionData","sharedData","errorPage"],"cases":[["LinkClicked",["Browser.UrlRequest"]],["UrlChanged",["Url.Url"]],["UserMsg",["PagesMsg.PagesMsg userMsg"]],["FormMsg",["Form.Msg (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"]],["UpdateCacheAndUrlNew",["Basics.Bool","Url.Url","Maybe.Maybe userMsg","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData )"]],["FetcherComplete",["Basics.Bool","String.String","Basics.Int","Result.Result Http.Error ( Maybe.Maybe userMsg, Pages.Internal.Platform.ActionDataOrRedirect actionData )"]],["FetcherStarted",["String.String","Basics.Int","Pages.Internal.Platform.FormData","Time.Posix"]],["PageScrollComplete",[]],["HotReloadCompleteNew",["Bytes.Bytes"]],["ProcessFetchResponse",["Basics.Int","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData )","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData ) -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":["userModel","pageData","actionData","sharedData"],"type":"{ key : Maybe.Maybe Browser.Navigation.Key, url : Url.Url, currentPath : String.String, ariaNavigationAnnouncement : String.String, pageData : Result.Result String.String { userModel : userModel, pageData : pageData, sharedData : sharedData, actionData : Maybe.Maybe actionData }, notFound : Maybe.Maybe { reason : Pages.Internal.NotFoundReason.NotFoundReason, path : UrlPath.UrlPath }, userFlags : Json.Decode.Value, transition : Maybe.Maybe ( Basics.Int, Pages.Navigation.Navigation ), nextTransitionKey : Basics.Int, inFlightFetchers : Dict.Dict String.String ( Basics.Int, Pages.ConcurrentSubmission.ConcurrentSubmission actionData ), pageFormState : Form.Model, pendingRedirect : Basics.Bool, pendingData : Maybe.Maybe ( pageData, sharedData, Maybe.Maybe actionData ) }"},{"name":"Program","comment":" ","args":["userModel","userMsg","pageData","actionData","sharedData","errorPage"],"type":"Platform.Program Pages.Internal.Platform.Flags (Pages.Internal.Platform.Model userModel pageData actionData sharedData) (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"},{"name":"RequestInfo","comment":" ","args":[],"type":"{ contentType : String.String, body : String.String }"}],"values":[{"name":"application","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Platform.Program Pages.Internal.Platform.Flags (Pages.Internal.Platform.Model userModel pageData actionData sharedData) (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"},{"name":"init","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Flags -> Url.Url -> Maybe.Maybe Browser.Navigation.Key -> ( Pages.Internal.Platform.Model userModel pageData actionData sharedData, Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage )"},{"name":"update","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage -> Pages.Internal.Platform.Model userModel pageData actionData sharedData -> ( Pages.Internal.Platform.Model userModel pageData actionData sharedData, Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage )"},{"name":"view","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Model userModel pageData actionData sharedData -> Browser.Document (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"}],"binops":[]},{"name":"Pages.Internal.Platform.Cli","comment":" Exposed for internal use only (used in generated code).\n\n@docs Flags, Model, Msg, Program, cliApplication, init, requestDecoder, update, currentCompatibilityKey\n\n","unions":[{"name":"Msg","comment":" ","args":[],"cases":[["GotDataBatch",["Json.Decode.Value"]],["GotBuildError",["BuildError.BuildError"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":["route"],"type":"{ staticResponses : BackendTask.BackendTask FatalError.FatalError Pages.Internal.Platform.Effect.Effect, errors : List.List BuildError.BuildError, maybeRequestJson : RenderRequest.RenderRequest route, isDevServer : Basics.Bool }"},{"name":"Program","comment":" ","args":["route"],"type":"Platform.Program Pages.Internal.Platform.Cli.Flags (Pages.Internal.Platform.Cli.Model route) Pages.Internal.Platform.Cli.Msg"}],"values":[{"name":"cliApplication","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel (Maybe.Maybe route) pageData actionData sharedData effect mappedMsg errorPage -> Pages.Internal.Platform.Cli.Program (Maybe.Maybe route)"},{"name":"currentCompatibilityKey","comment":" ","type":"Basics.Int"},{"name":"init","comment":" ","type":"Pages.SiteConfig.SiteConfig -> RenderRequest.RenderRequest route -> Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect mappedMsg errorPage -> Json.Decode.Value -> ( Pages.Internal.Platform.Cli.Model route, Pages.Internal.Platform.Effect.Effect )"},{"name":"requestDecoder","comment":" ","type":"Json.Decode.Decoder Pages.StaticHttp.Request.Request"},{"name":"update","comment":" ","type":"Pages.Internal.Platform.Cli.Msg -> Pages.Internal.Platform.Cli.Model route -> ( Pages.Internal.Platform.Cli.Model route, Pages.Internal.Platform.Effect.Effect )"}],"binops":[]},{"name":"Pages.Internal.Platform.GeneratorApplication","comment":" Exposed for internal use only (used in generated code).\n\n@docs Program, Flags, Model, Msg, init, requestDecoder, update, app, JsonValue\n\n","unions":[{"name":"Msg","comment":" ","args":[],"cases":[["GotDataBatch",["Json.Decode.Value"]],["GotBuildError",["BuildError.BuildError"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"{ compatibilityKey : Basics.Int }"},{"name":"JsonValue","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":[],"type":"{ staticResponses : BackendTask.BackendTask FatalError.FatalError (), errors : List.List BuildError.BuildError }"},{"name":"Program","comment":" ","args":[],"type":"Cli.Program.StatefulProgram Pages.Internal.Platform.GeneratorApplication.Model Pages.Internal.Platform.GeneratorApplication.Msg (BackendTask.BackendTask FatalError.FatalError ()) Pages.Internal.Platform.GeneratorApplication.Flags"}],"values":[{"name":"app","comment":" ","type":"Pages.GeneratorProgramConfig.GeneratorProgramConfig -> Pages.Internal.Platform.GeneratorApplication.Program"},{"name":"init","comment":" ","type":"BackendTask.BackendTask FatalError.FatalError () -> Cli.Program.FlagsIncludingArgv Pages.Internal.Platform.GeneratorApplication.Flags -> ( Pages.Internal.Platform.GeneratorApplication.Model, Pages.Internal.Platform.Effect.Effect )"},{"name":"requestDecoder","comment":" ","type":"Json.Decode.Decoder Pages.StaticHttp.Request.Request"},{"name":"update","comment":" ","type":"Pages.Internal.Platform.GeneratorApplication.Msg -> Pages.Internal.Platform.GeneratorApplication.Model -> ( Pages.Internal.Platform.GeneratorApplication.Model, Pages.Internal.Platform.Effect.Effect )"}],"binops":[]},{"name":"Pages.Internal.ResponseSketch","comment":"\n\n@docs ResponseSketch\n\n","unions":[{"name":"ResponseSketch","comment":" ","args":["data","action","shared"],"cases":[["RenderPage",["data","Maybe.Maybe action"]],["HotUpdate",["data","shared","Maybe.Maybe action"]],["Redirect",["String.String"]],["NotFound",["{ reason : Pages.Internal.NotFoundReason.NotFoundReason, path : UrlPath.UrlPath }"]],["Action",["action"]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.Internal.RoutePattern","comment":" Exposed for internal use only (used in generated code).\n\n@docs Ending, RoutePattern, Segment, view, toVariant, routeToBranch\n\n@docs Param, RouteParam, fromModuleName, hasRouteParams, repeatWithoutOptionalEnding, toModuleName, toRouteParamTypes, toRouteParamsRecord, toVariantName\n\n","unions":[{"name":"Ending","comment":" ","args":[],"cases":[["Optional",["String.String"]],["RequiredSplat",[]],["OptionalSplat",[]]]},{"name":"Param","comment":" ","args":[],"cases":[["RequiredParam",[]],["OptionalParam",[]],["RequiredSplatParam",[]],["OptionalSplatParam",[]]]},{"name":"RouteParam","comment":" ","args":[],"cases":[["StaticParam",["String.String"]],["DynamicParam",["String.String"]],["OptionalParam2",["String.String"]],["RequiredSplatParam2",[]],["OptionalSplatParam2",[]]]},{"name":"Segment","comment":" ","args":[],"cases":[["StaticSegment",["String.String"]],["DynamicSegment",["String.String"]]]}],"aliases":[{"name":"RoutePattern","comment":" ","args":[],"type":"{ segments : List.List Pages.Internal.RoutePattern.Segment, ending : Maybe.Maybe Pages.Internal.RoutePattern.Ending }"}],"values":[{"name":"fromModuleName","comment":" ","type":"List.List String.String -> Maybe.Maybe Pages.Internal.RoutePattern.RoutePattern"},{"name":"hasRouteParams","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Basics.Bool"},{"name":"repeatWithoutOptionalEnding","comment":" ","type":"List.List Pages.Internal.RoutePattern.RouteParam -> Maybe.Maybe (List.List Pages.Internal.RoutePattern.RouteParam)"},{"name":"routeToBranch","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( Elm.CodeGen.Pattern, Elm.CodeGen.Expression )"},{"name":"toModuleName","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List String.String"},{"name":"toRouteParamTypes","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( String.String, Pages.Internal.RoutePattern.Param )"},{"name":"toRouteParamsRecord","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( String.String, Elm.Annotation.Annotation )"},{"name":"toVariant","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Elm.Variant"},{"name":"toVariantName","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> { variantName : String.String, params : List.List Pages.Internal.RoutePattern.RouteParam }"},{"name":"view","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Html.Html msg"}],"binops":[]},{"name":"Pages.Internal.Router","comment":" Exposed for internal use only (used in generated code).\n\n@docs Matcher, firstMatch, fromOptionalSplat, maybeToList, nonEmptyToList, toNonEmpty\n\n","unions":[],"aliases":[{"name":"Matcher","comment":" ","args":["route"],"type":"{ pattern : String.String, toRoute : List.List (Maybe.Maybe String.String) -> Maybe.Maybe route }"}],"values":[{"name":"firstMatch","comment":" ","type":"List.List (Pages.Internal.Router.Matcher route) -> String.String -> Maybe.Maybe route"},{"name":"fromOptionalSplat","comment":" ","type":"Maybe.Maybe String.String -> List.List String.String"},{"name":"maybeToList","comment":" ","type":"Maybe.Maybe String.String -> List.List String.String"},{"name":"nonEmptyToList","comment":" ","type":"( String.String, List.List String.String ) -> List.List String.String"},{"name":"toNonEmpty","comment":" ","type":"String.String -> ( String.String, List.List String.String )"}],"binops":[]},{"name":"Pages.Manifest","comment":" Represents the configuration of a\n[web manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest).\n\nYou pass your `Pages.Manifest.Config` record into the `Pages.Manifest.generator`\nin your `app/Api.elm` module to define a file generator that will build a `manifest.json` file as part of your build.\n\n import Pages.Manifest as Manifest\n import Pages.Manifest.Category\n\n manifest : Manifest.Config\n manifest =\n Manifest.init\n { name = static.siteName\n , description = \"elm-pages - \" ++ tagline\n , startUrl = Route.Index {} |> Route.toPath\n , icons =\n [ icon webp 192\n , icon webp 512\n , icon MimeType.Png 192\n , icon MimeType.Png 512\n ]\n }\n |> Manifest.withShortName \"elm-pages\"\n\n@docs Config, Icon\n\n\n## Builder options\n\n@docs init\n\n@docs withBackgroundColor, withCategories, withDisplayMode, withIarcRatingId, withLang, withOrientation, withShortName, withThemeColor\n\n\n## Arbitrary Fields Escape Hatch\n\n@docs withField\n\n\n## Config options\n\n@docs DisplayMode, Orientation, IconPurpose\n\n\n## Generating a Manifest.json\n\n@docs generator\n\n\n## Functions for use by the generated code (`Pages.elm`)\n\n@docs toJson\n\n","unions":[{"name":"DisplayMode","comment":" See \n","args":[],"cases":[["Fullscreen",[]],["Standalone",[]],["MinimalUi",[]],["Browser",[]]]},{"name":"IconPurpose","comment":" \n","args":[],"cases":[["IconPurposeMonochrome",[]],["IconPurposeMaskable",[]],["IconPurposeAny",[]]]},{"name":"Orientation","comment":" \n","args":[],"cases":[["Any",[]],["Natural",[]],["Landscape",[]],["LandscapePrimary",[]],["LandscapeSecondary",[]],["Portrait",[]],["PortraitPrimary",[]],["PortraitSecondary",[]]]}],"aliases":[{"name":"Config","comment":" Represents a [web app manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest)\n(see above for how to use it).\n","args":[],"type":"{ backgroundColor : Maybe.Maybe Color.Color, categories : List.List Pages.Manifest.Category.Category, displayMode : Pages.Manifest.DisplayMode, orientation : Pages.Manifest.Orientation, description : String.String, iarcRatingId : Maybe.Maybe String.String, name : String.String, themeColor : Maybe.Maybe Color.Color, startUrl : UrlPath.UrlPath, shortName : Maybe.Maybe String.String, icons : List.List Pages.Manifest.Icon, lang : LanguageTag.LanguageTag, otherFields : Dict.Dict String.String Json.Encode.Value }"},{"name":"Icon","comment":" \n","args":[],"type":"{ src : Pages.Url.Url, sizes : List.List ( Basics.Int, Basics.Int ), mimeType : Maybe.Maybe MimeType.MimeImage, purposes : List.List Pages.Manifest.IconPurpose }"}],"values":[{"name":"generator","comment":" A generator for `Api.elm` to include a manifest.json. The String argument is the canonical URL of the site.\n\n module Api exposing (routes)\n\n import ApiRoute\n import Pages.Manifest\n\n routes :\n BackendTask FatalError (List Route)\n -> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)\n -> List (ApiRoute.ApiRoute ApiRoute.Response)\n routes getStaticRoutes htmlToString =\n [ Pages.Manifest.generator\n Site.canonicalUrl\n Manifest.config\n ]\n\n","type":"String.String -> BackendTask.BackendTask FatalError.FatalError Pages.Manifest.Config -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"init","comment":" Setup a minimal Manifest.Config. You can then use the `with...` builder functions to set additional options.\n","type":"{ description : String.String, name : String.String, startUrl : UrlPath.UrlPath, icons : List.List Pages.Manifest.Icon } -> Pages.Manifest.Config"},{"name":"toJson","comment":" Feel free to use this, but in 99% of cases you won't need it. The generated\ncode will run this for you to generate your `manifest.json` file automatically!\n","type":"String.String -> Pages.Manifest.Config -> Json.Encode.Value"},{"name":"withBackgroundColor","comment":" Set .\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withCategories","comment":" Set .\n","type":"List.List Pages.Manifest.Category.Category -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withDisplayMode","comment":" Set .\n","type":"Pages.Manifest.DisplayMode -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withField","comment":" Escape hatch for specifying fields that aren't exposed through this module otherwise. The possible supported properties\nin a manifest file can change over time, so see [MDN manifest.json docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json)\nfor a full listing of the current supported properties.\n","type":"String.String -> Json.Encode.Value -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withIarcRatingId","comment":" Set .\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withLang","comment":" Set .\n","type":"LanguageTag.LanguageTag -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withOrientation","comment":" Set .\n","type":"Pages.Manifest.Orientation -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withShortName","comment":" Set .\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withThemeColor","comment":" Set .\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"}],"binops":[]},{"name":"Pages.Manifest.Category","comment":" See and\n\n\n@docs toString, Category\n\n@docs books, business, education, entertainment, finance, fitness, food, games, government, health, kids, lifestyle, magazines, medical, music, navigation, news, personalization, photo, politics, productivity, security, shopping, social, sports, travel, utilities, weather\n\n\n## Custom categories\n\n@docs custom\n\n","unions":[{"name":"Category","comment":" Represents a known, valid category, as specified by\n. If this document is updated\nand I don't add it, please open an issue or pull request to let me know!\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"books","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"business","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"custom","comment":" It's best to use the pre-defined categories to ensure that clients (Android, iOS,\nChrome, Windows app store, etc.) are aware of it and can handle it appropriately.\nBut, if you're confident about using a custom one, you can do so with `Pages.Manifest.custom`.\n","type":"String.String -> Pages.Manifest.Category.Category"},{"name":"education","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"entertainment","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"finance","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"fitness","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"food","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"games","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"government","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"health","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"kids","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"lifestyle","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"magazines","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"medical","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"music","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"navigation","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"news","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"personalization","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"photo","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"politics","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"productivity","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"security","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"shopping","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"social","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"sports","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"toString","comment":" Turn a category into its official String representation, as seen\nhere: .\n","type":"Pages.Manifest.Category.Category -> String.String"},{"name":"travel","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"utilities","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"weather","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"}],"binops":[]},{"name":"Pages.Navigation","comment":" `elm-pages` maintains a single `Maybe Navigation` state which is accessible from your `Route` modules through `app.navigation`.\n\nYou can use it to show a loading indicator while a page is loading:\n\n import Pages.Navigation as Navigation\n\n pageLoadingIndicator app =\n case app.navigation of\n Just (Navigation.Loading path _) ->\n Spinner.view\n\n Nothing ->\n emptyView\n\n emptyView : Html msg\n emptyView =\n Html.text \"\"\n\nYou can also use it to derive Pending UI or Optimistic UI from a pending form submission:\n\n import Form\n import Form.Handler\n import Pages.Navigation as Navigation\n\n view app =\n let\n optimisticProduct : Maybe Product\n optimisticProduct =\n case app.navigation of\n Just (Navigation.Submitting formData) ->\n formHandler\n |> Form.Handler.run formData\n |> Form.toResult\n |> Result.toMaybe\n\n Just (Navigation.LoadAfterSubmit formData path _) ->\n formHandler\n |> Form.Handler.run formData\n |> Form.toResult\n |> Result.toMaybe\n\n Nothing ->\n Nothing\n in\n -- our `productsView` function could show a loading spinner (Pending UI),\n -- or it could assume the product will be created successfully (Optimistic UI) and\n -- display it as a regular product in the list\n productsView optimisticProduct app.data.products\n\n allForms : Form.Handler.Handler String Product\n allForms =\n Form.Handler.init identity productForm\n\n editItemForm : Form.HtmlForm String Product input msg\n editItemForm =\n Debug.todo \"Form definition here\"\n\n@docs Navigation, LoadingState\n\n","unions":[{"name":"LoadingState","comment":" ","args":[],"cases":[["Redirecting",[]],["Load",[]],["ActionRedirect",[]]]},{"name":"Navigation","comment":" Represents the global page navigation state of the app.\n\n - `Loading` - navigating to a page, for example from a link click, or from a programmatic navigation with `Browser.Navigation.pushUrl`.\n - `Submitting` - submitting a form using the default submission strategy (note that Forms rendered with the [`Pages.Form.withConcurrent`](Pages-Form#withConcurrent) Option have their state managed in `app.concurrentSubmissions` instead of `app.navigation`).\n - `LoadAfterSubmit` - the state immediately after `Submitting` - allows you to continue using the `FormData` from a submission while a data reload or redirect is occurring.\n\n","args":[],"cases":[["Submitting",["Pages.FormData.FormData"]],["LoadAfterSubmit",["Pages.FormData.FormData","UrlPath.UrlPath","Pages.Navigation.LoadingState"]],["Loading",["UrlPath.UrlPath","Pages.Navigation.LoadingState"]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.PageUrl","comment":" Same as a Url in `elm/url`, but slightly more structured. The path portion of the URL is parsed into a `List String` representing each segment, and\nthe query params are parsed into a `Dict String (List String)`.\n\n@docs PageUrl, toUrl\n\n@docs parseQueryParams\n\n","unions":[],"aliases":[{"name":"PageUrl","comment":" ","args":[],"type":"{ protocol : Url.Protocol, host : String.String, port_ : Maybe.Maybe Basics.Int, path : UrlPath.UrlPath, query : Dict.Dict String.String (List.List String.String), fragment : Maybe.Maybe String.String }"}],"values":[{"name":"parseQueryParams","comment":" ","type":"String.String -> Dict.Dict String.String (List.List String.String)"},{"name":"toUrl","comment":" ","type":"Pages.PageUrl.PageUrl -> Url.Url"}],"binops":[]},{"name":"Pages.Script","comment":" An elm-pages Script is a way to execute an `elm-pages` `BackendTask`.\n\nRead more about using the `elm-pages` CLI to run (or bundle) scripts, plus a brief tutorial, at .\n\n@docs Script\n\n\n## Defining Scripts\n\n@docs withCliOptions, withoutCliOptions\n\n\n## File System Utilities\n\n@docs writeFile\n\n\n## Utilities\n\n@docs log\n\n\n## Errors\n\n@docs Error\n\n","unions":[{"name":"Error","comment":" The recoverable error type for file writes. You can use `BackendTask.allowFatal` if you want to allow the program to crash\nwith an error message if a file write is unsuccessful.\n","args":[],"cases":[["FileWriteError",[]]]}],"aliases":[{"name":"Script","comment":" The type for your `run` function that can be executed by `elm-pages run`.\n","args":[],"type":"Pages.Internal.Script.Script"}],"values":[{"name":"log","comment":" Log to stdout.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.log \"Hello!\"\n |> BackendTask.allowFatal\n )\n\n","type":"String.String -> BackendTask.BackendTask error ()"},{"name":"withCliOptions","comment":" Same as [`withoutCliOptions`](#withoutCliOptions), but allows you to define a CLI Options Parser so the user can\npass in additional options for the script.\n\nUses .\n\nRead more at .\n\n","type":"Cli.Program.Config cliOptions -> (cliOptions -> BackendTask.BackendTask FatalError.FatalError ()) -> Pages.Script.Script"},{"name":"withoutCliOptions","comment":" Define a simple Script (no CLI Options).\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.log \"Hello!\"\n |> BackendTask.allowFatal\n )\n\n","type":"BackendTask.BackendTask FatalError.FatalError () -> Pages.Script.Script"},{"name":"writeFile","comment":" Write a file to the file system.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.writeFile\n { path = \"hello.json\"\n , body = \"\"\"{ \"message\": \"Hello, World!\" }\"\"\"\n }\n |> BackendTask.allowFatal\n )\n\n","type":"{ path : String.String, body : String.String } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : Pages.Script.Error } ()"}],"binops":[]},{"name":"Pages.Url","comment":" Some of the `elm-pages` APIs will take internal URLs and ensure that they have the `canonicalSiteUrl` prepended.\n\nThat's the purpose for this type. If you have an external URL, like `Pages.Url.external \"https://google.com\"`,\nthen the canonicalUrl will not be prepended when it is used in a head tag.\n\nIf you refer to a local page, like `Route.Index |> Route.toPath |> Pages.Url.fromPath`, or `Pages.Url.fromPath`\n\n@docs Url, external, fromPath, toAbsoluteUrl, toString\n\n","unions":[{"name":"Url","comment":" ","args":[],"cases":[]}],"aliases":[],"values":[{"name":"external","comment":" ","type":"String.String -> Pages.Url.Url"},{"name":"fromPath","comment":" ","type":"UrlPath.UrlPath -> Pages.Url.Url"},{"name":"toAbsoluteUrl","comment":" ","type":"String.String -> Pages.Url.Url -> String.String"},{"name":"toString","comment":" ","type":"Pages.Url.Url -> String.String"}],"binops":[]},{"name":"PagesMsg","comment":" In `elm-pages`, Route modules have their own `Msg` type which can be used like a normal TEA (The Elm Architecture) app.\nBut the `Msg` defined in a `Route` module is wrapped in the `PagesMsg` type.\n\n@docs PagesMsg\n\nYou can wrap your Route Module's `Msg` using `fromMsg`.\n\n@docs fromMsg\n\n@docs map, noOp\n\n","unions":[],"aliases":[{"name":"PagesMsg","comment":" ","args":["userMsg"],"type":"Pages.Internal.Msg.Msg userMsg"}],"values":[{"name":"fromMsg","comment":"\n\n import Form\n import Pages.Form\n import PagesMsg exposing (PagesMsg)\n\n type Msg\n = ToggleMenu\n\n view :\n Maybe PageUrl\n -> Shared.Model\n -> Model\n -> App Data ActionData RouteParams\n -> View (PagesMsg Msg)\n view maybeUrl sharedModel model app =\n { title = \"My Page\"\n , view =\n [ button\n -- we need to wrap our Route module's `Msg` here so we have a `PagesMsg Msg`\n [ onClick (PagesMsg.fromMsg ToggleMenu) ]\n []\n\n -- `Pages.Form.renderHtml` gives us `Html (PagesMsg msg)`, so we don't need to wrap its Msg type\n , logoutForm\n |> Pages.Form.renderHtml []\n Pages.Form.Serial\n (Form.options \"logout\"\n |> Form.withOnSubmit (\\_ -> NewItemSubmitted)\n )\n app\n ]\n }\n\n","type":"userMsg -> PagesMsg.PagesMsg userMsg"},{"name":"map","comment":" ","type":"(a -> b) -> PagesMsg.PagesMsg a -> PagesMsg.PagesMsg b"},{"name":"noOp","comment":" A Msg that is handled by the elm-pages framework and does nothing. Helpful for when you don't want to register a callback.\n\n import Browser.Dom as Dom\n import PagesMsg exposing (PagesMsg)\n import Task\n\n resetViewport : Cmd (PagesMsg msg)\n resetViewport =\n Dom.setViewport 0 0\n |> Task.perform (\\() -> PagesMsg.noOp)\n\n","type":"PagesMsg.PagesMsg userMsg"}],"binops":[]},{"name":"Scaffold.Form","comment":" This module helps you with scaffolding a form in `elm-pages`, similar to how rails generators are used to scaffold out forms to\nget up and running quickly with the starting point for a form with different field types. See also [`Scaffold.Route`](Scaffold-Route).\n\nSee the `AddRoute` script in the starter template for an example. It's usually easiest to modify that script as a starting\npoint rather than using this API from scratch.\n\nUsing the `AddRoute` script from the default starter template, you can run a command like this:\n\n`npx elm-pages run AddRoute Profile.Username_.Edit first last bio:textarea dob:date` to generate a Route module `app/Route/Profile/Username_/Edit.elm`\nwith the wiring form a `Form`.\n\n[Learn more about writing and running elm-pages Scripts for scaffolding](https://elm-pages-v3.netlify.app/docs/elm-pages-scripts#scaffolding-a-route-module).\n\n@docs Kind, provide, restArgsParser\n\n@docs Context\n\n@docs recordEncoder, fieldEncoder\n\n","unions":[{"name":"Kind","comment":" ","args":[],"cases":[["FieldInt",[]],["FieldText",[]],["FieldTextarea",[]],["FieldFloat",[]],["FieldTime",[]],["FieldDate",[]],["FieldCheckbox",[]]]}],"aliases":[{"name":"Context","comment":" ","args":[],"type":"{ errors : Elm.Expression, submitting : Elm.Expression, submitAttempted : Elm.Expression, data : Elm.Expression, expression : Elm.Expression }"}],"values":[{"name":"fieldEncoder","comment":" A lower-level, more granular version of `recordEncoder` - lets you generate a JSON Encoder `Expression` for an individual Field rather than a group of Fields.\n","type":"Elm.Expression -> String.String -> Scaffold.Form.Kind -> Elm.Expression"},{"name":"provide","comment":" ","type":"{ fields : List.List ( String.String, Scaffold.Form.Kind ), elmCssView : Basics.Bool, view : { formState : Scaffold.Form.Context, params : List.List { name : String.String, kind : Scaffold.Form.Kind, param : Elm.Expression } } -> Elm.Expression } -> Maybe.Maybe { formHandlers : Elm.Expression, form : Elm.Expression, declarations : List.List Elm.Declaration }"},{"name":"recordEncoder","comment":" Generate a JSON Encoder for the form fields. This can be helpful for sending the validated form data through a\nBackendTask.Custom or to an external API from your scaffolded Route Module code.\n","type":"Elm.Expression -> List.List ( String.String, Scaffold.Form.Kind ) -> Elm.Expression"},{"name":"restArgsParser","comment":" This parser handles the following field types (or `text` if none is provided):\n\n - `text`\n - `textarea`\n - `checkbox`\n - `time`\n - `date`\n\nThe naming convention follows the same naming as the HTML form field elements or attributes that are used to represent them.\nIn addition to using the appropriate field type, this will also give you an Elm type with the corresponding base type (like `Date` for `date` or `Bool` for `checkbox`).\n\n","type":"Cli.Option.Option (List.List String.String) (List.List ( String.String, Scaffold.Form.Kind )) Cli.Option.RestArgsOption"}],"binops":[]},{"name":"Scaffold.Route","comment":" This module provides some functions for scaffolding code for a new Route Module. It uses [`elm-codegen`'s API](https://package.elm-lang.org/packages/mdgriffith/elm-codegen/latest/) for generating code.\n\nTypically you'll want to use this via the `elm-pages run` CLI command. The default starter template includes a Script that uses these functions, which you can tweak to customize your scaffolding commands.\n[Learn more about writing and running elm-pages Scripts for scaffolding](https://elm-pages-v3.netlify.app/docs/elm-pages-scripts#scaffolding-a-route-module).\n\nIt's typically easiest to modify the `AddRoute` script from the starter template and adjust it to your needs rather than writing one from scratch.\n\n\n## Initializing the Generator Builder\n\nThese functions mirror the `RouteBuilder` API that you use in your Route modules to define your route. The difference is that\ninstead of defining a route, this is defining a code generator for a Route module.\n\n@docs buildWithLocalState, buildWithSharedState, buildNoState, Builder\n\n@docs Type\n\n\n## Generating Server-Rendered Pages\n\n@docs serverRender\n\n\n## Generating pre-rendered pages\n\n@docs preRender, single\n\n\n## Including Additional elm-codegen Declarations\n\n@docs addDeclarations\n\n\n## CLI Options Parsing Helpers\n\n@docs moduleNameCliArg\n\n","unions":[{"name":"Builder","comment":" ","args":[],"cases":[]},{"name":"Type","comment":" ","args":[],"cases":[["Alias",["Elm.Annotation.Annotation"]],["Custom",["List.List Elm.Variant"]]]}],"aliases":[],"values":[{"name":"addDeclarations","comment":" The helpers in this module help you generate a Route module file with the core boilerplate abstracted away.\n\nYou can also define additional top-level declarations in the generated Route module using this helper.\n\n","type":"List.List Elm.Declaration -> Scaffold.Route.Builder -> Scaffold.Route.Builder"},{"name":"buildNoState","comment":" ","type":"{ view : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"buildWithLocalState","comment":" ","type":"{ view : { shared : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { shared : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { routeParams : Elm.Expression, path : Elm.Expression, shared : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Scaffold.Route.Type, model : Scaffold.Route.Type } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"buildWithSharedState","comment":" ","type":"{ view : { shared : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { shared : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { routeParams : Elm.Expression, path : Elm.Expression, shared : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Scaffold.Route.Type, model : Scaffold.Route.Type } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"moduleNameCliArg","comment":" A positional argument for elm-cli-options-parser that does a Regex validation to check that the module name is a valid Elm Route module name.\n","type":"Cli.Option.Option from String.String builderState -> Cli.Option.Option from (List.List String.String) builderState"},{"name":"preRender","comment":" ","type":"{ data : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression ), pages : Elm.Expression, head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"},{"name":"serverRender","comment":" ","type":"{ data : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression ), action : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"},{"name":"single","comment":" ","type":"{ data : ( Scaffold.Route.Type, Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"}],"binops":[]},{"name":"Server.Request","comment":" A `Server.Request.Parser` lets you send a `Server.Response.Response` based on an incoming HTTP request. For example,\nusing a `Server.Request.Parser`, you could check a session cookie to decide whether to respond by rendering a page\nfor the logged-in user, or else respond with an HTTP redirect response (see the [`Server.Response` docs](Server-Response)).\n\nYou can access the incoming HTTP request's:\n\n - Headers\n - Cookies\n - [`method`](#method)\n - URL query parameters\n - [`requestTime`](#requestTime) (as a `Time.Posix`)\n\nNote that this data is not available for pre-rendered pages or pre-rendered API Routes, only for server-rendered pages.\nThis is because when a page is pre-rendered, there _is_ no incoming HTTP request to respond to, it is rendered before a user\nrequests the page and then the pre-rendered page is served as a plain file (without running your Route Module).\n\nThat's why `RouteBuilder.preRender` has `data : RouteParams -> BackendTask Data`:\n\n import BackendTask exposing (BackendTask)\n import RouteBuilder exposing (StatelessRoute)\n\n type alias Data =\n {}\n\n data : RouteParams -> BackendTask Data\n data routeParams =\n BackendTask.succeed Data\n\n route : StatelessRoute RouteParams Data ActionData\n route =\n RouteBuilder.preRender\n { data = data\n , head = head\n , pages = pages\n }\n |> RouteBuilder.buildNoState { view = view }\n\nA server-rendered Route Module _does_ have access to a user's incoming HTTP request because it runs every time the page\nis loaded. That's why `data` is a `Request.Parser` in server-rendered Route Modules. Since you have an incoming HTTP request for server-rendered routes,\n`RouteBuilder.serverRender` has `data : RouteParams -> Request.Parser (BackendTask (Response Data))`. That means that you\ncan use the incoming HTTP request data to choose how to respond. For example, you could check for a dark-mode preference\ncookie and render a light- or dark-themed page and render a different page.\n\n\n## Building a Request Parser\n\n`Request.Parser` means you can pull out data from the incoming HTTP request.\n\nWith a pre-rendered Route, we wouldn't be able to access the query param before rendering the page, but for a server-rendered Route\nwe can dynamically respond to the incoming HTTP request (including its query params).\n\nFor example, we might send the user an email with a link like `https://example.com/products/123?coupon=xmas22`.\nWhen the user clicks the link, we want to respond based on that `coupon` query parameter and render the page with the coupon data\n(either by directly checking the coupon with our DB through [`BackendTask.Custom`](BackendTask#Custom), or through an API request with [`BackendTask.Http`](BackendTask#Http)).\n\n module Route.Products.Id_ exposing (..)\n\n import BackendTask exposing (BackendTask)\n import RouteBuilder exposing (StatelessRoute)\n import Server.Request as Request exposing (Request)\n import Server.Response as Response exposing (Response)\n\n type alias Data =\n { coupon : Maybe Coupon }\n\n data :\n RouteParams\n -> Request.Parser (BackendTask (Response Data))\n data routeParams =\n Request.queryParam \"coupon\"\n |> Request.map\n (\\maybeCouponCode ->\n maybeCouponCode\n |> lookupCoupon\n |> BackendTask.map (\\maybeCoupon ->\n (Response.render { coupon = maybeCoupon })\n )\n\n route : StatelessRoute RouteParams Data ActionData\n route =\n RouteBuilder.serverRender\n { head = head\n , data = data\n }\n |> RouteBuilder.buildNoState { view = view }\n\n@docs Request\n\n@docs header, headers\n\n\n## Body\n\n@docs body, jsonBody\n\n\n## Forms\n\n@docs formData, formDataWithServerValidation\n\n@docs rawFormData\n\n\n## Content Type\n\n@docs matchesContentType\n\n\n## Direct Values\n\n@docs rawUrl\n\n@docs method, cookies\n\n@docs requestTime\n\n@docs acceptContentTypes\n\n\n## Query Parameters\n\n@docs queryParam, queryParams\n\n\n## Cookies\n\n@docs cookie\n\n\n## Headers\n\n\n## Method Type\n\n@docs Method, methodToString\n\n","unions":[{"name":"Method","comment":" ","args":[],"cases":[["Connect",[]],["Delete",[]],["Get",[]],["Head",[]],["Options",[]],["Patch",[]],["Post",[]],["Put",[]],["Trace",[]],["NonStandard",["String.String"]]]}],"aliases":[{"name":"Request","comment":" ","args":[],"type":"Internal.Request.Request"}],"values":[{"name":"acceptContentTypes","comment":" ","type":"( String.String, List.List String.String ) -> Server.Request.Request -> Basics.Bool"},{"name":"body","comment":" ","type":"Server.Request.Request -> Maybe.Maybe String.String"},{"name":"cookie","comment":" ","type":"String.String -> Server.Request.Request -> Maybe.Maybe String.String"},{"name":"cookies","comment":" ","type":"Server.Request.Request -> Dict.Dict String.String String.String"},{"name":"formData","comment":" Takes a [`Form.Handler.Handler`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form-Handler) and\nparses the raw form data into a [`Form.Validated`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Validated) value.\n\nThis is the standard pattern for dealing with form data in `elm-pages`. You can share your code for your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Form)\ndefinitions between your client and server code, using this function to parse the raw form data into a `Form.Validated` value for the backend,\nand [`Pages.Form`](Pages-Form) to render the `Form` on the client.\n\nSince we are sharing the `Form` definition between frontend and backend, we get to re-use the same validation logic so we gain confidence that\nthe validation errors that the user sees on the client are protected on our backend, and vice versa.\n\n import BackendTask\n import Form\n import Server.Request\n\n type Action\n = Delete\n | CreateOrUpdate Post\n\n formHandlers : Form.Handler.Handler String Action\n formHandlers =\n deleteForm\n |> Form.Handler.init (\\() -> Delete)\n |> Form.Handler.with CreateOrUpdate createOrUpdateForm\n\n deleteForm : Form.HtmlForm String () input msg\n\n createOrUpdateForm : Form.HtmlForm String Post Post msg\n\n action :\n RouteParams\n -> Server.Request.Parser (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response ActionData ErrorPage.ErrorPage))\n action routeParams =\n Server.Request.map\n (\\( formResponse, parsedForm ) ->\n case parsedForm of\n Form.Valid Delete ->\n deletePostBySlug routeParams.slug\n |> BackendTask.map\n (\\() -> Route.redirectTo Route.Index)\n\n Form.Valid (CreateOrUpdate post) ->\n let\n createPost : Bool\n createPost =\n okForm.slug == \"new\"\n in\n createOrUpdatePost post\n |> BackendTask.map\n (\\() ->\n Route.redirectTo\n (Route.Admin__Slug_ { slug = okForm.slug })\n )\n\n Form.Invalid _ invalidForm ->\n BackendTask.succeed\n (Server.Response.render\n { errors = formResponse }\n )\n )\n (Server.Request.formData formHandlers)\n\nYou can handle form submissions as either GET or POST requests. Note that for security reasons, it's important to performing mutations with care from GET requests,\nsince a GET request can be performed from an outside origin by embedding an image that points to the given URL. So a logout submission should be protected by\nusing `POST` to ensure that you can't log users out by embedding an image with a logout URL in it.\n\nIf the request has HTTP method `GET`, the form data will come from the query parameters.\n\nIf the request has the HTTP method `POST` _and_ the `Content-Type` is `application/x-www-form-urlencoded`, it will return the\ndecoded form data from the body of the request.\n\nOtherwise, this `Parser` will not match.\n\nNote that in server-rendered Route modules, your `data` function will handle `GET` requests (and will _not_ receive any `POST` requests),\nwhile your `action` will receive POST (and other non-GET) requests.\n\nBy default, [`Form`]'s are rendered with a `POST` method, and you can configure them to submit `GET` requests using [`withGetMethod`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#withGetMethod).\nSo you will want to handle any `Form`'s rendered using `withGetMethod` in your Route's `data` function, or otherwise handle forms in `action`.\n\n","type":"Form.Handler.Handler error combined -> Server.Request.Request -> Maybe.Maybe ( Form.ServerResponse error, Form.Validated error combined )"},{"name":"formDataWithServerValidation","comment":" ","type":"Pages.Form.Handler error combined -> Server.Request.Request -> Maybe.Maybe (BackendTask.BackendTask FatalError.FatalError (Result.Result (Form.ServerResponse error) ( Form.ServerResponse error, combined )))"},{"name":"header","comment":" ","type":"String.String -> Server.Request.Request -> Maybe.Maybe String.String"},{"name":"headers","comment":" ","type":"Server.Request.Request -> Dict.Dict String.String String.String"},{"name":"jsonBody","comment":" ","type":"Json.Decode.Decoder value -> Server.Request.Request -> Maybe.Maybe (Result.Result Json.Decode.Error value)"},{"name":"matchesContentType","comment":" ","type":"String.String -> Server.Request.Request -> Basics.Bool"},{"name":"method","comment":" ","type":"Server.Request.Request -> Server.Request.Method"},{"name":"methodToString","comment":" Gets the HTTP Method as a String, like 'GET', 'PUT', etc.\n","type":"Server.Request.Method -> String.String"},{"name":"queryParam","comment":" Get `Nothing` if the query param with the given name is missing, or `Just` the value if it is present.\n\nIf there are multiple query params with the same name, the first one is returned.\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc\n -- parses into: Just \"abc\"\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc&coupon=xyz\n -- parses into: Just \"abc\"\n\n queryParam \"coupon\"\n\n -- url: http://example.com\n -- parses into: Nothing\n\nSee also [`queryParams`](#queryParams), or [`rawUrl`](#rawUrl) if you need something more low-level.\n\n","type":"String.String -> Server.Request.Request -> Maybe.Maybe String.String"},{"name":"queryParams","comment":" Gives all query params from the URL.\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc\n -- parses into: Dict.fromList [(\"coupon\", [\"abc\"])]\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc&coupon=xyz\n -- parses into: Dict.fromList [(\"coupon\", [\"abc\", \"xyz\"])]\n\n","type":"Server.Request.Request -> Dict.Dict String.String (List.List String.String)"},{"name":"rawFormData","comment":" Get the raw key-value pairs from a form submission.\n\nIf the request has the HTTP method `GET`, it will return the query parameters.\n\nIf the request has the HTTP method `POST` _and_ the `Content-Type` is `application/x-www-form-urlencoded`, it will return the\ndecoded form data from the body of the request.\n\nOtherwise, this `Parser` will not match.\n\nNote that in server-rendered Route modules, your `data` function will handle `GET` requests (and will _not_ receive any `POST` requests),\nwhile your `action` will receive POST (and other non-GET) requests.\n\nBy default, [`Form`]'s are rendered with a `POST` method, and you can configure them to submit `GET` requests using [`withGetMethod`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#withGetMethod).\nSo you will want to handle any `Form`'s rendered using `withGetMethod` in your Route's `data` function, or otherwise handle forms in `action`.\n\n","type":"Server.Request.Request -> Maybe.Maybe (List.List ( String.String, String.String ))"},{"name":"rawUrl","comment":" ","type":"Server.Request.Request -> String.String"},{"name":"requestTime","comment":" ","type":"Server.Request.Request -> Time.Posix"}],"binops":[]},{"name":"Server.Response","comment":"\n\n\n## Responses\n\n@docs Response\n\nThere are two top-level response types:\n\n1. Server Responses\n2. Render Responses\n\nA Server Response is a way to directly send a low-level server response, with no additional magic. You can set a String body,\na list of headers, the status code, etc. The Server Response helpers like `json` and `temporaryRedirect` are just helpers for\nbuilding up those low-level Server Responses.\n\nRender Responses are a little more special in the way they are connected to your elm-pages app. They allow you to render\nthe current Route Module. To do that, you'll need to pass along the `data` for your Route Module.\n\nYou can use `withHeader` and `withStatusCode` to customize either type of Response (Server Responses or Render Responses).\n\n\n## Server Responses\n\n@docs json, plainText, temporaryRedirect, permanentRedirect\n\n\n## Custom Responses\n\n@docs emptyBody, body, bytesBody, base64Body\n\n\n## Render Responses\n\n@docs render\n\n\n## Rendering Error Pages\n\n@docs errorPage, mapError\n\n@docs map\n\n\n## Amending Responses\n\n@docs withHeader, withHeaders, withStatusCode, withSetCookieHeader\n\n\n## Internals\n\n@docs toJson\n\n","unions":[],"aliases":[{"name":"Response","comment":" ","args":["data","error"],"type":"PageServerResponse.PageServerResponse data error"}],"values":[{"name":"base64Body","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"body","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"bytesBody","comment":" ","type":"Bytes.Bytes -> Server.Response.Response data error"},{"name":"emptyBody","comment":" ","type":"Server.Response.Response data error"},{"name":"errorPage","comment":" ","type":"errorPage -> Server.Response.Response data errorPage"},{"name":"json","comment":" ","type":"Json.Encode.Value -> Server.Response.Response data error"},{"name":"map","comment":" ","type":"(data -> mappedData) -> Server.Response.Response data error -> Server.Response.Response mappedData error"},{"name":"mapError","comment":" ","type":"(errorPage -> mappedErrorPage) -> Server.Response.Response data errorPage -> Server.Response.Response data mappedErrorPage"},{"name":"permanentRedirect","comment":" Build a 308 permanent redirect response.\n\nPermanent redirects tell the browser that a resource has permanently moved. If you redirect because a user is not logged in,\nthen you **do not** want to use a permanent redirect because the page they are looking for hasn't changed, you are just\ntemporarily pointing them to a new page since they need to authenticate.\n\nPermanent redirects are aggressively cached so be careful not to use them when you mean to use temporary redirects instead.\n\nIf you need to specifically rely on a 301 permanent redirect (see on the difference between 301 and 308),\nuse `customResponse` instead.\n\n","type":"String.String -> Server.Response.Response data error"},{"name":"plainText","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"render","comment":" ","type":"data -> Server.Response.Response data error"},{"name":"temporaryRedirect","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"toJson","comment":" ","type":"Server.Response.Response Basics.Never Basics.Never -> Json.Encode.Value"},{"name":"withHeader","comment":" ","type":"String.String -> String.String -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withHeaders","comment":" ","type":"List.List ( String.String, String.String ) -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withSetCookieHeader","comment":" ","type":"Server.SetCookie.SetCookie -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withStatusCode","comment":" ","type":"Basics.Int -> Server.Response.Response data Basics.Never -> Server.Response.Response data Basics.Never"}],"binops":[]},{"name":"Server.Session","comment":" You can manage server state with HTTP cookies using this Server.Session API. Server-rendered pages define a `Server.Request.Parser`\nto choose which requests to respond to and how to extract structured data from the incoming request.\n\n\n## Using Sessions in a Request.Parser\n\nUsing these functions, you can store and read session data in cookies to maintain state between requests.\nFor example, TODO:\n\n action : RouteParams -> Request.Parser (BackendTask (Response ActionData ErrorPage))\n action routeParams =\n MySession.withSession\n (Request.formDataWithServerValidation (form |> Form.initCombinedServer identity))\n (\\nameResultData session ->\n nameResultData\n |> BackendTask.map\n (\\nameResult ->\n case nameResult of\n Err errors ->\n ( session\n |> Result.withDefault Nothing\n |> Maybe.withDefault Session.empty\n , Response.render\n { errors = errors\n }\n )\n\n Ok ( _, name ) ->\n ( session\n |> Result.withDefault Nothing\n |> Maybe.withDefault Session.empty\n |> Session.insert \"name\" name\n |> Session.withFlash \"message\" (\"Welcome \" ++ name ++ \"!\")\n , Route.redirectTo Route.Greet\n )\n )\n )\n\nThe elm-pages framework will manage signing these cookies using the `secrets : BackendTask (List String)` you pass in.\nThat means that the values you set in your session will be directly visible to anyone who has access to the cookie\n(so don't directly store sensitive data in your session). Since the session cookie is signed using the secret you provide,\nthe cookie will be invalidated if it is tampered with because it won't match when elm-pages verifies that it has been\nsigned with your secrets. Of course you need to provide secure secrets and treat your secrets with care.\n\n\n### Rotating Secrets\n\nThe first String in `secrets : BackendTask (List String)` will be used to sign sessions, while the remaining String's will\nstill be used to attempt to \"unsign\" the cookies. So if you have a single secret:\n\n Session.withSession\n { name = \"mysession\"\n , secrets =\n BackendTask.map List.singleton\n (Env.expect \"SESSION_SECRET2022-09-01\")\n , options = Nothing\n }\n\nThen you add a second secret\n\n Session.withSession\n { name = \"mysession\"\n , secrets =\n BackendTask.map2\n (\\newSecret oldSecret -> [ newSecret, oldSecret ])\n (Env.expect \"SESSION_SECRET2022-12-01\")\n (Env.expect \"SESSION_SECRET2022-09-01\")\n , options = Nothing\n }\n\nThe new secret (`2022-12-01`) will be used to sign all requests. This API always re-signs using the newest secret in the list\nwhenever a new request comes in (even if the Session key-value pairs are unchanged), so these cookies get \"refreshed\" with the latest\nsigning secret when a new request comes in.\n\nHowever, incoming requests with a cookie signed using the old secret (`2022-09-01`) will still successfully be unsigned\nbecause they are still in the rotation (and then subsequently \"refreshed\" and signed using the new secret).\n\nThis allows you to rotate your session secrets (for security purposes). When a secret goes out of the rotation,\nit will invalidate all cookies signed with that. For example, if we remove our old secret from the rotation:\n\n Session.withSession\n { name = \"mysession\"\n , secrets =\n BackendTask.map List.singleton\n (Env.expect \"SESSION_SECRET2022-12-01\")\n , options = Nothing\n }\n\nAnd then a user makes a request but had a session signed with our old secret (`2022-09-01`), the session will be invalid\n(so `withSession` would parse the session for that request as `Nothing`). It's standard for cookies to have an expiration date,\nso there's nothing wrong with an old session expiring (and the browser will eventually delete old cookies), just be aware of that when rotating secrets.\n\n@docs withSession, withSessionResult\n\n@docs NotLoadedReason\n\n\n## Creating and Updating Sessions\n\n@docs Session, empty, get, insert, remove, update, withFlash\n\n","unions":[{"name":"NotLoadedReason","comment":" ","args":[],"cases":[["NoSessionCookie",[]],["InvalidSessionCookie",[]]]},{"name":"Session","comment":" ","args":[],"cases":[]}],"aliases":[],"values":[{"name":"empty","comment":" ","type":"Server.Session.Session"},{"name":"get","comment":" ","type":"String.String -> Server.Session.Session -> Maybe.Maybe String.String"},{"name":"insert","comment":" ","type":"String.String -> String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"remove","comment":" ","type":"String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"update","comment":" ","type":"String.String -> (Maybe.Maybe String.String -> Maybe.Maybe String.String) -> Server.Session.Session -> Server.Session.Session"},{"name":"withFlash","comment":" ","type":"String.String -> String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"withSession","comment":" ","type":"{ name : String.String, secrets : BackendTask.BackendTask error (List.List String.String), options : Maybe.Maybe Server.SetCookie.Options } -> (Server.Session.Session -> BackendTask.BackendTask error ( Server.Session.Session, Server.Response.Response data errorPage )) -> Server.Request.Request -> BackendTask.BackendTask error (Server.Response.Response data errorPage)"},{"name":"withSessionResult","comment":" ","type":"{ name : String.String, secrets : BackendTask.BackendTask error (List.List String.String), options : Maybe.Maybe Server.SetCookie.Options } -> (Result.Result Server.Session.NotLoadedReason Server.Session.Session -> BackendTask.BackendTask error ( Server.Session.Session, Server.Response.Response data errorPage )) -> Server.Request.Request -> BackendTask.BackendTask error (Server.Response.Response data errorPage)"}],"binops":[]},{"name":"Server.SetCookie","comment":" Server-rendered pages in your `elm-pages` can set cookies. `elm-pages` provides two high-level ways to work with cookies:\n\n - [`Server.Session.withSession`](Server-Session#withSession)\n - [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader)\n\n[`Server.Session.withSession`](Server-Session#withSession) provides a high-level way to manage key-value pairs of data using cookie storage,\nwhereas `Server.Response.withSetCookieHeader` gives a more low-level tool for setting cookies. It's often best to use the\nmost high-level tool that will fit your use case.\n\nYou can learn more about the basics of cookies in the Web Platform in these helpful MDN documentation pages:\n\n - \n - \n\n@docs SetCookie, setCookie\n\n\n## Building Options\n\nUsually you'll want to start by creating default `Options` with `options` and then overriding defaults using the `with...` helpers.\n\n import Server.SetCookie as SetCookie\n\n options : SetCookie.Options\n options =\n SetCookie.options\n |> SetCookie.nonSecure\n |> SetCookie.withMaxAge 123\n |> SetCookie.makeVisibleToJavaScript\n |> SetCookie.withoutPath\n |> SetCookie.setCookie \"id\" \"a3fWa\"\n\n@docs Options, options\n\n@docs SameSite, withSameSite\n\n@docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, withDomain, withExpiration, withMaxAge, withPath, withoutPath\n\n\n## Internal\n\n@docs toString\n\n","unions":[{"name":"SameSite","comment":" Possible values for [the cookie's same-site value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value).\n\nThe default option is [`Lax`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#lax) (Lax does not send\ncookies in cross-origin requests so it is a good default for most cases, but [`Strict`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#strict)\nis even more restrictive).\n\nOverride the default option using [`withSameSite`](#withSameSite).\n\n","args":[],"cases":[["Strict",[]],["Lax",[]],["None",[]]]}],"aliases":[{"name":"Options","comment":" The set of possible configuration options. You can configure this record directly, or use the `with...` helpers.\n","args":[],"type":"{ expiration : Maybe.Maybe Time.Posix, visibleToJavaScript : Basics.Bool, maxAge : Maybe.Maybe Basics.Int, path : Maybe.Maybe String.String, domain : Maybe.Maybe String.String, secure : Basics.Bool, sameSite : Maybe.Maybe Server.SetCookie.SameSite }"},{"name":"SetCookie","comment":" ","args":[],"type":"{ name : String.String, value : String.String, options : Server.SetCookie.Options }"}],"values":[{"name":"makeVisibleToJavaScript","comment":" The default option in this API is for HttpOnly cookies .\n\nCookies can be exposed so you can read them from JavaScript using `Document.cookie`. When this is intended and understood\nthen there's nothing unsafe about that (for example, if you are setting a `darkMode` cookie and what to access that\ndynamically). In this API you opt into exposing a cookie you set to JavaScript to ensure cookies aren't exposed to JS unintentionally.\n\nIn general if you can accomplish your goal using HttpOnly cookies (i.e. not using `makeVisibleToJavaScript`) then\nit's a good practice. With server-rendered `elm-pages` applications you can often manage your session state by pulling\nin session data from cookies in a `BackendTask` (which is resolved server-side before it ever reaches the browser).\n\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"nonSecure","comment":" Secure (only sent over https, or localhost on http) is the default. This overrides that and\nremoves the `Secure` attribute from the cookie.\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"options","comment":" Initialize the default `SetCookie` `Options`. Can be configured directly through a record update, or with `withExpiration`, etc.\n","type":"Server.SetCookie.Options"},{"name":"setCookie","comment":" Create a `SetCookie` record with the given name, value, and [`Options`](Options]. To add a `Set-Cookie` header, you can\npass this value with [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader). Or for more low-level\nuses you can stringify the value manually with [`toString`](#toString).\n","type":"String.String -> String.String -> Server.SetCookie.Options -> Server.SetCookie.SetCookie"},{"name":"toString","comment":" Usually you'll want to use [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader) instead.\n\nThis is a low-level helper that's there in case you want it but most users will never need this.\n\n","type":"Server.SetCookie.SetCookie -> String.String"},{"name":"withDomain","comment":" Sets the `Set-Cookie`'s [`Domain`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value).\n","type":"String.String -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withExpiration","comment":" ","type":"Time.Posix -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withImmediateExpiration","comment":" Sets [`Expires`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#expiresdate) to `Time.millisToPosix 0`,\nwhich effectively tells the browser to delete the cookie immediately (by giving it an expiration date in the past).\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withMaxAge","comment":" Sets the `Set-Cookie`'s [`Max-Age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#max-agenumber).\n","type":"Basics.Int -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withPath","comment":" Sets the `Set-Cookie`'s [`Path`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value).\n\nThe default value is `/`, which will match any sub-directories or the root directory. See also [\\`withoutPath](#withoutPath)\n\n","type":"String.String -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withSameSite","comment":" The default SameSite policy is Lax if one is not explicitly set. See the SameSite section in .\n","type":"Server.SetCookie.SameSite -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withoutPath","comment":"\n\n> If the server omits the Path attribute, the user agent will use the \"directory\" of the request-uri's path component as the default value.\n\nSource: . See .\n\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"}],"binops":[]},{"name":"UrlPath","comment":" Represents the path portion of a URL (not query parameters, fragment, protocol, port, etc.).\n\nThis helper lets you combine together path parts without worrying about having too many or too few slashes.\nThese two examples will result in the same URL, even though the first example has trailing and leading slashes, and the\nsecond does not.\n\n UrlPath.join [ \"/blog/\", \"/post-1/\" ]\n |> UrlPath.toAbsolute\n --> \"/blog/post-1\"\n\n UrlPath.join [ \"blog\", \"post-1\" ]\n |> UrlPath.toAbsolute\n --> \"/blog/post-1\"\n\nWe can also safely join Strings that include multiple path parts, a single path part per string, or a mix of the two:\n\n UrlPath.join [ \"/articles/archive/\", \"1977\", \"06\", \"10\", \"post-1\" ]\n |> UrlPath.toAbsolute\n --> \"/articles/archive/1977/06/10/post-1\"\n\n\n## Creating UrlPaths\n\n@docs UrlPath, join, fromString\n\n\n## Turning UrlPaths to String\n\n@docs toAbsolute, toRelative, toSegments\n\n","unions":[],"aliases":[{"name":"UrlPath","comment":" The path portion of the URL, normalized to ensure that path segments are joined with `/`s in the right places (no doubled up or missing slashes).\n","args":[],"type":"List.List String.String"}],"values":[{"name":"fromString","comment":" Create a UrlPath from a path String.\n\n UrlPath.fromString \"blog/post-1/\"\n |> UrlPath.toAbsolute\n |> Expect.equal \"/blog/post-1\"\n\n","type":"String.String -> UrlPath.UrlPath"},{"name":"join","comment":" Turn a Path to a relative URL.\n","type":"UrlPath.UrlPath -> UrlPath.UrlPath"},{"name":"toAbsolute","comment":" Turn a UrlPath to an absolute URL (with no trailing slash).\n","type":"UrlPath.UrlPath -> String.String"},{"name":"toRelative","comment":" Turn a UrlPath to a relative URL.\n","type":"UrlPath.UrlPath -> String.String"},{"name":"toSegments","comment":" ","type":"String.String -> List.List String.String"}],"binops":[]}] \ No newline at end of file diff --git a/examples/end-to-end/app/Route/ErrorHandling.elm b/examples/end-to-end/app/Route/ErrorHandling.elm index 9a17809f..57cdc019 100644 --- a/examples/end-to-end/app/Route/ErrorHandling.elm +++ b/examples/end-to-end/app/Route/ErrorHandling.elm @@ -8,7 +8,7 @@ import Html.Styled exposing (text) import Pages.PageUrl exposing (PageUrl) import PagesMsg exposing (PagesMsg) import RouteBuilder exposing (App, StatefulRoute, StatelessRoute) -import Server.Request as Request exposing (Parser, Request) +import Server.Request as Request exposing (Request) import Server.Response as Response exposing (Response) import Shared import View exposing (View) diff --git a/examples/end-to-end/app/Route/Test/BasicAuth.elm b/examples/end-to-end/app/Route/Test/BasicAuth.elm index 4f0c85d1..e438a674 100644 --- a/examples/end-to-end/app/Route/Test/BasicAuth.elm +++ b/examples/end-to-end/app/Route/Test/BasicAuth.elm @@ -9,7 +9,7 @@ import Html.Styled exposing (div, text) import Pages.PageUrl exposing (PageUrl) import PagesMsg exposing (PagesMsg) import RouteBuilder exposing (App, StatefulRoute, StatelessRoute) -import Server.Request as Request exposing (Parser, Request) +import Server.Request as Request exposing (Request) import Server.Response as Response exposing (Response) import Shared import View exposing (View) diff --git a/examples/end-to-end/app/Route/Test/ResponseHeaders.elm b/examples/end-to-end/app/Route/Test/ResponseHeaders.elm index 2ab06f76..22a75f26 100644 --- a/examples/end-to-end/app/Route/Test/ResponseHeaders.elm +++ b/examples/end-to-end/app/Route/Test/ResponseHeaders.elm @@ -2,15 +2,13 @@ module Route.Test.ResponseHeaders exposing (ActionData, Data, Model, Msg, route) import BackendTask exposing (BackendTask) import BackendTask.File -import BuildError exposing (BuildError) import ErrorPage exposing (ErrorPage) import FatalError exposing (FatalError) import Head import Html.Styled exposing (div, text) -import Pages.PageUrl exposing (PageUrl) import PagesMsg exposing (PagesMsg) import RouteBuilder exposing (App, StatefulRoute, StatelessRoute) -import Server.Request as Request exposing (Parser, Request) +import Server.Request exposing (Request) import Server.Response as Response exposing (Response) import Shared import View exposing (View) diff --git a/src/Internal/Request.elm b/src/Internal/Request.elm index 9caf9512..0d6349ee 100644 --- a/src/Internal/Request.elm +++ b/src/Internal/Request.elm @@ -1,4 +1,4 @@ -module Internal.Request exposing (Parser(..), Request(..), RequestRecord, fakeRequest, toRequest) +module Internal.Request exposing (Request(..), RequestRecord, fakeRequest, toRequest) import CookieParser import Dict exposing (Dict) @@ -6,10 +6,6 @@ import Json.Decode as Decode import Time -type Parser decodesTo validationError - = Parser (Decode.Decoder ( Result validationError decodesTo, List validationError )) - - type Request = Request RequestRecord diff --git a/src/Server/Request.elm b/src/Server/Request.elm index ff4337b2..9f5fab00 100644 --- a/src/Server/Request.elm +++ b/src/Server/Request.elm @@ -1,113 +1,19 @@ module Server.Request exposing - ( Parser - , succeed, fromResult, skip + ( Request + , header, headers + , body, jsonBody , formData, formDataWithServerValidation , rawFormData + , matchesContentType , rawUrl - , method, rawBody, rawHeaders - , requestTime, optionalHeader, expectContentType - , acceptMethod, acceptContentTypes - , map, map2, oneOf, andMap, andThen - , queryParam, expectQueryParam, queryParams + , method, cookies + , requestTime + , acceptContentTypes + , queryParam, queryParams , cookie - , expectHeader - , File, expectMultiPartFormPost - , expectBody - , map3, map4, map5, map6, map7, map8, map9 , Method(..), methodToString - , errorsToString, errorToString, getDecoder, ValidationError - , Request, body, cookies, header, jsonBody, matchesContentType ) -{-| - -@docs Parser - -@docs succeed, fromResult, skip - - -## Forms - -@docs formData, formDataWithServerValidation - -@docs rawFormData - - -## Direct Values - -@docs rawUrl - -@docs method, rawBody, allCookies, rawHeaders - -@docs requestTime, optionalHeader, expectContentType, expectJsonBody - -@docs acceptMethod, acceptContentTypes - - -## Transforming - -@docs map, map2, oneOf, andMap, andThen - - -## Query Parameters - -@docs queryParam, expectQueryParam, queryParams - - -## Cookies - -@docs cookie - - -## Headers - -@docs expectHeader - - -## Multi-part forms and file uploads - -@docs File, expectMultiPartFormPost - - -## Request Parsers That Can Fail - -@docs expectBody - - -## Map Functions - -@docs map3, map4, map5, map6, map7, map8, map9 - - -## Method Type - -@docs Method, methodToString - - -## Internals - -@docs errorsToString, errorToString, getDecoder, ValidationError - --} - -import BackendTask exposing (BackendTask) -import CookieParser -import Dict exposing (Dict) -import FatalError exposing (FatalError) -import Form -import Form.Handler -import Form.Validation as Validation -import FormData -import Internal.Request -import Json.Decode -import Json.Encode -import List.NonEmpty -import Pages.Form -import QueryParams -import Time -import Url - - {-| A `Server.Request.Parser` lets you send a `Server.Response.Response` based on an incoming HTTP request. For example, using a `Server.Request.Parser`, you could check a session cookie to decide whether to respond by rendering a page for the logged-in user, or else respond with an HTTP redirect response (see the [`Server.Response` docs](Server-Response)). @@ -194,340 +100,72 @@ When the user clicks the link, we want to respond based on that `coupon` query p } |> RouteBuilder.buildNoState { view = view } --} -type alias Parser decodesTo = - Internal.Request.Parser decodesTo ValidationError +@docs Request + +@docs header, headers -oneOfInternal : List ValidationError -> List (Json.Decode.Decoder ( Result ValidationError decodesTo, List ValidationError )) -> Json.Decode.Decoder ( Result ValidationError decodesTo, List ValidationError ) -oneOfInternal previousErrors optimizedDecoders = - -- elm-review: known-unoptimized-recursion - case optimizedDecoders of - [] -> - Json.Decode.succeed ( Err (OneOf previousErrors), [] ) +## Body - [ single ] -> - single - |> Json.Decode.map - (\result -> - result - |> Tuple.mapFirst (Result.mapError (\error -> OneOf (previousErrors ++ [ error ]))) - ) - - first :: rest -> - first - |> Json.Decode.andThen - (\( firstResult, firstErrors ) -> - case ( firstResult, firstErrors ) of - ( Ok okFirstResult, [] ) -> - Json.Decode.succeed ( Ok okFirstResult, [] ) - - ( Ok _, otherErrors ) -> - oneOfInternal (previousErrors ++ otherErrors) rest - - ( Err error, _ ) -> - case error of - OneOf errors -> - oneOfInternal (previousErrors ++ errors) rest - - _ -> - oneOfInternal (previousErrors ++ [ error ]) rest - ) +@docs body, jsonBody -{-| A Parser that always matches and parsers into the given value. +## Forms - import Server.Request as Request exposing (Request) +@docs formData, formDataWithServerValidation - data routeParams = - Request.succeed - (BackendTask.succeed - (Response.render { data = {} }) - ) +@docs rawFormData + + +## Content Type + +@docs matchesContentType + + +## Direct Values + +@docs rawUrl + +@docs method, cookies + +@docs requestTime + +@docs acceptContentTypes + + +## Query Parameters + +@docs queryParam, queryParams + + +## Cookies + +@docs cookie + + +## Headers + + +## Method Type + +@docs Method, methodToString -} -succeed : value -> Parser value -succeed value = - Internal.Request.Parser (Json.Decode.succeed ( Ok value, [] )) - -{-| TODO internal only --} -getDecoder : Parser (BackendTask error response) -> Json.Decode.Decoder (Result ( ValidationError, List ValidationError ) (BackendTask error response)) -getDecoder (Internal.Request.Parser decoder) = - decoder - |> Json.Decode.map - (\( result, validationErrors ) -> - case ( result, validationErrors ) of - ( Ok value, [] ) -> - value - |> Ok - - ( Ok _, firstError :: rest ) -> - Err ( firstError, rest ) - - ( Err fatalError, errors ) -> - Err ( fatalError, errors ) - ) - - -{-| -} -type ValidationError - = ValidationError String - | OneOf (List ValidationError) - | MissingQueryParam { missingParam : String, allQueryParams : String } - - -{-| -} -errorsToString : ( ValidationError, List ValidationError ) -> String -errorsToString validationErrors = - validationErrors - |> List.NonEmpty.toList - |> List.map errorToString - |> String.join "\n" - - -{-| TODO internal only --} -errorToString : ValidationError -> String -errorToString validationError_ = - -- elm-review: known-unoptimized-recursion - case validationError_ of - ValidationError message -> - message - - OneOf validationErrors -> - "Server.Request.oneOf failed in the following " - ++ String.fromInt (List.length validationErrors) - ++ " ways:\n\n" - ++ (validationErrors - |> List.indexedMap (\index error -> "(" ++ String.fromInt (index + 1) ++ ") " ++ errorToString error) - |> String.join "\n\n" - ) - - MissingQueryParam record -> - "Missing query param \"" ++ record.missingParam ++ "\". Query string was `" ++ record.allQueryParams ++ "`" - - -{-| -} -map : (a -> b) -> Parser a -> Parser b -map mapFn (Internal.Request.Parser decoder) = - Internal.Request.Parser - (Json.Decode.map - (\( result, errors ) -> - ( Result.map mapFn result, errors ) - ) - decoder - ) - - -{-| -} -oneOf : List (Parser a) -> Parser a -oneOf serverRequests = - Internal.Request.Parser - (oneOfInternal [] - (List.map - (\(Internal.Request.Parser decoder) -> decoder) - serverRequests - ) - ) - - -{-| Decode an argument and provide it to a function in a decoder. - - decoder : Decoder String - decoder = - succeed (String.repeat) - |> andMap (field "count" int) - |> andMap (field "val" string) - - - """ { "val": "hi", "count": 3 } """ - |> decodeString decoder - --> Success "hihihi" - --} -andMap : Parser a -> Parser (a -> b) -> Parser b -andMap = - map2 (|>) - - -{-| -} -andThen : (a -> Parser b) -> Parser a -> Parser b -andThen toRequestB (Internal.Request.Parser requestA) = - Json.Decode.andThen - (\value -> - case value of - ( Ok okValue, _ ) -> - okValue - |> toRequestB - |> unwrap - - ( Err error, errors ) -> - Json.Decode.succeed ( Err error, errors ) - ) - requestA - |> Internal.Request.Parser - - -unwrap : Parser a -> Json.Decode.Decoder ( Result ValidationError a, List ValidationError ) -unwrap (Internal.Request.Parser decoder_) = - decoder_ - - -{-| -} -map2 : (a -> b -> c) -> Parser a -> Parser b -> Parser c -map2 f (Internal.Request.Parser jdA) (Internal.Request.Parser jdB) = - Internal.Request.Parser - (Json.Decode.map2 - (\( result1, errors1 ) ( result2, errors2 ) -> - ( Result.map2 f result1 result2 - , errors1 ++ errors2 - ) - ) - jdA - jdB - ) - - -{-| -} -map3 : - (value1 -> value2 -> value3 -> valueCombined) - -> Parser value1 - -> Parser value2 - -> Parser value3 - -> Parser valueCombined -map3 combineFn request1 request2 request3 = - succeed combineFn - |> andMap request1 - |> andMap request2 - |> andMap request3 - - -{-| -} -map4 : - (value1 -> value2 -> value3 -> value4 -> valueCombined) - -> Parser value1 - -> Parser value2 - -> Parser value3 - -> Parser value4 - -> Parser valueCombined -map4 combineFn request1 request2 request3 request4 = - succeed combineFn - |> andMap request1 - |> andMap request2 - |> andMap request3 - |> andMap request4 - - -{-| -} -map5 : - (value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined) - -> Parser value1 - -> Parser value2 - -> Parser value3 - -> Parser value4 - -> Parser value5 - -> Parser valueCombined -map5 combineFn request1 request2 request3 request4 request5 = - succeed combineFn - |> andMap request1 - |> andMap request2 - |> andMap request3 - |> andMap request4 - |> andMap request5 - - -{-| -} -map6 : - (value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined) - -> Parser value1 - -> Parser value2 - -> Parser value3 - -> Parser value4 - -> Parser value5 - -> Parser value6 - -> Parser valueCombined -map6 combineFn request1 request2 request3 request4 request5 request6 = - succeed combineFn - |> andMap request1 - |> andMap request2 - |> andMap request3 - |> andMap request4 - |> andMap request5 - |> andMap request6 - - -{-| -} -map7 : - (value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> valueCombined) - -> Parser value1 - -> Parser value2 - -> Parser value3 - -> Parser value4 - -> Parser value5 - -> Parser value6 - -> Parser value7 - -> Parser valueCombined -map7 combineFn request1 request2 request3 request4 request5 request6 request7 = - succeed combineFn - |> andMap request1 - |> andMap request2 - |> andMap request3 - |> andMap request4 - |> andMap request5 - |> andMap request6 - |> andMap request7 - - -{-| -} -map8 : - (value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> valueCombined) - -> Parser value1 - -> Parser value2 - -> Parser value3 - -> Parser value4 - -> Parser value5 - -> Parser value6 - -> Parser value7 - -> Parser value8 - -> Parser valueCombined -map8 combineFn request1 request2 request3 request4 request5 request6 request7 request8 = - succeed combineFn - |> andMap request1 - |> andMap request2 - |> andMap request3 - |> andMap request4 - |> andMap request5 - |> andMap request6 - |> andMap request7 - |> andMap request8 - - -{-| -} -map9 : - (value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> value9 -> valueCombined) - -> Parser value1 - -> Parser value2 - -> Parser value3 - -> Parser value4 - -> Parser value5 - -> Parser value6 - -> Parser value7 - -> Parser value8 - -> Parser value9 - -> Parser valueCombined -map9 combineFn request1 request2 request3 request4 request5 request6 request7 request8 request9 = - succeed combineFn - |> andMap request1 - |> andMap request2 - |> andMap request3 - |> andMap request4 - |> andMap request5 - |> andMap request6 - |> andMap request7 - |> andMap request8 - |> andMap request9 +import BackendTask exposing (BackendTask) +import Dict exposing (Dict) +import FatalError exposing (FatalError) +import Form +import Form.Handler +import Form.Validation as Validation +import FormData +import Internal.Request +import Json.Decode +import List.NonEmpty +import Pages.Form +import QueryParams +import Time +import Url optionalField : String -> Json.Decode.Decoder a -> Json.Decode.Decoder (Maybe a) @@ -548,49 +186,10 @@ optionalField fieldName decoder_ = |> Json.Decode.andThen finishDecoding -{-| Turn a Result into a Request. Useful with `andThen`. Turns `Err` into a skipped request handler (non-matching request), -and `Ok` values into a `succeed` (matching request). --} -fromResult : Result String value -> Parser value -fromResult result = - case result of - Ok okValue -> - succeed okValue - - Err error -> - skipInternal (ValidationError error) - - -jsonFromResult : Result String value -> Json.Decode.Decoder value -jsonFromResult result = - case result of - Ok okValue -> - Json.Decode.succeed okValue - - Err error -> - Json.Decode.fail error - - {-| -} -expectHeader : String -> Parser String -expectHeader headerName = - optionalField (headerName |> String.toLower) Json.Decode.string - |> Json.Decode.field "headers" - |> noErrors - |> Internal.Request.Parser - |> andThen - (\value -> - fromResult - (value |> Result.fromMaybe "Missing field headers") - ) - - -{-| -} -rawHeaders : Parser (Dict String String) -rawHeaders = - Json.Decode.field "headers" (Json.Decode.dict Json.Decode.string) - |> noErrors - |> Internal.Request.Parser +headers : Request -> Dict String String +headers (Internal.Request.Request req) = + req.rawHeaders {-| -} @@ -599,52 +198,16 @@ requestTime (Internal.Request.Request req) = req.time -noErrors : Json.Decode.Decoder value -> Json.Decode.Decoder ( Result ValidationError value, List ValidationError ) -noErrors decoder = - decoder - |> Json.Decode.map (\value -> ( Ok value, [] )) - - {-| -} -acceptContentTypes : ( String, List String ) -> Parser value -> Parser value -acceptContentTypes ( accepted1, accepted ) (Internal.Request.Parser decoder) = - -- TODO this should parse content-types so it doesn't need to be an exact match (support `; q=...`, etc.) - optionalField ("Accept" |> String.toLower) Json.Decode.string - |> Json.Decode.field "headers" - |> Json.Decode.andThen - (\acceptHeader -> - if List.NonEmpty.fromCons accepted1 accepted |> List.NonEmpty.member (acceptHeader |> Maybe.withDefault "") then - decoder +acceptContentTypes : ( String, List String ) -> Request -> Bool +acceptContentTypes ( accepted1, accepted ) (Internal.Request.Request req) = + -- TODO this should parse accept to separate out parts so it doesn't need to be an exact match (support `; q=...`, etc.) + case req.rawHeaders |> Dict.get "accept" of + Nothing -> + False - else - decoder - |> appendError - (ValidationError - ("Expected Accept header " ++ String.join ", " (accepted1 :: accepted) ++ " but was " ++ (acceptHeader |> Maybe.withDefault "")) - ) - ) - |> Internal.Request.Parser - - -{-| -} -acceptMethod : ( Method, List Method ) -> Parser value -> Parser value -acceptMethod ( accepted1, accepted ) (Internal.Request.Parser decoder) = - (Json.Decode.field "method" Json.Decode.string - |> Json.Decode.map methodFromString - |> Json.Decode.andThen - (\method_ -> - if (accepted1 :: accepted) |> List.member method_ then - decoder - - else - decoder - |> appendError - (ValidationError - ("Expected HTTP method " ++ String.join ", " ((accepted1 :: accepted) |> List.map methodToString) ++ " but was " ++ methodToString method_) - ) - ) - ) - |> Internal.Request.Parser + Just acceptHeader -> + List.NonEmpty.fromCons accepted1 accepted |> List.NonEmpty.member acceptHeader {-| -} @@ -653,57 +216,6 @@ method (Internal.Request.Request req) = req.method |> methodFromString -appendError : ValidationError -> Json.Decode.Decoder ( value, List ValidationError ) -> Json.Decode.Decoder ( value, List ValidationError ) -appendError error decoder = - decoder - |> Json.Decode.map (Tuple.mapSecond (\errors -> error :: errors)) - - -{-| Same as [`queryParam`](#queryParam), but instead of returning `Nothing` if the query param is missing, it will result -a non-matching Parser (will try a subsequent parser if there is a oneOf). - - import Server.Request as Request - - data = - Request.oneOf - [ Request.expectQueryParam "token" - |> Request.map (\token -> showProtectedPageOrRedirect token) - , Request.succeed guestPageView - ] - --} -expectQueryParam : String -> Parser String -expectQueryParam name = - rawUrl - |> andThen - (\url_ -> - case url_ |> Url.fromString |> Maybe.andThen .query of - Just queryString -> - let - maybeParamValue : Maybe String - maybeParamValue = - queryString - |> QueryParams.fromString - |> Dict.get name - |> Maybe.andThen List.head - in - case maybeParamValue of - Just okParamValue -> - succeed okParamValue - - Nothing -> - skipInternal - (MissingQueryParam - { missingParam = name - , allQueryParams = queryString - } - ) - - Nothing -> - skipInternal (ValidationError ("Expected query param \"" ++ name ++ "\", but there were no query params.")) - ) - - {-| Get `Nothing` if the query param with the given name is missing, or `Just` the value if it is present. If there are multiple query params with the same name, the first one is returned. @@ -723,7 +235,7 @@ If there are multiple query params with the same name, the first one is returned -- url: http://example.com -- parses into: Nothing -See also [`expectQueryParam`](#expectQueryParam) and [`queryParams`](#queryParams), or [`rawUrl`](#rawUrl) if you need something more low-level. +See also [`queryParams`](#queryParams), or [`rawUrl`](#rawUrl) if you need something more low-level. -} queryParam : String -> Request -> Maybe String @@ -764,80 +276,10 @@ queryParams (Internal.Request.Request req) = |> Maybe.withDefault Dict.empty -{-| This is a Request.Parser that will never match an HTTP request. Similar to `Json.Decode.fail`. - -Why would you want it to always fail? It's helpful for building custom `Server.Request.Parser`. For example, let's say -you wanted to define a custom `Server.Request.Parser` to use an XML Decoding package on the request body. -You could define a custom function like this - - import Server.Request as Request - - expectXmlBody : XmlDecoder value -> Request.Parser value - expectXmlBody xmlDecoder = - Request.expectBody - |> Request.andThen - (\bodyAsString -> - case runXmlDecoder xmlDecoder bodyAsString of - Ok decodedXml -> - Request.succeed decodedXml - - Err error -> - Request.skip ("XML could not be decoded " ++ xmlErrorToString error) - ) - -Note that when we said `Request.skip`, remaining Request Parsers will run (for example if you use [`Server.Request.oneOf`](#oneOf)). -You could build this with different semantics if you wanted to handle _any_ valid XML body. This Request Parser will _not_ -handle any valid XML body. It will only handle requests that can match the XmlDecoder that is passed in. - -So when you define your `Server.Request.Parser`s, think carefully about whether you want to handle invalid cases and give an -error, or fall through to other Parsers. There's no universal right answer, it's just something to decide for your use case. - - expectXmlBody : Request.Parser value - expectXmlBody = - Request.map2 - acceptContentTypes - Request.expectBody - |> Request.andThen - (\bodyAsString -> - case runXmlDecoder xmlDecoder bodyAsString of - Ok decodedXml -> - Request.succeed decodedXml - - Err error -> - Request.skip ("XML could not be decoded " ++ xmlErrorToString error) - ) - --} -skip : String -> Parser value -skip errorMessage = - skipInternal (ValidationError errorMessage) - - -skipInternal : ValidationError -> Parser value -skipInternal validationError_ = - Internal.Request.Parser - (Json.Decode.succeed - ( Err validationError_, [] ) - ) - - {-| -} -rawUrl : Parser String -rawUrl = - Json.Decode.maybe - (Json.Decode.string - |> Json.Decode.field "rawUrl" - ) - |> Json.Decode.map - (\url_ -> - case url_ of - Just justValue -> - ( Ok justValue, [] ) - - Nothing -> - ( Err (ValidationError "Internal error - expected rawUrl field but the adapter script didn't provide one."), [] ) - ) - |> Internal.Request.Parser +rawUrl : Request -> String +rawUrl (Internal.Request.Request req) = + req.rawUrl {-| -} @@ -847,15 +289,6 @@ header headerName (Internal.Request.Request req) = |> Dict.get (headerName |> String.toLower) -{-| -} -optionalHeader : String -> Parser (Maybe String) -optionalHeader headerName = - optionalField (headerName |> String.toLower) Json.Decode.string - |> Json.Decode.field "headers" - |> noErrors - |> Internal.Request.Parser - - {-| -} cookie : String -> Request -> Maybe String cookie name (Internal.Request.Request req) = @@ -869,54 +302,51 @@ cookies (Internal.Request.Request req) = req.cookies -formField_ : String -> Parser String -formField_ name = - optionalField name Json.Decode.string - |> Json.Decode.map - (\value -> - case value of - Just justValue -> - ( Ok justValue, [] ) - Nothing -> - ( Err (ValidationError ("Missing form field '" ++ name ++ "'")), [] ) - ) - |> Internal.Request.Parser - - -optionalFormField_ : String -> Parser (Maybe String) -optionalFormField_ name = - optionalField name Json.Decode.string - |> noErrors - |> Internal.Request.Parser - - -{-| -} -type alias File = - { name : String - , mimeType : String - , body : String - } - - -fileField_ : String -> Parser File -fileField_ name = - optionalField name - (Json.Decode.map3 File - (Json.Decode.field "filename" Json.Decode.string) - (Json.Decode.field "mimeType" Json.Decode.string) - (Json.Decode.field "body" Json.Decode.string) - ) - |> Json.Decode.map - (\value -> - case value of - Just justValue -> - ( Ok justValue, [] ) - - Nothing -> - ( Err (ValidationError ("Missing form field " ++ name)), [] ) - ) - |> Internal.Request.Parser +--formField_ : String -> Parser String +--formField_ name = +-- optionalField name Json.Decode.string +-- |> Json.Decode.map +-- (\value -> +-- case value of +-- Just justValue -> +-- ( Ok justValue, [] ) +-- +-- Nothing -> +-- ( Err (ValidationError ("Missing form field '" ++ name ++ "'")), [] ) +-- ) +-- |> Internal.Request.Parser +-- +-- +--optionalFormField_ : String -> Parser (Maybe String) +--optionalFormField_ name = +-- optionalField name Json.Decode.string +-- |> noErrors +-- |> Internal.Request.Parser +--{-| -} +--type alias File = +-- { name : String +-- , mimeType : String +-- , body : String +-- } +--fileField_ : String -> Parser File +--fileField_ name = +-- optionalField name +-- (Json.Decode.map3 File +-- (Json.Decode.field "filename" Json.Decode.string) +-- (Json.Decode.field "mimeType" Json.Decode.string) +-- (Json.Decode.field "body" Json.Decode.string) +-- ) +-- |> Json.Decode.map +-- (\value -> +-- case value of +-- Just justValue -> +-- ( Ok justValue, [] ) +-- +-- Nothing -> +-- ( Err (ValidationError ("Missing form field " ++ name)), [] ) +-- ) +-- |> Internal.Request.Parser runForm : Validation.Validation error parsed kind constraints -> Form.Validated error parsed @@ -1132,64 +562,41 @@ rawFormData request = Nothing -{-| -} -expectMultiPartFormPost : - ({ field : String -> Parser String - , optionalField : String -> Parser (Maybe String) - , fileField : String -> Parser File - } - -> Parser decodedForm - ) - -> Parser decodedForm -expectMultiPartFormPost toForm = - map2 - (\_ value -> - value - ) - (expectContentType "multipart/form-data") - (toForm - { field = formField_ - , optionalField = optionalFormField_ - , fileField = fileField_ - } - |> (\(Internal.Request.Parser decoder) -> decoder) - -- @@@ TODO is it possible to do multipart form data parsing in pure Elm? - |> Json.Decode.field "multiPartFormData" - |> Internal.Request.Parser - |> acceptMethod ( Post, [] ) - ) + +--{-| -} +--expectMultiPartFormPost : +-- ({ field : String -> Parser String +-- , optionalField : String -> Parser (Maybe String) +-- , fileField : String -> Parser File +-- } +-- -> Parser decodedForm +-- ) +-- -> Parser decodedForm +--expectMultiPartFormPost toForm = +-- map2 +-- (\_ value -> +-- value +-- ) +-- (expectContentType "multipart/form-data") +-- (toForm +-- { field = formField_ +-- , optionalField = optionalFormField_ +-- , fileField = fileField_ +-- } +-- |> (\(Internal.Request.Parser decoder) -> decoder) +-- -- @@@ TODO is it possible to do multipart form data parsing in pure Elm? +-- |> Json.Decode.field "multiPartFormData" +-- |> Internal.Request.Parser +-- |> acceptMethod ( Post, [] ) +-- ) + + +rawContentType : Request -> Maybe String +rawContentType (Internal.Request.Request req) = + req.rawHeaders |> Dict.get "content-type" {-| -} -expectContentType : String -> Parser () -expectContentType expectedContentType = - optionalField "content-type" Json.Decode.string - |> Json.Decode.field "headers" - |> noErrors - |> Internal.Request.Parser - |> andThen - (\maybeContentType -> - case maybeContentType of - Nothing -> - skipInternal <| - ValidationError ("Expected content-type `" ++ expectedContentType ++ "` but there was no content-type header.") - - Just contentType -> - if (contentType |> parseContentType) == (expectedContentType |> parseContentType) then - succeed () - - else - skipInternal <| ValidationError ("Expected content-type to be " ++ expectedContentType ++ " but it was " ++ contentType) - ) - - -rawContentType : Parser (Maybe String) -rawContentType = - optionalField ("content-type" |> String.toLower) Json.Decode.string - |> noErrors - |> Internal.Request.Parser - - matchesContentType : String -> Request -> Bool matchesContentType expectedContentType (Internal.Request.Request req) = req.rawHeaders @@ -1225,25 +632,6 @@ jsonBody jsonBodyDecoder ((Internal.Request.Request req) as request) = Nothing -{-| -} -rawBody : Parser (Maybe String) -rawBody = - Json.Decode.field "body" (Json.Decode.nullable Json.Decode.string) - |> noErrors - |> Internal.Request.Parser - - -{-| Same as [`rawBody`](#rawBody), but will only match when a body is present in the HTTP request. --} -expectBody : Parser String -expectBody = - rawBody - |> andThen - (Result.fromMaybe "Expected body but none was present." - >> fromResult - ) - - {-| -} type Method = Connect @@ -1328,6 +716,7 @@ methodToString method_ = nonStandardMethod +{-| -} type alias Request = Internal.Request.Request