[{"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 DataSource 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 DataSource 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\nIn a future release, ApiRoutes may be able to run at request-time in a serverless function, allowing you to use pure Elm code to create dynamic APIs, and even pulling in data from\nDataSources dynamically.\n\n@docs ApiRoute, ApiRouteBuilder, Response\n\n@docs capture, literal, slash, succeed\n\n\n## Pre-Rendering\n\n@docs single, preRender\n\n\n## Server Rendering\n\n@docs preRenderWithFallback, serverRender\n\n\n## Including Head Tags\n\n@docs withGlobalHeadTags\n\n\n## Internals\n\n@docs toJson, getBuildTimeRoutes, getGlobalHeadTagsDataSource\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 -> DataSource.DataSource (List.List String.String)"},{"name":"getGlobalHeadTagsDataSource","comment":" ","type":"ApiRoute.ApiRoute response -> Maybe.Maybe (DataSource.DataSource (List.List Head.Tag))"},{"name":"literal","comment":" ","type":"String.String -> ApiRoute.ApiRouteBuilder a constructor -> ApiRoute.ApiRouteBuilder a constructor"},{"name":"preRender","comment":" ","type":"(constructor -> DataSource.DataSource (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (DataSource.DataSource String.String) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"preRenderWithFallback","comment":" ","type":"(constructor -> DataSource.DataSource (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (DataSource.DataSource (Server.Response.Response Basics.Never)) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"serverRender","comment":" ","type":"ApiRoute.ApiRouteBuilder (Server.Request.Request (DataSource.DataSource (Server.Response.Response Basics.Never))) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"single","comment":" ","type":"ApiRoute.ApiRouteBuilder (DataSource.DataSource 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":"DataSource.DataSource (List.List Head.Tag) -> ApiRoute.ApiRoute response -> ApiRoute.ApiRoute response"}],"binops":[]},{"name":"DataSource","comment":" In an `elm-pages` app, each page can define a value `data` which is a `DataSource` that will be resolved **before** `init` is called. That means it is also available\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 `DataSource` lets you pull in data from:\n\n - Local files ([`DataSource.File`](DataSource-File))\n - HTTP requests ([`DataSource.Http`](DataSource-Http))\n - Globs, i.e. listing out local files based on a pattern like `content/*.txt` ([`DataSource.Glob`](DataSource-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 ([`DataSource.Port`](DataSource-Port))\n - Hardcoded data (`DataSource.succeed \"Hello!\"`)\n - Or any combination of the above, using `DataSource.map2`, `DataSource.andThen`, or other combining/continuing helpers from this module\n\n\n## Where Does DataSource Data Come From?\n\nData from a `DataSource` is resolved when you load a page in the `elm-pages` dev server, or when you run `elm-pages build`.\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. DataSource's allow you to pull in data that you wouldn't normally be able to access from an Elm app, like local files, or listings of files in a folder. Not only that, but the dev server knows to automatically hot reload the data when the files it depends on change, so you can edit the files you used in your DataSource and see the page hot reload as you save!\n2. Because `elm-pages` has a build step, you know that your `DataSource.Http` requests succeeded, your decoders succeeded, your custom DataSource validations succeeded, and everything went smoothly. If something went wrong, you get a build failure and can deal with the issues before the site goes live. That means your users won't see those errors, and as a developer you don't need to handle those error cases in your code! Think of it as \"parse, don't validate\", but for your entire build.\n3. You don't have to worry about an API being down, or hitting it repeatedly. You can build in data and it will end up as JSON files served up with all the other assets of your site. If your CDN (static site host) is down, then the rest of your site is probably down anyway. If your site host is up, then so is all of your `DataSource` data. Also, it will be served up extremely quickly without needing to wait for any database queries to be performed, `andThen` requests to be resolved, etc., because all of that work and waiting was done at build-time!\n4. You can pre-render pages, including the SEO meta tags, with all that rich, well-typed Elm data available! That's something you can't accomplish with a vanilla Elm app, and it's one of the main use cases for elm-pages.\n\n\n## Mental Model\n\nYou can think of a DataSource as a declarative (not imperative) definition of data. It represents where to get the data from, and how to transform it (map, combine with other DataSources, etc.).\n\nEven though an HTTP request is non-deterministic, you should think of it that way as much as possible with a DataSource because elm-pages will only perform a given DataSource.Http request once, and\nit will share the result between any other DataSource.Http requests that have the exact same URL, Method, Body, and Headers.\n\nSo calling a function to increment a counter on a server through an HTTP request would not be a good fit for a `DataSource`. Let's imagine we have an HTTP endpoint that gives these stateful results when called repeatedly:\n\n\n-> Returns 1\n\n-> Returns 2\n\n-> Returns 3\n\nIf we define a `DataSource` that hits that endpoint:\n\n data =\n DataSource.Http.get\n \"https://my-api.example.com/increment-counter\"\n Decode.int\n\nNo matter how many places we use that `DataSource`, its response will be \"locked in\" (let's say the response was `3`, then every page would have the same value of `3` for that request).\n\nSo even though HTTP requests, JavaScript code, etc. can be non-deterministic, a `DataSource` always represents a single snapshot of a resource, and those values will be re-used as if they were a deterministic, declarative resource.\nSo it's best to use that mental model to avoid confusion.\n\n\n## Basics\n\n@docs DataSource\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","unions":[],"aliases":[{"name":"DataSource","comment":" A DataSource represents data that will be gathered at build time. Multiple `DataSource`s can be combined together using the `mapN` functions,\nvery similar to how you can manipulate values with Json Decoders in Elm.\n","args":["value"],"type":"Pages.StaticHttpRequest.RawRequest value"}],"values":[{"name":"andMap","comment":" A helper for combining `DataSource`s in pipelines.\n","type":"DataSource.DataSource a -> DataSource.DataSource (a -> b) -> DataSource.DataSource b"},{"name":"andThen","comment":" Build off of the response from a previous `DataSource` 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 DataSource\n import Json.Decode as Decode exposing (Decoder)\n\n licenseData : DataSource String\n licenseData =\n DataSource.Http.get\n (Secrets.succeed \"https://api.github.com/repos/dillonkearns/elm-pages\")\n (Decode.at [ \"license\", \"url\" ] Decode.string)\n |> DataSource.andThen\n (\\licenseUrl ->\n DataSource.Http.get (Secrets.succeed licenseUrl) (Decode.field \"description\" Decode.string)\n )\n\n","type":"(a -> DataSource.DataSource b) -> DataSource.DataSource a -> DataSource.DataSource b"},{"name":"combine","comment":" Turn a list of `StaticHttp.Request`s into a single one.\n\n import DataSource\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 (DataSource.DataSource value) -> DataSource.DataSource (List.List value)"},{"name":"fail","comment":" Stop the StaticHttp chain with the given error message. If you reach a `fail` in your request,\nyou will get a build error. Or in the dev server, you will see the error message in an overlay in your browser (and in\nthe terminal).\n","type":"String.String -> DataSource.DataSource a"},{"name":"fromResult","comment":" Turn an Err into a DataSource failure.\n","type":"Result.Result String.String value -> DataSource.DataSource 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 DataSource\n import Json.Decode as Decode exposing (Decoder)\n\n view =\n DataSource.Http.get\n (Secrets.succeed \"https://api.github.com/repos/dillonkearns/elm-pages\")\n (Decode.field \"stargazers_count\" Decode.int)\n |> DataSource.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) -> DataSource.DataSource a -> DataSource.DataSource 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) -> DataSource.DataSource a -> DataSource.DataSource b -> DataSource.DataSource c"},{"name":"map3","comment":" ","type":"(value1 -> value2 -> value3 -> valueCombined) -> DataSource.DataSource value1 -> DataSource.DataSource value2 -> DataSource.DataSource value3 -> DataSource.DataSource valueCombined"},{"name":"map4","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> valueCombined) -> DataSource.DataSource value1 -> DataSource.DataSource value2 -> DataSource.DataSource value3 -> DataSource.DataSource value4 -> DataSource.DataSource valueCombined"},{"name":"map5","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined) -> DataSource.DataSource value1 -> DataSource.DataSource value2 -> DataSource.DataSource value3 -> DataSource.DataSource value4 -> DataSource.DataSource value5 -> DataSource.DataSource valueCombined"},{"name":"map6","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined) -> DataSource.DataSource value1 -> DataSource.DataSource value2 -> DataSource.DataSource value3 -> DataSource.DataSource value4 -> DataSource.DataSource value5 -> DataSource.DataSource value6 -> DataSource.DataSource valueCombined"},{"name":"map7","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> valueCombined) -> DataSource.DataSource value1 -> DataSource.DataSource value2 -> DataSource.DataSource value3 -> DataSource.DataSource value4 -> DataSource.DataSource value5 -> DataSource.DataSource value6 -> DataSource.DataSource value7 -> DataSource.DataSource valueCombined"},{"name":"map8","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> valueCombined) -> DataSource.DataSource value1 -> DataSource.DataSource value2 -> DataSource.DataSource value3 -> DataSource.DataSource value4 -> DataSource.DataSource value5 -> DataSource.DataSource value6 -> DataSource.DataSource value7 -> DataSource.DataSource value8 -> DataSource.DataSource valueCombined"},{"name":"map9","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> value9 -> valueCombined) -> DataSource.DataSource value1 -> DataSource.DataSource value2 -> DataSource.DataSource value3 -> DataSource.DataSource value4 -> DataSource.DataSource value5 -> DataSource.DataSource value6 -> DataSource.DataSource value7 -> DataSource.DataSource value8 -> DataSource.DataSource value9 -> DataSource.DataSource valueCombined"},{"name":"resolve","comment":" Helper to remove an inner layer of Request wrapping.\n","type":"DataSource.DataSource (List.List (DataSource.DataSource value)) -> DataSource.DataSource (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 DataSource\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 -> DataSource.DataSource a"}],"binops":[]},{"name":"DataSource.Env","comment":"\n\n@docs get, expect\n\n","unions":[],"aliases":[],"values":[{"name":"expect","comment":" ","type":"String.String -> DataSource.DataSource String.String"},{"name":"get","comment":" ","type":"String.String -> DataSource.DataSource (Maybe.Maybe String.String)"}],"binops":[]},{"name":"DataSource.File","comment":" This module lets you read files from the local filesystem as a [`DataSource`](DataSource#DataSource).\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","unions":[],"aliases":[],"values":[{"name":"bodyWithFrontmatter","comment":"\n\n import DataSource exposing (DataSource)\n import DataSource.File as File\n import Decode as Decode exposing (Decoder)\n\n blogPost : DataSource 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 DataSource 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 DataSource exposing (DataSource)\n import DataSource.File as File\n import Decode as Decode exposing (Decoder)\n import Html exposing (Html)\n\n example :\n DataSource\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 -> DataSource.DataSource 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 DataSource exposing (DataSource)\n\n data : DataSource 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 -> DataSource.DataSource String.String"},{"name":"jsonFile","comment":" Read a file as JSON.\n\nThe Decode will strip off any unused JSON data.\n\n import DataSource exposing (DataSource)\n import DataSource.File as File\n\n sourceDirectories : DataSource (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 -> DataSource.DataSource 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 DataSource exposing (DataSource)\n import DataSource.File as File\n import Decode as Decode exposing (Decoder)\n\n blogPost : DataSource 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 [`DataSource`](DataSource) API along with [`DataSource.Glob`](DataSource.Glob).\n\n import DataSource exposing (DataSource)\n import DataSource.File as File\n import Decode as Decode exposing (Decoder)\n\n blogPostFiles : DataSource (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.toDataSource\n\n allMetadata : DataSource (List BlogPostMetadata)\n allMetadata =\n blogPostFiles\n |> DataSource.map\n (List.map\n (File.onlyFrontmatter\n blogPostDecoder\n )\n )\n |> DataSource.resolve\n\n","type":"Json.Decode.Decoder frontmatter -> String.String -> DataSource.DataSource 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 DataSource exposing (DataSource)\n import DataSource.File as File\n\n elmJsonFile : DataSource String\n elmJsonFile =\n File.rawFile \"hello.txt\"\n\n","type":"String.String -> DataSource.DataSource String.String"}],"binops":[]},{"name":"DataSource.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 [`DataSource`](DataSource#DataSource). See the [`DataSource`](DataSource) module documentation\nfor ways you can combine and map `DataSource`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 `DataSource.Glob` API, you could get all of those files like so:\n\n import DataSource exposing (DataSource)\n\n blogPostsGlob : DataSource (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.toDataSource\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 `DataSource` like this:\n\n DataSource.succeed [ \"first-post\", \"second-post\" ]\n\nOf course, if you add or remove matching files, the DataSource will get those new files (unlike `DataSource.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 DataSource exposing (DataSource)\n import DataSource.Glob as Glob\n\n blogPostsGlob : DataSource (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.toDataSource\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 DataSource exposing (DataSource)\n\n blogPostsGlob :\n DataSource\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.toDataSource\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 `DataSource` with the current `.md` files in our `blog` folder:\n\n DataSource.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` `DataSource` that we defined above.\n\n import DataSource.File\n import Json.Decode as Decode exposing (Decoder)\n\n titles : DataSource (List BlogPost)\n titles =\n blogPosts\n |> DataSource.map\n (List.map\n (\\blogPost ->\n DataSource.File.request\n blogPost.filePath\n (DataSource.File.frontmatter blogFrontmatterDecoder)\n )\n )\n |> DataSource.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 DataSource.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, toDataSource\n\n@docs oneOf\n\n@docs zeroOrMore, atLeastOne\n\n","unions":[],"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":"DataSource.Internal.Glob.Glob a"}],"values":[{"name":"atLeastOne","comment":" ","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> DataSource.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 : DataSource 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.toDataSource\n\nThe file `archive/1977/06/10/apple-2-released.md` will give us this match:\n\n matches : List ArchivesArticle\n matches =\n DataSource.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":"DataSource.Glob.Glob a -> DataSource.Glob.Glob (a -> value) -> DataSource.Glob.Glob value"},{"name":"captureFilePath","comment":"\n\n import DataSource exposing (DataSource)\n import DataSource.Glob as Glob\n\n blogPosts :\n DataSource\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.toDataSource\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":"DataSource.Glob.Glob (String.String -> value) -> DataSource.Glob.Glob value"},{"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":"DataSource.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 `DataSource` 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 DataSource exposing (DataSource)\n import DataSource.Glob as Glob\n\n findBlogBySlug : String -> DataSource 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 : DataSource String\n results =\n DataSource.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 `DataSource` error saying `More than one file matched.` Keep in mind that `DataSource` 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":"DataSource.Glob.Glob a -> DataSource.DataSource a"},{"name":"expectUniqueMatchFromList","comment":" ","type":"List.List (DataSource.Glob.Glob a) -> DataSource.DataSource 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 DataSource exposing (DataSource)\n import DataSource.Glob as Glob\n\n slides : DataSource (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.toDataSource\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 : DataSource (List Int)\n matches =\n DataSource.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":"DataSource.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 DataSource exposing (DataSource)\nimport DataSource.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 -> DataSource.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 DataSource exposing (DataSource)\n import DataSource.Glob as Glob\n\n blogPostsGlob : DataSource (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.toDataSource\n\nIf you want to validate file formats, you can combine that with some `DataSource` helpers to turn a `Glob (Result String value)` into\na `DataSource (List value)`.\n\nFor example, you could take a date and parse it.\n\n import DataSource exposing (DataSource)\n import DataSource.Glob as Glob\n\n example : DataSource (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.toDataSource\n |> DataSource.map (List.map DataSource.fromResult)\n |> DataSource.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) -> DataSource.Glob.Glob a -> DataSource.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":"DataSource.Glob.Glob a -> DataSource.Glob.Glob value -> DataSource.Glob.Glob value"},{"name":"oneOf","comment":"\n\n import DataSource.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 : DataSource (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 : DataSource (List DataFile)\n results =\n DataSource.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 : DataSource (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.toDataSource\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 : DataSource (List String)\n results =\n DataSource.succeed\n [ \"first-post\"\n , \"second-post\"\n ]\n\n","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> DataSource.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 DataSource exposing (DataSource)\n import DataSource.Glob as Glob\n\n example : DataSource (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.toDataSource\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 : DataSource (List ( List String, String ))\n matches =\n DataSource.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 : DataSource (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 : DataSource (List String)\n matches =\n DataSource.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":"DataSource.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 -> DataSource.Glob.Glob constructor"},{"name":"toDataSource","comment":" In order to get match data from your glob, turn it into a `DataSource` with this function.\n","type":"DataSource.Glob.Glob a -> DataSource.DataSource (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 DataSource exposing (DataSource)\n import DataSource.Glob as Glob\n\n type alias BlogPost =\n { year : String\n , month : String\n , day : String\n , slug : String\n }\n\n example : DataSource (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.toDataSource\n\n```shell\n\n- blog/\n - 2021-05-27/\n - first-post.md\n```\n\nThat will match to:\n\n results : DataSource (List BlogPost)\n results =\n DataSource.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":"DataSource.Glob.Glob String.String"},{"name":"zeroOrMore","comment":" ","type":"List.List String.String -> DataSource.Glob.Glob (Maybe.Maybe String.String)"}],"binops":[]},{"name":"DataSource.Http","comment":" `DataSource.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 - `DataSource.Http.Request`s are performed once at build time (`Http.Request`s are performed at runtime, at whenever point you perform them)\n - `DataSource.Http.Request`s have a built-in `DataSource.andThen` that allows you to perform follow-up requests without using tasks\n\n\n## Scenarios where DataSource.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 `DataSource.Http.Request`s as well as the simplicity and robustness that comes with it. Read more about these benefits\nin [this article introducing DataSource.Http requests and some concepts around it](https://elm-pages.com/blog/static-http).\n\n\n## Scenarios where DataSource.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@docs RequestDetails\n@docs get, request\n\n\n## Decoding Request Body\n\n@docs Expect, expectString, expectJson, expectBytes, expectWhatever\n\n\n## Expecting Responses\n\n@docs Response, Metadata, Error\n\n@docs expectStringResponse, expectBytesResponse\n\n\n## Building a DataSource.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\n\n","unions":[{"name":"Error","comment":" ","args":[],"cases":[["BadUrl",["String.String"]],["Timeout",[]],["NetworkError",[]],["BadStatus",["DataSource.Http.Metadata","String.String"]],["BadBody",["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 DataSource.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":[]},{"name":"Response","comment":" ","args":["body"],"cases":[["BadUrl_",["String.String"]],["Timeout_",[]],["NetworkError_",[]],["BadStatus_",["DataSource.Http.Metadata","body"]],["GoodStatus_",["DataSource.Http.Metadata","body"]]]}],"aliases":[{"name":"Body","comment":" A body for a DataSource.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 }"},{"name":"RequestDetails","comment":" The full details to perform a DataSource.Http request.\n","args":[],"type":"{ url : String.String, method : String.String, headers : List.List ( String.String, String.String ), body : DataSource.Http.Body }"}],"values":[{"name":"emptyBody","comment":" Build an empty body for a DataSource.Http request. See [elm/http's `Http.emptyBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#emptyBody).\n","type":"DataSource.Http.Body"},{"name":"expectBytes","comment":" ","type":"Bytes.Decode.Decoder value -> DataSource.Http.Expect value"},{"name":"expectBytesResponse","comment":" ","type":"(DataSource.Http.Response Bytes.Bytes -> value) -> DataSource.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 `DataSource.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 -> DataSource.Http.Expect value"},{"name":"expectString","comment":" Request a raw String. You can validate the String if you need to check the formatting, or try to parse it\nin something besides JSON. Be sure to use the `DataSource.Http.request` function if you want an optimized request that\nstrips out unused JSON to optimize your asset size.\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 request =\n DataSource.Http.unoptimizedRequest\n { url = \"https://example.com/file.txt\"\n , method = \"GET\"\n , headers = []\n , body = DataSource.Http.emptyBody\n }\n (DataSource.Http.expectString\n (\\string ->\n if String.toUpper string == string then\n Ok string\n\n else\n Err \"String was not uppercased\"\n )\n )\n\n","type":"(String.String -> value) -> DataSource.Http.Expect value"},{"name":"expectStringResponse","comment":" ","type":"(DataSource.Http.Response String.String -> value) -> DataSource.Http.Expect value"},{"name":"expectWhatever","comment":" ","type":"value -> DataSource.Http.Expect value"},{"name":"get","comment":" A simplified helper around [`DataSource.Http.request`](#request), which builds up a DataSource.Http GET request.\n\n import DataSource\n import DataSource.Http\n import Json.Decode as Decode exposing (Decoder)\n\n getRequest : DataSource Int\n getRequest =\n DataSource.Http.get\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 -> DataSource.DataSource a"},{"name":"jsonBody","comment":" Builds a JSON body for a DataSource.Http request. See [elm/http's `Http.jsonBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#jsonBody).\n","type":"Json.Encode.Value -> DataSource.Http.Body"},{"name":"request","comment":" Build a `DataSource.Http` request (analogous to [Http.request](https://package.elm-lang.org/packages/elm/http/latest/Http#request)).\nThis function takes in all the details to build a `DataSource.Http` request, but you can build your own simplified helper functions\nwith this as a low-level detail, or you can use functions like [DataSource.Http.get](#get).\n","type":"DataSource.Http.RequestDetails -> DataSource.Http.Expect a -> DataSource.DataSource a"},{"name":"stringBody","comment":" Builds a string body for a DataSource.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 -> DataSource.Http.Body"}],"binops":[]},{"name":"DataSource.Port","comment":"\n\n@docs get\n\n","unions":[],"aliases":[],"values":[{"name":"get","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 `DataSource.Port`, you send and receive JSON to JavaScript running in NodeJS during build-time. This means that you can call shell scripts, or run NPM packages that are installed, or anything else you could do with NodeJS.\n\nA `DataSource.Port` will call an async JavaScript function with the given name. The function receives the input JSON value, and the Decoder is used to decode the return value of the async function.\n\nHere is the Elm code and corresponding JavaScript definition for getting an environment variable (or a build error if it isn't found).\n\n import DataSource exposing (DataSource)\n import DataSource.Port\n import Json.Encode\n import OptimizedDecoder as Decode\n\n data : DataSource String\n data =\n DataSource.Port.get \"environmentVariable\"\n (Json.Encode.string \"EDITOR\")\n Decode.string\n\n -- will resolve to \"VIM\" if you run `EDITOR=vim elm-pages dev`\n\n```javascript\nconst kleur = require(\"kleur\");\n\n\nmodule.exports =\n /**\n * @param { unknown } fromElm\n * @returns { Promise }\n */\n {\n environmentVariable: async function (name) {\n const result = process.env[name];\n if (result) {\n return result;\n } else {\n throw `No environment variable called ${kleur\n .yellow()\n .underline(name)}\\n\\nAvailable:\\n\\n${Object.keys(process.env).join(\n \"\\n\"\n )}`;\n }\n },\n }\n```\n\n\n## Error Handling\n\n`port-data-source.js`\n\nAny time you throw an exception from a DataSource.Port definition, it will result in a build error in your `elm-pages build` or dev server. In the example above, if the environment variable\nis not found it will result in a build failure. Notice that the NPM package `kleur` is being used in this example to add color to the output for that build error. You can use any tool you\nprefer to add ANSI color codes within the error string in an exception and it will show up with color output in the build output and dev server.\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.\n\n","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder b -> DataSource.DataSource b"}],"binops":[]},{"name":"Form","comment":"\n\n\n## Wiring\n\n@docs Model, Msg, init, update, submitHandlers, toHtml, ServerUpdate\n\n\n## Defining a Form\n\n@docs Form, succeed\n\n\n## Building Up the Form View Layout\n\n@docs wrap, wrapFields\n\n\n## Form Submit Status\n\nThe form submissions are handled internally. Both tracking the submit status, and performing the underlying HTTP request.\n\n@docs isSubmitting, SubmitStatus\n\n\n## Rendering a Field\n\n@docs FieldRenderInfo, FieldStatus, isAtLeast\n\n\n## Appending to forms\n\n@docs with, append, appendForm\n\n\n## Fields\n\n@docs Field\n\n\n## Initial Values\n\n@docs withInitialValue\n\n\n## Field Types\n\n@docs checkbox, date, time, email, hidden, multiple, int, float, password, radio, range, telephone, text, url, floatRange, search\n\n\n## Input Fields\n\n\n### Submit Buttons\n\n@docs submit\n\n\n## Built-In Browser Validations\n\nWhenever possible, it's best to use the platform. For example, if you mark a field as number, the UI will give you number inputs. If you use an email input field, a mobile phone can display a special email input keyboard, or a desktop browser can suggest autofill input based on that.\n\nA Date type can be entered with the native date picker UI of the user's browser, which can be mobile-friendly by using the native mobile browser's built-in UI. But this also implies a validation, and can't be parsed into an Elm type. So you get two for the price of one. A UI, and a valdation. The validations are run on both client and server, so you can trust them without having to maintain duplicate logic for the server-side.\n\n\n### Required\n\n@docs required\n\n\n### Custom Client-Side Validations\n\n@docs validate\n\n\n### Server-Side Validations\n\n@docs withServerValidation\n\n\n### Minimum and Maximum Values\n\n@docs withMax, withMin\n\nSteps\n\n@docs withStep\n\n\n## Forms\n\n\n## Validations\n\n\n## Not Named Properly Yet\n\n@docs submitHandlers2, toHtml2\n\n\n## Internals?\n\n@docs hasErrors2, rawValues, runClientValidations, withClientValidation, withClientValidation2\n\n@docs FieldInfoSimple, FieldState, FinalFieldInfo, FormInfo, No, RawFieldState, TimeOfDay, Yes\n\n","unions":[{"name":"Field","comment":" ","args":["error","value","view","constraints"],"cases":[]},{"name":"FieldStatus","comment":" ","args":[],"cases":[["NotVisited",[]],["Focused",[]],["Changed",[]],["Blurred",[]]]},{"name":"Form","comment":" ","args":["error","value","view"],"cases":[["Form",["List.List ( List.List (Form.FieldInfoSimple error view), List.List view -> List.List view )","(String.String -> Server.Request.Request (Maybe.Maybe String.String)) -> Server.Request.Request (Result.Result (List.List ( String.String, List.List error )) ( value, List.List ( String.String, List.List error ) ))","(String.String -> Server.Request.Request (Maybe.Maybe String.String)) -> Server.Request.Request (DataSource.DataSource (List.List ( String.String, Form.RawFieldState error )))","Form.FieldState error -> Result.Result (List.List ( String.String, List.List error )) ( value, List.List ( String.String, List.List error ) )"]]]},{"name":"Msg","comment":" ","args":[],"cases":[["OnFieldInput",["{ name : String.String, value : String.String }"]],["OnFieldFocus",["{ name : String.String }"]],["OnBlur",["{ name : String.String }"]],["SubmitForm",[]],["GotFormResponse",["Result.Result Http.Error (Form.FieldState String.String)"]]]},{"name":"No","comment":" ","args":[],"cases":[]},{"name":"SubmitStatus","comment":" ","args":[],"cases":[["NotSubmitted",[]],["Submitting",[]],["Submitted",[]]]},{"name":"Yes","comment":" ","args":[],"cases":[]}],"aliases":[{"name":"FieldInfoSimple","comment":" ","args":["error","view"],"type":"{ name : String.String, initialValue : Maybe.Maybe String.String, type_ : String.String, required : Basics.Bool, serverValidation : Maybe.Maybe String.String -> DataSource.DataSource (List.List error), toHtml : Form.FormInfo -> Basics.Bool -> Form.FinalFieldInfo error -> Maybe.Maybe (Form.RawFieldState error) -> view, properties : List.List ( String.String, Json.Encode.Value ), clientValidations : Maybe.Maybe String.String -> Result.Result (List.List error) () }"},{"name":"FieldRenderInfo","comment":" ","args":["error"],"type":"{ toInput : List.List (Html.Attribute Form.Msg), toLabel : List.List (Html.Attribute Form.Msg), errors : List.List error, submitStatus : Form.SubmitStatus, status : Form.FieldStatus }"},{"name":"FieldState","comment":" ","args":["error"],"type":"Dict.Dict String.String (Form.RawFieldState error)"},{"name":"FinalFieldInfo","comment":" ","args":["error"],"type":"{ name : String.String, initialValue : Maybe.Maybe String.String, type_ : String.String, required : Basics.Bool, serverValidation : Maybe.Maybe String.String -> DataSource.DataSource (List.List error), properties : List.List ( String.String, Json.Encode.Value ) }"},{"name":"FormInfo","comment":" ","args":[],"type":"{ submitStatus : Form.SubmitStatus }"},{"name":"Model","comment":" ","args":[],"type":"{ fields : Form.FieldState String.String, isSubmitting : Form.SubmitStatus, formErrors : Dict.Dict String.String (List.List String.String) }"},{"name":"RawFieldState","comment":" ","args":["error"],"type":"{ raw : Maybe.Maybe String.String, errors : List.List error, status : Form.FieldStatus }"},{"name":"ServerUpdate","comment":" ","args":[],"type":"Dict.Dict String.String (Form.RawFieldState String.String)"},{"name":"TimeOfDay","comment":" ","args":[],"type":"{ hours : Basics.Int, minutes : Basics.Int }"}],"values":[{"name":"append","comment":" ","type":"Form.Field error value view constraints -> Form.Form error form view -> Form.Form error form view"},{"name":"appendForm","comment":" ","type":"(form1 -> form2 -> form) -> Form.Form error form1 view -> Form.Form error form2 view -> Form.Form error form view"},{"name":"checkbox","comment":" ","type":"String.String -> Basics.Bool -> (Form.FieldRenderInfo error -> view) -> Form.Field error Basics.Bool view { required : () }"},{"name":"date","comment":" ","type":"String.String -> { invalid : String.String -> error } -> (Form.FieldRenderInfo error -> view) -> Form.Field error (Maybe.Maybe Date.Date) view { min : Date.Date, max : Date.Date, required : (), wasMapped : Form.No, initial : Date.Date }"},{"name":"email","comment":" ","type":"Form.Field error value view { constraints | plainText : () } -> Form.Field error value view constraints"},{"name":"float","comment":" ","type":"String.String -> { invalid : String.String -> error } -> (Form.FieldRenderInfo error -> view) -> Form.Field error (Maybe.Maybe Basics.Float) view { min : Basics.Float, max : Basics.Float, required : (), wasMapped : Form.No, initial : Basics.Float }"},{"name":"floatRange","comment":" ","type":"String.String -> { missing : error, invalid : String.String -> error } -> { initial : Basics.Float, min : Basics.Float, max : Basics.Float } -> (Form.FieldRenderInfo error -> view) -> Form.Field error Basics.Float view { step : Basics.Float }"},{"name":"hasErrors2","comment":" ","type":"Form.Model -> Basics.Bool"},{"name":"hidden","comment":" ","type":"String.String -> String.String -> (List.List (Html.Attribute Form.Msg) -> view) -> Form.Field error String.String view { initial : String.String }"},{"name":"init","comment":" ","type":"Form.Form String.String value view -> Form.Model"},{"name":"int","comment":" ","type":"String.String -> { invalid : String.String -> error } -> (Form.FieldRenderInfo error -> view) -> Form.Field error (Maybe.Maybe Basics.Int) view { min : Basics.Int, max : Basics.Int, required : (), wasMapped : Form.No, initial : Basics.Int }"},{"name":"isAtLeast","comment":" ","type":"Form.FieldStatus -> Form.FieldStatus -> Basics.Bool"},{"name":"isSubmitting","comment":" ","type":"Form.Model -> Basics.Bool"},{"name":"multiple","comment":" ","type":"Form.Field error value view { constraints | multiple : () } -> Form.Field error value view constraints"},{"name":"password","comment":" ","type":"Form.Field error value view { constraints | plainText : () } -> Form.Field error value view constraints"},{"name":"radio","comment":" ","type":"String.String -> error -> ( ( String.String, item ), List.List ( String.String, item ) ) -> (item -> Form.FieldRenderInfo error -> view) -> ({ errors : List.List error, submitStatus : Form.SubmitStatus, status : Form.FieldStatus } -> List.List view -> view) -> Form.Field error (Maybe.Maybe item) view { required : (), wasMapped : Form.No }"},{"name":"range","comment":" ","type":"String.String -> { missing : error, invalid : String.String -> error } -> { initial : Basics.Int, min : Basics.Int, max : Basics.Int } -> (Form.FieldRenderInfo error -> view) -> Form.Field error Basics.Int view {}"},{"name":"rawValues","comment":" ","type":"Form.Model -> Dict.Dict String.String String.String"},{"name":"required","comment":" ","type":"error -> Form.Field error (Maybe.Maybe value) view { constraints | required : (), wasMapped : Form.No } -> Form.Field error value view { constraints | wasMapped : Form.No }"},{"name":"runClientValidations","comment":" ","type":"Form.Model -> Form.Form String.String value view -> Result.Result (List.List ( String.String, List.List String.String )) ( value, List.List ( String.String, List.List String.String ) )"},{"name":"search","comment":" ","type":"Form.Field error value view { constraints | plainText : () } -> Form.Field error value view constraints"},{"name":"submit","comment":" ","type":"({ attrs : List.List (Html.Attribute Form.Msg), formHasErrors : Basics.Bool } -> view) -> Form.Field error () view {}"},{"name":"submitHandlers","comment":" ","type":"Form.Form String.String decoded view -> (Form.Model -> Result.Result () decoded -> DataSource.DataSource data) -> Server.Request.Request (DataSource.DataSource (Server.Response.Response data))"},{"name":"submitHandlers2","comment":" ","type":"Form.Form String.String decoded view -> (Form.Model -> Result.Result () decoded -> DataSource.DataSource (Server.Response.Response data)) -> Server.Request.Request (DataSource.DataSource (Server.Response.Response data))"},{"name":"succeed","comment":" ","type":"constructor -> Form.Form error constructor view"},{"name":"telephone","comment":" ","type":"Form.Field error value view { constraints | plainText : () } -> Form.Field error value view constraints"},{"name":"text","comment":" ","type":"String.String -> (Form.FieldRenderInfo error -> view) -> Form.Field error (Maybe.Maybe String.String) view { required : (), plainText : (), wasMapped : Form.No, initial : String.String }"},{"name":"time","comment":" ","type":"String.String -> { invalid : String.String -> error } -> (Form.FieldRenderInfo error -> view) -> Form.Field error (Maybe.Maybe Form.TimeOfDay) view { required : (), wasMapped : Form.No }"},{"name":"toHtml","comment":" ","type":"{ pageReloadSubmit : Basics.Bool } -> (List.List (Html.Attribute Form.Msg) -> List.List view -> view) -> Form.Model -> Form.Form String.String value view -> view"},{"name":"toHtml2","comment":" ","type":"{ makeHttpRequest : List.List ( String.String, String.String ) -> msg } -> (List.List (Html.Attribute msg) -> List.List view -> view) -> Form.Model -> Form.Form String.String value view -> view"},{"name":"update","comment":" ","type":"(Form.Msg -> msg) -> (Result.Result Http.Error (Form.FieldState String.String) -> msg) -> Form.Form String.String value view -> Form.Msg -> Form.Model -> ( Form.Model, Platform.Cmd.Cmd msg )"},{"name":"url","comment":" ","type":"Form.Field error value view { constraints | plainText : () } -> Form.Field error value view constraints"},{"name":"validate","comment":" ","type":"(form -> List.List ( String.String, List.List error )) -> Form.Form error form view -> Form.Form error form view"},{"name":"with","comment":" ","type":"Form.Field error value view constraints -> Form.Form error (value -> form) view -> Form.Form error form view"},{"name":"withClientValidation","comment":" ","type":"(value -> Result.Result error mapped) -> Form.Field error value view constraints -> Form.Field error mapped view { constraints | wasMapped : Form.Yes }"},{"name":"withClientValidation2","comment":" ","type":"(value -> Result.Result (List.List error) ( mapped, List.List error )) -> Form.Field error value view constraints -> Form.Field error mapped view { constraints | wasMapped : Form.Yes }"},{"name":"withInitialValue","comment":" ","type":"Form.Value.Value valueType -> Form.Field error value view { constraints | initial : valueType } -> Form.Field error value view constraints"},{"name":"withMax","comment":" ","type":"Form.Value.Value valueType -> Form.Field error value view { constraints | max : valueType } -> Form.Field error value view constraints"},{"name":"withMin","comment":" ","type":"Form.Value.Value valueType -> Form.Field error value view { constraints | min : valueType } -> Form.Field error value view constraints"},{"name":"withServerValidation","comment":" ","type":"(value -> DataSource.DataSource (List.List error)) -> Form.Field error value view constraints -> Form.Field error value view constraints"},{"name":"withStep","comment":" ","type":"Form.Value.Value valueType -> Form.Field error value view { constraints | step : valueType } -> Form.Field error value view constraints"},{"name":"wrap","comment":" ","type":"(List.List view -> view) -> Form.Form error form view -> Form.Form error form view"},{"name":"wrapFields","comment":" ","type":"List.List ( List.List (Form.FieldInfoSimple error view), List.List view -> List.List view ) -> (List.List view -> view) -> List.List ( List.List (Form.FieldInfoSimple error view), List.List view -> List.List view )"}],"binops":[]},{"name":"Form.Value","comment":"\n\n@docs Value, date, float, int, string, toString\n\n","unions":[{"name":"Value","comment":" ","args":["dataType"],"cases":[]}],"aliases":[],"values":[{"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 low-level functions for building up\nvalues that will be rendered into the page's `` tag\nwhen you run `elm-pages build`. Most likely the `Head.Seo` module\nwill do everything you need out of the box, and you will just need to import `Head`\nso you can use the `Tag` type in your type annotations.\n\nBut this module might be useful if you have a special use case, or if you are\nwriting a plugin package to extend `elm-pages`.\n\n@docs Tag, metaName, metaProperty, metaRedirect\n@docs rssLink, sitemapLink, rootLanguage, manifestLink\n\n\n## Structured Data\n\n@docs structuredData\n\n\n## `AttributeValue`s\n\n@docs AttributeValue\n@docs currentPageFullUrl, urlAttribute, raw\n\n\n## Icons\n\n@docs appleTouchIcon, icon\n\n\n## Functions for use by generated code\n\n@docs toJson, canonicalLink\n\n","unions":[{"name":"AttributeValue","comment":" Values, such as between the `<>`'s here:\n\n```html\n\" content=\"\" />\n```\n\n","args":[],"cases":[]},{"name":"Tag","comment":" Values that can be passed to the generated `Pages.application` config\nthrough the `head` function.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"appleTouchIcon","comment":" Note: the type must be png.\nSee .\n\nIf a size is provided, it will be turned into square dimensions as per the recommendations here: \n\nImages must be png's, and non-transparent images are recommended. Current recommended dimensions are 180px and 192px.\n\n","type":"Maybe.Maybe Basics.Int -> Pages.Url.Url -> Head.Tag"},{"name":"canonicalLink","comment":" It's recommended that you use the `Seo` module helpers, which will provide this\nfor you, rather than directly using this.\n\nExample:\n\n Head.canonicalLink \"https://elm-pages.com\"\n\n","type":"Maybe.Maybe String.String -> Head.Tag"},{"name":"currentPageFullUrl","comment":" Create an `AttributeValue` representing the current page's full url.\n","type":"Head.AttributeValue"},{"name":"icon","comment":" ","type":"List.List ( Basics.Int, Basics.Int ) -> MimeType.MimeImage -> Pages.Url.Url -> Head.Tag"},{"name":"manifestLink","comment":" Let's you link to your manifest.json file, see .\n","type":"String.String -> Head.Tag"},{"name":"metaName","comment":" Example:\n\n Head.metaName \"twitter:card\" (Head.raw \"summary_large_image\")\n\nResults in ``\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaProperty","comment":" Example:\n\n Head.metaProperty \"fb:app_id\" (Head.raw \"123456789\")\n\nResults in ``\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaRedirect","comment":" Example:\n\n metaRedirect (Raw \"0; url=https://google.com\")\n\nResults in ``\n\n","type":"Head.AttributeValue -> Head.Tag"},{"name":"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\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\n...\n\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\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\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\n```\n\nTo get that data, you would write this in your `elm-pages` head tags:\n\n import Json.Encode as Encode\n\n {-| \n -}\n encodeArticle :\n { title : String\n , description : String\n , author : StructuredDataHelper { authorMemberOf | personOrOrganization : () } authorPossibleFields\n , publisher : StructuredDataHelper { publisherMemberOf | personOrOrganization : () } publisherPossibleFields\n , url : String\n , imageUrl : String\n , datePublished : String\n , mainEntityOfPage : Encode.Value\n }\n -> Head.Tag\n encodeArticle info =\n Encode.object\n [ ( \"@context\", Encode.string \"http://schema.org/\" )\n , ( \"@type\", Encode.string \"Article\" )\n , ( \"headline\", Encode.string info.title )\n , ( \"description\", Encode.string info.description )\n , ( \"image\", Encode.string info.imageUrl )\n , ( \"author\", encode info.author )\n , ( \"publisher\", encode info.publisher )\n , ( \"url\", Encode.string info.url )\n , ( \"datePublished\", Encode.string info.datePublished )\n , ( \"mainEntityOfPage\", info.mainEntityOfPage )\n ]\n |> Head.structuredData\n\nTake a look at this [Google Search Gallery](https://developers.google.com/search/docs/guides/search-gallery)\nto see some examples of how structured data can be used by search engines to give rich search results. It can help boost\nyour rankings, get better engagement for your content, and also make your content more accessible. For example,\nvoice assistant devices can make use of structured data. If you're hosting a conference and want to make the event\ndate and location easy for attendees to find, this can make that information more accessible.\n\nFor the current version of API, you'll need to make sure that the format is correct and contains the required and recommended\nstructure.\n\nCheck out for a comprehensive listing of possible data types and fields. And take a look at\nGoogle's [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool)\ntoo make sure that your structured data is valid and includes the recommended values.\n\nIn the future, `elm-pages` will likely support a typed API, but schema.org is a massive spec, and changes frequently.\nAnd there are multiple sources of information on the possible and recommended structure. So it will take some time\nfor the right API design to evolve. In the meantime, this allows you to make use of this for SEO purposes.\n\n","type":"Json.Encode.Value -> Head.Tag"},{"name":"toJson","comment":" Feel free to use this, but in 99% of cases you won't need it. The generated\ncode will run this for you to generate your `manifest.json` file automatically!\n","type":"String.String -> String.String -> Head.Tag -> Json.Encode.Value"},{"name":"urlAttribute","comment":" Create an `AttributeValue` from an `ImagePath`.\n","type":"Pages.Url.Url -> Head.AttributeValue"}],"binops":[]},{"name":"Head.Seo","comment":" \n\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 and \n\nSkipping this for now, if there's a use case I can add it in:\n\n - og:determiner - The word that appears before this object's title in a sentence. An enum of (a, an, the, \"\", auto). If auto is chosen, the consumer of your data should chose between \"a\" or \"an\". Default is \"\" (blank).\n\n","args":[],"type":"{ title : String.String, image : Head.Seo.Image, canonicalUrlOverride : Maybe.Maybe String.String, description : String.String, siteName : String.String, audio : Maybe.Maybe Head.Seo.Audio, video : Maybe.Maybe Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale, alternateLocales : List.List Head.Seo.Locale, twitterCard : Head.Twitter.TwitterCard }"},{"name":"Image","comment":" See \n","args":[],"type":"{ url : Pages.Url.Url, alt : String.String, dimensions : Maybe.Maybe { width : Basics.Int, height : Basics.Int }, mimeType : Maybe.Maybe Head.Seo.MimeType }"}],"values":[{"name":"article","comment":" See \n","type":"{ tags : List.List String.String, section : Maybe.Maybe String.String, publishedTime : Maybe.Maybe Head.Seo.Iso8601DateTime, modifiedTime : Maybe.Maybe Head.Seo.Iso8601DateTime, expirationTime : Maybe.Maybe Head.Seo.Iso8601DateTime } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"audioPlayer","comment":" Will be displayed as a Player card in twitter\nSee: \n\nOpenGraph audio will also be included.\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, audio : Head.Seo.Audio, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"book","comment":" See \n","type":"Head.Seo.Common -> { tags : List.List String.String, isbn : Maybe.Maybe String.String, releaseDate : Maybe.Maybe Head.Seo.Iso8601DateTime } -> List.List Head.Tag"},{"name":"profile","comment":" See \n","type":"{ firstName : String.String, lastName : String.String, username : Maybe.Maybe String.String } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"song","comment":" See \n","type":"Head.Seo.Common -> { duration : Maybe.Maybe Basics.Int, album : Maybe.Maybe Basics.Int, disc : Maybe.Maybe Basics.Int, track : Maybe.Maybe Basics.Int } -> List.List Head.Tag"},{"name":"summary","comment":" Will be displayed as a large card in twitter\nSee: \n\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"summaryLarge","comment":" Will be displayed as a large card in twitter\nSee: \n\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"videoPlayer","comment":" Will be displayed as a Player card in twitter\nSee: \n\nOpenGraph video will also be included.\nThe options will also be used to build up the appropriate OpenGraph `` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, video : Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"website","comment":" \n","type":"Head.Seo.Common -> List.List Head.Tag"}],"binops":[]},{"name":"Pages.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.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## Config options\n\n@docs DisplayMode, Orientation, IconPurpose\n\n\n## Generating a Manifest.json\n\n@docs generator\n\n\n## Functions for use by the generated code (`Pages.elm`)\n\n@docs toJson\n\n","unions":[{"name":"DisplayMode","comment":" See \n","args":[],"cases":[["Fullscreen",[]],["Standalone",[]],["MinimalUi",[]],["Browser",[]]]},{"name":"IconPurpose","comment":" \n","args":[],"cases":[["IconPurposeMonochrome",[]],["IconPurposeMaskable",[]],["IconPurposeAny",[]]]},{"name":"Orientation","comment":" \n","args":[],"cases":[["Any",[]],["Natural",[]],["Landscape",[]],["LandscapePrimary",[]],["LandscapeSecondary",[]],["Portrait",[]],["PortraitPrimary",[]],["PortraitSecondary",[]]]}],"aliases":[{"name":"Config","comment":" Represents a [web app manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest)\n(see above for how to use it).\n","args":[],"type":"{ backgroundColor : Maybe.Maybe Color.Color, categories : List.List Pages.Manifest.Category.Category, displayMode : Pages.Manifest.DisplayMode, orientation : Pages.Manifest.Orientation, description : String.String, iarcRatingId : Maybe.Maybe String.String, name : String.String, themeColor : Maybe.Maybe Color.Color, startUrl : Path.Path, shortName : Maybe.Maybe String.String, icons : List.List Pages.Manifest.Icon, lang : LanguageTag.LanguageTag }"},{"name":"Icon","comment":" \n","args":[],"type":"{ src : Pages.Url.Url, sizes : List.List ( Basics.Int, Basics.Int ), mimeType : Maybe.Maybe MimeType.MimeImage, purposes : List.List Pages.Manifest.IconPurpose }"}],"values":[{"name":"generator","comment":" A generator for Api.elm to include a manifest.json.\n","type":"String.String -> DataSource.DataSource 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 .\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withCategories","comment":" Set .\n","type":"List.List Pages.Manifest.Category.Category -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withDisplayMode","comment":" Set .\n","type":"Pages.Manifest.DisplayMode -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withIarcRatingId","comment":" Set .\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withLang","comment":" Set .\n","type":"LanguageTag.LanguageTag -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withOrientation","comment":" Set .\n","type":"Pages.Manifest.Orientation -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withShortName","comment":" Set .\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withThemeColor","comment":" Set .\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"}],"binops":[]},{"name":"Pages.Manifest.Category","comment":" See and\n\n\n@docs toString, Category\n\n@docs books, business, education, entertainment, finance, fitness, food, games, government, health, kids, lifestyle, magazines, medical, music, navigation, news, personalization, photo, politics, productivity, security, shopping, social, sports, travel, utilities, weather\n\n\n## Custom categories\n\n@docs custom\n\n","unions":[{"name":"Category","comment":" Represents a known, valid category, as specified by\n. If this document is updated\nand I don't add it, please open an issue or pull request to let me know!\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"books","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"business","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"custom","comment":" It's best to use the pre-defined categories to ensure that clients (Android, iOS,\nChrome, Windows app store, etc.) are aware of it and can handle it appropriately.\nBut, if you're confident about using a custom one, you can do so with `Pages.Manifest.custom`.\n","type":"String.String -> Pages.Manifest.Category.Category"},{"name":"education","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"entertainment","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"finance","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"fitness","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"food","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"games","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"government","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"health","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"kids","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"lifestyle","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"magazines","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"medical","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"music","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"navigation","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"news","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"personalization","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"photo","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"politics","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"productivity","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"security","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"shopping","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"social","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"sports","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"toString","comment":" Turn a category into its official String representation, as seen\nhere: .\n","type":"Pages.Manifest.Category.Category -> String.String"},{"name":"travel","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"utilities","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"weather","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"}],"binops":[]},{"name":"Pages.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.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 Request\n\n@docs Method, methodToString\n\n@docs succeed\n\n@docs requestTime, optionalHeader, expectContentType, expectJsonBody, jsonBodyResult\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 expectQueryParam\n\n\n## Cookies\n\n@docs cookie, expectCookie\n\n\n## Headers\n\n@docs expectHeader\n\n\n## Form Posts\n\n@docs expectFormPost\n\n\n## Multi-part forms and file uploads\n\n@docs File, expectMultiPartFormPost\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":"Request","comment":" ","args":["decodesTo"],"cases":[["Request",["Json.Decode.Decoder ( Result.Result Server.Request.ValidationError decodesTo, List.List Server.Request.ValidationError )"]]]},{"name":"ValidationError","comment":" ","args":[],"cases":[]}],"aliases":[{"name":"File","comment":" ","args":[],"type":"{ name : String.String, mimeType : String.String, body : String.String }"}],"values":[{"name":"acceptContentTypes","comment":" ","type":"( String.String, List.List String.String ) -> Server.Request.Request value -> Server.Request.Request value"},{"name":"acceptMethod","comment":" ","type":"( Server.Request.Method, List.List Server.Request.Method ) -> Server.Request.Request value -> Server.Request.Request value"},{"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.Request a -> Server.Request.Request (a -> b) -> Server.Request.Request b"},{"name":"andThen","comment":" ","type":"(a -> Server.Request.Request b) -> Server.Request.Request a -> Server.Request.Request b"},{"name":"cookie","comment":" ","type":"String.String -> Server.Request.Request (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":"expectContentType","comment":" ","type":"String.String -> Server.Request.Request Basics.Bool"},{"name":"expectCookie","comment":" ","type":"String.String -> Server.Request.Request String.String"},{"name":"expectFormPost","comment":" ","type":"({ field : String.String -> Server.Request.Request String.String, optionalField : String.String -> Server.Request.Request (Maybe.Maybe String.String) } -> Server.Request.Request decodedForm) -> Server.Request.Request decodedForm"},{"name":"expectHeader","comment":" ","type":"String.String -> Server.Request.Request String.String"},{"name":"expectJsonBody","comment":" ","type":"Json.Decode.Decoder value -> Server.Request.Request value"},{"name":"expectMultiPartFormPost","comment":" ","type":"({ field : String.String -> Server.Request.Request String.String, optionalField : String.String -> Server.Request.Request (Maybe.Maybe String.String), fileField : String.String -> Server.Request.Request Server.Request.File } -> Server.Request.Request decodedForm) -> Server.Request.Request decodedForm"},{"name":"expectQueryParam","comment":" ","type":"String.String -> Server.Request.Request String.String"},{"name":"getDecoder","comment":" TODO internal only\n","type":"Server.Request.Request (DataSource.DataSource response) -> Json.Decode.Decoder (Result.Result ( Server.Request.ValidationError, List.List Server.Request.ValidationError ) (DataSource.DataSource response))"},{"name":"jsonBodyResult","comment":" ","type":"Json.Decode.Decoder value -> Server.Request.Request (Result.Result Json.Decode.Error value)"},{"name":"map","comment":" ","type":"(a -> b) -> Server.Request.Request a -> Server.Request.Request b"},{"name":"map2","comment":" ","type":"(a -> b -> c) -> Server.Request.Request a -> Server.Request.Request b -> Server.Request.Request c"},{"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.Request a) -> Server.Request.Request a"},{"name":"optionalHeader","comment":" ","type":"String.String -> Server.Request.Request (Maybe.Maybe String.String)"},{"name":"requestTime","comment":" ","type":"Server.Request.Request Time.Posix"},{"name":"succeed","comment":" ","type":"value -> Server.Request.Request 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 Page Module. To do that, you'll need to pass along the `data` for your Page 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@docs customResponse, RawResponse\n\n\n## Render Responses\n\n@docs render\n\n@docs map\n\n\n## Amending Responses\n\n@docs withHeader, withStatusCode, withSetCookieHeader\n\n\n## Internals\n\n@docs toJson\n\n","unions":[],"aliases":[{"name":"RawResponse","comment":" ","args":[],"type":"{ statusCode : Basics.Int, headers : List.List ( String.String, String.String ), body : Maybe.Maybe String.String, isBase64Encoded : Basics.Bool }"},{"name":"Response","comment":" ","args":["data"],"type":"PageServerResponse.PageServerResponse data"}],"values":[{"name":"customResponse","comment":" ","type":"Server.Response.RawResponse -> Server.Response.Response data"},{"name":"json","comment":" ","type":"Json.Encode.Value -> Server.Response.Response data"},{"name":"map","comment":" ","type":"(data -> mappedData) -> Server.Response.Response data -> Server.Response.Response mappedData"},{"name":"permanentRedirect","comment":" Build a 308 permanent redirect response.\n\nPermanent redirects tell the browser that a resource has permanently moved. If you redirect because a user is not logged in,\nthen you **do not** want to use a permanent redirect because the page they are looking for hasn't changed, you are just\ntemporarily pointing them to a new page since they need to authenticate.\n\nPermanent redirects are aggressively cached so be careful not to use them when you mean to use temporary redirects instead.\n\nIf you need to specifically rely on a 301 permanent redirect (see on the difference between 301 and 308),\nuse `customResponse` instead.\n\n","type":"String.String -> Server.Response.Response data"},{"name":"plainText","comment":" ","type":"String.String -> Server.Response.Response data"},{"name":"render","comment":" ","type":"data -> Server.Response.Response data"},{"name":"temporaryRedirect","comment":" ","type":"String.String -> Server.Response.Response data"},{"name":"toJson","comment":" ","type":"Server.Response.Response Basics.Never -> Json.Encode.Value"},{"name":"withHeader","comment":" ","type":"String.String -> String.String -> Server.Response.Response data -> Server.Response.Response data"},{"name":"withSetCookieHeader","comment":" ","type":"Server.SetCookie.SetCookie -> Server.Response.Response data -> Server.Response.Response data"},{"name":"withStatusCode","comment":" ","type":"Basics.Int -> Server.Response.Response data -> Server.Response.Response data"}],"binops":[]},{"name":"Server.Session","comment":"\n\n@docs Decoder, NotLoadedReason, Session, SessionUpdate, Value, clearFlashCookies, empty, expectSession, flash, flashPrefix, get, insert, noUpdates, oneUpdate, remove, setValues, succeed, unwrap, update, updateAllFields, withFlash, withFlash2, withSession\n\n","unions":[{"name":"NotLoadedReason","comment":" ","args":[],"cases":[["NoCookies",[]],["MissingHeaders",[]]]},{"name":"Session","comment":" ","args":[],"cases":[["Session",["Dict.Dict String.String Server.Session.Value"]]]},{"name":"SessionUpdate","comment":" ","args":[],"cases":[["SessionUpdate",["Dict.Dict String.String String.String"]]]},{"name":"Value","comment":" ","args":[],"cases":[["Persistent",["String.String"]],["ExpiringFlash",["String.String"]],["NewFlash",["String.String"]]]}],"aliases":[{"name":"Decoder","comment":" ","args":["decoded"],"type":"Json.Decode.Decoder decoded"}],"values":[{"name":"clearFlashCookies","comment":" ","type":"Dict.Dict String.String String.String -> Dict.Dict String.String String.String"},{"name":"empty","comment":" ","type":"Server.Session.Session"},{"name":"expectSession","comment":" ","type":"{ name : String.String, secrets : DataSource.DataSource (List.List String.String), sameSite : String.String } -> Server.Request.Request request -> (request -> Result.Result () Server.Session.Session -> DataSource.DataSource ( Server.Session.Session, Server.Response.Response data )) -> Server.Request.Request (DataSource.DataSource (Server.Response.Response data))"},{"name":"flash","comment":" ","type":"String.String -> String.String -> Server.Session.SessionUpdate"},{"name":"flashPrefix","comment":" ","type":"String.String"},{"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":"noUpdates","comment":" ","type":"Server.Session.SessionUpdate"},{"name":"oneUpdate","comment":" ","type":"String.String -> String.String -> Server.Session.SessionUpdate"},{"name":"remove","comment":" ","type":"String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"setValues","comment":" ","type":"Server.Session.Session -> Json.Encode.Value"},{"name":"succeed","comment":" ","type":"constructor -> Server.Session.Decoder constructor"},{"name":"unwrap","comment":" ","type":"Server.Session.Value -> String.String"},{"name":"update","comment":" ","type":"String.String -> (Maybe.Maybe String.String -> Maybe.Maybe String.String) -> Server.Session.Session -> Server.Session.Session"},{"name":"updateAllFields","comment":" ","type":"Dict.Dict String.String String.String -> Server.Session.SessionUpdate"},{"name":"withFlash","comment":" ","type":"String.String -> String.String -> Server.Session.SessionUpdate -> Server.Session.SessionUpdate"},{"name":"withFlash2","comment":" ","type":"String.String -> String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"withSession","comment":" ","type":"{ name : String.String, secrets : DataSource.DataSource (List.List String.String), sameSite : String.String } -> Server.Request.Request request -> (request -> Result.Result () (Maybe.Maybe Server.Session.Session) -> DataSource.DataSource ( Server.Session.Session, Server.Response.Response data )) -> Server.Request.Request (DataSource.DataSource (Server.Response.Response data))"}],"binops":[]},{"name":"Server.SetCookie","comment":" \n\n\n\n@docs SetCookie, SameSite\n@docs withImmediateExpiration, httpOnly, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite\n\n@docs toString\n\n","unions":[{"name":"SameSite","comment":" ","args":[],"cases":[["Strict",[]],["Lax",[]],["None",[]]]}],"aliases":[{"name":"SetCookie","comment":" ","args":[],"type":"{ name : String.String, value : String.String, expiration : Maybe.Maybe Time.Posix, httpOnly : 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 }"}],"values":[{"name":"httpOnly","comment":" ","type":"Server.SetCookie.SetCookie -> Server.SetCookie.SetCookie"},{"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.SetCookie -> Server.SetCookie.SetCookie"},{"name":"setCookie","comment":" ","type":"String.String -> String.String -> Server.SetCookie.SetCookie"},{"name":"toString","comment":" ","type":"Server.SetCookie.SetCookie -> String.String"},{"name":"withDomain","comment":" ","type":"String.String -> Server.SetCookie.SetCookie -> Server.SetCookie.SetCookie"},{"name":"withExpiration","comment":" ","type":"Time.Posix -> Server.SetCookie.SetCookie -> Server.SetCookie.SetCookie"},{"name":"withImmediateExpiration","comment":" ","type":"Server.SetCookie.SetCookie -> Server.SetCookie.SetCookie"},{"name":"withMaxAge","comment":" ","type":"Basics.Int -> Server.SetCookie.SetCookie -> Server.SetCookie.SetCookie"},{"name":"withPath","comment":" ","type":"String.String -> Server.SetCookie.SetCookie -> Server.SetCookie.SetCookie"},{"name":"withSameSite","comment":" The default SameSite policy is Lax if one is not explicitly set. See the SameSite section in .\n","type":"Server.SetCookie.SameSite -> Server.SetCookie.SetCookie -> Server.SetCookie.SetCookie"}],"binops":[]}]