mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-12-26 13:21:42 +03:00
1 line
206 KiB
JSON
1 line
206 KiB
JSON
[{"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":" ","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 `StaticHttp.Request`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 : StaticHttp.Request (List Pokemon)\n pokemonDetailRequest =\n StaticHttp.get\n (Secrets.succeed \"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 StaticHttp.get (Secrets.succeed url)\n (Decode.at\n [ \"sprites\", \"front_default\" ]\n Decode.string\n |> Decode.map (Pokemon name)\n )\n )\n )\n )\n )\n |> StaticHttp.andThen StaticHttp.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 an Err into a BackendTask failure.\n","type":"Result.Result error value -> BackendTask.BackendTask error value"},{"name":"map","comment":" Transform a request into an arbitrary value. The same underlying HTTP requests will be performed during the build\nstep, but mapping allows you to change the resulting values by applying functions to the results.\n\nA common use for this is to map your data into your elm-pages view:\n\n import BackendTask\n import Json.Decode as Decode exposing (Decoder)\n\n view =\n BackendTask.Http.get\n (Secrets.succeed \"https://api.github.com/repos/dillonkearns/elm-pages\")\n (Decode.field \"stargazers_count\" Decode.int)\n |> BackendTask.map\n (\\stars ->\n { view =\n \\model viewForPage ->\n { title = \"Current stars: \" ++ String.fromInt stars\n , body = Html.text <| \"⭐️ \" ++ String.fromInt stars\n , head = []\n }\n }\n )\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<unknown> }\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","unions":[{"name":"Error","comment":" ","args":[],"cases":[["Error",[]],["ErrorInCustomBackendTaskFile",[]],["MissingCustomBackendTaskFile",[]],["CustomBackendTaskNotDefined",["{ name : String.String }"]],["CustomBackendTaskException",["Json.Decode.Value"]],["ExportIsNotFunction",[]]]}],"aliases":[],"values":[{"name":"run","comment":" ","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder b -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Custom.Error } b"}],"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 as 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 as 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 as 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 as 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":" <https://github.com/mrmlnc/fast-glob#onlyfiles>\n\n<https://github.com/mrmlnc/fast-glob#onlydirectories>\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","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 <https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>\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 suitable for cryptographically secure random generation 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).\n\n","type":"Random.Generator value -> BackendTask.BackendTask error value"}],"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, fromString, recoverable\n\n","unions":[],"aliases":[{"name":"FatalError","comment":" ","args":[],"type":"Pages.Internal.FatalError.FatalError"}],"values":[{"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":"Form","comment":" One of the core features of elm-pages is helping you manage form data end-to-end, including\n\n - Presenting the HTML form with its fields\n - Maintaining client-side form state\n - Showing validation errors on the client-side\n - Receiving a form submission on the server-side\n - Using the exact same client-side validations on the server-side\n - Letting you run server-only Validations with BackendTask's (things like checking for a unique username)\n\nBecause elm-pages is a framework, it has its own internal Model and Msg's. That means you, the user,\ncan offload some of the responsibility to elm-pages and build an interactive form with real-time\nclient-side state and validation errors without wiring up your own Model and Msg's to manage that\nstate. You define the source of truth for your form (how to parse it into data or errors), and\nelm-pages manages the state.\n\nLet's look at a sign-up form example.\n\n\n### Step 1 - Define the Form\n\nWhat to look for:\n\n**The field declarations**\n\nBelow the `Form.init` call you will find all of the form's fields declared with\n\n |> Form.field ...\n\nThese are the form's field declarations.\n\nThese fields each have individual validations. For example, `|> Field.required ...` means we'll get a validation\nerror if that field is empty (similar for checking the minimum password length).\n\nThere will be a corresponding parameter in the function we pass in to `Form.init` for every\nfield declaration (in this example, `\\email password passwordConfirmation -> ...`).\n\n**The `combine` validation**\n\nIn addition to the validation errors that individual fields can have independently (like\nrequired fields or minimum password length), we can also do _dependent validations_.\n\nWe use the [`Form.Validation`](Form-Validation) module to take each individual field and combine\nthem into a type and/or errors.\n\n**The `view`**\n\nTotally customizable. Uses [`Form.FieldView`](Form-FieldView) to render all of the fields declared.\n\n import BackendTask exposing (BackendTask)\n import ErrorPage exposing (ErrorPage)\n import Form\n import Form.Field as Field\n import Form.FieldView as FieldView\n import Form.Validation as Validation\n import Html exposing (Html)\n import Html.Attributes as Attr\n import Route\n import Server.Request as Request\n import Server.Response exposing (Response)\n\n type alias NewUser =\n { email : String\n , password : String\n }\n\n signupForm : Form.HtmlForm String NewUser () Msg\n signupForm =\n Form.init\n (\\email password passwordConfirmation ->\n { combine =\n Validation.succeed Login\n |> Validation.andMap email\n |> Validation.andMap\n (Validation.map2\n (\\pass confirmation ->\n if pass == confirmation then\n Validation.succeed pass\n\n else\n passwordConfirmation\n |> Validation.fail\n \"Must match password\"\n )\n password\n passwordConfirmation\n |> Validation.andThen identity\n )\n , view =\n \\info ->\n [ Html.label []\n [ fieldView info \"Email\" email\n , fieldView info \"Password\" password\n , fieldView info \"Confirm Password\" passwordConfirmation\n ]\n , Html.button []\n [ if info.isTransitioning then\n Html.text \"Signing Up...\"\n\n else\n Html.text \"Sign Up\"\n ]\n ]\n }\n )\n |> Form.field \"email\"\n (Field.text\n |> Field.required \"Required\"\n )\n |> Form.field \"password\"\n passwordField\n |> Form.field \"passwordConfirmation\"\n passwordField\n\n passwordField =\n Field.text\n |> Field.password\n |> Field.required \"Required\"\n |> Field.withClientValidation\n (\\password ->\n ( Just password\n , if String.length password < 4 then\n [ \"Must be at least 4 characters\" ]\n\n else\n []\n )\n )\n\n fieldView :\n Form.Context String data\n -> String\n -> Validation.Field String parsed FieldView.Input\n -> Html msg\n fieldView formState label field =\n Html.div []\n [ Html.label []\n [ Html.text (label ++ \" \")\n , field |> Form.FieldView.input []\n ]\n , (if formState.submitAttempted then\n formState.errors\n |> Form.errorsForField field\n |> List.map\n (\\error ->\n Html.li [] [ Html.text error ]\n )\n\n else\n []\n )\n |> Html.ul [ Attr.style \"color\" \"red\" ]\n ]\n\n\n### Step 2 - Render the Form's View\n\n view maybeUrl sharedModel app =\n { title = \"Sign Up\"\n , body =\n [ form\n |> Form.toDynamicTransition \"login\"\n |> Form.renderHtml [] Nothing app ()\n ]\n }\n\n\n### Step 3 - Handle Server-Side Form Submissions\n\n action : RouteParams -> Request.Parser (BackendTask (Response ActionData ErrorPage))\n action routeParams =\n Request.formData [ signupForm ]\n |> Request.map\n (\\signupResult ->\n case signupResult of\n Ok newUser ->\n newUser\n |> myCreateUserBackendTask\n |> BackendTask.map\n (\\() ->\n -- redirect to the home page\n -- after successful sign-up\n Route.redirectTo Route.Index\n )\n\n Err _ ->\n Route.redirectTo Route.Login\n |> BackendTask.succeed\n )\n\n myCreateUserBackendTask : BackendTask ()\n myCreateUserBackendTask =\n BackendTask.fail\n \"TODO - make a database call to create a new user\"\n\n\n## Building a Form Parser\n\n@docs Form, HtmlForm, StyledHtmlForm, DoneForm\n\n@docs Response\n\n@docs init\n\n\n## Adding Fields\n\n@docs field, hiddenField, hiddenKind\n\n\n## View Functions\n\n@docs Context\n\n\n## Rendering Forms\n\n@docs renderHtml, renderStyledHtml\n\n@docs FinalForm, withGetMethod, toDynamicTransition, toDynamicFetcher\n\n\n## Showing Errors\n\n@docs Errors, errorsForField\n\n\n## Running Parsers\n\n@docs parse, runServerSide, runOneOfServerSide\n\n\n## Combining Forms to Run on Server\n\n@docs ServerForms\n\n@docs initCombined, combine, initCombinedServer, combineServer\n\n\n## Dynamic Fields\n\n@docs dynamic\n\n@docs AppContext\n\n\n## Submission\n\n@docs toServerForm, withOnSubmit\n\n","unions":[{"name":"Errors","comment":" ","args":["error"],"cases":[]},{"name":"FinalForm","comment":" ","args":["error","parsed","data","view","userMsg"],"cases":[]},{"name":"Form","comment":" ","args":["error","combineAndView","input"],"cases":[]},{"name":"ServerForms","comment":" ","args":["error","parsed"],"cases":[["ServerForms",["List.List (Form.Form error (Form.Validation.Combined error parsed) Basics.Never)"]]]}],"aliases":[{"name":"AppContext","comment":" ","args":["app","actionData"],"type":"{ app | path : Path.Path, action : Maybe.Maybe actionData, transition : Maybe.Maybe Pages.Transition.Transition, fetchers : Dict.Dict String.String (Pages.Transition.FetcherState (Maybe.Maybe actionData)), pageFormState : Dict.Dict String.String { fields : Dict.Dict String.String { value : String.String, status : Form.FieldStatus.FieldStatus }, submitAttempted : Basics.Bool } }"},{"name":"Context","comment":" ","args":["error","data"],"type":"{ errors : Form.Errors error, isTransitioning : Basics.Bool, submitAttempted : Basics.Bool, data : data }"},{"name":"DoneForm","comment":" ","args":["error","parsed","data","view"],"type":"Form.Form error { combine : Form.Validation.Combined error parsed, view : Form.Context error data -> view } data"},{"name":"HtmlForm","comment":" ","args":["error","parsed","input","msg"],"type":"Form.Form error { combine : Form.Validation.Combined error parsed, view : Form.Context error input -> List.List (Html.Html (Pages.Msg.Msg msg)) } input"},{"name":"Response","comment":" ","args":["error"],"type":"Pages.Internal.Form.Response error"},{"name":"StyledHtmlForm","comment":" ","args":["error","parsed","data","msg"],"type":"Form.Form error { combine : Form.Validation.Combined error parsed, view : Form.Context error data -> List.List (Html.Styled.Html (Pages.Msg.Msg msg)) } data"}],"values":[{"name":"combine","comment":" ","type":"(parsed -> combined) -> Form.Form error { combineAndView | combine : Form.Validation.Validation error parsed kind constraints } input -> Form.ServerForms error combined -> Form.ServerForms error combined"},{"name":"combineServer","comment":" ","type":"(parsed -> combined) -> Form.Form error { combineAndView | combine : Form.Validation.Combined error (BackendTask.BackendTask backendTaskError (Form.Validation.Validation error parsed kind constraints)) } input -> Form.ServerForms error (BackendTask.BackendTask backendTaskError (Form.Validation.Validation error combined kind constraints)) -> Form.ServerForms error (BackendTask.BackendTask backendTaskError (Form.Validation.Validation error combined kind constraints))"},{"name":"dynamic","comment":" ","type":"(decider -> Form.Form error { combine : Form.Validation.Validation error parsed named constraints1, view : subView } data) -> Form.Form error ({ combine : decider -> Form.Validation.Validation error parsed named constraints1, view : decider -> subView } -> combineAndView) data -> Form.Form error combineAndView data"},{"name":"errorsForField","comment":" ","type":"Form.Validation.Field error parsed kind -> Form.Errors error -> List.List error"},{"name":"field","comment":" Declare a visible field for the form.\n\nUse [`Form.Field`](Form-Field) to define the field and its validations.\n\n form =\n Form.init\n (\\email ->\n { combine =\n Validation.succeed NewUser\n |> Validation.andMap email\n , view = \\info -> [{- render fields -}]\n }\n )\n |> Form.field \"email\"\n (Field.text |> Field.required \"Required\")\n\n","type":"String.String -> Form.Field.Field error parsed data kind constraints -> Form.Form error (Form.Validation.Field error parsed kind -> combineAndView) data -> Form.Form error combineAndView data"},{"name":"hiddenField","comment":" Declare a hidden field for the form.\n\nUnlike [`field`](#field) declarations which are rendered using [`Form.FieldView`](Form-FieldView)\nfunctions, `hiddenField` inputs are automatically inserted into the form when you render it.\n\nYou define the field's validations the same way as for `field`, with the\n[`Form.Field`](Form-Field) API.\n\n form =\n Form.init\n (\\quantity productId ->\n { combine = {- combine fields -}\n , view = \\info -> [{- render visible fields -}]\n }\n )\n |> Form.field \"quantity\"\n (Field.int |> Field.required \"Required\")\n |> Form.field \"productId\"\n (Field.text\n |> Field.required \"Required\"\n |> Field.withInitialValue (\\product -> Form.Value.string product.id)\n )\n\n","type":"String.String -> Form.Field.Field error parsed data kind constraints -> Form.Form error (Form.Validation.Field error parsed Form.FieldView.Hidden -> combineAndView) data -> Form.Form error combineAndView data"},{"name":"hiddenKind","comment":" ","type":"( String.String, String.String ) -> error -> Form.Form error combineAndView data -> Form.Form error combineAndView data"},{"name":"init","comment":" ","type":"combineAndView -> Form.Form String.String combineAndView data"},{"name":"initCombined","comment":" ","type":"(parsed -> combined) -> Form.Form error { combineAndView | combine : Form.Validation.Validation error parsed kind constraints } input -> Form.ServerForms error combined"},{"name":"initCombinedServer","comment":" ","type":"(parsed -> combined) -> Form.Form error { combineAndView | combine : Form.Validation.Combined error (BackendTask.BackendTask backendTaskError (Form.Validation.Validation error parsed kind constraints)) } input -> Form.ServerForms error (BackendTask.BackendTask backendTaskError (Form.Validation.Validation error combined kind constraints))"},{"name":"parse","comment":" ","type":"String.String -> Form.AppContext app actionData -> data -> Form.Form error { info | combine : Form.Validation.Validation error parsed named constraints } data -> ( Maybe.Maybe parsed, Dict.Dict String.String (List.List error) )"},{"name":"renderHtml","comment":" ","type":"List.List (Html.Attribute (Pages.Msg.Msg msg)) -> (actionData -> Maybe.Maybe (Form.Response error)) -> Form.AppContext app actionData -> data -> Form.FinalForm error (Form.Validation.Validation error parsed named constraints) data (Form.Context error data -> List.List (Html.Html (Pages.Msg.Msg msg))) msg -> Html.Html (Pages.Msg.Msg msg)"},{"name":"renderStyledHtml","comment":" ","type":"List.List (Html.Styled.Attribute (Pages.Msg.Msg msg)) -> (actionData -> Maybe.Maybe (Form.Response error)) -> Form.AppContext app actionData -> data -> Form.FinalForm error (Form.Validation.Validation error parsed named constraints) data (Form.Context error data -> List.List (Html.Styled.Html (Pages.Msg.Msg msg))) msg -> Html.Styled.Html (Pages.Msg.Msg msg)"},{"name":"runOneOfServerSide","comment":" ","type":"List.List ( String.String, String.String ) -> Form.ServerForms error parsed -> ( Maybe.Maybe parsed, Dict.Dict String.String (List.List error) )"},{"name":"runServerSide","comment":" ","type":"List.List ( String.String, String.String ) -> Form.Form error (Form.Validation.Validation error parsed kind constraints) data -> ( Basics.Bool, ( Maybe.Maybe parsed, Dict.Dict String.String (List.List error) ) )"},{"name":"toDynamicFetcher","comment":" ","type":"String.String -> Form.Form error { combine : Form.Validation.Validation error parsed field constraints, view : Form.Context error data -> view } data -> Form.FinalForm error (Form.Validation.Validation error parsed field constraints) data (Form.Context error data -> view) userMsg"},{"name":"toDynamicTransition","comment":" ","type":"String.String -> Form.Form error { combine : Form.Validation.Validation error parsed field constraints, view : Form.Context error data -> view } data -> Form.FinalForm error (Form.Validation.Validation error parsed field constraints) data (Form.Context error data -> view) userMsg"},{"name":"toServerForm","comment":" ","type":"Form.Form error { combine : Form.Validation.Validation error combined kind constraints, view : viewFn } data -> Form.Form error { combine : Form.Validation.Validation error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined kind constraints)) kind constraints, view : viewFn } data"},{"name":"withGetMethod","comment":" ","type":"Form.FinalForm error parsed data view userMsg -> Form.FinalForm error parsed data view userMsg"},{"name":"withOnSubmit","comment":" ","type":"({ fields : List.List ( String.String, String.String ) } -> userMsg) -> Form.FinalForm error parsed data view userMsg -> Form.FinalForm error parsed data view userMsg"}],"binops":[]},{"name":"Form.Field","comment":"\n\n\n## Base Fields\n\n@docs text, checkbox, int, float\n\n\n## Multiple Choice Fields\n\n@docs select, range, OutsideRange\n\n\n## Date/Time Fields\n\n@docs date, time, TimeOfDay\n\n\n## Other\n\n@docs Field, FieldInfo, exactValue\n\n\n## Field Configuration\n\n@docs required, withClientValidation, withInitialValue, map\n\n\n## Text Field Display Options\n\n@docs email, password, search, telephone, url, textarea\n\n\n## Numeric Field Options\n\n@docs withMax, withMin, withStep, withMinLength, withMaxLength\n\n\n## Phantom Options\n\n@docs No, Yes\n\n","unions":[{"name":"Field","comment":" ","args":["error","parsed","data","kind","constraints"],"cases":[["Field",["Form.Field.FieldInfo error parsed data","kind"]]]},{"name":"No","comment":" ","args":[],"cases":[]},{"name":"OutsideRange","comment":" ","args":[],"cases":[["AboveRange",[]],["BelowRange",[]]]},{"name":"Yes","comment":" ","args":[],"cases":[]}],"aliases":[{"name":"FieldInfo","comment":" ","args":["error","parsed","data"],"type":"{ initialValue : Maybe.Maybe (data -> String.String), decode : Maybe.Maybe String.String -> ( Maybe.Maybe parsed, List.List error ), properties : List.List ( String.String, Json.Encode.Value ) }"},{"name":"TimeOfDay","comment":" ","args":[],"type":"{ hours : Basics.Int, minutes : Basics.Int }"}],"values":[{"name":"checkbox","comment":" ","type":"Form.Field.Field error Basics.Bool data Form.FieldView.Input { required : (), initial : Basics.Bool }"},{"name":"date","comment":" ","type":"{ invalid : String.String -> error } -> Form.Field.Field error (Maybe.Maybe Date.Date) data Form.FieldView.Input { min : Date.Date, max : Date.Date, required : (), wasMapped : Form.Field.No, initial : Date.Date }"},{"name":"email","comment":" ","type":"Form.Field.Field error parsed data Form.FieldView.Input { constraints | plainText : () } -> Form.Field.Field error parsed data Form.FieldView.Input constraints"},{"name":"exactValue","comment":" ","type":"String.String -> error -> Form.Field.Field error String.String data Form.FieldView.Input { required : (), plainText : (), wasMapped : Form.Field.No, initial : String.String }"},{"name":"float","comment":" ","type":"{ invalid : String.String -> error } -> Form.Field.Field error (Maybe.Maybe Basics.Float) data Form.FieldView.Input { min : Basics.Float, max : Basics.Float, required : (), wasMapped : Form.Field.No, initial : Basics.Float }"},{"name":"int","comment":" ","type":"{ invalid : String.String -> error } -> Form.Field.Field error (Maybe.Maybe Basics.Int) data Form.FieldView.Input { min : Basics.Int, max : Basics.Int, required : (), wasMapped : Form.Field.No, step : Basics.Int, initial : Basics.Int }"},{"name":"map","comment":" ","type":"(parsed -> mapped) -> Form.Field.Field error parsed data kind constraints -> Form.Field.Field error mapped data kind { constraints | wasMapped : Form.Field.Yes }"},{"name":"password","comment":" ","type":"Form.Field.Field error parsed data Form.FieldView.Input { constraints | plainText : () } -> Form.Field.Field error parsed data Form.FieldView.Input constraints"},{"name":"range","comment":" ","type":"{ min : Form.Value.Value valueType, max : Form.Value.Value valueType, initial : data -> Form.Value.Value valueType, missing : error, invalid : Form.Field.OutsideRange -> error } -> Form.Field.Field error (Maybe.Maybe valueType) data kind { constraints | required : (), initial : valueType, min : valueType, max : valueType, wasMapped : Form.Field.No } -> Form.Field.Field error valueType data Form.FieldView.Input { constraints | wasMapped : Form.Field.No }"},{"name":"required","comment":" ","type":"error -> Form.Field.Field error (Maybe.Maybe parsed) data kind { constraints | required : (), wasMapped : Form.Field.No } -> Form.Field.Field error parsed data kind { constraints | wasMapped : Form.Field.No }"},{"name":"search","comment":" ","type":"Form.Field.Field error parsed data Form.FieldView.Input { constraints | plainText : () } -> Form.Field.Field error parsed data Form.FieldView.Input constraints"},{"name":"select","comment":" ","type":"List.List ( String.String, option ) -> (String.String -> error) -> Form.Field.Field error (Maybe.Maybe option) data (Form.FieldView.Options option) { required : (), wasMapped : Form.Field.No }"},{"name":"telephone","comment":" ","type":"Form.Field.Field error parsed data Form.FieldView.Input { constraints | plainText : () } -> Form.Field.Field error parsed data Form.FieldView.Input constraints"},{"name":"text","comment":" ","type":"Form.Field.Field error (Maybe.Maybe String.String) data Form.FieldView.Input { required : (), plainText : (), wasMapped : Form.Field.No, initial : String.String, minlength : (), maxlength : () }"},{"name":"textarea","comment":" ","type":"Form.Field.Field error parsed data Form.FieldView.Input { constraints | plainText : () } -> Form.Field.Field error parsed data Form.FieldView.Input constraints"},{"name":"time","comment":" <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/time>\n","type":"{ invalid : String.String -> error } -> Form.Field.Field error (Maybe.Maybe Form.Field.TimeOfDay) data Form.FieldView.Input { required : (), wasMapped : Form.Field.No }"},{"name":"url","comment":" ","type":"Form.Field.Field error parsed data Form.FieldView.Input { constraints | plainText : () } -> Form.Field.Field error parsed data Form.FieldView.Input constraints"},{"name":"withClientValidation","comment":" ","type":"(parsed -> ( Maybe.Maybe mapped, List.List error )) -> Form.Field.Field error parsed data kind constraints -> Form.Field.Field error mapped data kind { constraints | wasMapped : Form.Field.Yes }"},{"name":"withInitialValue","comment":" ","type":"(data -> Form.Value.Value valueType) -> Form.Field.Field error value data kind { constraints | initial : valueType } -> Form.Field.Field error value data kind constraints"},{"name":"withMax","comment":" ","type":"Form.Value.Value valueType -> error -> Form.Field.Field error parsed data kind { constraints | max : valueType } -> Form.Field.Field error parsed data kind constraints"},{"name":"withMaxLength","comment":" ","type":"Basics.Int -> error -> Form.Field.Field error parsed data kind { constraints | maxlength : () } -> Form.Field.Field error parsed data kind constraints"},{"name":"withMin","comment":" ","type":"Form.Value.Value valueType -> error -> Form.Field.Field error parsed data kind { constraints | min : valueType } -> Form.Field.Field error parsed data kind constraints"},{"name":"withMinLength","comment":" ","type":"Basics.Int -> error -> Form.Field.Field error parsed data kind { constraints | minlength : () } -> Form.Field.Field error parsed data kind constraints"},{"name":"withStep","comment":" ","type":"Form.Value.Value valueType -> Form.Field.Field msg error value view { constraints | step : valueType } -> Form.Field.Field msg error value view constraints"}],"binops":[]},{"name":"Form.FieldStatus","comment":" elm-pages manages the client-side state of fields, including Status which you can use to determine when\nin the user's workflow to show validation errors.\n\n\n## Field Status\n\n@docs FieldStatus, fieldStatusToString\n\n","unions":[{"name":"FieldStatus","comment":" ","args":[],"cases":[["NotVisited",[]],["Focused",[]],["Changed",[]],["Blurred",[]]]}],"aliases":[],"values":[{"name":"fieldStatusToString","comment":" ","type":"Form.FieldStatus.FieldStatus -> String.String"}],"binops":[]},{"name":"Form.FieldView","comment":"\n\n@docs Input, InputType, Options, input, inputTypeToString, radio, toHtmlProperties, Hidden, select\n\n\n## Html.Styled Helpers\n\n@docs radioStyled, inputStyled\n\n","unions":[{"name":"Hidden","comment":" There are no render helpers for hidden fields because the `Form.renderHtml` helper functions automatically render hidden fields for you.\n","args":[],"cases":[["Hidden",[]]]},{"name":"Input","comment":" ","args":[],"cases":[["Input",["Form.FieldView.InputType"]]]},{"name":"InputType","comment":" ","args":[],"cases":[["Text",[]],["Number",[]],["Range",[]],["Radio",[]],["Date",[]],["Time",[]],["Checkbox",[]],["Tel",[]],["Search",[]],["Password",[]],["Email",[]],["Url",[]],["Textarea",[]]]},{"name":"Options","comment":" ","args":["a"],"cases":[["Options",["String.String -> Maybe.Maybe a","List.List String.String"]]]}],"aliases":[],"values":[{"name":"input","comment":" ","type":"List.List (Html.Attribute msg) -> Form.Validation.Field error parsed Form.FieldView.Input -> Html.Html msg"},{"name":"inputStyled","comment":" ","type":"List.List (Html.Styled.Attribute msg) -> Form.Validation.Field error parsed Form.FieldView.Input -> Html.Styled.Html msg"},{"name":"inputTypeToString","comment":" ","type":"Form.FieldView.InputType -> String.String"},{"name":"radio","comment":" ","type":"List.List (Html.Attribute msg) -> (parsed -> (List.List (Html.Attribute msg) -> Html.Html msg) -> Html.Html msg) -> Form.Validation.Field error parsed2 (Form.FieldView.Options parsed) -> Html.Html msg"},{"name":"radioStyled","comment":" ","type":"List.List (Html.Styled.Attribute msg) -> (parsed -> (List.List (Html.Styled.Attribute msg) -> Html.Styled.Html msg) -> Html.Styled.Html msg) -> Form.Validation.Field error parsed2 (Form.FieldView.Options parsed) -> Html.Styled.Html msg"},{"name":"select","comment":" ","type":"List.List (Html.Attribute msg) -> (parsed -> ( List.List (Html.Attribute msg), String.String )) -> Form.Validation.Field error parsed2 (Form.FieldView.Options parsed) -> Html.Html msg"},{"name":"toHtmlProperties","comment":" ","type":"List.List ( String.String, Json.Encode.Value ) -> List.List (Html.Attribute msg)"}],"binops":[]},{"name":"Form.FormData","comment":"\n\n@docs FormData, Method\n\n","unions":[{"name":"Method","comment":" ","args":[],"cases":[["Get",[]],["Post",[]]]}],"aliases":[{"name":"FormData","comment":" ","args":[],"type":"{ fields : List.List ( String.String, String.String ), method : Form.FormData.Method, action : String.String, id : Maybe.Maybe String.String }"}],"values":[],"binops":[]},{"name":"Form.Validation","comment":"\n\n\n## Validations\n\n@docs Combined, Field, Validation\n\n@docs andMap, andThen, fail, fromMaybe, fromResult, map, map2, parseWithError, succeed, succeed2, withError, withErrorIf, withFallback\n\n\n## Field Metadata\n\n@docs value, fieldName, fieldStatus\n\n\n## Mapping\n\n@docs map3, map4, map5, map6, map7, map8, map9\n\n\n## Global Validation\n\n@docs global\n\n\n## Temporary?\n\n@docs mapWithNever\n\n","unions":[],"aliases":[{"name":"Combined","comment":" ","args":["error","parsed"],"type":"Form.Validation.Validation error parsed Basics.Never Basics.Never"},{"name":"Field","comment":" ","args":["error","parsed","kind"],"type":"Form.Validation.Validation error parsed kind { field : kind }"},{"name":"Validation","comment":" ","args":["error","parsed","kind","constraints"],"type":"Pages.Internal.Form.Validation error parsed kind constraints"}],"values":[{"name":"andMap","comment":" ","type":"Form.Validation.Validation error a named1 constraints1 -> Form.Validation.Validation error (a -> b) named2 constraints2 -> Form.Validation.Combined error b"},{"name":"andThen","comment":" ","type":"(parsed -> Form.Validation.Validation error mapped named1 constraints1) -> Form.Validation.Validation error parsed named2 constraints2 -> Form.Validation.Combined error mapped"},{"name":"fail","comment":" ","type":"error -> Form.Validation.Field error parsed1 field -> Form.Validation.Combined error parsed"},{"name":"fieldName","comment":" ","type":"Form.Validation.Field error parsed kind -> String.String"},{"name":"fieldStatus","comment":" ","type":"Form.Validation.Field error parsed kind -> Form.FieldStatus.FieldStatus"},{"name":"fromMaybe","comment":" ","type":"Maybe.Maybe parsed -> Form.Validation.Combined error parsed"},{"name":"fromResult","comment":" ","type":"Form.Validation.Field error (Result.Result error parsed) kind -> Form.Validation.Combined error parsed"},{"name":"global","comment":" ","type":"Form.Validation.Field error () Basics.Never"},{"name":"map","comment":" ","type":"(parsed -> mapped) -> Form.Validation.Validation error parsed named constraint -> Form.Validation.Validation error mapped named constraint"},{"name":"map2","comment":" ","type":"(a -> b -> c) -> Form.Validation.Validation error a named1 constraints1 -> Form.Validation.Validation error b named2 constraints2 -> Form.Validation.Combined error c"},{"name":"map3","comment":" ","type":"(a1 -> a2 -> a3 -> a4) -> Form.Validation.Validation error a1 named1 constraints1 -> Form.Validation.Validation error a2 named2 constraints2 -> Form.Validation.Validation error a3 named3 constraints3 -> Form.Validation.Combined error a4"},{"name":"map4","comment":" ","type":"(a1 -> a2 -> a3 -> a4 -> a5) -> Form.Validation.Validation error a1 named1 constraints1 -> Form.Validation.Validation error a2 named2 constraints2 -> Form.Validation.Validation error a3 named3 constraints3 -> Form.Validation.Validation error a4 named4 constraints4 -> Form.Validation.Combined error a5"},{"name":"map5","comment":" ","type":"(a1 -> a2 -> a3 -> a4 -> a5 -> a6) -> Form.Validation.Validation error a1 named1 constraints1 -> Form.Validation.Validation error a2 named2 constraints2 -> Form.Validation.Validation error a3 named3 constraints3 -> Form.Validation.Validation error a4 named4 constraints4 -> Form.Validation.Validation error a5 named5 constraints5 -> Form.Validation.Combined error a6"},{"name":"map6","comment":" ","type":"(a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7) -> Form.Validation.Validation error a1 named1 constraints1 -> Form.Validation.Validation error a2 named2 constraints2 -> Form.Validation.Validation error a3 named3 constraints3 -> Form.Validation.Validation error a4 named4 constraints4 -> Form.Validation.Validation error a5 named5 constraints5 -> Form.Validation.Validation error a6 named6 constraints6 -> Form.Validation.Combined error a7"},{"name":"map7","comment":" ","type":"(a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8) -> Form.Validation.Validation error a1 named1 constraints1 -> Form.Validation.Validation error a2 named2 constraints2 -> Form.Validation.Validation error a3 named3 constraints3 -> Form.Validation.Validation error a4 named4 constraints4 -> Form.Validation.Validation error a5 named5 constraints5 -> Form.Validation.Validation error a6 named6 constraints6 -> Form.Validation.Validation error a7 named7 constraints7 -> Form.Validation.Combined error a8"},{"name":"map8","comment":" ","type":"(a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9) -> Form.Validation.Validation error a1 named1 constraints1 -> Form.Validation.Validation error a2 named2 constraints2 -> Form.Validation.Validation error a3 named3 constraints3 -> Form.Validation.Validation error a4 named4 constraints4 -> Form.Validation.Validation error a5 named5 constraints5 -> Form.Validation.Validation error a6 named6 constraints6 -> Form.Validation.Validation error a7 named7 constraints7 -> Form.Validation.Validation error a8 named8 constraints8 -> Form.Validation.Combined error a9"},{"name":"map9","comment":" ","type":"(a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9 -> a10) -> Form.Validation.Validation error a1 named1 constraints1 -> Form.Validation.Validation error a2 named2 constraints2 -> Form.Validation.Validation error a3 named3 constraints3 -> Form.Validation.Validation error a4 named4 constraints4 -> Form.Validation.Validation error a5 named5 constraints5 -> Form.Validation.Validation error a6 named6 constraints6 -> Form.Validation.Validation error a7 named7 constraints7 -> Form.Validation.Validation error a8 named8 constraints8 -> Form.Validation.Validation error a9 named9 constraints9 -> Form.Validation.Combined error a10"},{"name":"mapWithNever","comment":" ","type":"(parsed -> mapped) -> Form.Validation.Validation error parsed named constraint -> Form.Validation.Validation error mapped Basics.Never Basics.Never"},{"name":"parseWithError","comment":" ","type":"parsed -> ( String.String, error ) -> Form.Validation.Combined error parsed"},{"name":"succeed","comment":" ","type":"parsed -> Form.Validation.Combined error parsed"},{"name":"succeed2","comment":" ","type":"parsed -> Form.Validation.Validation error parsed kind constraints"},{"name":"value","comment":" ","type":"Form.Validation.Validation error parsed named constraint -> Maybe.Maybe parsed"},{"name":"withError","comment":" ","type":"Form.Validation.Field error parsed1 field -> error -> Form.Validation.Validation error parsed2 named constraints -> Form.Validation.Validation error parsed2 named constraints"},{"name":"withErrorIf","comment":" ","type":"Basics.Bool -> Form.Validation.Field error ignored field -> error -> Form.Validation.Validation error parsed named constraints -> Form.Validation.Validation error parsed named constraints"},{"name":"withFallback","comment":" ","type":"parsed -> Form.Validation.Validation error parsed named constraints -> Form.Validation.Validation error parsed named constraints"}],"binops":[]},{"name":"Form.Value","comment":"\n\n@docs Value, date, float, int, string, bool, toString\n\n\n## Comparison\n\n@docs compare\n\n","unions":[{"name":"Value","comment":" ","args":["dataType"],"cases":[]}],"aliases":[],"values":[{"name":"bool","comment":" ","type":"Basics.Bool -> Form.Value.Value Basics.Bool"},{"name":"compare","comment":" You probably don't need this helper as it's mostly useful for internal implementation.\n","type":"String.String -> Form.Value.Value value -> Basics.Order"},{"name":"date","comment":" ","type":"Date.Date -> Form.Value.Value Date.Date"},{"name":"float","comment":" ","type":"Basics.Float -> Form.Value.Value Basics.Float"},{"name":"int","comment":" ","type":"Basics.Int -> Form.Value.Value Basics.Int"},{"name":"string","comment":" ","type":"String.String -> Form.Value.Value String.String"},{"name":"toString","comment":" ","type":"Form.Value.Value dataType -> String.String"}],"binops":[]},{"name":"Head","comment":" This module contains functions for building up\ntags with metadata that will be rendered into the page's `<head>` 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 = \"<https://elm-pages.com\">\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 (StatelessRoute, StaticPayload)\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 StaticPayload 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 `<head>` tags.\nIt is not viable or reliable to add `<head>` 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<meta name=\"<THIS IS A VALUE>\" content=\"<THIS IS A VALUE>\" />\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 <https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html>.\n\nIf a size is provided, it will be turned into square dimensions as per the recommendations here: <https://developers.google.com/web/fundamentals/design-and-ux/browser-customization/#safari>\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 <https://developer.mozilla.org/en-US/docs/Web/Manifest#deploying_a_manifest>.\n","type":"String.String -> Head.Tag"},{"name":"metaName","comment":" Example:\n\n Head.metaName \"twitter:card\" (Head.raw \"summary_large_image\")\n\nResults in `<meta name=\"twitter:card\" content=\"summary_large_image\" />`\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 `<meta property=\"fb:app_id\" content=\"123456789\" />`\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 `<meta http-equiv=\"refresh\" content=\"0; url=https://google.com\" />`\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 - `<script src=\"...\">`\n - `<link href=\"/style.css\">`\n - `<link rel=\"preload\">`\n\nThe following tag would successfully render as it is a non-loading tag:\n\n Head.nonLoadingNode \"link\"\n [ ( \"rel\", Head.raw \"alternate\" )\n , ( \"type\", Head.raw \"application/atom+xml\" )\n , ( \"title\", Head.raw \"News\" )\n , ( \"href\", Head.raw \"/news/atom\" )\n ]\n\nRenders the head tag:\n\n`<link rel=\"alternate\" type=\"application/atom+xml\" title=\"News\" href=\"/news/atom\">`\n\n","type":"String.String -> List.List ( String.String, Head.AttributeValue ) -> Head.Tag"},{"name":"raw","comment":" Create a raw `AttributeValue` (as opposed to some kind of absolute URL).\n","type":"String.String -> Head.AttributeValue"},{"name":"rootLanguage","comment":" Set the language for a page.\n\n<https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang>\n\n import Head\n import LanguageTag\n import LanguageTag.Language\n\n LanguageTag.Language.de -- sets the page's language to German\n |> LanguageTag.build LanguageTag.emptySubtags\n |> Head.rootLanguage\n\nThis results pre-rendered HTML with a global lang tag set.\n\n```html\n<html lang=\"no\">\n...\n</html>\n```\n\n","type":"LanguageTag.LanguageTag -> Head.Tag"},{"name":"rssLink","comment":" Add a link to the site's RSS feed.\n\nExample:\n\n rssLink \"/feed.xml\"\n\n```html\n<link rel=\"alternate\" type=\"application/rss+xml\" href=\"/rss.xml\">\n```\n\n","type":"String.String -> Head.Tag"},{"name":"sitemapLink","comment":" Add a link to the site's RSS feed.\n\nExample:\n\n sitemapLink \"/feed.xml\"\n\n```html\n<link rel=\"sitemap\" type=\"application/xml\" href=\"/sitemap.xml\">\n```\n\n","type":"String.String -> Head.Tag"},{"name":"structuredData","comment":" You can learn more about structured data in [Google's intro to structured data](https://developers.google.com/search/docs/guides/intro-structured-data).\n\nWhen you add a `structuredData` item to one of your pages in `elm-pages`, it will add `json-ld` data to your document that looks like this:\n\n```html\n<script type=\"application/ld+json\">\n{\n \"@context\":\"http://schema.org/\",\n \"@type\":\"Article\",\n \"headline\":\"Extensible Markdown Parsing in Pure Elm\",\n \"description\":\"Introducing a new parser that extends your palette with no additional syntax\",\n \"image\":\"https://elm-pages.com/images/article-covers/extensible-markdown-parsing.jpg\",\n \"author\":{\n \"@type\":\"Person\",\n \"name\":\"Dillon Kearns\"\n },\n \"publisher\":{\n \"@type\":\"Person\",\n \"name\":\"Dillon Kearns\"\n },\n \"url\":\"https://elm-pages.com/blog/extensible-markdown-parsing-in-elm\",\n \"datePublished\":\"2019-10-08\",\n \"mainEntityOfPage\":{\n \"@type\":\"SoftwareSourceCode\",\n \"codeRepository\":\"https://github.com/dillonkearns/elm-pages\",\n \"description\":\"A statically typed site generator for Elm.\",\n \"author\":\"Dillon Kearns\",\n \"programmingLanguage\":{\n \"@type\":\"ComputerLanguage\",\n \"url\":\"http://elm-lang.org/\",\n \"name\":\"Elm\",\n \"image\":\"http://elm-lang.org/\",\n \"identifier\":\"http://elm-lang.org/\"\n }\n }\n}\n</script>\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 {-| <https://schema.org/Article>\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 <https://schema.org> 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":" <https://ogp.me/#>\n<https://developers.facebook.com/docs/sharing/opengraph>\n\nThis module encapsulates some of the best practices for SEO for your site.\n\n`elm-pages` will pre-render each of the static pages (in your `content` directory) so that\nweb crawlers can efficiently and accurately process it. The functions in this module are for use\nwith the `head` function that you pass to your Pages config (`Pages.application`).\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 <https://ogp.me/#metadata> and <https://ogp.me/#optional>\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 <https://ogp.me/#structured>\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 <https://ogp.me/#type_article>\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: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card>\n\nOpenGraph audio will also be included.\nThe options will also be used to build up the appropriate OpenGraph `<meta>` 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 <https://ogp.me/#type_book>\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 <https://ogp.me/#type_profile>\n","type":"{ firstName : String.String, lastName : String.String, username : Maybe.Maybe String.String } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"song","comment":" See <https://ogp.me/#type_music.song>\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: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary>\n\nThe options will also be used to build up the appropriate OpenGraph `<meta>` 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: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary-card-with-large-image>\n\nThe options will also be used to build up the appropriate OpenGraph `<meta>` 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: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card>\n\nOpenGraph video will also be included.\nThe options will also be used to build up the appropriate OpenGraph `<meta>` 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":" <https://ogp.me/#type_website>\n","type":"Head.Seo.Common -> List.List Head.Tag"}],"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.FormState","comment":"\n\n@docs Event, FieldEvent, FieldState, FormState, PageFormState, init, listeners, setField, setSubmitAttempted, update\n\n","unions":[{"name":"Event","comment":" ","args":[],"cases":[["InputEvent",["String.String"]],["FocusEvent",[]],["BlurEvent",[]]]}],"aliases":[{"name":"FieldEvent","comment":" ","args":[],"type":"{ value : String.String, formId : String.String, name : String.String, event : Pages.FormState.Event }"},{"name":"FieldState","comment":" ","args":[],"type":"{ value : String.String, status : Form.FieldStatus.FieldStatus }"},{"name":"FormState","comment":" ","args":[],"type":"{ fields : Dict.Dict String.String Pages.FormState.FieldState, submitAttempted : Basics.Bool }"},{"name":"PageFormState","comment":" ","args":[],"type":"Dict.Dict String.String Pages.FormState.FormState"}],"values":[{"name":"init","comment":" ","type":"Pages.FormState.FormState"},{"name":"listeners","comment":" ","type":"String.String -> List.List (Html.Attribute (Pages.Msg.Msg userMsg))"},{"name":"setField","comment":" ","type":"{ formId : String.String, name : String.String, value : String.String } -> Pages.FormState.PageFormState -> Pages.FormState.PageFormState"},{"name":"setSubmitAttempted","comment":" ","type":"String.String -> Pages.FormState.PageFormState -> Pages.FormState.PageFormState"},{"name":"update","comment":" ","type":"Json.Decode.Value -> Pages.FormState.PageFormState -> Pages.FormState.PageFormState"}],"binops":[]},{"name":"Pages.Generate","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 codegen` CLI command. The default starter template includes a file that uses these functions, which you can tweak to customize your scaffolding commands.\nLearn more about [the `elm-pages run` CLI command in its docs page](https://elm-pages.com/docs/run-command).\n\n\n## Initializing the Generator Builder\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","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":" ","type":"List.List Elm.Declaration -> Pages.Generate.Builder -> Pages.Generate.Builder"},{"name":"buildNoState","comment":" ","type":"{ view : { maybeUrl : Elm.Expression, sharedModel : Elm.Expression, app : Elm.Expression } -> Elm.Expression } -> Pages.Generate.Builder -> Elm.File"},{"name":"buildWithLocalState","comment":" ","type":"{ view : { maybeUrl : Elm.Expression, sharedModel : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { pageUrl : Elm.Expression, sharedModel : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { pageUrl : Elm.Expression, sharedModel : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { maybePageUrl : Elm.Expression, routeParams : Elm.Expression, path : Elm.Expression, sharedModel : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Pages.Generate.Type, model : Pages.Generate.Type } -> Pages.Generate.Builder -> Elm.File"},{"name":"buildWithSharedState","comment":" ","type":"{ view : { maybeUrl : Elm.Expression, sharedModel : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { pageUrl : Elm.Expression, sharedModel : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { pageUrl : Elm.Expression, sharedModel : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { maybePageUrl : Elm.Expression, routeParams : Elm.Expression, path : Elm.Expression, sharedModel : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Pages.Generate.Type, model : Pages.Generate.Type } -> Pages.Generate.Builder -> Elm.File"},{"name":"preRender","comment":" ","type":"{ data : ( Pages.Generate.Type, Elm.Expression -> Elm.Expression ), pages : Elm.Expression, head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Pages.Generate.Builder"},{"name":"serverRender","comment":" ","type":"{ data : ( Pages.Generate.Type, Elm.Expression -> Elm.Expression ), action : ( Pages.Generate.Type, Elm.Expression -> Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Pages.Generate.Builder"},{"name":"single","comment":" ","type":"{ data : ( Pages.Generate.Type, Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Pages.Generate.Builder"}],"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 : Path.Path, 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 Form.FormData.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",["Form.FormData.FormData"]],["SubmitFetcher",["String.String","Basics.Int","Form.FormData.FormData"]],["Batch",["List.List (Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage)"]],["UserCmd",["userEffect"]],["CancelRequest",["Basics.Int"]]]},{"name":"Msg","comment":" ","args":["userMsg","pageData","actionData","sharedData","errorPage"],"cases":[["LinkClicked",["Browser.UrlRequest"]],["UrlChanged",["Url.Url"]],["UserMsg",["Pages.Msg.Msg userMsg"]],["SetField",["{ formId : String.String, name : String.String, value : String.String }"]],["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, Maybe.Maybe actionData )"]],["FetcherStarted",["String.String","Basics.Int","Form.FormData.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 : Path.Path }, userFlags : Json.Decode.Value, transition : Maybe.Maybe ( Basics.Int, Pages.Transition.Transition ), nextTransitionKey : Basics.Int, inFlightFetchers : Dict.Dict String.String ( Basics.Int, Pages.Transition.FetcherState actionData ), pageFormState : Pages.FormState.PageFormState, 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 Flags, Model, Msg, init, requestDecoder, update, app\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":"Model","comment":" ","args":[],"type":"{ staticResponses : BackendTask.BackendTask FatalError.FatalError (), errors : List.List BuildError.BuildError }"}],"values":[{"name":"app","comment":" ","type":"Pages.GeneratorProgramConfig.GeneratorProgramConfig -> Cli.Program.StatefulProgram Pages.Internal.Platform.GeneratorApplication.Model Pages.Internal.Platform.GeneratorApplication.Msg (BackendTask.BackendTask FatalError.FatalError ()) Pages.Internal.Platform.GeneratorApplication.Flags"},{"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 : Path.Path }"]],["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.application` function\n(from your generated `Pages.elm` file).\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 <https://developer.mozilla.org/en-US/docs/Web/Manifest/display>\n","args":[],"cases":[["Fullscreen",[]],["Standalone",[]],["MinimalUi",[]],["Browser",[]]]},{"name":"IconPurpose","comment":" <https://w3c.github.io/manifest/#dfn-icon-purposes>\n","args":[],"cases":[["IconPurposeMonochrome",[]],["IconPurposeMaskable",[]],["IconPurposeAny",[]]]},{"name":"Orientation","comment":" <https://developer.mozilla.org/en-US/docs/Web/Manifest/orientation>\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 : Path.Path, 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":" <https://developer.mozilla.org/en-US/docs/Web/Manifest/icons>\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.\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 : Path.Path, 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 <https://developer.mozilla.org/en-US/docs/Web/Manifest/background_color>.\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withCategories","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/categories>.\n","type":"List.List Pages.Manifest.Category.Category -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withDisplayMode","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/display>.\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 <https://developer.mozilla.org/en-US/docs/Web/Manifest/iarc_rating_id>.\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withLang","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/lang>.\n","type":"LanguageTag.LanguageTag -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withOrientation","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/orientation>.\n","type":"Pages.Manifest.Orientation -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withShortName","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/short_name>.\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withThemeColor","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/theme_color>.\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"}],"binops":[]},{"name":"Pages.Manifest.Category","comment":" See <https://github.com/w3c/manifest/wiki/Categories> and\n<https://developer.mozilla.org/en-US/docs/Web/Manifest/categories>\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<https://github.com/w3c/manifest/wiki/Categories>. 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: <https://github.com/w3c/manifest/wiki/Categories>.\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.Msg","comment":"\n\n@docs Msg\n\n@docs map, onSubmit, fetcherOnSubmit, submitIfValid\n\n","unions":[{"name":"Msg","comment":" ","args":["userMsg"],"cases":[["UserMsg",["userMsg"]],["Submit",["Form.FormData.FormData"]],["SubmitIfValid",["String.String","Form.FormData.FormData","Basics.Bool"]],["SubmitFetcher",["String.String","Form.FormData.FormData","Basics.Bool","Maybe.Maybe userMsg"]],["FormFieldEvent",["Json.Decode.Value"]]]}],"aliases":[],"values":[{"name":"fetcherOnSubmit","comment":" ","type":"Maybe.Maybe ({ fields : List.List ( String.String, String.String ) } -> userMsg) -> String.String -> (List.List ( String.String, String.String ) -> Basics.Bool) -> Html.Attribute (Pages.Msg.Msg userMsg)"},{"name":"map","comment":" ","type":"(a -> b) -> Pages.Msg.Msg a -> Pages.Msg.Msg b"},{"name":"onSubmit","comment":" ","type":"Html.Attribute (Pages.Msg.Msg userMsg)"},{"name":"submitIfValid","comment":" ","type":"String.String -> (List.List ( String.String, String.String ) -> Basics.Bool) -> Html.Attribute (Pages.Msg.Msg userMsg)"}],"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 [`Path`](Path) type, and\nthe query params use the [`QueryParams`](QueryParams) type which allows you to parse just the query params or access them into a Dict.\n\nBecause `elm-pages` takes care of the main routing for pages in your app, the standard Elm URL parser API isn't suited\nto parsing query params individually, which is why the structure of these types is different.\n\n@docs PageUrl, toUrl\n\n","unions":[],"aliases":[{"name":"PageUrl","comment":" ","args":[],"type":"{ protocol : Url.Protocol, host : String.String, port_ : Maybe.Maybe Basics.Int, path : Path.Path, query : Maybe.Maybe QueryParams.QueryParams, fragment : Maybe.Maybe String.String }"}],"values":[{"name":"toUrl","comment":" ","type":"Pages.PageUrl.PageUrl -> Url.Url"}],"binops":[]},{"name":"Pages.Script","comment":"\n\n@docs Script\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":" ","args":[],"cases":[["FileWriteError",[]]]}],"aliases":[{"name":"Script","comment":" ","args":[],"type":"Pages.Internal.Script.Script"}],"values":[{"name":"log","comment":" ","type":"String.String -> BackendTask.BackendTask error ()"},{"name":"withCliOptions","comment":" ","type":"Cli.Program.Config cliOptions -> (cliOptions -> BackendTask.BackendTask FatalError.FatalError ()) -> Pages.Script.Script"},{"name":"withoutCliOptions","comment":" ","type":"BackendTask.BackendTask FatalError.FatalError () -> Pages.Script.Script"},{"name":"writeFile","comment":" ","type":"{ path : String.String, body : String.String } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : Pages.Script.Error } ()"}],"binops":[]},{"name":"Pages.Transition","comment":"\n\n@docs Transition, LoadingState, map\n\n\n## Fetchers\n\n@docs FetcherState, FetcherSubmitStatus\n\n","unions":[{"name":"FetcherSubmitStatus","comment":" ","args":["actionData"],"cases":[["FetcherSubmitting",[]],["FetcherReloading",["actionData"]],["FetcherComplete",["actionData"]]]},{"name":"LoadingState","comment":" ","args":[],"cases":[["Redirecting",[]],["Load",[]],["ActionRedirect",[]]]},{"name":"Transition","comment":" ","args":[],"cases":[["Submitting",["Form.FormData.FormData"]],["LoadAfterSubmit",["Form.FormData.FormData","Path.Path","Pages.Transition.LoadingState"]],["Loading",["Path.Path","Pages.Transition.LoadingState"]]]}],"aliases":[{"name":"FetcherState","comment":" ","args":["actionData"],"type":"{ status : Pages.Transition.FetcherSubmitStatus actionData, payload : Form.FormData.FormData, initiatedAt : Time.Posix }"}],"values":[{"name":"map","comment":" ","type":"(a -> b) -> Pages.Transition.FetcherState a -> Pages.Transition.FetcherState b"}],"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":"Path.Path -> 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":"Path","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 Path.join [ \"/blog/\", \"/post-1/\" ]\n |> Path.toAbsolute\n --> \"/blog/post-1\"\n\n Path.join [ \"blog\", \"post-1\" ]\n |> Path.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 Path.join [ \"/articles/archive/\", \"1977\", \"06\", \"10\", \"post-1\" ]\n |> Path.toAbsolute\n --> \"/articles/archive/1977/06/10/post-1\"\n\n\n## Creating Paths\n\n@docs Path, join, fromString\n\n\n## Turning Paths to String\n\n@docs toAbsolute, toRelative, toSegments\n\n","unions":[{"name":"Path","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":[],"cases":[]}],"aliases":[],"values":[{"name":"fromString","comment":" Create a Path from a path String.\n\n Path.fromString \"blog/post-1/\"\n |> Path.toAbsolute\n |> Expect.equal \"/blog/post-1\"\n\n","type":"String.String -> Path.Path"},{"name":"join","comment":" Create a Path from multiple path parts. Each part can either be a single path segment, like `blog`, or a\nmulti-part path part, like `blog/post-1`.\n","type":"List.List String.String -> Path.Path"},{"name":"toAbsolute","comment":" Turn a Path to an absolute URL (with no trailing slash).\n","type":"Path.Path -> String.String"},{"name":"toRelative","comment":" Turn a Path to a relative URL.\n","type":"Path.Path -> String.String"},{"name":"toSegments","comment":" ","type":"Path.Path -> List.List String.String"}],"binops":[]},{"name":"QueryParams","comment":" Represents the query portion of a URL. You can use `toDict` or `toString` to turn it into basic types, or you can\nparse it into a custom type using the other functions in this module.\n\n@docs QueryParams\n\n\n## Parsing\n\n@docs Parser\n\n@docs andThen, fail, fromResult, fromString, optionalString, parse, string, strings, succeed\n\n\n## Combining\n\n@docs map2, oneOf\n\n\n## Accessing as Built-In Types\n\n@docs toDict, toString\n\n","unions":[{"name":"Parser","comment":" ","args":["a"],"cases":[]},{"name":"QueryParams","comment":" ","args":[],"cases":[]}],"aliases":[],"values":[{"name":"andThen","comment":" ","type":"(a -> QueryParams.Parser b) -> QueryParams.Parser a -> QueryParams.Parser b"},{"name":"fail","comment":" ","type":"String.String -> QueryParams.Parser a"},{"name":"fromResult","comment":" ","type":"Result.Result String.String a -> QueryParams.Parser a"},{"name":"fromString","comment":" ","type":"String.String -> QueryParams.QueryParams"},{"name":"map2","comment":" ","type":"(a -> b -> combined) -> QueryParams.Parser a -> QueryParams.Parser b -> QueryParams.Parser combined"},{"name":"oneOf","comment":" ","type":"List.List (QueryParams.Parser a) -> QueryParams.Parser a"},{"name":"optionalString","comment":" ","type":"String.String -> QueryParams.Parser (Maybe.Maybe String.String)"},{"name":"parse","comment":" ","type":"QueryParams.Parser a -> QueryParams.QueryParams -> Result.Result String.String a"},{"name":"string","comment":" ","type":"String.String -> QueryParams.Parser String.String"},{"name":"strings","comment":" ","type":"String.String -> QueryParams.Parser (List.List String.String)"},{"name":"succeed","comment":" ","type":"a -> QueryParams.Parser a"},{"name":"toDict","comment":" ","type":"QueryParams.QueryParams -> Dict.Dict String.String (List.List String.String)"},{"name":"toString","comment":" ","type":"QueryParams.QueryParams -> String.String"}],"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 method, rawBody, allCookies, rawHeaders, queryParams\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\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\nThat's a mouthful, so let's unpack what it means.\n\n`Request.Parser` means you can pull out\n\ndata from the request payload using a Server Request Parser.\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 {}\n\n data :\n RouteParams\n -> Request.Parser (BackendTask (Response Data))\n data routeParams =\n {}\n |> Server.Response.render\n |> BackendTask.succeed\n |> Request.succeed\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":" ","type":"String.String -> Server.Request.Parser String.String"},{"name":"formData","comment":" ","type":"Form.ServerForms error combined -> Server.Request.Parser ( Form.Response error, Result.Result { fields : List.List ( String.String, String.String ), errors : Dict.Dict String.String (List.List error) } combined )"},{"name":"formDataWithServerValidation","comment":" ","type":"Form.ServerForms error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined kind constraints)) -> Server.Request.Parser (BackendTask.BackendTask FatalError.FatalError (Result.Result (Form.Response error) ( Form.Response 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":" ","type":"String.String -> Server.Request.Parser (Maybe.Maybe String.String)"},{"name":"queryParams","comment":" ","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":" ","type":"Server.Request.Parser (List.List ( String.String, String.String ))"},{"name":"rawHeaders","comment":" ","type":"Server.Request.Parser (Dict.Dict String.String 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":" ","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 <https://stackoverflow.com/a/42138726> 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 = cookieOptions\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 = cookieOptions\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 = cookieOptions\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\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 : 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 - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>\n - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>\n\n@docs SetCookie\n\n@docs SameSite\n\n\n## Options\n\n@docs Options, initOptions\n\n@docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite\n\n\n## Internal\n\n@docs toString\n\n","unions":[{"name":"SameSite","comment":" ","args":[],"cases":[["Strict",[]],["Lax",[]],["None",[]]]}],"aliases":[{"name":"Options","comment":" ","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":"initOptions","comment":" ","type":"Server.SetCookie.Options"},{"name":"makeVisibleToJavaScript","comment":" The default option in this API is for HttpOnly cookies <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#httponly>.\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":"setCookie","comment":" ","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":" ","type":"String.String -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withExpiration","comment":" ","type":"Time.Posix -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withImmediateExpiration","comment":" ","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withMaxAge","comment":" ","type":"Basics.Int -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withPath","comment":" ","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 <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes>.\n","type":"Server.SetCookie.SameSite -> Server.SetCookie.Options -> Server.SetCookie.Options"}],"binops":[]}] |