mirror of
https://github.com/dillonkearns/elm-pages.git
synced 2024-09-11 09:05:28 +03:00
1 line
258 KiB
JSON
1 line
258 KiB
JSON
[{"name":"ApiRoute","comment":" ApiRoute's are defined in `src/Api.elm` and are a way to generate files either statically pre-rendered at build-time (like RSS feeds, sitemaps, or any text-based file that you output with an Elm function),\nor server-rendered at runtime (like a JSON API endpoint, or an RSS feed that gives you fresh data without rebuilding your site).\n\nYour ApiRoute's get access to a [`BackendTask`](BackendTask) so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in\nthe BackendTask for your ApiRoutes and it won't effect the payload size. Instead, the size of an ApiRoute is just the content you output for that route.\n\nSimilar to your elm-pages Route Modules, ApiRoute's can be either server-rendered or pre-rendered. Let's compare the differences between pre-rendered and server-rendered ApiRoutes, and the different\nuse cases they support.\n\n\n## Pre-Rendering\n\nA pre-rendered ApiRoute is just a generated file. For example:\n\n - [An RSS feed](https://github.com/dillonkearns/elm-pages/blob/131f7b750cdefb2ba7a34a06be06dfbfafc79a86/examples/docs/app/Api.elm#L77-L84) ([Output file](https://elm-pages.com/blog/feed.xml))\n - [A calendar feed in the ical format](https://github.com/dillonkearns/incrementalelm.com/blob/d4934d899d06232dc66dcf9f4b5eccc74bbc60d3/src/Api.elm#L51-L60) ([Output file](https://incrementalelm.com/live.ics))\n - A redirect file for a hosting provider like Netlify\n\nYou could even generate a JavaScript file, an Elm file, or any file with a String body! It's really just a way to generate files, which are typically used to serve files to a user or Browser, but you execute them, copy them, etc. The only limit is your imagination!\nThe beauty is that you have a way to 1) pull in type-safe data using BackendTask's, and 2) write those files, and all in pure Elm!\n\n@docs single, preRender\n\n\n## Server Rendering\n\nYou could use server-rendered ApiRoutes to do a lot of similar things, the main difference being that it will be served up through a URL and generated on-demand when that URL is requested.\nSo for example, for an RSS feed or ical calendar feed like in the pre-rendered examples, you could build the same routes, but you would be pulling in the list of posts or calendar events on-demand rather\nthan upfront at build-time. That means you can hit your database and serve up always-up-to-date data.\n\nNot only that, but your server-rendered ApiRoutes have access to the incoming HTTP request payload just like your server-rendered Route Modules do. Just as with server-rendered Route Modules,\na server-rendered ApiRoute accesses the incoming HTTP request through a [Server.Request.Parser](Server-Request). Consider the use cases that this opens up:\n\n - Serve up protected assets. For example, gated content, like a paid subscriber feed for a podcast that checks authentication information in a query parameter to authenticate that a user has an active paid subscription before serving up the Pro RSS feed.\n - Serve up user-specific content, either through a cookie or other means of authentication\n - Look at the [accepted content-type in the request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) and use that to choose a response format, like XML or JSON ([full example](https://github.com/dillonkearns/elm-pages/blob/131f7b750cdefb2ba7a34a06be06dfbfafc79a86/examples/end-to-end/app/Api.elm#L76-L107)).\n - Look at the [accepted language in the request headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language) and use that to choose a language for the response data.\n\n@docs serverRender\n\nYou can also do a hybrid approach using `preRenderWithFallback`. This allows you to pre-render a set of routes at build-time, but build additional routes that weren't rendered at build-time on the fly on the server.\nConceptually, this is just a delayed version of a pre-rendered route. Because of that, you _do not_ have access to the incoming HTTP request (no `Server.Request.Parser` like in server-rendered ApiRoute's).\nThe strategy used to build these routes will differ depending on your hosting provider and the elm-pages adapter you have setup, but generally ApiRoute's that use `preRenderWithFallback` will be cached on the server\nso within a certain time interval (or in the case of [Netlify's DPR](https://www.netlify.com/blog/2021/04/14/distributed-persistent-rendering-a-new-jamstack-approach-for-faster-builds/), until a new build is done)\nthat asset will be served up if that URL was already served up by the server.\n\n@docs preRenderWithFallback\n\n\n## Defining ApiRoute's\n\nYou define your ApiRoute's in `app/Api.elm`. Here's a simple example:\n\n module Api exposing (routes)\n\n import ApiRoute\n import BackendTask exposing (BackendTask)\n import FatalError exposing (FatalError)\n import Server.Request\n\n routes :\n BackendTask FatalError (List Route)\n -> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)\n -> List (ApiRoute.ApiRoute ApiRoute.Response)\n routes getStaticRoutes htmlToString =\n [ preRenderedExample\n , requestPrinterExample\n ]\n\n {-| Generates the following files when you\n run `elm-pages build`:\n\n - `dist/users/1.json`\n - `dist/users/2.json`\n - `dist/users/3.json`\n\n When you host it, these static assets will\n be served at `/users/1.json`, etc.\n\n -}\n preRenderedExample : ApiRoute.ApiRoute ApiRoute.Response\n preRenderedExample =\n ApiRoute.succeed\n (\\userId ->\n BackendTask.succeed\n (Json.Encode.object\n [ ( \"id\", Json.Encode.string userId )\n , ( \"name\", \"Data for user \" ++ userId |> Json.Encode.string )\n ]\n |> Json.Encode.encode 2\n )\n )\n |> ApiRoute.literal \"users\"\n |> ApiRoute.slash\n |> ApiRoute.capture\n |> ApiRoute.literal \".json\"\n |> ApiRoute.preRender\n (\\route ->\n BackendTask.succeed\n [ route \"1\"\n , route \"2\"\n , route \"3\"\n ]\n )\n\n {-| This returns a JSON response that prints information about the incoming\n HTTP request. In practice you'd want to do something useful with that data,\n and use more of the high-level helpers from the Server.Request API.\n -}\n requestPrinterExample : ApiRoute ApiRoute.Response\n requestPrinterExample =\n ApiRoute.succeed\n (\\pageId revisionId request ->\n Encode.object\n [ ( \"pageId\"\n , Encode.string pageId\n )\n , ( \"revisionId\"\n , Encode.string revisionId\n )\n , ( \"body\"\n , request\n |> Server.Request.body\n |> Maybe.map Encode.string\n |> Maybe.withDefault Encode.null\n )\n , ( \"method\"\n , request\n |> Server.Request.method\n |> Server.Request.methodToString\n |> Encode.string\n )\n , ( \"cookies\"\n , request\n |> Server.Request.cookies\n |> Encode.dict\n identity\n Encode.string\n )\n , ( \"queryParams\"\n , request\n |> Server.Request.queryParams\n |> Encode.dict\n identity\n (Encode.list Encode.string)\n )\n ]\n |> Response.json\n |> BackendTask.succeed\n )\n -- Path: /pages/:pageId/revisions/:revisionId/request-test\n |> ApiRoute.literal \"pages\"\n |> ApiRoute.slash\n |> ApiRoute.capture\n |> ApiRoute.slash\n |> ApiRoute.literal \"revisions\"\n |> ApiRoute.slash\n |> ApiRoute.capture\n |> ApiRoute.slash\n |> ApiRoute.literal \"request-test\"\n |> ApiRoute.serverRender\n\n@docs ApiRoute, ApiRouteBuilder, Response\n\n@docs capture, literal, slash, succeed\n\n\n## Including Head Tags\n\n@docs withGlobalHeadTags\n\n\n## Internals\n\n@docs toJson, getBuildTimeRoutes, getGlobalHeadTagsBackendTask\n\n","unions":[],"aliases":[{"name":"ApiRoute","comment":" ","args":["response"],"type":"Internal.ApiRoute.ApiRoute response"},{"name":"ApiRouteBuilder","comment":" The intermediary value while building an ApiRoute definition.\n","args":["a","constructor"],"type":"Internal.ApiRoute.ApiRouteBuilder a constructor"},{"name":"Response","comment":" The final value from defining an ApiRoute.\n","args":[],"type":"Json.Encode.Value"}],"values":[{"name":"capture","comment":" Captures a dynamic segment from the route.\n","type":"ApiRoute.ApiRouteBuilder (String.String -> a) constructor -> ApiRoute.ApiRouteBuilder a (String.String -> constructor)"},{"name":"getBuildTimeRoutes","comment":" For internal use by generated code. Not so useful in user-land.\n","type":"ApiRoute.ApiRoute response -> BackendTask.BackendTask FatalError.FatalError (List.List String.String)"},{"name":"getGlobalHeadTagsBackendTask","comment":" For internal use.\n","type":"ApiRoute.ApiRoute response -> Maybe.Maybe (BackendTask.BackendTask FatalError.FatalError (List.List Head.Tag))"},{"name":"literal","comment":" A literal String segment of a route.\n","type":"String.String -> ApiRoute.ApiRouteBuilder a constructor -> ApiRoute.ApiRouteBuilder a constructor"},{"name":"preRender","comment":" Pre-render files for a given route pattern statically at build-time. If you only need to serve a single file, you can use [`single`](#single) instead.\n\n import ApiRoute\n import BackendTask\n import BackendTask.Http\n import Json.Decode as Decode\n import Json.Encode as Encode\n\n starsApi : ApiRoute ApiRoute.Response\n starsApi =\n ApiRoute.succeed\n (\\user repoName ->\n BackendTask.Http.getJson\n (\"https://api.github.com/repos/\" ++ user ++ \"/\" ++ repoName)\n (Decode.field \"stargazers_count\" Decode.int)\n |> BackendTask.allowFatal\n |> BackendTask.map\n (\\stars ->\n Encode.object\n [ ( \"repo\", Encode.string repoName )\n , ( \"stars\", Encode.int stars )\n ]\n |> Encode.encode 2\n )\n )\n |> ApiRoute.literal \"repo\"\n |> ApiRoute.slash\n |> ApiRoute.capture\n |> ApiRoute.slash\n |> ApiRoute.capture\n |> ApiRoute.slash\n |> ApiRoute.literal \".json\"\n |> ApiRoute.preRender\n (\\route ->\n BackendTask.succeed\n [ route \"dillonkearns\" \"elm-graphql\"\n , route \"dillonkearns\" \"elm-pages\"\n ]\n )\n\nYou can view these files in the dev server at <http://localhost:1234/repo/dillonkearns/elm-graphql.json>, and when you run `elm-pages build` this will result in the following files being generated:\n\n - `dist/repo/dillonkearns/elm-graphql.json`\n - `dist/repo/dillonkearns/elm-pages.json`\n\nNote: `dist` is the output folder for `elm-pages build`, so this will be accessible in your hosted site at `/repo/dillonkearns/elm-graphql.json` and `/repo/dillonkearns/elm-pages.json`.\n\n","type":"(constructor -> BackendTask.BackendTask FatalError.FatalError (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError String.String) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"preRenderWithFallback","comment":" ","type":"(constructor -> BackendTask.BackendTask FatalError.FatalError (List.List (List.List String.String))) -> ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Basics.Never Basics.Never)) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"serverRender","comment":" ","type":"ApiRoute.ApiRouteBuilder (Server.Request.Request -> BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Basics.Never Basics.Never)) constructor -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"single","comment":" Same as [`preRender`](#preRender), but for an ApiRoute that has no dynamic segments. This is just a bit simpler because\nsince there are no dynamic segments, you don't need to provide a BackendTask with the list of dynamic segments to pre-render because there is only a single possible route.\n","type":"ApiRoute.ApiRouteBuilder (BackendTask.BackendTask FatalError.FatalError String.String) (List.List String.String) -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"slash","comment":" A path separator within the route.\n","type":"ApiRoute.ApiRouteBuilder a constructor -> ApiRoute.ApiRouteBuilder a constructor"},{"name":"succeed","comment":" Starts the definition of a route with any captured segments.\n","type":"a -> ApiRoute.ApiRouteBuilder a (List.List String.String)"},{"name":"toJson","comment":" Turn the route into a pattern in JSON format. For internal uses.\n","type":"ApiRoute.ApiRoute response -> Json.Encode.Value"},{"name":"withGlobalHeadTags","comment":" Include head tags on every page's HTML.\n","type":"BackendTask.BackendTask FatalError.FatalError (List.List Head.Tag) -> ApiRoute.ApiRoute response -> ApiRoute.ApiRoute response"}],"binops":[]},{"name":"BackendTask","comment":" In an `elm-pages` app, each Route Module can define a value `data` which is a `BackendTask` that will be resolved **before** `init` is called. That means it is also available\nwhen the page's HTML is pre-rendered during the build step. You can also access the resolved data in `head` to use it for the page's SEO meta tags.\n\nA `BackendTask` lets you pull in data from:\n\n - Local files ([`BackendTask.File`](BackendTask-File))\n - HTTP requests ([`BackendTask.Http`](BackendTask-Http))\n - Globs, i.e. listing out local files based on a pattern like `content/*.txt` ([`BackendTask.Glob`](BackendTask-Glob))\n - Ports, i.e. getting JSON data from running custom NodeJS, similar to a port in a vanilla Elm app except run at build-time in NodeJS, rather than at run-time in the browser ([`BackendTask.Custom`](BackendTask-Custom))\n - Hardcoded data (`BackendTask.succeed \"Hello!\"`)\n - Or any combination of the above, using `BackendTask.map2`, `BackendTask.andThen`, or other combining/continuing helpers from this module\n\n\n## BackendTask's vs. Effect's/Cmd's\n\nBackendTask's are always resolved before the page is rendered and sent to the browser. A BackendTask is never executed\nin the Browser. Instead, the resolved data from the BackendTask is passed down to the Browser - it has been resolved\nbefore any client-side JavaScript ever executes. In the case of a pre-rendered route, this is during the CLI build phase,\nand for server-rendered routes its BackendTask is resolved on the server.\n\nEffect's/Cmd's are never executed on the CLI or server, they are only executed in the Browser. The data from a Route Module's\n`init` function is used to render the initial HTML on the server or build step, but the Effect isn't executed and `update` is never called\nbefore the page is hydrated in the Browser. This gives a deterministic mental model of what the first render will look like,\nand a nicely typed way to define the initial `Data` you have to render your initial view.\n\nBecause `elm-pages` hydrates into a full Elm single-page app, it does need the data in order to initialize the Elm app.\nSo why not just get the data the old-fashioned way, with `elm/http`, for example?\n\nA few reasons:\n\n1. BackendTask's allow you to pull in data that you wouldn't normally be able to access from an Elm app, like local files, or listings of files in a folder. Not only that, but the dev server knows to automatically hot reload the data when the files it depends on change, so you can edit the files you used in your BackendTask and see the page hot reload as you save!\n2. You can pre-render HTML for your pages, including the SEO meta tags, with all that rich, well-typed Elm data available! That's something you can't accomplish with a vanilla Elm app, and it's one of the main use cases for elm-pages.\n3. Because `elm-pages` has a build step, you know that your `BackendTask.Http` requests succeeded, your decoders succeeded, your custom BackendTask validations succeeded, and everything went smoothly. If something went wrong, you get a build failure and can deal with the issues before the site goes live. That means your users won't see those errors, and as a developer you don't need to handle those error cases in your code! Think of it as \"parse, don't validate\", but for your entire build. In the case of server-rendered routes, a BackendTask failure will render a 500 page, so more care needs to be taken to make sure all common errors are handled properly, but the tradeoff is that you can use BackendTask's to pull in highly dynamic data and even render user-specific pages.\n4. For static routes, you don't have to worry about an API being down, or hitting it repeatedly. You can build in data and it will end up as optimized binary-encoded data served up with all the other assets of your site. If your CDN (static site host) is down, then the rest of your site is probably down anyway. If your site host is up, then so is all of your `BackendTask` data. Also, it will be served up extremely quickly without needing to wait for any database queries to be performed, `andThen` requests to be resolved, etc., because all of that work and waiting was done at build-time!\n\n\n## Mental Model\n\nYou can think of a BackendTask as a declarative (not imperative) definition of data. It represents where to get the data from, and how to transform it (map, combine with other BackendTasks, etc.).\n\n\n## How do I actually use a BackendTask?\n\nThis is very similar to Cmd's in Elm. You don't perform a Cmd just by running that code, as you might in a language like JavaScript. Instead, a Cmd _will not do anything_ unless you pass it to The Elm Architecture to have it perform it for you.\nYou pass a Cmd to The Elm Architecture by returning it in `init` or `update`. So actually a `Cmd` is just data describing a side-effect that the Elm runtime can perform, and how to build a `Msg` once it's done.\n\n`BackendTask`'s are very similar. A `BackendTask` doesn't do anything just by \"running\" it. Just like a `Cmd`, it's only data that describes a side-effect to perform. Specifically, it describes a side-effect that the _elm-pages runtime_ can perform.\nThere are a few places where we can pass a `BackendTask` to the `elm-pages` runtime so it can perform it. Most commonly, you give a field called `data` in your Route Module's definition. Instead of giving a `Msg` when the side-effects are complete,\nthe page will render once all of the side-effects have run and all the data is resolved. `elm-pages` makes the resolved data available your Route Module's `init`, `view`, `update`, and `head` functions, similar to how a regular Elm app passes `Msg`'s in\nto `update`.\n\nAny place in your `elm-pages` app where the framework lets you pass in a value of type `BackendTask` is a place where you can give `elm-pages` a BackendTask to perform (for example, `Site.head` where you define global head tags for your site).\n\n\n## Basics\n\n@docs BackendTask\n\n@docs map, succeed, fail\n\n@docs fromResult\n\n\n## Chaining Requests\n\n@docs andThen, resolve, combine\n\n@docs andMap\n\n@docs map2, map3, map4, map5, map6, map7, map8, map9\n\n\n## FatalError Handling\n\n@docs allowFatal, mapError, onError, toResult\n\n\n## Scripting\n\n@docs do, doEach, sequence, failIf\n\n\n## BackendTask Context\n\nYou can set the following context for a `BackendTask`:\n\n - `inDir` - Set the working directory\n - `quiet` - Silence the output\n - `withEnv` - Set an environment variable\n\nIt's important to understand that a `BackendTask` does not run until it is passed to the `elm-pages` runtime (it is _not_ run as soon as it is defined,\nlike you may be familiar with in other languages like JavaScript). So you can use these functions to set the context\nof a `BackendTask` at any point before you pass it to and it will be applied when the `BackendTask` is run.\n\n@docs inDir, quiet, withEnv\n\n","unions":[],"aliases":[{"name":"BackendTask","comment":" A BackendTask represents data that will be gathered at build time. Multiple `BackendTask`s can be combined together using the `mapN` functions,\nvery similar to how you can manipulate values with Json Decoders in Elm.\n","args":["error","value"],"type":"Pages.StaticHttpRequest.RawRequest error value"}],"values":[{"name":"allowFatal","comment":" Ignore any recoverable error data and propagate the `FatalError`. Similar to a `Cmd` in The Elm Architecture,\na `FatalError` will not do anything except if it is returned at the top-level of your application. Read more\nin the [`FatalError` docs](FatalError).\n","type":"BackendTask.BackendTask { error | fatal : FatalError.FatalError } data -> BackendTask.BackendTask FatalError.FatalError data"},{"name":"andMap","comment":" A helper for combining `BackendTask`s in pipelines.\n","type":"BackendTask.BackendTask error a -> BackendTask.BackendTask error (a -> b) -> BackendTask.BackendTask error b"},{"name":"andThen","comment":" Build off of the response from a previous `BackendTask` request to build a follow-up request. You can use the data\nfrom the previous response to build up the URL, headers, etc. that you send to the subsequent request.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Http\n import FatalError exposing (FatalError)\n import Json.Decode as Decode\n\n licenseData : BackendTask FatalError String\n licenseData =\n BackendTask.Http.getJson\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.at [ \"license\", \"url\" ] Decode.string)\n |> BackendTask.andThen\n (\\licenseUrl ->\n BackendTask.Http.getJson licenseUrl (Decode.field \"description\" Decode.string)\n )\n |> BackendTask.allowFatal\n\n","type":"(a -> BackendTask.BackendTask error b) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b"},{"name":"combine","comment":" Turn a list of `BackendTask`s into a single one.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Http\n import FatalError exposing (FatalError)\n import Json.Decode as Decode\n\n type alias Pokemon =\n { name : String\n , sprite : String\n }\n\n pokemonDetailRequest : BackendTask FatalError (List Pokemon)\n pokemonDetailRequest =\n BackendTask.Http.getJson\n \"https://pokeapi.co/api/v2/pokemon/?limit=3\"\n (Decode.field \"results\"\n (Decode.list\n (Decode.map2 Tuple.pair\n (Decode.field \"name\" Decode.string)\n (Decode.field \"url\" Decode.string)\n |> Decode.map\n (\\( name, url ) ->\n BackendTask.Http.getJson url\n (Decode.at\n [ \"sprites\", \"front_default\" ]\n Decode.string\n |> Decode.map (Pokemon name)\n )\n )\n )\n )\n )\n |> BackendTask.andThen BackendTask.combine\n |> BackendTask.allowFatal\n\n","type":"List.List (BackendTask.BackendTask error value) -> BackendTask.BackendTask error (List.List value)"},{"name":"do","comment":" Ignore the resulting value of the BackendTask.\n","type":"BackendTask.BackendTask error value -> BackendTask.BackendTask error ()"},{"name":"doEach","comment":" Perform a List of `BackendTask`s with no output, one-by-one sequentially.\n\nSame as [`sequence`](#sequence), except it ignores the resulting value of each `BackendTask`.\n\n","type":"List.List (BackendTask.BackendTask error ()) -> BackendTask.BackendTask error ()"},{"name":"fail","comment":" ","type":"error -> BackendTask.BackendTask error a"},{"name":"failIf","comment":" If the condition is true, fail with the given `FatalError`. Otherwise, succeed with `()`.\n","type":"Basics.Bool -> FatalError.FatalError -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"fromResult","comment":" Turn `Ok` into `BackendTask.succeed` and `Err` into `BackendTask.fail`.\n","type":"Result.Result error value -> BackendTask.BackendTask error value"},{"name":"inDir","comment":" `inDir` sets the working directory for a `BackendTask`. The working directory of a `BackendTask` will be used to resolve relative paths\nand is relevant for the following types of `BackendTask`s:\n\n - Reading files ([`BackendTask.File`](BackendTask-File))\n - Running glob patterns ([`BackendTask.Glob`](BackendTask-Glob))\n - Executing shell commands ([`BackendTask.Stream.command`](BackendTask-Stream#command)) and [`Pages.Script.sh`](Pages-Script#command)\n\nSee the BackendTask Context section for more about how setting context works.\n\nFor example, these two values will produce the same result:\n\n import BackendTask.Glob as Glob\n\n example1 : BackendTask error (List String)\n example1 =\n Glob.fromString \"src/**/*.elm\"\n\n example2 : BackendTask error (List String)\n example2 =\n BackendTask.inDir \"src\" (Glob.fromString \"**/*.elm\")\n\nYou can also nest the working directory by using `inDir` multiple times:\n\n import BackendTask.Glob as Glob\n\n example3 : BackendTask error (List String)\n example3 =\n BackendTask.map2\n (\\routeModules specialModules ->\n { routeModules = routeModules\n , specialModules = specialModules\n }\n )\n (BackendTask.inDir \"Route\" (Glob.fromString \"**/*.elm\"))\n (Glob.fromString \"*.elm\")\n |> BackendTask.inDir \"app\"\n\nThe above example will list out files from `app/Route/**/*.elm` and `app/*.elm` because `inDir \"app\"` is applied to the `BackendTask.map2` which combines together both of the Glob tasks.\n\n`inDir` supports absolute paths. In this example, we apply a relative path on top of an absolute path, so the relative path will be resolved relative to the absolute path.\n\n import BackendTask.Glob as Glob\n\n example3 : BackendTask error (List String)\n example3 =\n BackendTask.map2\n (\\routeModules specialModules ->\n { routeModules = routeModules\n , specialModules = specialModules\n }\n )\n -- same as `Glob.fromString \"/projects/my-elm-pages-blog/app/Route/**/*.elm\"`\n (BackendTask.inDir \"Route\" (Glob.fromString \"**/*.elm\"))\n (Glob.fromString \"*.elm\")\n |> BackendTask.inDir \"/projects/my-elm-pages-blog/app\"\n\nYou can also use `BackendTask.inDir \"..\"` to go up a directory, or \\`BackendTask.inDir \"../..\" to go up two directories, etc.\n\nUse of trailing slashes is optional and does not change the behavior of `inDir`. Leading slashes distinguish between\nabsolute and relative paths, so `BackendTask.inDir \"./src\"` is exactly the same as `BackendTask.inDir \"src\"`, etc.\n\nEach level of nesting with `inDir` will resolve using [NodeJS's `path.resolve`](https://nodejs.org/api/path.html#pathresolvepaths).\n\n","type":"String.String -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"map","comment":" Transform a request into an arbitrary value. The same underlying task will be performed,\nbut mapping allows you to change the resulting values by applying functions to the results.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Http\n import FatalError exposing (FatalError)\n import Json.Decode as Decode\n\n starsMessage : BackendTask FatalError String\n starsMessage =\n BackendTask.Http.getJson\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"stargazers_count\" Decode.int)\n |> BackendTask.map\n (\\stars -> \"⭐️ \" ++ String.fromInt stars)\n |> BackendTask.allowFatal\n\n","type":"(a -> b) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b"},{"name":"map2","comment":" Like map, but it takes in two `BackendTask`s.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Env as Env\n import BackendTask.Http\n import FatalError exposing (FatalError)\n import Json.Decode as Decode\n\n type alias Data =\n { pokemon : List String, envValue : Maybe String }\n\n data : BackendTask FatalError Data\n data =\n BackendTask.map2 Data\n (BackendTask.Http.getJson\n \"https://pokeapi.co/api/v2/pokemon/?limit=100&offset=0\"\n (Decode.field \"results\"\n (Decode.list (Decode.field \"name\" Decode.string))\n )\n |> BackendTask.allowFatal\n )\n (Env.get \"HELLO\")\n\n","type":"(a -> b -> c) -> BackendTask.BackendTask error a -> BackendTask.BackendTask error b -> BackendTask.BackendTask error c"},{"name":"map3","comment":" ","type":"(value1 -> value2 -> value3 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error valueCombined"},{"name":"map4","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error valueCombined"},{"name":"map5","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error valueCombined"},{"name":"map6","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error valueCombined"},{"name":"map7","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error valueCombined"},{"name":"map8","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error value8 -> BackendTask.BackendTask error valueCombined"},{"name":"map9","comment":" ","type":"(value1 -> value2 -> value3 -> value4 -> value5 -> value6 -> value7 -> value8 -> value9 -> valueCombined) -> BackendTask.BackendTask error value1 -> BackendTask.BackendTask error value2 -> BackendTask.BackendTask error value3 -> BackendTask.BackendTask error value4 -> BackendTask.BackendTask error value5 -> BackendTask.BackendTask error value6 -> BackendTask.BackendTask error value7 -> BackendTask.BackendTask error value8 -> BackendTask.BackendTask error value9 -> BackendTask.BackendTask error valueCombined"},{"name":"mapError","comment":" ","type":"(error -> errorMapped) -> BackendTask.BackendTask error value -> BackendTask.BackendTask errorMapped value"},{"name":"onError","comment":" ","type":"(error -> BackendTask.BackendTask mappedError value) -> BackendTask.BackendTask error value -> BackendTask.BackendTask mappedError value"},{"name":"quiet","comment":" Sets the verbosity level to `quiet` in the context of the given `BackendTask` (including all nested `BackendTask`s and continuations within it).\n\nThis will turn off performance timing logs. It will also prevent shell commands from printing their output to the console when they are run\n(see [`BackendTask.Stream.command`](BackendTask-Stream#command)).\n\n","type":"BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"resolve","comment":" Helper to remove an inner layer of Request wrapping.\n","type":"BackendTask.BackendTask error (List.List (BackendTask.BackendTask error value)) -> BackendTask.BackendTask error (List.List value)"},{"name":"sequence","comment":" Perform a List of `BackendTask`s one-by-one sequentially. [`combine`](#combine) will perform them all in parallel, which is\ntypically a better default when you aren't sure which you want.\n\nSame as [`doEach`](#doEach), except it ignores the resulting value of each `BackendTask`.\n\n","type":"List.List (BackendTask.BackendTask error value) -> BackendTask.BackendTask error (List.List value)"},{"name":"succeed","comment":" This is useful for prototyping with some hardcoded data, or for having a view that doesn't have any BackendTask data.\n\n import BackendTask exposing (BackendTask)\n\n type alias RouteParams =\n { name : String }\n\n pages : BackendTask error (List RouteParams)\n pages =\n BackendTask.succeed [ { name = \"elm-pages\" } ]\n\n","type":"a -> BackendTask.BackendTask error a"},{"name":"toResult","comment":" ","type":"BackendTask.BackendTask error data -> BackendTask.BackendTask noError (Result.Result error data)"},{"name":"withEnv","comment":" ","type":"String.String -> String.String -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"}],"binops":[]},{"name":"BackendTask.Custom","comment":" In a vanilla Elm application, ports let you either send or receive JSON data between your Elm application and the JavaScript context in the user's browser at runtime.\n\nWith `BackendTask.Custom`, you send and receive JSON to JavaScript running in NodeJS. As with any `BackendTask`, Custom BackendTask's are either run at build-time (for pre-rendered routes) or at request-time (for server-rendered routes). See [`BackendTask`](BackendTask) for more about the\nlifecycle of `BackendTask`'s.\n\nThis means that you can call shell scripts, run NPM packages that are installed, or anything else you could do with NodeJS to perform custom side-effects, get some data, or both.\n\nA `BackendTask.Custom` will call an async JavaScript function with the given name from the definition in a file called `custom-backend-task.js` in your project's root directory. The function receives the input JSON value, and the Decoder is used to decode the return value of the async function.\n\n@docs run\n\nHere is the Elm code and corresponding JavaScript definition for getting an environment variable (or an `FatalError BackendTask.Custom.Error` if it isn't found). In this example,\nwe're using `BackendTask.allowFatal` to let the framework treat that as an unexpected exception, but we could also handle the possible failures of the `FatalError` (see [`FatalError`](FatalError)).\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Custom\n import Json.Encode\n import OptimizedDecoder as Decode\n\n data : BackendTask FatalError String\n data =\n BackendTask.Custom.run \"environmentVariable\"\n (Json.Encode.string \"EDITOR\")\n Decode.string\n |> BackendTask.allowFatal\n\n -- will resolve to \"VIM\" if you run `EDITOR=vim elm-pages dev`\n\n```javascript\n// custom-backend-task.js\n\n/**\n* @param { string } fromElm\n* @returns { Promise<string> }\n*/\nexport async function environmentVariable(name) {\n const result = process.env[name];\n if (result) {\n return result;\n } else {\n throw `No environment variable called ${name}\n\nAvailable:\n\n${Object.keys(process.env).join(\"\\n\")}\n`;\n }\n}\n```\n\n\n## Context Parameter\n\nIf you define a second parameter in an exported `custom-backend-task` file function, you can access the `context` object. This object is a JSON object that contains the following fields:\n\n - `cwd` - the current working directory for the `BackendTask`, set by calls to [`BackendTask.inDir`](BackendTask#inDir). If you don't use `BackendTask.inDir`, this will be the directory from which you are invoking `elm-pages`.\n - `env` - the environment variables for the `BackendTask`, set by calls to [`BackendTask.withEnv`](BackendTask#withEnv)\n - `quiet` - a boolean that is `true` if the `BackendTask` is running in quiet mode, set by calls to [`BackendTask.quiet`](BackendTask#quiet)\n\nIf your `BackendTask.Custom` implementation depends on relative file paths, `process.env`, or has logging, it is recommended to use the `context.cwd` and `context.env` fields to ensure\nthat the behavior of your `BackendTask.Custom` is consistent with the core `BackendTask` definitions provided by the framework. For example, the [`BackendTask.Glob`](BackendTask-Glob)\nAPI will resolve glob patterns relative to the `cwd` context.\n\n```js\nimport toml from 'toml';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\n\nexport async function readTomlFile(relativeFilePath, context) {\n const filePath = path.resolve(context.cwd, relativeFilePath);\n // toml.parse returns a JSON representation of the TOML input\n return toml.parse(fs.readFile(filePath));\n}\n```\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Custom\n import Json.Encode\n import OptimizedDecoder as Decode\n\n data : BackendTask FatalError String\n data =\n BackendTask.Custom.run \"parseTomlFile\"\n (Json.Encode.string \"my-file.toml\")\n myJsonDecoder\n |> BackendTask.allowFatal\n\n\n## Performance\n\nAs with any JavaScript or NodeJS code, avoid doing blocking IO operations. For example, avoid using `fs.readFileSync`, because blocking IO can slow down your elm-pages builds and dev server. `elm-pages` performs all `BackendTask`'s in parallel whenever possible.\nSo if you do `BackendTask.map2 Tuple.pair myHttpBackendTask myCustomBackendTask`, it will resolve those two in parallel. NodeJS performs best when you take advantage of its ability to do non-blocking I/O (file reads, HTTP requests, etc.). If you use `BackendTask.andThen`,\nit will need to resolve them in sequence rather than in parallel, but it's still best to avoid blocking IO operations in your Custom BackendTask definitions.\n\n\n## Error Handling\n\nThere are a few different things that can go wrong when running a custom-backend-task. These possible errors are captured in the `BackendTask.Custom.Error` type.\n\n@docs Error\n\nAny time you throw a JavaScript exception from a BackendTask.Custom definition, it will give you a `CustomBackendTaskException`. It's usually easier to add a `try`/`catch` in your JavaScript code in `custom-backend-task.js`\nto handle possible errors, but you can throw a JSON value and handle it in Elm in the `CustomBackendTaskException` call error.\n\n\n## Decoding JS Date Objects\n\nThese decoders are for use with decoding JS values of type `Date`. If you have control over the format, it may be better to\nbe more explicit with a [Rata Die](https://en.wikipedia.org/wiki/Rata_Die) number value or an ISO-8601 formatted date string instead.\nBut often JavaScript libraries and core APIs will give you JS Date objects, so this can be useful for working with those.\n\n@docs timeDecoder, dateDecoder\n\n","unions":[{"name":"Error","comment":" ","args":[],"cases":[["Error",[]],["ErrorInCustomBackendTaskFile",[]],["MissingCustomBackendTaskFile",[]],["CustomBackendTaskNotDefined",["{ name : String.String }"]],["CustomBackendTaskException",["Json.Decode.Value"]],["NonJsonException",["String.String"]],["ExportIsNotFunction",[]],["DecodeError",["Json.Decode.Error"]]]}],"aliases":[],"values":[{"name":"dateDecoder","comment":" The same as `timeDecoder`, but it converts the decoded `Time.Posix` value into a `Date` with `Date.fromPosix Time.utc`.\n\nJavaScript `Date` objects don't distinguish between values with only a date vs. values with both a date and a time. So be sure\nto use this decoder when you know the semantics represent a date with no associated time (or you're sure you don't care about the time).\n\n","type":"Json.Decode.Decoder Date.Date"},{"name":"run","comment":" ","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder b -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Custom.Error } b"},{"name":"timeDecoder","comment":" ","type":"Json.Decode.Decoder Time.Posix"}],"binops":[]},{"name":"BackendTask.Do","comment":"\n\n\n## **This is an optional and experimental module.** It is for doing a continuation style with your [`BackendTask`s](BackendTask).\n\nNote that in order for this style to be usable, you'll need to use a special formatting script that allows you to use\ncontinuation style syntax without indenting each level in the continuation.\n\n\n## Custom Formatting Script\n\nIt is a bit advanced and cumbersome, so beware before committing to this style. That said, here is a script you can use to\napply continuation-style formatting to your Elm code:\n\n<https://gist.github.com/xarvh/1b65cb00e7240f1ccfa0bdbf30f97c62>\n\nYou can see more discussion of continuation style in Elm in this Discourse post: <https://discourse.elm-lang.org/t/experimental-json-decoding-api/2121>.\n\n@docs do\n@docs allowFatal\n\n\n## Defining Your Own Continuation Utilities\n\n`do` is also helpful for building your own continuation-style utilities. For example, here is how [`glob`](#glob) is defined:\n\n glob : String -> (List String -> BackendTask FatalError a) -> BackendTask FatalError a\n glob pattern =\n do <| Glob.fromString pattern\n\nTo define helpers that have no resulting value, it is still useful to have an argument of `()` to allow the code formatter to\nrecognize it as a continuation chain.\n\n sh :\n String\n -> List String\n -> (() -> BackendTask FatalError b)\n -> BackendTask FatalError b\n sh command args =\n do <| Shell.sh command args\n\n@docs noop\n\n\n## Shell Commands\n\n@docs exec, command\n\n\n## Common Utilities\n\n@docs glob, log, env\n\n@docs each, failIf\n\n","unions":[],"aliases":[],"values":[{"name":"allowFatal","comment":" Same as [`do`](#do), but with a shorthand to call `BackendTask.allowFatal` on it.\n\n import BackendTask exposing (BackendTask)\n import FatalError exposing (FatalError)\n import BackendTask.File as BackendTask.File\n import BackendTask.Do exposing (allowFatal, do)\n\n example : BackendTask FatalError ()\n example =\n do (BackendTask.File.rawFile \"post-1.md\" |> BackendTask.allowFatal) <|\n \\post1 ->\n allowFatal (BackendTask.File.rawFile \"post-2.md\") <|\n \\post2 ->\n Script.log (post1 ++ \"\\n\\n\" ++ post2)\n\n","type":"BackendTask.BackendTask { error | fatal : FatalError.FatalError } data -> (data -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"command","comment":" A do-style helper for [`Script.command`](Pages-Script#command).\n","type":"String.String -> List.List String.String -> (String.String -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"do","comment":" Use any `BackendTask` into a continuation-style task.\n\n example : BackendTask FatalError ()\n example =\n do\n (Script.question \"What is your name? \")\n <|\n \\name ->\n \\() ->\n Script.log (\"Hello \" ++ name ++ \"!\")\n\n","type":"BackendTask.BackendTask error a -> (a -> BackendTask.BackendTask error b) -> BackendTask.BackendTask error b"},{"name":"each","comment":"\n\n checkCompilationInDir : String -> BackendTask FatalError ()\n checkCompilationInDir dir =\n glob (dir ++ \"/**/*.elm\") <|\n \\elmFiles ->\n each elmFiles\n (\\elmFile ->\n Shell.sh \"elm\" [ \"make\", elmFile, \"--output\", \"/dev/null\" ]\n |> BackendTask.quiet\n )\n <|\n \\_ ->\n noop\n\n","type":"List.List a -> (a -> BackendTask.BackendTask error b) -> (List.List b -> BackendTask.BackendTask error c) -> BackendTask.BackendTask error c"},{"name":"env","comment":" A do-style helper for [`Env.expect`](BackendTask-Env#expect).\n\n example : BackendTask FatalError ()\n example =\n env \"API_KEY\" <|\n \\apiKey ->\n allowFatal (apiRequest apiKey) <|\n \\() ->\n noop\n\n","type":"String.String -> (String.String -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"exec","comment":" A do-style helper for [`Script.exec`](Pages-Script#exec).\n","type":"String.String -> List.List String.String -> (() -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"failIf","comment":" A do-style helper for [`BackendTask.failIf`](BackendTask#failIf).\n","type":"Basics.Bool -> FatalError.FatalError -> (() -> BackendTask.BackendTask FatalError.FatalError b) -> BackendTask.BackendTask FatalError.FatalError b"},{"name":"glob","comment":" A continuation-style helper for [`Glob.fromString`](BackendTask-Glob#fromString).\n\nIn a shell script, you can think of this as a stand-in for globbing files directly within a command. The [`BackendTask.Stream.command`](BackendTask-Stream#command)\nwhich lets you run shell commands sanitizes and escapes all arguments passed, and does not do glob expansion, so this is helpful for translating\na shell script to Elm.\n\nThis example passes a list of matching file paths along to an `rm -f` command.\n\n example : BackendTask FatalError ()\n example =\n glob \"src/**/*.elm\" <|\n \\elmFiles ->\n log (\"Going to delete \" ++ String.fromInt (List.length elmFiles) ++ \" Elm files\") <|\n \\() ->\n exec \"rm\" (\"-f\" :: elmFiles) <|\n \\() ->\n noop\n\n","type":"String.String -> (List.List String.String -> BackendTask.BackendTask FatalError.FatalError a) -> BackendTask.BackendTask FatalError.FatalError a"},{"name":"log","comment":" A do-style helper for [`Script.log`](Pages-Script#log).\n\n example : BackendTask FatalError ()\n example =\n log \"Starting script...\" <|\n \\() ->\n -- ...\n log \"Done!\" <|\n \\() ->\n noop\n\n","type":"String.String -> (() -> BackendTask.BackendTask error b) -> BackendTask.BackendTask error b"},{"name":"noop","comment":" A `BackendTask` that does nothing. Defined as `BackendTask.succeed ()`.\n\nIt's a useful shorthand for when you want to end a continuation chain.\n\n example : BackendTask FatalError ()\n example =\n exec \"ls\" [ \"-l\" ] <|\n \\() ->\n log \"Hello, world!\" <|\n \\() ->\n noop\n\n","type":"BackendTask.BackendTask error ()"}],"binops":[]},{"name":"BackendTask.Env","comment":" Because BackendTask's in `elm-pages` never run in the browser (see [the BackendTask docs](BackendTask)), you can access environment variables securely. As long as the environment variable isn't sent\ndown into the final `Data` value, it won't end up in the client!\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Env\n import FatalError exposing (FatalError)\n\n type alias EnvVariables =\n { sendGridKey : String\n , siteUrl : String\n }\n\n sendEmail : Email -> BackendTask FatalError ()\n sendEmail email =\n BackendTask.map2 EnvVariables\n (BackendTask.Env.expect \"SEND_GRID_KEY\" |> BackendTask.allowFatal)\n (BackendTask.Env.get \"BASE_URL\"\n |> BackendTask.map (Maybe.withDefault \"http://localhost:1234\")\n )\n |> BackendTask.andThen (sendEmailBackendTask email)\n\n sendEmailBackendTask : Email -> EnvVariables -> BackendTask FatalError ()\n sendEmailBackendTask email envVariables =\n Debug.todo \"Not defined here\"\n\n@docs get, expect\n\n\n## Errors\n\n@docs Error\n\n","unions":[{"name":"Error","comment":" ","args":[],"cases":[["MissingEnvVariable",["String.String"]]]}],"aliases":[],"values":[{"name":"expect","comment":" Get an environment variable, or a BackendTask FatalError if there is no environment variable matching that name.\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Env.Error } String.String"},{"name":"get","comment":" Get an environment variable, or Nothing if there is no environment variable matching that name. This `BackendTask`\nwill never fail, but instead will return `Nothing` if the environment variable is missing.\n","type":"String.String -> BackendTask.BackendTask error (Maybe.Maybe String.String)"}],"binops":[]},{"name":"BackendTask.File","comment":" This module lets you read files from the local filesystem as a [`BackendTask`](BackendTask#BackendTask).\nFile paths are relative to the root of your `elm-pages` project (next to the `elm.json` file and `src/` directory), or\nyou can pass in absolute paths beginning with a `/`.\n\n\n## Files With Frontmatter\n\nFrontmatter is a convention used to keep metadata at the top of a file between `---`'s.\n\nFor example, you might have a file called `blog/hello-world.md` with this content:\n\n```markdown\n---\ntitle: Hello, World!\ntags: elm\n---\nHey there! This is my first post :)\n```\n\nThe frontmatter is in the [YAML format](https://en.wikipedia.org/wiki/YAML) here. You can also use JSON in your elm-pages frontmatter.\n\n```markdown\n---\n{\"title\": \"Hello, World!\", \"tags\": \"elm\"}\n---\nHey there! This is my first post :)\n```\n\nWhether it's YAML or JSON, you use an `Decode` to decode your frontmatter, so it feels just like using\nplain old JSON in Elm.\n\n@docs bodyWithFrontmatter, bodyWithoutFrontmatter, onlyFrontmatter\n\n\n## Reading Files Without Frontmatter\n\n@docs jsonFile, rawFile\n\n\n## FatalErrors\n\n@docs FileReadError\n\n","unions":[{"name":"FileReadError","comment":" ","args":["decoding"],"cases":[["FileDoesntExist",[]],["FileReadError",["String.String"]],["DecodingError",["decoding"]]]}],"aliases":[],"values":[{"name":"bodyWithFrontmatter","comment":"\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import FatalError exposing (FatalError)\n import Json.Decode as Decode exposing (Decoder)\n\n blogPost : BackendTask FatalError BlogPostMetadata\n blogPost =\n File.bodyWithFrontmatter blogPostDecoder\n \"blog/hello-world.md\"\n |> BackendTask.allowFatal\n\n type alias BlogPostMetadata =\n { body : String\n , title : String\n , tags : List String\n }\n\n blogPostDecoder : String -> Decoder BlogPostMetadata\n blogPostDecoder body =\n Decode.map2 (BlogPostMetadata body)\n (Decode.field \"title\" Decode.string)\n (Decode.field \"tags\" tagsDecoder)\n\n tagsDecoder : Decoder (List String)\n tagsDecoder =\n Decode.map (String.split \" \")\n Decode.string\n\nThis will give us a BackendTask that results in the following value:\n\n value =\n { body = \"Hey there! This is my first post :)\"\n , title = \"Hello, World!\"\n , tags = [ \"elm\" ]\n }\n\nIt's common to parse the body with a markdown parser or other format.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import FatalError exposing (FatalError)\n import Html exposing (Html)\n import Json.Decode as Decode\n\n example :\n BackendTask\n FatalError\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 |> BackendTask.allowFatal\n\n markdownToView :\n String\n -> Result String (List (Html msg))\n markdownToView markdownString =\n markdownString\n |> Markdown.Parser.parse\n |> Result.mapError (\\_ -> \"Markdown error.\")\n |> Result.andThen\n (\\blocks ->\n Markdown.Renderer.render\n Markdown.Renderer.defaultHtmlRenderer\n blocks\n )\n\n","type":"(String.String -> Json.Decode.Decoder frontmatter) -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } frontmatter"},{"name":"bodyWithoutFrontmatter","comment":" Same as `bodyWithFrontmatter` except it doesn't include the frontmatter.\n\nFor example, if you have a file called `blog/hello-world.md` with\n\n```markdown\n---\ntitle: Hello, World!\ntags: elm\n---\nHey there! This is my first post :)\n```\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import FatalError exposing (FatalError)\n\n data : BackendTask FatalError String\n data =\n File.bodyWithoutFrontmatter \"blog/hello-world.md\"\n |> BackendTask.allowFatal\n\nThen data will yield the value `\"Hey there! This is my first post :)\"`.\n\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } String.String"},{"name":"jsonFile","comment":" Read a file as JSON.\n\nThe Decode will strip off any unused JSON data.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import FatalError exposing (FatalError)\n import Json.Decode as Decode\n\n sourceDirectories : BackendTask FatalError (List String)\n sourceDirectories =\n File.jsonFile\n (Decode.field\n \"source-directories\"\n (Decode.list Decode.string)\n )\n \"elm.json\"\n |> BackendTask.allowFatal\n\n","type":"Json.Decode.Decoder a -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } a"},{"name":"onlyFrontmatter","comment":" Same as `bodyWithFrontmatter` except it doesn't include the body.\n\nThis is often useful when you're aggregating data, for example getting a listing of blog posts and need to extract\njust the metadata.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import FatalError exposing (FatalError)\n import Json.Decode as Decode exposing (Decoder)\n\n blogPost : BackendTask FatalError BlogPostMetadata\n blogPost =\n File.onlyFrontmatter\n blogPostDecoder\n \"blog/hello-world.md\"\n |> BackendTask.allowFatal\n\n type alias BlogPostMetadata =\n { title : String\n , tags : List String\n }\n\n blogPostDecoder : Decoder BlogPostMetadata\n blogPostDecoder =\n Decode.map2 BlogPostMetadata\n (Decode.field \"title\" Decode.string)\n (Decode.field \"tags\" (Decode.list Decode.string))\n\nIf you wanted to use this to get this metadata for all blog posts in a folder, you could use\nthe [`BackendTask`](BackendTask) API along with [`BackendTask.Glob`](BackendTask-Glob).\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import Decode exposing (Decoder)\n\n blogPostFiles : BackendTask (List String)\n blogPostFiles =\n Glob.succeed identity\n |> Glob.captureFilePath\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.match Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\n allMetadata : BackendTask (List BlogPostMetadata)\n allMetadata =\n blogPostFiles\n |> BackendTask.map\n (List.map\n (File.onlyFrontmatter\n blogPostDecoder\n )\n )\n |> BackendTask.resolve\n\n","type":"Json.Decode.Decoder frontmatter -> String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError Json.Decode.Error } frontmatter"},{"name":"rawFile","comment":" Get the raw file content. Unlike the frontmatter helpers in this module, this function will not strip off frontmatter if there is any.\n\nThis is the function you want if you are reading in a file directly. For example, if you read in a CSV file, a raw text file, or any other file that doesn't\nhave frontmatter.\n\nThere's a special function for reading in JSON files, [`jsonFile`](#jsonFile). If you're reading a JSON file then be sure to\nuse `jsonFile` to get the benefits of the `Decode` here.\n\nYou could read a file called `hello.txt` in your root project directory like this:\n\n import BackendTask exposing (BackendTask)\n import BackendTask.File as File\n import FatalError exposing (FatalError)\n\n elmJsonFile : BackendTask FatalError String\n elmJsonFile =\n File.rawFile \"hello.txt\"\n |> BackendTask.allowFatal\n\n","type":"String.String -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.File.FileReadError decoderError } String.String"}],"binops":[]},{"name":"BackendTask.Glob","comment":"\n\n@docs Glob\n\nThis module helps you get a List of matching file paths from your local file system as a [`BackendTask`](BackendTask#BackendTask). See the [`BackendTask`](BackendTask) module documentation\nfor ways you can combine and map `BackendTask`s.\n\nA common example would be to find all the markdown files of your blog posts. If you have all your blog posts in `content/blog/*.md`\n, then you could use that glob pattern in most shells to refer to each of those files.\n\nWith the `BackendTask.Glob` API, you could get all of those files like so:\n\n import BackendTask exposing (BackendTask)\n\n blogPostsGlob : BackendTask error (List String)\n blogPostsGlob =\n Glob.succeed (\\slug -> slug)\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nLet's say you have these files locally:\n\n```shell\n- elm.json\n- src/\n- content/\n - blog/\n - first-post.md\n - second-post.md\n```\n\nWe would end up with a `BackendTask` like this:\n\n BackendTask.succeed [ \"first-post\", \"second-post\" ]\n\nOf course, if you add or remove matching files, the BackendTask will get those new files (unlike `BackendTask.succeed`). That's why we have Glob!\n\nYou can even see the `elm-pages dev` server will automatically flow through any added/removed matching files with its hot module reloading.\n\nBut why did we get `\"first-post\"` instead of a full file path, like `\"content/blog/first-post.md\"`? That's the difference between\n`capture` and `match`.\n\n\n## Capture and Match\n\nThere are two functions for building up a Glob pattern: `capture` and `match`.\n\n`capture` and `match` both build up a `Glob` pattern that will match 0 or more files on your local file system.\nThere will be one argument for every `capture` in your pipeline, whereas `match` does not apply any arguments.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n blogPostsGlob : BackendTask error (List String)\n blogPostsGlob =\n Glob.succeed (\\slug -> slug)\n -- no argument from this, but we will only\n -- match files that begin with `content/blog/`\n |> Glob.match (Glob.literal \"content/blog/\")\n -- we get the value of the `wildcard`\n -- as the slug argument\n |> Glob.capture Glob.wildcard\n -- no argument from this, but we will only\n -- match files that end with `.md`\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nSo to understand _which_ files will match, you can ignore whether you are using `capture` or `match` and just read\nthe patterns you're using in order to understand what will match. To understand what Elm data type you will get\n_for each matching file_, you need to see which parts are being captured and how each of those captured values are being\nused in the function you use in `Glob.succeed`.\n\n@docs capture, match\n\n`capture` is a lot like building up a JSON decoder with a pipeline.\n\nLet's try our blogPostsGlob from before, but change every `match` to `capture`.\n\n import BackendTask exposing (BackendTask)\n\n blogPostsGlob :\n BackendTask\n error\n (List\n { filePath : String\n , slug : String\n }\n )\n blogPostsGlob =\n Glob.succeed\n (\\capture1 capture2 capture3 ->\n { filePath = capture1 ++ capture2 ++ capture3\n , slug = capture2\n }\n )\n |> Glob.capture (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.capture (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nNotice that we now need 3 arguments at the start of our pipeline instead of 1. That's because\nwe apply 1 more argument every time we do a `Glob.capture`, much like `Json.Decode.Pipeline.required`, or other pipeline APIs.\n\nNow we actually have the full file path of our files. But having that slug (like `first-post`) is also very helpful sometimes, so\nwe kept that in our record as well. So we'll now have the equivalent of this `BackendTask` with the current `.md` files in our `blog` folder:\n\n BackendTask.succeed\n [ { filePath = \"content/blog/first-post.md\"\n , slug = \"first-post\"\n }\n , { filePath = \"content/blog/second-post.md\"\n , slug = \"second-post\"\n }\n ]\n\nHaving the full file path lets us read in files. But concatenating it manually is tedious\nand error prone. That's what the [`captureFilePath`](#captureFilePath) helper is for.\n\n\n## Reading matching files\n\nYou can use the less powerful but more familiar and terse `fromString` helpers if you only need to find matching file paths\n(but don't care about parsing out parts of the paths). This is helpful when you need a reference to matching files and\nyou are only using the file paths to then read or do scripting tasks with those paths.\n\n@docs fromString, fromStringWithOptions\n\n@docs captureFilePath\n\nIn many cases you will want to take the matching files from a `Glob` and then read the body or frontmatter from matching files.\n\n\n## Reading Metadata for each Glob Match\n\nFor example, if we had files like this:\n\n```markdown\n---\ntitle: My First Post\n---\nThis is my first post!\n```\n\nThen we could read that title for our blog post list page using our `blogPosts` `BackendTask` that we defined above.\n\n import BackendTask.File\n import FatalError exposing (FatalError)\n import Json.Decode as Decode exposing (Decoder)\n\n titles : BackendTask FatalError (List BlogPost)\n titles =\n blogPosts\n |> BackendTask.map\n (List.map\n (\\blogPost ->\n BackendTask.File.onlyFrontmatter\n blogFrontmatterDecoder\n blogPost.filePath\n )\n )\n |> BackendTask.resolve\n |> BackendTask.allowFatal\n\n type alias BlogPost =\n { title : String }\n\n blogFrontmatterDecoder : Decoder BlogPost\n blogFrontmatterDecoder =\n Decode.map BlogPost\n (Decode.field \"title\" Decode.string)\n\nThat will give us\n\n BackendTask.succeed\n [ { title = \"My First Post\" }\n , { title = \"My Second Post\" }\n ]\n\n\n## Capturing Patterns\n\n@docs wildcard, recursiveWildcard\n\n\n## Capturing Specific Characters\n\n@docs int, digits\n\n\n## Capturing File Stats\n\nYou can access a file's stats including timestamps when the file was created and modified, and file size.\n\n@docs FileStats, captureStats\n\n\n## Matching a Specific Number of Files\n\n@docs expectUniqueMatch, expectUniqueMatchFromList\n\n\n## Glob Patterns\n\n@docs literal\n\n@docs map, succeed\n\n@docs oneOf\n\n@docs zeroOrMore, atLeastOne\n\n\n## Getting Glob Data from a BackendTask\n\n@docs toBackendTask\n\n\n### With Custom Options\n\n@docs toBackendTaskWithOptions\n\n@docs defaultOptions, Options, Include\n\n","unions":[{"name":"Include","comment":" <https://github.com/mrmlnc/fast-glob#onlyfiles>\n\n<https://github.com/mrmlnc/fast-glob#onlydirectories>\n\n","args":[],"cases":[["OnlyFiles",[]],["OnlyFolders",[]],["FilesAndFolders",[]]]}],"aliases":[{"name":"FileStats","comment":" The information about a file that you can access when you use [`captureStats`](#captureStats).\n","args":[],"type":"{ fullPath : String.String, sizeInBytes : Basics.Int, lastContentChange : Time.Posix, lastAccess : Time.Posix, lastFileChange : Time.Posix, createdAt : Time.Posix, isDirectory : Basics.Bool }"},{"name":"Glob","comment":" A pattern to match local files and capture parts of the path into a nice Elm data type.\n","args":["a"],"type":"BackendTask.Internal.Glob.Glob a"},{"name":"Options","comment":" Custom options you can pass in to run the glob with [`toBackendTaskWithOptions`](#toBackendTaskWithOptions).\n\n { includeDotFiles = Bool -- https://github.com/mrmlnc/fast-glob#dot\n , include = Include -- return results that are `OnlyFiles`, `OnlyFolders`, or both `FilesAndFolders` (default is `OnlyFiles`)\n , followSymbolicLinks = Bool -- https://github.com/mrmlnc/fast-glob#followsymboliclinks\n , caseSensitiveMatch = Bool -- https://github.com/mrmlnc/fast-glob#casesensitivematch\n , gitignore = Bool -- https://www.npmjs.com/package/globby#gitignore\n , maxDepth = Maybe Int -- https://github.com/mrmlnc/fast-glob#deep\n }\n\n","args":[],"type":"{ includeDotFiles : Basics.Bool, include : BackendTask.Glob.Include, followSymbolicLinks : Basics.Bool, caseSensitiveMatch : Basics.Bool, gitignore : Basics.Bool, maxDepth : Maybe.Maybe Basics.Int }"}],"values":[{"name":"atLeastOne","comment":" ","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> BackendTask.Glob.Glob ( a, List.List a )"},{"name":"capture","comment":" Adds on to the glob pattern, and captures it in the resulting Elm match value. That means this both changes which\nfiles will match, and gives you the sub-match as Elm data for each matching file.\n\nExactly the same as `match` except it also captures the matched sub-pattern.\n\n type alias ArchivesArticle =\n { year : String\n , month : String\n , day : String\n , slug : String\n }\n\n archives : BackendTask error ArchivesArticle\n archives =\n Glob.succeed ArchivesArticle\n |> Glob.match (Glob.literal \"archive/\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nThe file `archive/1977/06/10/apple-2-released.md` will give us this match:\n\n matches : List error ArchivesArticle\n matches =\n BackendTask.succeed\n [ { year = 1977\n , month = 6\n , day = 10\n , slug = \"apple-2-released\"\n }\n ]\n\nWhen possible, it's best to grab data and turn it into structured Elm data when you have it. That way,\nyou don't end up with duplicate validation logic and data normalization, and your code will be more robust.\n\nIf you only care about getting the full matched file paths, you can use `match`. `capture` is very useful because\nyou can pick apart structured data as you build up your glob pattern. This follows the principle of\n[Parse, Don't Validate](https://elm-radio.com/episode/parse-dont-validate/).\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.Glob.Glob (a -> value) -> BackendTask.Glob.Glob value"},{"name":"captureFilePath","comment":"\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n blogPosts :\n BackendTask\n error\n (List\n { filePath : String\n , slug : String\n }\n )\n blogPosts =\n Glob.succeed\n (\\filePath slug ->\n { filePath = filePath\n , slug = slug\n }\n )\n |> Glob.captureFilePath\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nThis function does not change which files will or will not match. It just gives you the full matching\nfile path in your `Glob` pipeline.\n\nWhenever possible, it's a good idea to use function to make sure you have an accurate file path when you need to read a file.\n\n","type":"BackendTask.Glob.Glob (String.String -> value) -> BackendTask.Glob.Glob value"},{"name":"captureStats","comment":"\n\n import BackendTask.Glob as Glob\n\n recentlyChangedRouteModules : BackendTask error (List ( Time.Posix, List String ))\n recentlyChangedRouteModules =\n Glob.succeed\n (\\fileStats directoryName fileName ->\n ( fileStats.lastContentChange\n , directoryName ++ [ fileName ]\n )\n )\n |> Glob.captureStats\n |> Glob.match (Glob.literal \"app/Route/\")\n |> Glob.capture Glob.recursiveWildcard\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".elm\")\n |> Glob.toBackendTask\n |> BackendTask.map\n (\\entries ->\n entries\n |> List.sortBy (\\( lastChanged, _ ) -> Time.posixToMillis lastChanged)\n |> List.reverse\n )\n\n","type":"BackendTask.Glob.Glob (BackendTask.Glob.FileStats -> value) -> BackendTask.Glob.Glob value"},{"name":"defaultOptions","comment":" The default options used in [`toBackendTask`](#toBackendTask). To use a custom set of options, use [`toBackendTaskWithOptions`](#toBackendTaskWithOptions).\n","type":"BackendTask.Glob.Options"},{"name":"digits","comment":" This is similar to [`wildcard`](#wildcard), but it will only match 1 or more digits (i.e. `[0-9]+`).\n\nSee [`int`](#int) for a convenience function to get an Int value instead of a String of digits.\n\n","type":"BackendTask.Glob.Glob String.String"},{"name":"expectUniqueMatch","comment":" Sometimes you want to make sure there is a unique file matching a particular pattern.\nThis is a simple helper that will give you a `BackendTask` error if there isn't exactly 1 matching file.\nIf there is exactly 1, then you successfully get back that single match.\n\nFor example, maybe you can have\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n findBlogBySlug : String -> BackendTask FatalError String\n findBlogBySlug slug =\n Glob.succeed identity\n |> Glob.captureFilePath\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.match (Glob.literal slug)\n |> Glob.match\n (Glob.oneOf\n ( ( \"\", () )\n , [ ( \"/index\", () ) ]\n )\n )\n |> Glob.match (Glob.literal \".md\")\n |> Glob.expectUniqueMatch\n |> BackendTask.allowFatal\n\nIf we used `findBlogBySlug \"first-post\"` with these files:\n\n```markdown\n- blog/\n - first-post/\n - index.md\n```\n\nThis would give us:\n\n results : BackendTask FatalError String\n results =\n BackendTask.succeed \"blog/first-post/index.md\"\n\nIf we used `findBlogBySlug \"first-post\"` with these files:\n\n```markdown\n- blog/\n - first-post.md\n - first-post/\n - index.md\n```\n\nThen we will get a `BackendTask` error saying `More than one file matched.` Keep in mind that `BackendTask` failures\nin build-time routes will cause a build failure, giving you the opportunity to fix the problem before users see the issue,\nso it's ideal to make this kind of assertion rather than having fallback behavior that could silently cover up\nissues (like if we had instead ignored the case where there are two or more matching blog post files).\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : String.String } a"},{"name":"expectUniqueMatchFromList","comment":" ","type":"List.List (BackendTask.Glob.Glob a) -> BackendTask.BackendTask String.String a"},{"name":"fromString","comment":" Runs a glob string directly, with `include = FilesAndFolders`. Behavior is similar to using glob patterns in a shell.\n\nIf you need to capture specific parts of the path, you can use `capture` and `match` functions instead. `fromString`\nonly allows you to capture a list of matching file paths.\n\nThe following glob syntax is supported:\n\n - `*` matches any number of characters except for `/`\n - `**` matches any number of characters including `/`\n\nFor example, if we have the following files:\n\n```shell\n- src/\n - Main.elm\n - Ui/\n - Icon.elm\n- content/\n - blog/\n - first-post.md\n - second-post.md\n```\n\n import BackendTask.Glob as Glob\n\n blogPosts : BackendTask error (List String)\n blogPosts =\n Glob.fromString \"content/blog/*.md\"\n\n --> BackendTask.succeed [ \"content/blog/first-post.md\", \"content/blog/second-post.md\" ]\n elmFiles : BackendTask error (List String)\n elmFiles =\n Glob.fromString \"src/**/*.elm\"\n\n --> BackendTask.succeed [ \"src/Main.elm\", \"src/Ui\", \"src/Ui/Icon.elm\" ]\n\n","type":"String.String -> BackendTask.BackendTask error (List.List String.String)"},{"name":"fromStringWithOptions","comment":" Same as [`fromString`](#fromString), but with custom [`Options`](#Options).\n","type":"BackendTask.Glob.Options -> String.String -> BackendTask.BackendTask error (List.List String.String)"},{"name":"int","comment":" Same as [`digits`](#digits), but it safely turns the digits String into an `Int`.\n\nLeading 0's are ignored.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n slides : BackendTask error (List Int)\n slides =\n Glob.succeed identity\n |> Glob.match (Glob.literal \"slide-\")\n |> Glob.capture Glob.int\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nWith files\n\n```shell\n- slide-no-match.md\n- slide-.md\n- slide-1.md\n- slide-01.md\n- slide-2.md\n- slide-03.md\n- slide-4.md\n- slide-05.md\n- slide-06.md\n- slide-007.md\n- slide-08.md\n- slide-09.md\n- slide-10.md\n- slide-11.md\n```\n\nYields\n\n matches : BackendTask error (List Int)\n matches =\n BackendTask.succeed\n [ 1\n , 1\n , 2\n , 3\n , 4\n , 5\n , 6\n , 7\n , 8\n , 9\n , 10\n , 11\n ]\n\nNote that neither `slide-no-match.md` nor `slide-.md` match.\nAnd both `slide-1.md` and `slide-01.md` match and turn into `1`.\n\n","type":"BackendTask.Glob.Glob Basics.Int"},{"name":"literal","comment":" Match a literal part of a path. Can include `/`s.\n\nSome common uses include\n\n - The leading part of a pattern, to say \"starts with `content/blog/`\"\n - The ending part of a pattern, to say \"ends with `.md`\"\n - In-between wildcards, to say \"these dynamic parts are separated by `/`\"\n\n```elm\nimport BackendTask exposing (BackendTask)\nimport BackendTask.Glob as Glob\n\nblogPosts =\n Glob.succeed\n (\\section slug ->\n { section = section, slug = slug }\n )\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n```\n\n","type":"String.String -> BackendTask.Glob.Glob String.String"},{"name":"map","comment":" A `Glob` can be mapped. This can be useful for transforming a sub-match in-place.\n\nFor example, if you wanted to take the slugs for a blog post and make sure they are normalized to be all lowercase, you\ncould use\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n blogPostsGlob : BackendTask error (List String)\n blogPostsGlob =\n Glob.succeed (\\slug -> slug)\n |> Glob.match (Glob.literal \"content/blog/\")\n |> Glob.capture (Glob.wildcard |> Glob.map String.toLower)\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nIf you want to validate file formats, you can combine that with some `BackendTask` helpers to turn a `Glob (Result String value)` into\na `BackendTask FatalError (List value)`.\n\nFor example, you could take a date and parse it.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n example : BackendTask FatalError (List ( String, String ))\n example =\n Glob.succeed\n (\\dateResult slug ->\n dateResult\n |> Result.map (\\okDate -> ( okDate, slug ))\n )\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.capture (Glob.recursiveWildcard |> Glob.map expectDateFormat)\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n |> BackendTask.map (List.map BackendTask.fromResult)\n |> BackendTask.resolve\n\n expectDateFormat : List String -> Result FatalError String\n expectDateFormat dateParts =\n case dateParts of\n [ year, month, date ] ->\n Ok (String.join \"-\" [ year, month, date ])\n\n _ ->\n Err <| FatalError.fromString \"Unexpected date format, expected yyyy/mm/dd folder structure.\"\n\n","type":"(a -> b) -> BackendTask.Glob.Glob a -> BackendTask.Glob.Glob b"},{"name":"match","comment":" Adds on to the glob pattern, but does not capture it in the resulting Elm match value. That means this changes which\nfiles will match, but does not change the Elm data type you get for each matching file.\n\nExactly the same as `capture` except it doesn't capture the matched sub-pattern.\n\n","type":"BackendTask.Glob.Glob a -> BackendTask.Glob.Glob value -> BackendTask.Glob.Glob value"},{"name":"oneOf","comment":"\n\n import BackendTask.Glob as Glob\n\n type Extension\n = Json\n | Yml\n\n type alias DataFile =\n { name : String\n , extension : String\n }\n\n dataFiles : BackendTask error (List DataFile)\n dataFiles =\n Glob.succeed DataFile\n |> Glob.match (Glob.literal \"my-data/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".\")\n |> Glob.capture\n (Glob.oneOf\n ( ( \"yml\", Yml )\n , [ ( \"json\", Json )\n ]\n )\n )\n\nIf we have the following files\n\n```shell\n- my-data/\n - authors.yml\n - events.json\n```\n\nThat gives us\n\n results : BackendTask error (List DataFile)\n results =\n BackendTask.succeed\n [ { name = \"authors\"\n , extension = Yml\n }\n , { name = \"events\"\n , extension = Json\n }\n ]\n\nYou could also match an optional file path segment using `oneOf`.\n\n rootFilesMd : BackendTask error (List String)\n rootFilesMd =\n Glob.succeed (\\slug -> slug)\n |> Glob.match (Glob.literal \"blog/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match\n (Glob.oneOf\n ( ( \"\", () )\n , [ ( \"/index\", () ) ]\n )\n )\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\nWith these files:\n\n```markdown\n- blog/\n - first-post.md\n - second-post/\n - index.md\n```\n\nThis would give us:\n\n results : BackendTask error (List String)\n results =\n BackendTask.succeed\n [ \"first-post\"\n , \"second-post\"\n ]\n\n","type":"( ( String.String, a ), List.List ( String.String, a ) ) -> BackendTask.Glob.Glob a"},{"name":"recursiveWildcard","comment":" Matches any number of characters, including `/`, as long as it's the only thing in a path part.\n\nIn contrast, `wildcard` will never match `/`, so it only matches within a single path part.\n\nThis is the elm-pages equivalent of `**/*.txt` in standard shell syntax:\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n example : BackendTask error (List ( List String, String ))\n example =\n Glob.succeed Tuple.pair\n |> Glob.match (Glob.literal \"articles/\")\n |> Glob.capture Glob.recursiveWildcard\n |> Glob.match (Glob.literal \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".txt\")\n |> Glob.toBackendTask\n\nWith these files:\n\n```shell\n- articles/\n - google-io-2021-recap.txt\n - archive/\n - 1977/\n - 06/\n - 10/\n - apple-2-announced.txt\n```\n\nWe would get the following matches:\n\n matches : BackendTask error (List ( List String, String ))\n matches =\n BackendTask.succeed\n [ ( [ \"archive\", \"1977\", \"06\", \"10\" ], \"apple-2-announced\" )\n , ( [], \"google-io-2021-recap\" )\n ]\n\nNote that the recursive wildcard conveniently gives us a `List String`, where\neach String is a path part with no slashes (like `archive`).\n\nAnd also note that it matches 0 path parts into an empty list.\n\nIf we didn't include the `wildcard` after the `recursiveWildcard`, then we would only get\na single level of matches because it is followed by a file extension.\n\n example : BackendTask error (List String)\n example =\n Glob.succeed identity\n |> Glob.match (Glob.literal \"articles/\")\n |> Glob.capture Glob.recursiveWildcard\n |> Glob.match (Glob.literal \".txt\")\n\n matches : BackendTask error (List String)\n matches =\n BackendTask.succeed\n [ \"google-io-2021-recap\"\n ]\n\nThis is usually not what is intended. Using `recursiveWildcard` is usually followed by a `wildcard` for this reason.\n\n","type":"BackendTask.Glob.Glob (List.List String.String)"},{"name":"succeed","comment":" `succeed` is how you start a pipeline for a `Glob`. You will need one argument for each `capture` in your `Glob`.\n","type":"constructor -> BackendTask.Glob.Glob constructor"},{"name":"toBackendTask","comment":" In order to get match data from your glob, turn it into a `BackendTask` with this function.\n","type":"BackendTask.Glob.Glob a -> BackendTask.BackendTask error (List.List a)"},{"name":"toBackendTaskWithOptions","comment":" Same as toBackendTask, but lets you set custom glob options. For example, to list folders instead of files,\n\n import BackendTask.Glob as Glob exposing (OnlyFolders, defaultOptions)\n\n matchingFiles : Glob a -> BackendTask error (List a)\n matchingFiles glob =\n glob\n |> Glob.toBackendTaskWithOptions { defaultOptions | include = OnlyFolders }\n\n","type":"BackendTask.Glob.Options -> BackendTask.Glob.Glob a -> BackendTask.BackendTask error (List.List a)"},{"name":"wildcard","comment":" Matches anything except for a `/` in a file path. You may be familiar with this syntax from shells like bash\nwhere you can run commands like `rm client/*.js` to remove all `.js` files in the `client` directory.\n\nJust like a `*` glob pattern in bash, this `Glob.wildcard` function will only match within a path part. If you need to\nmatch 0 or more path parts like, see `recursiveWildcard`.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Glob as Glob\n\n type alias BlogPost =\n { year : String\n , month : String\n , day : String\n , slug : String\n }\n\n example : BackendTask error (List BlogPost)\n example =\n Glob.succeed BlogPost\n |> Glob.match (Glob.literal \"blog/\")\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 \"/\")\n |> Glob.capture Glob.wildcard\n |> Glob.match (Glob.literal \".md\")\n |> Glob.toBackendTask\n\n```shell\n\n- blog/\n - 2021-05-27/\n - first-post.md\n```\n\nThat will match to:\n\n results : BackendTask error (List BlogPost)\n results =\n BackendTask.succeed\n [ { year = \"2021\"\n , month = \"05\"\n , day = \"27\"\n , slug = \"first-post\"\n }\n ]\n\nNote that we can \"destructure\" the date part of this file path in the format `yyyy-mm-dd`. The `wildcard` matches\nwill match _within_ a path part (think between the slashes of a file path). `recursiveWildcard` can match across path parts.\n\n","type":"BackendTask.Glob.Glob String.String"},{"name":"zeroOrMore","comment":" ","type":"List.List String.String -> BackendTask.Glob.Glob (Maybe.Maybe String.String)"}],"binops":[]},{"name":"BackendTask.Http","comment":" `BackendTask.Http` requests are an alternative to doing Elm HTTP requests the traditional way using the `elm/http` package.\n\nThe key differences are:\n\n - `BackendTask.Http.Request`s are performed once at build time (`Http.Request`s are performed at runtime, at whenever point you perform them)\n - `BackendTask.Http.Request`s have a built-in `BackendTask.andThen` that allows you to perform follow-up requests without using tasks\n\n\n## Scenarios where BackendTask.Http is a good fit\n\nIf you need data that is refreshed often you may want to do a traditional HTTP request with the `elm/http` package.\nThe kinds of situations that are served well by static HTTP are with data that updates moderately frequently or infrequently (or never).\nA common pattern is to trigger a new build when data changes. Many JAMstack services\nallow you to send a WebHook to your host (for example, Netlify is a good static file host that supports triggering builds with webhooks). So\nyou may want to have your site rebuild everytime your calendar feed has an event added, or whenever a page or article is added\nor updated on a CMS service like Contentful.\n\nIn scenarios like this, you can serve data that is just as up-to-date as it would be using `elm/http`, but you get the performance\ngains of using `BackendTask.Http.Request`s as well as the simplicity and robustness that comes with it. Read more about these benefits\nin [this article introducing BackendTask.Http requests and some concepts around it](https://elm-pages.com/blog/static-http).\n\n\n## Scenarios where BackendTask.Http is not a good fit\n\n - Data that is specific to the logged-in user\n - Data that needs to be the very latest and changes often (for example, sports scores)\n\n\n## Making a Request\n\n@docs get, getJson\n\n@docs post\n\n\n## Decoding Request Body\n\n@docs Expect, expectString, expectJson, expectBytes, expectWhatever\n\n\n## Error Handling\n\n@docs Error\n\n\n## General Requests\n\n@docs request\n\n\n## Building a BackendTask.Http Request Body\n\nThe way you build a body is analogous to the `elm/http` package. Currently, only `emptyBody` and\n`stringBody` are supported. If you have a use case that calls for a different body type, please open a Github issue\nand describe your use case!\n\n@docs Body, emptyBody, stringBody, jsonBody, bytesBody\n\n\n## Caching Options\n\n`elm-pages` performs GET requests using a local HTTP cache by default. These requests are not performed using Elm's `elm/http`,\nbut rather are performed in NodeJS. Under the hood it uses [the NPM package `make-fetch-happen`](https://github.com/npm/make-fetch-happen).\nOnly GET requests made with `get`, `getJson`, or `getWithOptions` use local caching. Requests made with [`BackendTask.Http.request`](#request)\nare not cached, even if the method is set to `GET`.\n\nIn dev mode, assets are cached more aggressively by default, whereas for a production build assets use a default to revalidate each cached response's freshness before using it (the `ForceRevalidate` [`CacheStrategy`](#CacheStrategy)).\n\nThe default caching behavior for GET requests is to use a local cache in `.elm-pages/http-cache`. This uses the same caching behavior\nthat browsers use to avoid re-downloading content when it hasn't changed. Servers can set HTTP response headers to explicitly control\nthis caching behavior.\n\n - [`cache-control` HTTP response headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) let you set a length of time before considering an asset stale. This could mean that the server considers it acceptable for an asset to be somewhat outdated, or this could mean that the asset is guaranteed to be up-to-date until it is stale - those semantics are up to the server.\n - `Last-Modified` and `ETag` HTTP response headers can be returned by the server allow [Conditional Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests). Conditional Requests let us send back the `Last-Modified` timestamp or `etag` hash for assets that are in our local cache to the server to check if the asset is fresh, and skip re-downloading it if it is unchanged (or download a fresh one otherwise).\n\nIt's important to note that depending on how the server sets these HTTP response headers, we may have outdated data - either because the server explicitly allows assets to become outdated with their cache-control headers, OR because cache-control headers are not set. When these headers aren't explicitly set, [clients are allowed to cache assets for 10% of the amount of time since it was last modified](https://httpwg.org/specs/rfc7234.html#heuristic.freshness).\nFor production builds, the default caching will ignore both the implicit and explicit information about an asset's freshness and _always_ revalidate it before using a locally cached response.\n\n@docs getWithOptions\n\n@docs CacheStrategy\n\n\n## Including HTTP Metadata\n\n@docs withMetadata, Metadata\n\n","unions":[{"name":"CacheStrategy","comment":" ","args":[],"cases":[["IgnoreCache",[]],["ForceRevalidate",[]],["ForceReload",[]],["ForceCache",[]],["ErrorUnlessCached",[]]]},{"name":"Error","comment":" ","args":[],"cases":[["BadUrl",["String.String"]],["Timeout",[]],["NetworkError",[]],["BadStatus",["BackendTask.Http.Metadata","String.String"]],["BadBody",["Maybe.Maybe Json.Decode.Error","String.String"]]]},{"name":"Expect","comment":" Analogous to the `Expect` type in the `elm/http` package. This represents how you will process the data that comes\nback in your BackendTask.Http request.\n\nYou can derive `ExpectJson` from `ExpectString`. Or you could build your own helper to process the String\nas XML, for example, or give an `elm-pages` build error if the response can't be parsed as XML.\n\n","args":["value"],"cases":[]}],"aliases":[{"name":"Body","comment":" A body for a BackendTask.Http request.\n","args":[],"type":"Pages.Internal.StaticHttpBody.Body"},{"name":"Metadata","comment":" ","args":[],"type":"{ url : String.String, statusCode : Basics.Int, statusText : String.String, headers : Dict.Dict String.String String.String }"}],"values":[{"name":"bytesBody","comment":" Build a body from `Bytes` for a BackendTask.Http request. See [elm/http's `Http.bytesBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#bytesBody).\n","type":"String.String -> Bytes.Bytes -> BackendTask.Http.Body"},{"name":"emptyBody","comment":" Build an empty body for a BackendTask.Http request. See [elm/http's `Http.emptyBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#emptyBody).\n","type":"BackendTask.Http.Body"},{"name":"expectBytes","comment":" ","type":"Bytes.Decode.Decoder value -> BackendTask.Http.Expect value"},{"name":"expectJson","comment":" Handle the incoming response as JSON and don't optimize the asset and strip out unused values.\nBe sure to use the `BackendTask.Http.request` function if you want an optimized request that\nstrips out unused JSON to optimize your asset size. This function makes sense to use for things like a GraphQL request\nwhere the JSON payload is already trimmed down to the data you explicitly requested.\n\nIf the function you pass to `expectString` yields an `Err`, then you will get a build error that will\nfail your `elm-pages` build and print out the String from the `Err`.\n\n","type":"Json.Decode.Decoder value -> BackendTask.Http.Expect value"},{"name":"expectString","comment":" Gives the HTTP response body as a raw String.\n\n import BackendTask exposing (BackendTask)\n import BackendTask.Http\n\n request : BackendTask String\n request =\n BackendTask.Http.request\n { url = \"https://example.com/file.txt\"\n , method = \"GET\"\n , headers = []\n , body = BackendTask.Http.emptyBody\n }\n BackendTask.Http.expectString\n\n","type":"BackendTask.Http.Expect String.String"},{"name":"expectWhatever","comment":" ","type":"value -> BackendTask.Http.Expect value"},{"name":"get","comment":" A simplified helper around [`BackendTask.Http.getWithOptions`](#getWithOptions), which builds up a GET request with\nthe default retries, timeout, and HTTP caching options. If you need to configure those options or include HTTP request headers,\nuse the more flexible `getWithOptions`.\n\n import BackendTask\n import BackendTask.Http\n import FatalError exposing (FatalError)\n\n getRequest : BackendTask (FatalError Error) String\n getRequest =\n BackendTask.Http.get\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n BackendTask.Http.expectString\n\n","type":"String.String -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"getJson","comment":" A simplified helper around [`BackendTask.Http.get`](#get), which builds up a BackendTask.Http GET request with `expectJson`.\n\n import BackendTask\n import BackendTask.Http\n import FatalError exposing (FatalError)\n import Json.Decode as Decode exposing (Decoder)\n\n getRequest : BackendTask (FatalError Error) Int\n getRequest =\n BackendTask.Http.getJson\n \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"stargazers_count\" Decode.int)\n\n","type":"String.String -> Json.Decode.Decoder a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"getWithOptions","comment":" Perform a GET request, with some additional options for the HTTP request, including options for caching behavior.\n\n - `retries` - Default is 0. Will try performing request again if set to a number greater than 0.\n - `timeoutInMs` - Default is no timeout.\n - `cacheStrategy` - The [caching options are passed to the NPM package `make-fetch-happen`](https://github.com/npm/make-fetch-happen#opts-cache)\n - `cachePath` - override the default directory for the local HTTP cache. This can be helpful if you want more granular control to clear some HTTP caches more or less frequently than others. Or you may want to preserve the local cache for some requests in your build server, but not store the cache for other requests.\n\n","type":"{ url : String.String, expect : BackendTask.Http.Expect a, headers : List.List ( String.String, String.String ), cacheStrategy : Maybe.Maybe BackendTask.Http.CacheStrategy, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int, cachePath : Maybe.Maybe String.String } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"jsonBody","comment":" Builds a JSON body for a BackendTask.Http request. See [elm/http's `Http.jsonBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#jsonBody).\n","type":"Json.Encode.Value -> BackendTask.Http.Body"},{"name":"post","comment":" ","type":"String.String -> BackendTask.Http.Body -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"request","comment":" ","type":"{ url : String.String, method : String.String, headers : List.List ( String.String, String.String ), body : BackendTask.Http.Body, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int } -> BackendTask.Http.Expect a -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Http.Error } a"},{"name":"stringBody","comment":" Builds a string body for a BackendTask.Http request. See [elm/http's `Http.stringBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#stringBody).\n\nNote from the `elm/http` docs:\n\n> The first argument is a [MIME type](https://en.wikipedia.org/wiki/Media_type) of the body. Some servers are strict about this!\n\n","type":"String.String -> String.String -> BackendTask.Http.Body"},{"name":"withMetadata","comment":" ","type":"(BackendTask.Http.Metadata -> value -> combined) -> BackendTask.Http.Expect value -> BackendTask.Http.Expect combined"}],"binops":[]},{"name":"BackendTask.Random","comment":"\n\n@docs generate\n\n@docs int32\n\n","unions":[],"aliases":[],"values":[{"name":"generate","comment":" Takes an `elm/random` `Random.Generator` and runs it using a randomly generated initial seed.\n\n type alias Data =\n { randomData : ( Int, Float )\n }\n\n data : BackendTask FatalError Data\n data =\n BackendTask.map Data\n (BackendTask.Random.generate generator)\n\n generator : Random.Generator ( Int, Float )\n generator =\n Random.map2 Tuple.pair (Random.int 0 100) (Random.float 0 100)\n\nThe random initial seed is generated using <https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>\nto generate a single 32-bit Integer. That 32-bit Integer is then used with `Random.initialSeed` to create an Elm Random.Seed value.\nThen that `Seed` used to run the `Generator`.\n\nNote that this is different than `elm/random`'s `Random.generate`. This difference shouldn't be problematic, and in fact the `BackendTask`\nrandom seed generation is more cryptographically independent because you can't determine the\nrandom seed based solely on the time at which it is run. Each time you call `BackendTask.generate` it uses a newly\ngenerated random seed to run the `Random.Generator` that is passed in. In contrast, `elm/random`'s `Random.generate`\ngenerates an initial seed using `Time.now`, and then continues with that same seed using using [`Random.step`](https://package.elm-lang.org/packages/elm/random/latest/Random#step)\nto get new random values after that. You can [see the implementation here](https://github.com/elm/random/blob/c1c9da4d861363cee1c93382d2687880279ed0dd/src/Random.elm#L865-L896).\nHowever, `elm/random` is still not suitable in general for cryptographic uses of random because it uses 32-bits for when it\nsteps through new seeds while running a single `Random.Generator`.\n\n","type":"Random.Generator value -> BackendTask.BackendTask error value"},{"name":"int32","comment":" Gives a random 32-bit Int. This can be useful if you want to do low-level things with a cryptographically sound\nrandom 32-bit integer.\n\nThe value comes from running this code in Node using <https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>:\n\n```js\nimport * as crypto from \"node:crypto\";\n\ncrypto.getRandomValues(new Uint32Array(1))[0]\n```\n\n","type":"BackendTask.BackendTask error Basics.Int"}],"binops":[]},{"name":"BackendTask.Stream","comment":" A `Stream` represents a flow of data through a pipeline.\n\nIt is typically\n\n - An input source, or Readable Stream (`Stream { read : (), write : Never }`)\n - An output destination, or Writable Stream (`Stream { read : Never, write : () }`)\n - And (optionally) a series of transformations in between, or Duplex Streams (`Stream { read : (), write : () }`)\n\nFor example, you could have a stream that\n\n - Reads from a file [`fileRead`](#fileRead)\n - Unzips the contents [`unzip`](#unzip)\n - Runs a shell command on the contents [`command`](#command)\n - And writes the result to a network connection [`httpWithInput`](#httpWithInput)\n\nFor example,\n\n import BackendTask.Stream as Stream exposing (Stream)\n\n example =\n Stream.fileRead \"data.txt\"\n |> Stream.unzip\n |> Stream.command \"wc\" [ \"-l\" ]\n |> Stream.httpWithInput\n { url = \"http://example.com\"\n , method = \"POST\"\n , headers = []\n , retries = Nothing\n , timeoutInMs = Nothing\n }\n |> Stream.run\n\nEnd example\n\n@docs Stream\n\n@docs pipe\n\n@docs fileRead, fileWrite, fromString, http, httpWithInput, stdin, stdout, stderr\n\n\n## Running Streams\n\n@docs read, readJson, readMetadata, run\n\n@docs Error\n\n\n## Shell Commands\n\nNote that the commands do not execute through a shell but rather directly executes a child process. That means that\nspecial shell syntax will have no effect, but instead will be interpreted as literal characters in arguments to the command.\n\nSo instead of `grep error < log.txt`, you would use\n\n module GrepErrors exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.fileRead \"log.txt\"\n |> Stream.pipe (Stream.command \"grep\" [ \"error\" ])\n |> Stream.stdout\n |> Stream.run\n )\n\n@docs command\n\n\n## Command Options\n\n@docs commandWithOptions\n\n@docs StderrOutput\n\n@docs CommandOptions, defaultCommandOptions, allowNon0Status, withOutput, withTimeout\n\n\n## Command Output Strategies\n\nThere are 3 things that effect the output behavior of a command:\n\n - The verbosity of the `BackendTask` context ([`BackendTask.quiet`](BackendTask#quiet))\n - Whether the `Stream` output is ignored ([`Stream.run`](#run)), or read ([`Stream.read`](#read))\n - [`withOutput`](#withOutput) (allows you to use stdout, stderr, or both)\n\nWith `BackendTask.quiet`, the output of the command will not print as it runs, but you still read it in Elm if you read the `Stream` (instead of using [`Stream.run`](#run)).\n\nThere are 3 ways to handle the output of a command:\n\n1. Read the output but don't print\n2. Print the output but don't read\n3. Ignore the output\n\nTo read the output (1), use [`Stream.read`](#read) or [`Stream.readJson`](#readJson). This will give you the output as a String or JSON object.\nRegardless of whether you use `BackendTask.quiet`, the output will be read and returned to Elm.\n\nTo let the output from the command natively print to the console (2), use [`Stream.run`](#run) without setting `BackendTask.quiet`. Based on\nthe command's `withOutput` configuration, either stderr, stdout, or both will print to the console. The native output will\nsometimes be treated more like running the command directly in the terminal, for example `elm make` will print progress\nmessages which will be cleared and updated in place.\n\nTo ignore the output (3), use [`Stream.run`](#run) with `BackendTask.quiet`. This will run the command without printing anything to the console.\nYou can also use [`Stream.read`](#read) and ignore the captured output, but this is less efficient than using `BackendTask.quiet` with `Stream.run`.\n\n\n## Compression Helpers\n\n module CompressionDemo exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.fileRead \"elm.json\"\n |> Stream.pipe Stream.gzip\n |> Stream.pipe (Stream.fileWrite \"elm.json.gz\")\n |> Stream.run\n |> BackendTask.andThen\n (\\_ ->\n Stream.fileRead \"elm.json.gz\"\n |> Stream.pipe Stream.unzip\n |> Stream.pipe Stream.stdout\n |> Stream.run\n )\n )\n\n@docs gzip, unzip\n\n\n## Custom Streams\n\n[`BackendTask.Custom`](BackendTask-Custom) lets you define custom `BackendTask`s from async NodeJS functions in your `custom-backend-task` file.\nSimilarly, you can define custom streams with async functions in your `custom-backend-task` file, returning native NodeJS Streams, and optionally functions to extract metadata.\n\n```js\nimport { Writable, Transform, Readable } from \"node:stream\";\n\nexport async function upperCaseStream(input, { cwd, env, quiet }) {\n return {\n metadata: () => \"Hi! I'm metadata from upperCaseStream!\",\n stream: new Transform({\n transform(chunk, encoding, callback) {\n callback(null, chunk.toString().toUpperCase());\n },\n }),\n };\n}\n\nexport async function customReadStream(input) {\n return new Readable({\n read(size) {\n this.push(\"Hello from customReadStream!\");\n this.push(null);\n },\n });\n}\n\nexport async function customWriteStream(input, { cwd, env, quiet }) {\n return {\n stream: new Writable({\n write(chunk, encoding, callback) {\n console.error(\"...received chunk...\");\n console.log(chunk.toString());\n callback();\n },\n }),\n metadata: () => {\n return \"Hi! I'm metadata from customWriteStream!\";\n },\n };\n}\n```\n\n module CustomStreamDemo exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.customRead \"customReadStream\" Encode.null\n |> Stream.pipe (Stream.customDuplex \"upperCaseStream\" Encode.null)\n |> Stream.pipe (Stream.customWrite \"customWriteStream\" Encode.null)\n |> Stream.run\n )\n\n To extract the metadata from the custom stream, you can use the `...WithMeta` functions:\n\n module CustomStreamDemoWithMeta exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.customReadWithMeta \"customReadStream\" Encode.null Decode.succeed\n |> Stream.pipe (Stream.customTransformWithMeta \"upperCaseStream\" Encode.null Decode.succeed)\n |> Stream.readMetadata\n |> BackendTask.allowFatal\n |> BackendTask.andThen\n (\\metadata ->\n Script.log (\"Metadata: \" ++ metadata)\n )\n )\n --> Script.log \"Metadata: Hi! I'm metadata from upperCaseStream!\"\n\n@docs customRead, customWrite, customDuplex\n\n\n### With Metadata Decoders\n\n@docs customReadWithMeta, customTransformWithMeta, customWriteWithMeta\n\n","unions":[{"name":"CommandOptions","comment":" Configuration for [`commandWithOptions`](#commandWithOptions).\n","args":[],"cases":[]},{"name":"Error","comment":" Running or reading a `Stream` can give one of two kinds of error:\n\n - `StreamError String` - when something in the middle of the stream fails\n - `CustomError error body` - when the `Stream` fails with a custom error\n\nA `CustomError` can only come from the final part of the stream.\n\nYou can define your own custom errors by decoding metadata to an `Err` in the `...WithMeta` helpers.\n\n","args":["error","body"],"cases":[["StreamError",["String.String"]],["CustomError",["error","Maybe.Maybe body"]]]},{"name":"StderrOutput","comment":" The output configuration for [`withOutput`](#withOutput). The default is `PrintStderr`.\n\n - `PrintStderr` - Print (but do not pass along) the `stderr` output of the command. Only `stdout` will be passed along as the body of the stream.\n - `IgnoreStderr` - Ignore the `stderr` output of the command, only include `stdout`\n - `MergeStderrAndStdout` - Both `stderr` and `stdout` will be passed along as the body of the stream.\n - `StderrInsteadOfStdout` - Only `stderr` will be passed along as the body of the stream. `stdout` will be ignored.\n\n","args":[],"cases":[["PrintStderr",[]],["IgnoreStderr",[]],["MergeStderrAndStdout",[]],["StderrInsteadOfStdout",[]]]},{"name":"Stream","comment":" Once you've defined a `Stream`, it can be turned into a `BackendTask` that will run it (and optionally read its output and metadata).\n","args":["error","metadata","kind"],"cases":[]}],"aliases":[],"values":[{"name":"allowNon0Status","comment":" By default, the `Stream` will halt with an error if a command returns a non-zero status code.\n\nWith `allowNon0Status`, the stream will continue without an error if the command returns a non-zero status code.\n\n","type":"BackendTask.Stream.CommandOptions -> BackendTask.Stream.CommandOptions"},{"name":"command","comment":" Run a command (or `child_process`). The command's output becomes the body of the `Stream`.\n","type":"String.String -> List.List String.String -> BackendTask.Stream.Stream Basics.Int () { read : read, write : write }"},{"name":"commandWithOptions","comment":" Pass in custom [`CommandOptions`](#CommandOptions) to configure the behavior of the command.\n\nFor example, `grep` will return a non-zero status code if it doesn't find any matches. To ignore the non-zero status code and proceed with\nempty output, you can use `allowNon0Status`.\n\n module GrepErrors exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.fileRead \"log.txt\"\n |> Stream.pipe\n (Stream.commandWithOptions\n (Stream.defaultCommandOptions |> Stream.allowNon0Status)\n \"grep\"\n [ \"error\" ]\n )\n |> Stream.pipe Stream.stdout\n |> Stream.run\n )\n\n","type":"BackendTask.Stream.CommandOptions -> String.String -> List.List String.String -> BackendTask.Stream.Stream Basics.Int () { read : read, write : write }"},{"name":"customDuplex","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `DuplexStream` it returns.\n","type":"String.String -> Json.Encode.Value -> BackendTask.Stream.Stream () () { read : (), write : () }"},{"name":"customRead","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `ReadableStream` it returns.\n","type":"String.String -> Json.Encode.Value -> BackendTask.Stream.Stream () () { read : (), write : Basics.Never }"},{"name":"customReadWithMeta","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `DuplexStream` it returns.\n","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder (Result.Result { fatal : FatalError.FatalError, recoverable : error } metadata) -> BackendTask.Stream.Stream error metadata { read : (), write : Basics.Never }"},{"name":"customTransformWithMeta","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `DuplexStream` and metadata function it returns.\n","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder (Result.Result { fatal : FatalError.FatalError, recoverable : error } metadata) -> BackendTask.Stream.Stream error metadata { read : (), write : () }"},{"name":"customWrite","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `WritableStream` it returns.\n","type":"String.String -> Json.Encode.Value -> BackendTask.Stream.Stream () () { read : Basics.Never, write : () }"},{"name":"customWriteWithMeta","comment":" Calls an async function from your `custom-backend-task` definitions and uses the NodeJS `WritableStream` and metadata function it returns.\n","type":"String.String -> Json.Encode.Value -> Json.Decode.Decoder (Result.Result { fatal : FatalError.FatalError, recoverable : error } metadata) -> BackendTask.Stream.Stream error metadata { read : Basics.Never, write : () }"},{"name":"defaultCommandOptions","comment":" The default options that are used for [`command`](#command). Used to build up `CommandOptions`\nto pass in to [`commandWithOptions`](#commandWithOptions).\n","type":"BackendTask.Stream.CommandOptions"},{"name":"fileRead","comment":" Open a file's contents as a Stream.\n\n module ReadFile exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.fileRead \"elm.json\"\n |> Stream.readJson (Decode.field \"source-directories\" (Decode.list Decode.string))\n |> BackendTask.allowFatal\n |> BackendTask.andThen\n (\\{ body } ->\n Script.log\n (\"The source directories are: \"\n ++ String.join \", \" body\n )\n )\n )\n\nIf you want to read a file but don't need to use any of the other Stream functions, you can use [`BackendTask.File.read`](BackendTask-File#rawFile) instead.\n\n","type":"String.String -> BackendTask.Stream.Stream () () { read : (), write : Basics.Never }"},{"name":"fileWrite","comment":" Write a Stream to a file.\n\n module WriteFile exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.fileRead \"logs.txt\"\n |> Stream.pipe (Stream.command \"grep\" [ \"error\" ])\n |> Stream.pipe (Stream.fileWrite \"errors.txt\")\n )\n\n","type":"String.String -> BackendTask.Stream.Stream () () { read : Basics.Never, write : () }"},{"name":"fromString","comment":" A handy way to turn either a hardcoded String, or any other value from Elm into a Stream.\n\n module HelloWorld exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.fromString \"Hello, World!\"\n |> Stream.stdout\n |> Stream.run\n |> BackendTask.allowFatal\n )\n\nA more programmatic use of `fromString` to use the result of a previous `BackendTask` to a `Stream`:\n\n module HelloWorld exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Glob.fromString \"src/**/*.elm\"\n |> BackendTask.andThen\n (\\elmFiles ->\n elmFiles\n |> String.join \", \"\n |> Stream.fromString\n |> Stream.pipe Stream.stdout\n |> Stream.run\n )\n )\n\n","type":"String.String -> BackendTask.Stream.Stream () () { read : (), write : Basics.Never }"},{"name":"gzip","comment":" Transforms the input with gzip compression.\n\nUnder the hood this builds a Stream using Node's [`zlib.createGzip`](https://nodejs.org/api/zlib.html#zlibcreategzipoptions).\n\n","type":"BackendTask.Stream.Stream () () { read : (), write : () }"},{"name":"http","comment":" Uses a regular HTTP request body (not a `Stream`). Streams the HTTP response body.\n\nIf you want to pass a stream as the request body, use [`httpWithInput`](#httpWithInput) instead.\n\nIf you don't need to stream the response body, you can use the functions from [`BackendTask.Http`](BackendTask-Http) instead.\n\n","type":"{ url : String.String, method : String.String, headers : List.List ( String.String, String.String ), body : BackendTask.Http.Body, retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int } -> BackendTask.Stream.Stream BackendTask.Http.Error BackendTask.Http.Metadata { read : (), write : Basics.Never }"},{"name":"httpWithInput","comment":" Streams the data from the input stream as the body of the HTTP request. The HTTP response body becomes the output stream.\n","type":"{ url : String.String, method : String.String, headers : List.List ( String.String, String.String ), retries : Maybe.Maybe Basics.Int, timeoutInMs : Maybe.Maybe Basics.Int } -> BackendTask.Stream.Stream BackendTask.Http.Error BackendTask.Http.Metadata { read : (), write : () }"},{"name":"pipe","comment":" You can build up a pipeline of streams by using the `pipe` function.\n\nThe stream you are piping to must be writable (`{ write : () }`),\nand the stream you are piping from must be readable (`{ read : () }`).\n\n module HelloWorld exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.fromString \"Hello, World!\"\n |> Stream.stdout\n |> Stream.run\n )\n\n","type":"BackendTask.Stream.Stream errorTo metaTo { read : toReadable, write : () } -> BackendTask.Stream.Stream errorFrom metaFrom { read : (), write : fromWriteable } -> BackendTask.Stream.Stream errorTo metaTo { read : toReadable, write : fromWriteable }"},{"name":"read","comment":" Read the body of the `Stream` as text.\n","type":"BackendTask.Stream.Stream error metadata { read : (), write : write } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Stream.Error error String.String } { metadata : metadata, body : String.String }"},{"name":"readJson","comment":" Read the body of the `Stream` as JSON.\n\n module ReadJson exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Json.Decode as Decode\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.fileRead \"data.json\"\n |> Stream.readJson (Decode.field \"name\" Decode.string)\n |> BackendTask.allowFatal\n |> BackendTask.andThen\n (\\{ body } ->\n Script.log (\"The name is: \" ++ body)\n )\n )\n\n","type":"Json.Decode.Decoder value -> BackendTask.Stream.Stream error metadata { read : (), write : write } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Stream.Error error value } { metadata : metadata, body : value }"},{"name":"readMetadata","comment":" Ignore the body of the `Stream`, while capturing the metadata from the final part of the Stream.\n","type":"BackendTask.Stream.Stream error metadata { read : read, write : write } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : BackendTask.Stream.Error error String.String } metadata"},{"name":"run","comment":" Gives a `BackendTask` to execute the `Stream`, ignoring its body and metadata.\n\nThis is useful if you only want the side-effect from the `Stream` and don't need to programmatically use its\noutput. For example, if the end result you want is:\n\n - Printing to the console\n - Writing to a file\n - Making an HTTP request\n\nIf you need to read the output of the `Stream`, use [`read`](#read), [`readJson`](#readJson), or [`readMetadata`](#readMetadata) instead.\n\n","type":"BackendTask.Stream.Stream error metadata kind -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"stderr","comment":" Similar to [`stdout`](#stdout), but writes to `stderr` instead.\n","type":"BackendTask.Stream.Stream () () { read : Basics.Never, write : () }"},{"name":"stdin","comment":" The `stdin` from the process. When you execute an `elm-pages` script, this will be the value that is piped in to it. For example, given this script module:\n\n module CountLines exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.stdin\n |> Stream.read\n |> BackendTask.allowFatal\n |> BackendTask.andThen\n (\\{ body } ->\n body\n |> String.lines\n |> List.length\n |> String.fromInt\n |> Script.log\n )\n )\n\nIf you run the script without any stdin, it will wait until stdin is closed.\n\n```shell\nelm-pages run script/src/CountLines.elm\n# pressing ctrl-d (or your platform-specific way of closing stdin) will print the number of lines in the input\n```\n\nOr you can pipe to it and it will read that input:\n\n```shell\nls | elm-pages run script/src/CountLines.elm\n# prints the number of files in the current directory\n```\n\n","type":"BackendTask.Stream.Stream () () { read : (), write : Basics.Never }"},{"name":"stdout","comment":" Streaming through to stdout can be a convenient way to print a pipeline directly without going through to Elm.\n\n module UnzipFile exposing (run)\n\n import BackendTask\n import BackendTask.Stream as Stream\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Stream.fileRead \"data.gzip.txt\"\n |> Stream.pipe Stream.unzip\n |> Stream.pipe Stream.stdout\n |> Stream.run\n |> BackendTask.allowFatal\n )\n\n","type":"BackendTask.Stream.Stream () () { read : Basics.Never, write : () }"},{"name":"unzip","comment":" Transforms the input by auto-detecting the header and decompressing either a Gzip- or Deflate-compressed stream.\n\nUnder the hood, this builds a Stream using Node's [`zlib.createUnzip`](https://nodejs.org/api/zlib.html#zlibcreateunzip).\n\n","type":"BackendTask.Stream.Stream () () { read : (), write : () }"},{"name":"withOutput","comment":" Configure the [`StderrOutput`](#StderrOutput) behavior.\n","type":"BackendTask.Stream.StderrOutput -> BackendTask.Stream.CommandOptions -> BackendTask.Stream.CommandOptions"},{"name":"withTimeout","comment":" By default, commands do not have a timeout. This will set the timeout, in milliseconds, for the given command. If that duration is exceeded,\nthe `Stream` will fail with an error.\n","type":"Basics.Int -> BackendTask.Stream.CommandOptions -> BackendTask.Stream.CommandOptions"}],"binops":[]},{"name":"BackendTask.Time","comment":"\n\n@docs now\n\n","unions":[],"aliases":[],"values":[{"name":"now","comment":" Gives a `Time.Posix` of when the `BackendTask` executes.\n\n type alias Data =\n { time : Time.Posix\n }\n\n data : BackendTask FatalError Data\n data =\n BackendTask.map Data\n BackendTask.Time.now\n\nIt's better to use [`Server.Request.requestTime`](Server-Request#requestTime) or `Pages.builtAt` when those are the semantics\nyou are looking for. `requestTime` gives you a single reliable and consistent time for when the incoming HTTP request was received in\na server-rendered Route or server-rendered API Route. `Pages.builtAt` gives a single reliable and consistent time when the\nsite was built.\n\n`BackendTask.Time.now` gives you the time that it happened to execute, which might give you what you need, but be\naware that the time you get is dependent on how BackendTask's are scheduled and executed internally in elm-pages, and\nits best to avoid depending on that variation when possible.\n\n","type":"BackendTask.BackendTask error Time.Posix"}],"binops":[]},{"name":"FatalError","comment":" The Elm language doesn't have the concept of exceptions or special control flow for errors. It just has\nCustom Types, and by convention types like `Result` and the `Err` variant are used to represent possible failure states\nand combine together different error states.\n\n`elm-pages` doesn't change that, Elm still doesn't have special exception control flow at the language level. It does have\na type, which is just a regular old Elm type, called `FatalError`. Why? Because this plain old Elm type does have one\nspecial characteristic - the `elm-pages` framework knows how to turn it into an error message. This becomes interesting\nbecause an `elm-pages` app has several places that accept a value of type `BackendTask FatalError.FatalError value`.\nThis design lets the `elm-pages` framework do some of the work for you.\n\nFor example, if you wanted to handle possible errors to present them to the user\n\n type alias Data =\n String\n\n data : RouteParams -> BackendTask FatalError Data\n data routeParams =\n BackendTask.Http.getJson \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"description\" Decode.string)\n |> BackendTask.onError\n (\\{ recoverable } ->\n case recoverable of\n BackendTask.Http.BadStatus metadata string ->\n if metadata.statusCode == 401 || metadata.statusCode == 403 || metadata.statusCode == 404 then\n BackendTask.succeed \"Either this repo doesn't exist or you don't have access to it.\"\n\n else\n -- we're only handling these expected error cases. In the case of an HTTP timeout,\n -- we'll let the error propagate as a FatalError\n BackendTask.fail error |> BackendTask.allowFatal\n\n _ ->\n BackendTask.fail error |> BackendTask.allowFatal\n )\n\nThis can be a lot of work for all possible errors, though. If you don't expect this kind of error (it's an _exceptional_ case),\nyou can let the framework handle it if the error ever does unexpectedly occur.\n\n data : RouteParams -> BackendTask FatalError Data\n data routeParams =\n BackendTask.Http.getJson \"https://api.github.com/repos/dillonkearns/elm-pages\"\n (Decode.field \"description\" Decode.string)\n |> BackendTask.allowFatal\n\nThis is especially useful for pages generated at build-time (`RouteBuilder.preRender`) where you want the build\nto fail if anything unexpected happens. With pre-rendered routes, you know that these error cases won't\nbe seen by users, so it's often a great idea to just let the framework handle these unexpected errors so a developer can\ndebug them and see what went wrong. In the example above, maybe we are only pre-rendering pages for a set of known\nGitHub Repositories, so a Not Found or Unauthorized HTTP error would be unexpected and should stop the build so we can fix the\nissue.\n\nIn the case of server-rendered Routes (`RouteBuilder.serverRender`), `elm-pages` will show your 500 error page\nwhen these errors occur.\n\n@docs FatalError, build, fromString, recoverable\n\n","unions":[],"aliases":[{"name":"FatalError","comment":" ","args":[],"type":"Pages.Internal.FatalError.FatalError"}],"values":[{"name":"build","comment":" Create a FatalError with a title and body.\n","type":"{ title : String.String, body : String.String } -> FatalError.FatalError"},{"name":"fromString","comment":" ","type":"String.String -> FatalError.FatalError"},{"name":"recoverable","comment":" ","type":"{ title : String.String, body : String.String } -> error -> { fatal : FatalError.FatalError, recoverable : error }"}],"binops":[]},{"name":"Head","comment":" This module contains functions for building up\ntags with metadata that will be rendered into the page's `<head>` tag\nwhen your page is pre-rendered (or server-rendered, in the case of your server-rendered Route Modules). See also [`Head.Seo`](Head-Seo),\nwhich has some helper functions for defining OpenGraph and Twitter tags.\n\nOne of the unique benefits of using `elm-pages` is that all of your routes (both pre-rendered and server-rendered) fully\nrender the HTML of your page. That includes the full initial `view` (with the BackendTask resolved, and the `Model` from `init`).\nThe HTML response also includes all of the `Head` tags, which are defined in two places:\n\n1. `app/Site.elm` - there is a `head` definition in `Site.elm` where you define global head tags that will be included on every rendered page.\n\n2. In each Route Module - there is a `head` function where you have access to both the resolved `BackendTask` and the `RouteParams` for the page and can return head tags based on that.\n\nHere is a common set of global head tags that we can define in `Site.elm`:\n\n module Site exposing (canonicalUrl, config)\n\n import BackendTask exposing (BackendTask)\n import Head\n import MimeType\n import SiteConfig exposing (SiteConfig)\n\n config : SiteConfig\n config =\n { canonicalUrl = \"<https://elm-pages.com\">\n , head = head\n }\n\n head : BackendTask (List Head.Tag)\n head =\n [ Head.metaName \"viewport\" (Head.raw \"width=device-width,initial-scale=1\")\n , Head.metaName \"mobile-web-app-capable\" (Head.raw \"yes\")\n , Head.metaName \"theme-color\" (Head.raw \"#ffffff\")\n , Head.metaName \"apple-mobile-web-app-capable\" (Head.raw \"yes\")\n , Head.metaName \"apple-mobile-web-app-status-bar-style\" (Head.raw \"black-translucent\")\n , Head.icon [ ( 32, 32 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 32)\n , Head.icon [ ( 16, 16 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 16)\n , Head.appleTouchIcon (Just 180) (cloudinaryIcon MimeType.Png 180)\n , Head.appleTouchIcon (Just 192) (cloudinaryIcon MimeType.Png 192)\n ]\n |> BackendTask.succeed\n\nAnd here is a `head` function for a Route Module for a blog post. Note that we have access to our `BackendTask` Data and\nare using it to populate article metadata like the article's image, publish date, etc.\n\n import Article\n import BackendTask\n import Date\n import Head\n import Head.Seo\n import Path\n import Route exposing (Route)\n import RouteBuilder exposing (App, StatelessRoute)\n\n type alias RouteParams =\n { slug : String }\n\n type alias Data =\n { metadata : ArticleMetadata\n , body : List Markdown.Block.Block\n }\n\n route : StatelessRoute RouteParams Data ActionData\n route =\n RouteBuilder.preRender\n { data = data\n , head = head\n , pages = pages\n }\n |> RouteBuilder.buildNoState { view = view }\n\n head :\n App Data ActionData RouteParams\n -> List Head.Tag\n head static =\n let\n metadata =\n static.data.metadata\n in\n Head.Seo.summaryLarge\n { canonicalUrlOverride = Nothing\n , siteName = \"elm-pages\"\n , image =\n { url = metadata.image\n , alt = metadata.description\n , dimensions = Nothing\n , mimeType = Nothing\n }\n , description = metadata.description\n , locale = Nothing\n , title = metadata.title\n }\n |> Head.Seo.article\n { tags = []\n , section = Nothing\n , publishedTime = Just (DateOrDateTime.Date metadata.published)\n , modifiedTime = Nothing\n , expirationTime = Nothing\n }\n\n\n## Why is pre-rendered HTML important? Does it still matter for SEO?\n\nMany search engines are able to execute JavaScript now. However, not all are, and even with crawlers like Google, there\nis a longer lead time for your pages to be indexed when you have HTML with a blank page that is only visible after the JavaScript executes.\n\nBut most importantly, many tools that unfurl links will not execute JavaScript at all, but rather simply do a simple pass to parse your `<head>` tags.\nIt is not viable or reliable to add `<head>` tags for metadata on the client-side, it must be present in the initial HTML payload. Otherwise you may not\nget unfurling preview content when you share a link to your site on Slack, Twitter, etc.\n\n\n## Building up Head Tags\n\n@docs Tag, metaName, metaProperty, metaRedirect\n@docs rssLink, sitemapLink, rootLanguage, manifestLink\n\n@docs nonLoadingNode\n\n\n## Structured Data\n\n@docs structuredData\n\n\n## `AttributeValue`s\n\n@docs AttributeValue\n@docs currentPageFullUrl, urlAttribute, raw\n\n\n## Icons\n\n@docs appleTouchIcon, icon\n\n\n## Functions for use by generated code\n\n@docs toJson, canonicalLink\n\n","unions":[{"name":"AttributeValue","comment":" Values, such as between the `<>`'s here:\n\n```html\n<meta name=\"<THIS IS A VALUE>\" content=\"<THIS IS A VALUE>\" />\n```\n\n","args":[],"cases":[]},{"name":"Tag","comment":" Values that can be passed to the generated `Pages.application` config\nthrough the `head` function.\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"appleTouchIcon","comment":" Note: the type must be png.\nSee <https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html>.\n\nIf a size is provided, it will be turned into square dimensions as per the recommendations here: <https://developers.google.com/web/fundamentals/design-and-ux/browser-customization/#safari>\n\nImages must be png's, and non-transparent images are recommended. Current recommended dimensions are 180px and 192px.\n\n","type":"Maybe.Maybe Basics.Int -> Pages.Url.Url -> Head.Tag"},{"name":"canonicalLink","comment":" It's recommended that you use the `Seo` module helpers, which will provide this\nfor you, rather than directly using this.\n\nExample:\n\n Head.canonicalLink \"https://elm-pages.com\"\n\n","type":"Maybe.Maybe String.String -> Head.Tag"},{"name":"currentPageFullUrl","comment":" Create an `AttributeValue` representing the current page's full url.\n","type":"Head.AttributeValue"},{"name":"icon","comment":" ","type":"List.List ( Basics.Int, Basics.Int ) -> MimeType.MimeImage -> Pages.Url.Url -> Head.Tag"},{"name":"manifestLink","comment":" Let's you link to your manifest.json file, see <https://developer.mozilla.org/en-US/docs/Web/Manifest#deploying_a_manifest>.\n","type":"String.String -> Head.Tag"},{"name":"metaName","comment":" Example:\n\n Head.metaName \"twitter:card\" (Head.raw \"summary_large_image\")\n\nResults in `<meta name=\"twitter:card\" content=\"summary_large_image\" />`\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaProperty","comment":" Example:\n\n Head.metaProperty \"fb:app_id\" (Head.raw \"123456789\")\n\nResults in `<meta property=\"fb:app_id\" content=\"123456789\" />`\n\n","type":"String.String -> Head.AttributeValue -> Head.Tag"},{"name":"metaRedirect","comment":" Example:\n\n metaRedirect (Raw \"0; url=https://google.com\")\n\nResults in `<meta http-equiv=\"refresh\" content=\"0; url=https://google.com\" />`\n\n","type":"Head.AttributeValue -> Head.Tag"},{"name":"nonLoadingNode","comment":" Escape hatch for any head tags that don't have high-level helpers. This lets you build arbitrary head nodes as long as they\nare not loading or preloading directives.\n\nTags that do loading/pre-loading will not work from this function. `elm-pages` uses ViteJS for loading assets like\nscript tags, stylesheets, fonts, etc., and allows you to customize which assets to preload and how through the elm-pages.config.mjs file.\nSee the full discussion of the design in [#339](https://github.com/dillonkearns/elm-pages/discussions/339).\n\nSo for example the following tags would _not_ load if defined through `nonLoadingNode`, and would instead need to be registered through Vite:\n\n - `<script src=\"...\">`\n - `<link href=\"/style.css\">`\n - `<link rel=\"preload\">`\n\nThe following tag would successfully render as it is a non-loading tag:\n\n Head.nonLoadingNode \"link\"\n [ ( \"rel\", Head.raw \"alternate\" )\n , ( \"type\", Head.raw \"application/atom+xml\" )\n , ( \"title\", Head.raw \"News\" )\n , ( \"href\", Head.raw \"/news/atom\" )\n ]\n\nRenders the head tag:\n\n`<link rel=\"alternate\" type=\"application/atom+xml\" title=\"News\" href=\"/news/atom\">`\n\n","type":"String.String -> List.List ( String.String, Head.AttributeValue ) -> Head.Tag"},{"name":"raw","comment":" Create a raw `AttributeValue` (as opposed to some kind of absolute URL).\n","type":"String.String -> Head.AttributeValue"},{"name":"rootLanguage","comment":" Set the language for a page.\n\n<https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang>\n\n import Head\n import LanguageTag\n import LanguageTag.Language\n\n LanguageTag.Language.de -- sets the page's language to German\n |> LanguageTag.build LanguageTag.emptySubtags\n |> Head.rootLanguage\n\nThis results pre-rendered HTML with a global lang tag set.\n\n```html\n<html lang=\"no\">\n...\n</html>\n```\n\n","type":"LanguageTag.LanguageTag -> Head.Tag"},{"name":"rssLink","comment":" Add a link to the site's RSS feed.\n\nExample:\n\n rssLink \"/feed.xml\"\n\n```html\n<link rel=\"alternate\" type=\"application/rss+xml\" href=\"/rss.xml\">\n```\n\n","type":"String.String -> Head.Tag"},{"name":"sitemapLink","comment":" Add a link to the site's RSS feed.\n\nExample:\n\n sitemapLink \"/feed.xml\"\n\n```html\n<link rel=\"sitemap\" type=\"application/xml\" href=\"/sitemap.xml\">\n```\n\n","type":"String.String -> Head.Tag"},{"name":"structuredData","comment":" You can learn more about structured data in [Google's intro to structured data](https://developers.google.com/search/docs/guides/intro-structured-data).\n\nWhen you add a `structuredData` item to one of your pages in `elm-pages`, it will add `json-ld` data to your document that looks like this:\n\n```html\n<script type=\"application/ld+json\">\n{\n \"@context\":\"http://schema.org/\",\n \"@type\":\"Article\",\n \"headline\":\"Extensible Markdown Parsing in Pure Elm\",\n \"description\":\"Introducing a new parser that extends your palette with no additional syntax\",\n \"image\":\"https://elm-pages.com/images/article-covers/extensible-markdown-parsing.jpg\",\n \"author\":{\n \"@type\":\"Person\",\n \"name\":\"Dillon Kearns\"\n },\n \"publisher\":{\n \"@type\":\"Person\",\n \"name\":\"Dillon Kearns\"\n },\n \"url\":\"https://elm-pages.com/blog/extensible-markdown-parsing-in-elm\",\n \"datePublished\":\"2019-10-08\",\n \"mainEntityOfPage\":{\n \"@type\":\"SoftwareSourceCode\",\n \"codeRepository\":\"https://github.com/dillonkearns/elm-pages\",\n \"description\":\"A statically typed site generator for Elm.\",\n \"author\":\"Dillon Kearns\",\n \"programmingLanguage\":{\n \"@type\":\"ComputerLanguage\",\n \"url\":\"http://elm-lang.org/\",\n \"name\":\"Elm\",\n \"image\":\"http://elm-lang.org/\",\n \"identifier\":\"http://elm-lang.org/\"\n }\n }\n}\n</script>\n```\n\nTo get that data, you would write this in your `elm-pages` head tags:\n\n import Json.Encode as Encode\n\n {-| <https://schema.org/Article>\n -}\n encodeArticle :\n { title : String\n , description : String\n , author : StructuredDataHelper { authorMemberOf | personOrOrganization : () } authorPossibleFields\n , publisher : StructuredDataHelper { publisherMemberOf | personOrOrganization : () } publisherPossibleFields\n , url : String\n , imageUrl : String\n , datePublished : String\n , mainEntityOfPage : Encode.Value\n }\n -> Head.Tag\n encodeArticle info =\n Encode.object\n [ ( \"@context\", Encode.string \"http://schema.org/\" )\n , ( \"@type\", Encode.string \"Article\" )\n , ( \"headline\", Encode.string info.title )\n , ( \"description\", Encode.string info.description )\n , ( \"image\", Encode.string info.imageUrl )\n , ( \"author\", encode info.author )\n , ( \"publisher\", encode info.publisher )\n , ( \"url\", Encode.string info.url )\n , ( \"datePublished\", Encode.string info.datePublished )\n , ( \"mainEntityOfPage\", info.mainEntityOfPage )\n ]\n |> Head.structuredData\n\nTake a look at this [Google Search Gallery](https://developers.google.com/search/docs/guides/search-gallery)\nto see some examples of how structured data can be used by search engines to give rich search results. It can help boost\nyour rankings, get better engagement for your content, and also make your content more accessible. For example,\nvoice assistant devices can make use of structured data. If you're hosting a conference and want to make the event\ndate and location easy for attendees to find, this can make that information more accessible.\n\nFor the current version of API, you'll need to make sure that the format is correct and contains the required and recommended\nstructure.\n\nCheck out <https://schema.org> for a comprehensive listing of possible data types and fields. And take a look at\nGoogle's [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool)\ntoo make sure that your structured data is valid and includes the recommended values.\n\nIn the future, `elm-pages` will likely support a typed API, but schema.org is a massive spec, and changes frequently.\nAnd there are multiple sources of information on the possible and recommended structure. So it will take some time\nfor the right API design to evolve. In the meantime, this allows you to make use of this for SEO purposes.\n\n","type":"Json.Encode.Value -> Head.Tag"},{"name":"toJson","comment":" Feel free to use this, but in 99% of cases you won't need it. The generated\ncode will run this for you to generate your `manifest.json` file automatically!\n","type":"String.String -> String.String -> Head.Tag -> Json.Encode.Value"},{"name":"urlAttribute","comment":" Create an `AttributeValue` from an `ImagePath`.\n","type":"Pages.Url.Url -> Head.AttributeValue"}],"binops":[]},{"name":"Head.Seo","comment":" <https://developers.facebook.com/docs/sharing/opengraph>\n\nThis module encapsulates some of the best practices for SEO for your site.\n\n`elm-pages` pre-renders the HTML for your pages (either at build-time or server-render time) so that\nweb crawlers can efficiently and accurately process it. The functions in this module are for use\nwith the `head` function in your `Route` modules to help you build up a set of `<meta>` tags that\nincludes common meta tags used for rich link previews, namely [OpenGraph tags](https://ogp.me/) and [Twitter card tags](https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards).\n\n import Date\n import Head\n import Head.Seo as Seo\n\n\n -- justinmimbs/date package\n type alias ArticleMetadata =\n { title : String\n , description : String\n , published : Date\n , author : Data.Author.Author\n }\n\n head : ArticleMetadata -> List Head.Tag\n head articleMetadata =\n Seo.summaryLarge\n { canonicalUrlOverride = Nothing\n , siteName = \"elm-pages\"\n , image =\n { url = Pages.images.icon\n , alt = articleMetadata.description\n , dimensions = Nothing\n , mimeType = Nothing\n }\n , description = articleMetadata.description\n , locale = Nothing\n , title = articleMetadata.title\n }\n |> Seo.article\n { tags = []\n , section = Nothing\n , publishedTime = Just (Date.toIsoString articleMetadata.published)\n , modifiedTime = Nothing\n , expirationTime = Nothing\n }\n\n@docs Common, Image, article, audioPlayer, book, profile, song, summary, summaryLarge, videoPlayer, website\n\n","unions":[],"aliases":[{"name":"Common","comment":" These fields apply to any type in the og object types\nSee <https://ogp.me/#metadata> and <https://ogp.me/#optional>\n\nSkipping this for now, if there's a use case I can add it in:\n\n - og:determiner - The word that appears before this object's title in a sentence. An enum of (a, an, the, \"\", auto). If auto is chosen, the consumer of your data should chose between \"a\" or \"an\". Default is \"\" (blank).\n\n","args":[],"type":"{ title : String.String, image : Head.Seo.Image, canonicalUrlOverride : Maybe.Maybe String.String, description : String.String, siteName : String.String, audio : Maybe.Maybe Head.Seo.Audio, video : Maybe.Maybe Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale, alternateLocales : List.List Head.Seo.Locale, twitterCard : Head.Twitter.TwitterCard }"},{"name":"Image","comment":" See <https://ogp.me/#structured>\n","args":[],"type":"{ url : Pages.Url.Url, alt : String.String, dimensions : Maybe.Maybe { width : Basics.Int, height : Basics.Int }, mimeType : Maybe.Maybe MimeType.MimeType }"}],"values":[{"name":"article","comment":" See <https://ogp.me/#type_article>\n","type":"{ tags : List.List String.String, section : Maybe.Maybe String.String, publishedTime : Maybe.Maybe DateOrDateTime.DateOrDateTime, modifiedTime : Maybe.Maybe DateOrDateTime.DateOrDateTime, expirationTime : Maybe.Maybe DateOrDateTime.DateOrDateTime } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"audioPlayer","comment":" Will be displayed as a Player card in twitter\nSee: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card>\n\nOpenGraph audio will also be included.\nThe options will also be used to build up the appropriate OpenGraph `<meta>` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, audio : Head.Seo.Audio, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"book","comment":" See <https://ogp.me/#type_book>\n","type":"Head.Seo.Common -> { tags : List.List String.String, isbn : Maybe.Maybe String.String, releaseDate : Maybe.Maybe DateOrDateTime.DateOrDateTime } -> List.List Head.Tag"},{"name":"profile","comment":" See <https://ogp.me/#type_profile>\n","type":"{ firstName : String.String, lastName : String.String, username : Maybe.Maybe String.String } -> Head.Seo.Common -> List.List Head.Tag"},{"name":"song","comment":" See <https://ogp.me/#type_music.song>\n","type":"Head.Seo.Common -> { duration : Maybe.Maybe Basics.Int, album : Maybe.Maybe Basics.Int, disc : Maybe.Maybe Basics.Int, track : Maybe.Maybe Basics.Int } -> List.List Head.Tag"},{"name":"summary","comment":" Will be displayed as a large card in twitter\nSee: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary>\n\nThe options will also be used to build up the appropriate OpenGraph `<meta>` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"summaryLarge","comment":" Will be displayed as a large card in twitter\nSee: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary-card-with-large-image>\n\nThe options will also be used to build up the appropriate OpenGraph `<meta>` tags.\n\nNote: You cannot include audio or video tags with summaries.\nIf you want one of those, use `audioPlayer` or `videoPlayer`\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"videoPlayer","comment":" Will be displayed as a Player card in twitter\nSee: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card>\n\nOpenGraph video will also be included.\nThe options will also be used to build up the appropriate OpenGraph `<meta>` tags.\n\n","type":"{ canonicalUrlOverride : Maybe.Maybe String.String, siteName : String.String, image : Head.Seo.Image, description : String.String, title : String.String, video : Head.Seo.Video, locale : Maybe.Maybe Head.Seo.Locale } -> Head.Seo.Common"},{"name":"website","comment":" <https://ogp.me/#type_website>\n","type":"Head.Seo.Common -> List.List Head.Tag"}],"binops":[]},{"name":"Pages.ConcurrentSubmission","comment":" When you render a `Form` with the [`Pages.Form.withConcurrent`](Pages-Form#withConcurrent) `Option`, the state of in-flight and completed submissions will be available\nfrom your `Route` module through `app.concurrentSubmissions` as a `Dict String (ConcurrentSubmission (Maybe Action))`.\n\nYou can use this state to declaratively derive Pending UI or Optimistic UI from your pending submissions (without managing the state in your `Model`, since `elm-pages`\nmanages form submission state for you).\n\nYou can [see the full-stack TodoMVC example](https://github.com/dillonkearns/elm-pages/blob/master/examples/todos/app/Route/Visibility__.elm) for a complete example of deriving Pending UI state from `app.concurrentSubmissions`.\n\nFor example, this how the TodoMVC example derives the list of new items that are being created (but are still pending).\n\n view :\n App Data ActionData RouteParams\n -> Shared.Model\n -> Model\n -> View (PagesMsg Msg)\n view app shared model =\n let\n pendingActions : List Action\n pendingActions =\n app.concurrentSubmissions\n |> Dict.values\n |> List.filterMap\n (\\{ status, payload } ->\n case status of\n Pages.ConcurrentSubmission.Complete _ ->\n Nothing\n\n _ ->\n allForms\n |> Form.Handler.run payload.fields\n |> Form.toResult\n |> Result.toMaybe\n )\n\n newPendingItems : List Entry\n newPendingItems =\n pendingActions\n |> List.filterMap\n (\\submission ->\n case submission of\n Add description ->\n Just\n { description = description\n , completed = False\n , id = \"\"\n , isPending = True\n }\n\n _ ->\n -- `newPendingItems` only cares about pending Add actions. Other values will use\n -- pending submissions for other types of Actions.\n Nothing\n )\n in\n itemsView app newPendingItems\n\n allForms : Form.Handler.Handler String Action\n allForms =\n |> Form.Handler.init Add addItemForm\n -- |> Form.Handler.with ...\n\n\n type Action\n = UpdateEntry ( String, String )\n | Add String\n | Delete String\n | DeleteComplete\n | Check ( Bool, String )\n | CheckAll Bool\n\n@docs ConcurrentSubmission, Status\n\n@docs map\n\n","unions":[{"name":"Status","comment":" The status of a `ConcurrentSubmission`.\n\n - `Submitting` - The submission is in-flight.\n - `Reloading` - The submission has completed, and the page is now reloading the `Route`'s `data` to reflect the new state. The `actionData` holds any data returned from the `Route`'s `action`.\n - `Complete` - The submission has completed, and the `Route`'s `data` has since reloaded so the state reflects the refreshed state after completing this specific form submission. The `actionData` holds any data returned from the `Route`'s `action`.\n\n","args":["actionData"],"cases":[["Submitting",[]],["Reloading",["actionData"]],["Complete",["actionData"]]]}],"aliases":[{"name":"ConcurrentSubmission","comment":" ","args":["actionData"],"type":"{ status : Pages.ConcurrentSubmission.Status actionData, payload : Pages.FormData.FormData, initiatedAt : Time.Posix }"}],"values":[{"name":"map","comment":" `map` a `ConcurrentSubmission`. Not needed for most high-level cases since this state is managed by the `elm-pages` framework for you.\n","type":"(a -> b) -> Pages.ConcurrentSubmission.ConcurrentSubmission a -> Pages.ConcurrentSubmission.ConcurrentSubmission b"}],"binops":[]},{"name":"Pages.Fetcher","comment":"\n\n@docs Fetcher, FetcherInfo, submit, map\n\n","unions":[{"name":"Fetcher","comment":" ","args":["decoded"],"cases":[["Fetcher",["Pages.Fetcher.FetcherInfo decoded"]]]}],"aliases":[{"name":"FetcherInfo","comment":" ","args":["decoded"],"type":"{ decoder : Result.Result Http.Error Bytes.Bytes -> decoded, fields : List.List ( String.String, String.String ), headers : List.List ( String.String, String.String ), url : Maybe.Maybe String.String }"}],"values":[{"name":"map","comment":" ","type":"(a -> b) -> Pages.Fetcher.Fetcher a -> Pages.Fetcher.Fetcher b"},{"name":"submit","comment":" ","type":"Bytes.Decode.Decoder decoded -> { fields : List.List ( String.String, String.String ), headers : List.List ( String.String, String.String ) } -> Pages.Fetcher.Fetcher (Result.Result Http.Error decoded)"}],"binops":[]},{"name":"Pages.Flags","comment":"\n\n@docs Flags\n\n","unions":[{"name":"Flags","comment":" elm-pages apps run in two different contexts\n\n1. In the browser (like a regular Elm app)\n2. In pre-render mode. For example when you run `elm-pages build`, there is no browser involved, it just runs Elm directly.\n\nYou can pass in Flags and use them in your `Shared.init` function. You can store data in your `Shared.Model` from these flags and then access it across any page.\n\nYou will need to handle the `PreRender` case with no flags value because there is no browser to get flags from. For example, say you wanted to get the\ncurrent user's Browser window size and pass it in as a flag. When that page is pre-rendered, you need to decide on a value to use for the window size\nsince there is no window (the user hasn't requested the page yet, and the page isn't even loaded in a Browser window yet).\n\n","args":[],"cases":[["BrowserFlags",["Json.Decode.Value"]],["PreRenderFlags",[]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.Form","comment":" `elm-pages` has a built-in integration with [`dillonkearns/elm-form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/). See the `dillonkearns/elm-form`\ndocs and examples for more information on how to define your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form). This module is the interface for rendering your `Form` in your `elm-pages` app.\n\nBy rendering your `Form` with this module,\nyou get all of the boilerplate managed for you automatically by the `elm-pages` framework. That means you do not need to use `Form.init`, `Form.update`, `Form.Model` since these are all\nabstracted away. In addition to that, in-flight form state is automatically managed for you and exposed through the `app` argument in your Route modules.\n\nThis means that you can declaratively derive Pending UI or Optimistic UI state from `app.navigation` or `app.concurrentSubmissions` in your Route modules, and even build a\nrich dynamic page that shows pending submissions in the UI without using your Route module's `Model`! This is the power of this abstraction - it's less error-prone to\ndeclaratively derive state rather than imperatively managing your `Model`.\n\n\n## Rendering Forms\n\n@docs renderHtml, renderStyledHtml\n\n@docs Options\n\n\n## Form Submission Strategies\n\nWhen you render with [`Pages.Form.renderHtml`](#renderHtml) or [`Pages.Form.renderStyledHtml`](#renderStyledHtml),\n`elm-pages` progressively enhances form submissions to manage the requests through Elm (instead of as a vanilla HTML form submission, which performs a full page reload).\n\nBy default, `elm-pages` Forms will use the same mental model as the browser's default form submission behavior. That is, the form submission state will be tied to the page's navigation state.\nIf you click a link while a form is submitting, the form submission will be cancelled and the page will navigate to the new page. Conceptually, you can think of this as being tied to the navigation state.\nA form submission is part of the page's navigation state, and so is a page navigation. So if you have a page with an edit form, a delete form (no inputs but only a delete button), and a link to a new page,\nyou can interact with any of these and it will cancel the previous interactions.\n\nYou can access this state through `app.navigation` in your `Route` module, which is a value of type [`Pages.Navigation`](Pages-Navigation).\n\nThis default form submission strategy is a good fit for more linear actions. This is more traditional server submission behavior that you might be familiar with from Rails or other server frameworks without JavaScript enhancement.\n\n@docs withConcurrent\n\n\n## Server-Side Validation\n\n@docs FormWithServerValidations, Handler\n\n","unions":[],"aliases":[{"name":"FormWithServerValidations","comment":" ","args":["error","combined","input","view"],"type":"Form.Form error { combine : Form.Validation.Validation error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never)) Basics.Never Basics.Never, view : Form.Context error input -> view } (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never)) input"},{"name":"Handler","comment":" ","args":["error","combined"],"type":"Form.Handler.Handler error (BackendTask.BackendTask FatalError.FatalError (Form.Validation.Validation error combined Basics.Never Basics.Never))"},{"name":"Options","comment":" A replacement for [`Form.Options`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Options)\nwith some extra configuration for the `elm-pages` integration. You can use this type to annotate your form's options.\n","args":["error","parsed","input","msg"],"type":"Form.Options error parsed input msg { concurrent : Basics.Bool }"}],"values":[{"name":"renderHtml","comment":" A replacement for `Form.renderHtml` from `dillonkearns/elm-form` that integrates with `elm-pages`. Use this to render your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form)\nas `elm/html` `Html`.\n","type":"List.List (Html.Attribute (PagesMsg.PagesMsg userMsg)) -> Pages.Form.Options error parsed input userMsg -> { app | pageFormState : Form.Model, navigation : Maybe.Maybe Pages.Navigation.Navigation, concurrentSubmissions : Dict.Dict String.String (Pages.ConcurrentSubmission.ConcurrentSubmission (Maybe.Maybe action)) } -> Form.Form error { combine : Form.Validation.Validation error parsed named constraints, view : Form.Context error input -> List.List (Html.Html (PagesMsg.PagesMsg userMsg)) } parsed input -> Html.Html (PagesMsg.PagesMsg userMsg)"},{"name":"renderStyledHtml","comment":" A replacement for `Form.renderStyledHtml` from `dillonkearns/elm-form` that integrates with `elm-pages`. Use this to render your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form)\nas `rtfeldman/elm-css` `Html.Styled.Html`.\n","type":"List.List (Html.Styled.Attribute (PagesMsg.PagesMsg userMsg)) -> Pages.Form.Options error parsed input userMsg -> { app | pageFormState : Form.Model, navigation : Maybe.Maybe Pages.Navigation.Navigation, concurrentSubmissions : Dict.Dict String.String (Pages.ConcurrentSubmission.ConcurrentSubmission (Maybe.Maybe action)) } -> Form.Form error { combine : Form.Validation.Validation error parsed named constraints, view : Form.Context error input -> List.List (Html.Styled.Html (PagesMsg.PagesMsg userMsg)) } parsed input -> Html.Styled.Html (PagesMsg.PagesMsg userMsg)"},{"name":"withConcurrent","comment":" Instead of using the default submission strategy (tied to the page's navigation state), you can use `withConcurrent`.\n`withConcurrent` allows multiple form submissions to be in flight at the same time. It is useful for more dynamic applications. A good rule of thumb\nis if you could have multiple pending spinners on the page at the same time, you should use `withConcurrent`. For example, if you have a page with a list of items,\nsay a Twitter clone. If you click the like button on a Tweet, it won't result in a page navigation. You can click the like button on multiple Tweets at the same time\nand they will all submit independently.\n\nIn the case of Twitter, there isn't an indication of a loading spinner on the like button because it is expected that it will succeed. This is an example of a User Experience (UX) pattern\ncalled Optimistic UI. Since it is very likely that liking a Tweet will be successful, the UI will update the UI as if it has immediately succeeded even though the request is still in flight.\nIf the request fails, the UI will be updated to reflect the failure with an animation to show that something went wrong.\n\nThe `withConcurrent` is a good fit for either of these UX patterns (Optimistic UI or Pending UI, i.e. showing a loading spinner). You can derive either of these\nvisual states from the `app.concurrentSubmissions` field in your `Route` module.\n\nYou can call `withConcurrent` on your `Form.Options`. Note that while `withConcurrent` will allow multiple form submissions to be in flight at the same time independently,\nthe ID of the Form will still have a unique submission. For example, if you click submit on a form with the ID `\"edit-123\"` and then submit it again before the first submission has completed,\nthe second submission will cancel the first submission. So it is important to use unique IDs for forms that represent unique operations.\n\n import Form\n import Pages.Form\n\n todoItemView app todo =\n deleteItemForm\n |> Pages.Form.renderHtml []\n (Form.options (\"delete-\" ++ todo.id)\n |> Form.withInput todo\n |> Pages.Form.withConcurrent\n )\n app\n\n","type":"Pages.Form.Options error parsed input msg -> Pages.Form.Options error parsed input msg"}],"binops":[]},{"name":"Pages.FormData","comment":"\n\n@docs FormData\n\n","unions":[],"aliases":[{"name":"FormData","comment":" The payload for form submissions.\n","args":[],"type":"{ fields : List.List ( String.String, String.String ), method : Form.Method, action : String.String, id : Maybe.Maybe String.String }"}],"values":[],"binops":[]},{"name":"Pages.Internal.NotFoundReason","comment":" Exposed for internal use only (used in generated code).\n\n@docs ModuleContext, NotFoundReason, Payload, Record, document\n\n","unions":[{"name":"NotFoundReason","comment":" ","args":[],"cases":[["NoMatchingRoute",[]],["NotPrerendered",["Pages.Internal.NotFoundReason.ModuleContext","List.List Pages.Internal.NotFoundReason.Record"]],["NotPrerenderedOrHandledByFallback",["Pages.Internal.NotFoundReason.ModuleContext","List.List Pages.Internal.NotFoundReason.Record"]],["UnhandledServerRoute",["Pages.Internal.NotFoundReason.ModuleContext"]]]}],"aliases":[{"name":"ModuleContext","comment":" ","args":[],"type":"{ moduleName : List.List String.String, routePattern : Pages.Internal.RoutePattern.RoutePattern, matchedRouteParams : Pages.Internal.NotFoundReason.Record }"},{"name":"Payload","comment":" ","args":[],"type":"{ path : UrlPath.UrlPath, reason : Pages.Internal.NotFoundReason.NotFoundReason }"},{"name":"Record","comment":" ","args":[],"type":"List.List ( String.String, String.String )"}],"values":[{"name":"document","comment":" ","type":"List.List Pages.Internal.RoutePattern.RoutePattern -> Pages.Internal.NotFoundReason.Payload -> { title : String.String, body : List.List (Html.Html msg) }"}],"binops":[]},{"name":"Pages.Internal.Platform","comment":" Exposed for internal use only (used in generated code).\n\n@docs Flags, Model, Msg, Program, application, init, update\n\n@docs Effect, RequestInfo, view\n\n","unions":[{"name":"Effect","comment":" ","args":["userMsg","pageData","actionData","sharedData","userEffect","errorPage"],"cases":[["ScrollToTop",[]],["NoEffect",[]],["BrowserLoadUrl",["String.String"]],["BrowserPushUrl",["String.String"]],["BrowserReplaceUrl",["String.String"]],["FetchPageData",["Basics.Int","Maybe.Maybe Pages.Internal.Platform.FormData","Url.Url","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData ) -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage"]],["Submit",["Pages.Internal.Platform.FormData"]],["SubmitFetcher",["String.String","Basics.Int","Pages.Internal.Platform.FormData"]],["Batch",["List.List (Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage)"]],["UserCmd",["userEffect"]],["CancelRequest",["Basics.Int"]],["RunCmd",["Platform.Cmd.Cmd (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"]]]},{"name":"Msg","comment":" ","args":["userMsg","pageData","actionData","sharedData","errorPage"],"cases":[["LinkClicked",["Browser.UrlRequest"]],["UrlChanged",["Url.Url"]],["UserMsg",["PagesMsg.PagesMsg userMsg"]],["FormMsg",["Form.Msg (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"]],["UpdateCacheAndUrlNew",["Basics.Bool","Url.Url","Maybe.Maybe userMsg","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData )"]],["FetcherComplete",["Basics.Bool","String.String","Basics.Int","Result.Result Http.Error ( Maybe.Maybe userMsg, Pages.Internal.Platform.ActionDataOrRedirect actionData )"]],["FetcherStarted",["String.String","Basics.Int","Pages.Internal.Platform.FormData","Time.Posix"]],["PageScrollComplete",[]],["HotReloadCompleteNew",["Bytes.Bytes"]],["ProcessFetchResponse",["Basics.Int","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData )","Result.Result Http.Error ( Url.Url, Pages.Internal.ResponseSketch.ResponseSketch pageData actionData sharedData ) -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":["userModel","pageData","actionData","sharedData"],"type":"{ key : Maybe.Maybe Browser.Navigation.Key, url : Url.Url, currentPath : String.String, ariaNavigationAnnouncement : String.String, pageData : Result.Result String.String { userModel : userModel, pageData : pageData, sharedData : sharedData, actionData : Maybe.Maybe actionData }, notFound : Maybe.Maybe { reason : Pages.Internal.NotFoundReason.NotFoundReason, path : UrlPath.UrlPath }, userFlags : Json.Decode.Value, transition : Maybe.Maybe ( Basics.Int, Pages.Navigation.Navigation ), nextTransitionKey : Basics.Int, inFlightFetchers : Dict.Dict String.String ( Basics.Int, Pages.ConcurrentSubmission.ConcurrentSubmission actionData ), pageFormState : Form.Model, pendingRedirect : Basics.Bool, pendingData : Maybe.Maybe ( pageData, sharedData, Maybe.Maybe actionData ) }"},{"name":"Program","comment":" ","args":["userModel","userMsg","pageData","actionData","sharedData","errorPage"],"type":"Platform.Program Pages.Internal.Platform.Flags (Pages.Internal.Platform.Model userModel pageData actionData sharedData) (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"},{"name":"RequestInfo","comment":" ","args":[],"type":"{ contentType : String.String, body : String.String }"}],"values":[{"name":"application","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Platform.Program Pages.Internal.Platform.Flags (Pages.Internal.Platform.Model userModel pageData actionData sharedData) (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"},{"name":"init","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Flags -> Url.Url -> Maybe.Maybe Browser.Navigation.Key -> ( Pages.Internal.Platform.Model userModel pageData actionData sharedData, Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage )"},{"name":"update","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage -> Pages.Internal.Platform.Model userModel pageData actionData sharedData -> ( Pages.Internal.Platform.Model userModel pageData actionData sharedData, Pages.Internal.Platform.Effect userMsg pageData actionData sharedData userEffect errorPage )"},{"name":"view","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage) errorPage -> Pages.Internal.Platform.Model userModel pageData actionData sharedData -> Browser.Document (Pages.Internal.Platform.Msg userMsg pageData actionData sharedData errorPage)"}],"binops":[]},{"name":"Pages.Internal.Platform.Cli","comment":" Exposed for internal use only (used in generated code).\n\n@docs Flags, Model, Msg, Program, cliApplication, init, requestDecoder, update, currentCompatibilityKey\n\n","unions":[{"name":"Msg","comment":" ","args":[],"cases":[["GotDataBatch",["Json.Decode.Value"]],["GotBuildError",["BuildError.BuildError"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":["route"],"type":"{ staticResponses : BackendTask.BackendTask FatalError.FatalError Pages.Internal.Platform.Effect.Effect, errors : List.List BuildError.BuildError, maybeRequestJson : RenderRequest.RenderRequest route, isDevServer : Basics.Bool }"},{"name":"Program","comment":" ","args":["route"],"type":"Platform.Program Pages.Internal.Platform.Cli.Flags (Pages.Internal.Platform.Cli.Model route) Pages.Internal.Platform.Cli.Msg"}],"values":[{"name":"cliApplication","comment":" ","type":"Pages.ProgramConfig.ProgramConfig userMsg userModel (Maybe.Maybe route) pageData actionData sharedData effect mappedMsg errorPage -> Pages.Internal.Platform.Cli.Program (Maybe.Maybe route)"},{"name":"currentCompatibilityKey","comment":" ","type":"Basics.Int"},{"name":"init","comment":" ","type":"Pages.SiteConfig.SiteConfig -> RenderRequest.RenderRequest route -> Pages.ProgramConfig.ProgramConfig userMsg userModel route pageData actionData sharedData effect mappedMsg errorPage -> Json.Decode.Value -> ( Pages.Internal.Platform.Cli.Model route, Pages.Internal.Platform.Effect.Effect )"},{"name":"requestDecoder","comment":" ","type":"Json.Decode.Decoder Pages.StaticHttp.Request.Request"},{"name":"update","comment":" ","type":"Pages.Internal.Platform.Cli.Msg -> Pages.Internal.Platform.Cli.Model route -> ( Pages.Internal.Platform.Cli.Model route, Pages.Internal.Platform.Effect.Effect )"}],"binops":[]},{"name":"Pages.Internal.Platform.GeneratorApplication","comment":" Exposed for internal use only (used in generated code).\n\n@docs Program, Flags, Model, Msg, init, requestDecoder, update, app, JsonValue\n\n","unions":[{"name":"Msg","comment":" ","args":[],"cases":[["GotDataBatch",["Json.Decode.Value"]],["GotBuildError",["BuildError.BuildError"]]]}],"aliases":[{"name":"Flags","comment":" ","args":[],"type":"{ compatibilityKey : Basics.Int }"},{"name":"JsonValue","comment":" ","args":[],"type":"Json.Decode.Value"},{"name":"Model","comment":" ","args":[],"type":"{ staticResponses : BackendTask.BackendTask FatalError.FatalError (), errors : List.List BuildError.BuildError }"},{"name":"Program","comment":" ","args":[],"type":"Cli.Program.StatefulProgram Pages.Internal.Platform.GeneratorApplication.Model Pages.Internal.Platform.GeneratorApplication.Msg (BackendTask.BackendTask FatalError.FatalError ()) Pages.Internal.Platform.GeneratorApplication.Flags"}],"values":[{"name":"app","comment":" ","type":"Pages.GeneratorProgramConfig.GeneratorProgramConfig -> Pages.Internal.Platform.GeneratorApplication.Program"},{"name":"init","comment":" ","type":"BackendTask.BackendTask FatalError.FatalError () -> Cli.Program.FlagsIncludingArgv Pages.Internal.Platform.GeneratorApplication.Flags -> ( Pages.Internal.Platform.GeneratorApplication.Model, Pages.Internal.Platform.Effect.Effect )"},{"name":"requestDecoder","comment":" ","type":"Json.Decode.Decoder Pages.StaticHttp.Request.Request"},{"name":"update","comment":" ","type":"Pages.Internal.Platform.GeneratorApplication.Msg -> Pages.Internal.Platform.GeneratorApplication.Model -> ( Pages.Internal.Platform.GeneratorApplication.Model, Pages.Internal.Platform.Effect.Effect )"}],"binops":[]},{"name":"Pages.Internal.ResponseSketch","comment":"\n\n@docs ResponseSketch\n\n","unions":[{"name":"ResponseSketch","comment":" ","args":["data","action","shared"],"cases":[["RenderPage",["data","Maybe.Maybe action"]],["HotUpdate",["data","shared","Maybe.Maybe action"]],["Redirect",["String.String"]],["NotFound",["{ reason : Pages.Internal.NotFoundReason.NotFoundReason, path : UrlPath.UrlPath }"]],["Action",["action"]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.Internal.RoutePattern","comment":" Exposed for internal use only (used in generated code).\n\n@docs Ending, RoutePattern, Segment, view, toVariant, routeToBranch\n\n@docs Param, RouteParam, fromModuleName, hasRouteParams, repeatWithoutOptionalEnding, toModuleName, toRouteParamTypes, toRouteParamsRecord, toVariantName\n\n","unions":[{"name":"Ending","comment":" ","args":[],"cases":[["Optional",["String.String"]],["RequiredSplat",[]],["OptionalSplat",[]]]},{"name":"Param","comment":" ","args":[],"cases":[["RequiredParam",[]],["OptionalParam",[]],["RequiredSplatParam",[]],["OptionalSplatParam",[]]]},{"name":"RouteParam","comment":" ","args":[],"cases":[["StaticParam",["String.String"]],["DynamicParam",["String.String"]],["OptionalParam2",["String.String"]],["RequiredSplatParam2",[]],["OptionalSplatParam2",[]]]},{"name":"Segment","comment":" ","args":[],"cases":[["StaticSegment",["String.String"]],["DynamicSegment",["String.String"]]]}],"aliases":[{"name":"RoutePattern","comment":" ","args":[],"type":"{ segments : List.List Pages.Internal.RoutePattern.Segment, ending : Maybe.Maybe Pages.Internal.RoutePattern.Ending }"}],"values":[{"name":"fromModuleName","comment":" ","type":"List.List String.String -> Maybe.Maybe Pages.Internal.RoutePattern.RoutePattern"},{"name":"hasRouteParams","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Basics.Bool"},{"name":"repeatWithoutOptionalEnding","comment":" ","type":"List.List Pages.Internal.RoutePattern.RouteParam -> Maybe.Maybe (List.List Pages.Internal.RoutePattern.RouteParam)"},{"name":"routeToBranch","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( Elm.CodeGen.Pattern, Elm.CodeGen.Expression )"},{"name":"toModuleName","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List String.String"},{"name":"toRouteParamTypes","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( String.String, Pages.Internal.RoutePattern.Param )"},{"name":"toRouteParamsRecord","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> List.List ( String.String, Elm.Annotation.Annotation )"},{"name":"toVariant","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Elm.Variant"},{"name":"toVariantName","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> { variantName : String.String, params : List.List Pages.Internal.RoutePattern.RouteParam }"},{"name":"view","comment":" ","type":"Pages.Internal.RoutePattern.RoutePattern -> Html.Html msg"}],"binops":[]},{"name":"Pages.Internal.Router","comment":" Exposed for internal use only (used in generated code).\n\n@docs Matcher, firstMatch, fromOptionalSplat, maybeToList, nonEmptyToList, toNonEmpty\n\n","unions":[],"aliases":[{"name":"Matcher","comment":" ","args":["route"],"type":"{ pattern : String.String, toRoute : List.List (Maybe.Maybe String.String) -> Maybe.Maybe route }"}],"values":[{"name":"firstMatch","comment":" ","type":"List.List (Pages.Internal.Router.Matcher route) -> String.String -> Maybe.Maybe route"},{"name":"fromOptionalSplat","comment":" ","type":"Maybe.Maybe String.String -> List.List String.String"},{"name":"maybeToList","comment":" ","type":"Maybe.Maybe String.String -> List.List String.String"},{"name":"nonEmptyToList","comment":" ","type":"( String.String, List.List String.String ) -> List.List String.String"},{"name":"toNonEmpty","comment":" ","type":"String.String -> ( String.String, List.List String.String )"}],"binops":[]},{"name":"Pages.Manifest","comment":" Represents the configuration of a\n[web manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest).\n\nYou pass your `Pages.Manifest.Config` record into the `Pages.Manifest.generator`\nin your `app/Api.elm` module to define a file generator that will build a `manifest.json` file as part of your build.\n\n import Pages.Manifest as Manifest\n import Pages.Manifest.Category\n\n manifest : Manifest.Config\n manifest =\n Manifest.init\n { name = static.siteName\n , description = \"elm-pages - \" ++ tagline\n , startUrl = Route.Index {} |> Route.toPath\n , icons =\n [ icon webp 192\n , icon webp 512\n , icon MimeType.Png 192\n , icon MimeType.Png 512\n ]\n }\n |> Manifest.withShortName \"elm-pages\"\n\n@docs Config, Icon\n\n\n## Builder options\n\n@docs init\n\n@docs withBackgroundColor, withCategories, withDisplayMode, withIarcRatingId, withLang, withOrientation, withShortName, withThemeColor\n\n\n## Arbitrary Fields Escape Hatch\n\n@docs withField\n\n\n## Config options\n\n@docs DisplayMode, Orientation, IconPurpose\n\n\n## Generating a Manifest.json\n\n@docs generator\n\n\n## Functions for use by the generated code (`Pages.elm`)\n\n@docs toJson\n\n","unions":[{"name":"DisplayMode","comment":" See <https://developer.mozilla.org/en-US/docs/Web/Manifest/display>\n","args":[],"cases":[["Fullscreen",[]],["Standalone",[]],["MinimalUi",[]],["Browser",[]]]},{"name":"IconPurpose","comment":" <https://w3c.github.io/manifest/#dfn-icon-purposes>\n","args":[],"cases":[["IconPurposeMonochrome",[]],["IconPurposeMaskable",[]],["IconPurposeAny",[]]]},{"name":"Orientation","comment":" <https://developer.mozilla.org/en-US/docs/Web/Manifest/orientation>\n","args":[],"cases":[["Any",[]],["Natural",[]],["Landscape",[]],["LandscapePrimary",[]],["LandscapeSecondary",[]],["Portrait",[]],["PortraitPrimary",[]],["PortraitSecondary",[]]]}],"aliases":[{"name":"Config","comment":" Represents a [web app manifest file](https://developer.mozilla.org/en-US/docs/Web/Manifest)\n(see above for how to use it).\n","args":[],"type":"{ backgroundColor : Maybe.Maybe Color.Color, categories : List.List Pages.Manifest.Category.Category, displayMode : Pages.Manifest.DisplayMode, orientation : Pages.Manifest.Orientation, description : String.String, iarcRatingId : Maybe.Maybe String.String, name : String.String, themeColor : Maybe.Maybe Color.Color, startUrl : UrlPath.UrlPath, shortName : Maybe.Maybe String.String, icons : List.List Pages.Manifest.Icon, lang : LanguageTag.LanguageTag, otherFields : Dict.Dict String.String Json.Encode.Value }"},{"name":"Icon","comment":" <https://developer.mozilla.org/en-US/docs/Web/Manifest/icons>\n","args":[],"type":"{ src : Pages.Url.Url, sizes : List.List ( Basics.Int, Basics.Int ), mimeType : Maybe.Maybe MimeType.MimeImage, purposes : List.List Pages.Manifest.IconPurpose }"}],"values":[{"name":"generator","comment":" A generator for `Api.elm` to include a manifest.json. The String argument is the canonical URL of the site.\n\n module Api exposing (routes)\n\n import ApiRoute\n import Pages.Manifest\n\n routes :\n BackendTask FatalError (List Route)\n -> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)\n -> List (ApiRoute.ApiRoute ApiRoute.Response)\n routes getStaticRoutes htmlToString =\n [ Pages.Manifest.generator\n Site.canonicalUrl\n Manifest.config\n ]\n\n","type":"String.String -> BackendTask.BackendTask FatalError.FatalError Pages.Manifest.Config -> ApiRoute.ApiRoute ApiRoute.Response"},{"name":"init","comment":" Setup a minimal Manifest.Config. You can then use the `with...` builder functions to set additional options.\n","type":"{ description : String.String, name : String.String, startUrl : UrlPath.UrlPath, icons : List.List Pages.Manifest.Icon } -> Pages.Manifest.Config"},{"name":"toJson","comment":" Feel free to use this, but in 99% of cases you won't need it. The generated\ncode will run this for you to generate your `manifest.json` file automatically!\n","type":"String.String -> Pages.Manifest.Config -> Json.Encode.Value"},{"name":"withBackgroundColor","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/background_color>.\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withCategories","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/categories>.\n","type":"List.List Pages.Manifest.Category.Category -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withDisplayMode","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/display>.\n","type":"Pages.Manifest.DisplayMode -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withField","comment":" Escape hatch for specifying fields that aren't exposed through this module otherwise. The possible supported properties\nin a manifest file can change over time, so see [MDN manifest.json docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json)\nfor a full listing of the current supported properties.\n","type":"String.String -> Json.Encode.Value -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withIarcRatingId","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/iarc_rating_id>.\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withLang","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/lang>.\n","type":"LanguageTag.LanguageTag -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withOrientation","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/orientation>.\n","type":"Pages.Manifest.Orientation -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withShortName","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/short_name>.\n","type":"String.String -> Pages.Manifest.Config -> Pages.Manifest.Config"},{"name":"withThemeColor","comment":" Set <https://developer.mozilla.org/en-US/docs/Web/Manifest/theme_color>.\n","type":"Color.Color -> Pages.Manifest.Config -> Pages.Manifest.Config"}],"binops":[]},{"name":"Pages.Manifest.Category","comment":" See <https://github.com/w3c/manifest/wiki/Categories> and\n<https://developer.mozilla.org/en-US/docs/Web/Manifest/categories>\n\n@docs toString, Category\n\n@docs books, business, education, entertainment, finance, fitness, food, games, government, health, kids, lifestyle, magazines, medical, music, navigation, news, personalization, photo, politics, productivity, security, shopping, social, sports, travel, utilities, weather\n\n\n## Custom categories\n\n@docs custom\n\n","unions":[{"name":"Category","comment":" Represents a known, valid category, as specified by\n<https://github.com/w3c/manifest/wiki/Categories>. If this document is updated\nand I don't add it, please open an issue or pull request to let me know!\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"books","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"business","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"custom","comment":" It's best to use the pre-defined categories to ensure that clients (Android, iOS,\nChrome, Windows app store, etc.) are aware of it and can handle it appropriately.\nBut, if you're confident about using a custom one, you can do so with `Pages.Manifest.custom`.\n","type":"String.String -> Pages.Manifest.Category.Category"},{"name":"education","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"entertainment","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"finance","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"fitness","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"food","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"games","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"government","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"health","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"kids","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"lifestyle","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"magazines","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"medical","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"music","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"navigation","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"news","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"personalization","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"photo","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"politics","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"productivity","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"security","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"shopping","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"social","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"sports","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"toString","comment":" Turn a category into its official String representation, as seen\nhere: <https://github.com/w3c/manifest/wiki/Categories>.\n","type":"Pages.Manifest.Category.Category -> String.String"},{"name":"travel","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"utilities","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"},{"name":"weather","comment":" Creates the described category.\n","type":"Pages.Manifest.Category.Category"}],"binops":[]},{"name":"Pages.Navigation","comment":" `elm-pages` maintains a single `Maybe Navigation` state which is accessible from your `Route` modules through `app.navigation`.\n\nYou can use it to show a loading indicator while a page is loading:\n\n import Pages.Navigation as Navigation\n\n pageLoadingIndicator app =\n case app.navigation of\n Just (Navigation.Loading path _) ->\n Spinner.view\n\n Nothing ->\n emptyView\n\n emptyView : Html msg\n emptyView =\n Html.text \"\"\n\nYou can also use it to derive Pending UI or Optimistic UI from a pending form submission:\n\n import Form\n import Form.Handler\n import Pages.Navigation as Navigation\n\n view app =\n let\n optimisticProduct : Maybe Product\n optimisticProduct =\n case app.navigation of\n Just (Navigation.Submitting formData) ->\n formHandler\n |> Form.Handler.run formData\n |> Form.toResult\n |> Result.toMaybe\n\n Just (Navigation.LoadAfterSubmit formData path _) ->\n formHandler\n |> Form.Handler.run formData\n |> Form.toResult\n |> Result.toMaybe\n\n Nothing ->\n Nothing\n in\n -- our `productsView` function could show a loading spinner (Pending UI),\n -- or it could assume the product will be created successfully (Optimistic UI) and\n -- display it as a regular product in the list\n productsView optimisticProduct app.data.products\n\n allForms : Form.Handler.Handler String Product\n allForms =\n Form.Handler.init identity productForm\n\n editItemForm : Form.HtmlForm String Product input msg\n editItemForm =\n Debug.todo \"Form definition here\"\n\n@docs Navigation, LoadingState\n\n","unions":[{"name":"LoadingState","comment":" ","args":[],"cases":[["Redirecting",[]],["Load",[]],["ActionRedirect",[]]]},{"name":"Navigation","comment":" Represents the global page navigation state of the app.\n\n - `Loading` - navigating to a page, for example from a link click, or from a programmatic navigation with `Browser.Navigation.pushUrl`.\n - `Submitting` - submitting a form using the default submission strategy (note that Forms rendered with the [`Pages.Form.withConcurrent`](Pages-Form#withConcurrent) Option have their state managed in `app.concurrentSubmissions` instead of `app.navigation`).\n - `LoadAfterSubmit` - the state immediately after `Submitting` - allows you to continue using the `FormData` from a submission while a data reload or redirect is occurring.\n\n","args":[],"cases":[["Submitting",["Pages.FormData.FormData"]],["LoadAfterSubmit",["Pages.FormData.FormData","UrlPath.UrlPath","Pages.Navigation.LoadingState"]],["Loading",["UrlPath.UrlPath","Pages.Navigation.LoadingState"]]]}],"aliases":[],"values":[],"binops":[]},{"name":"Pages.PageUrl","comment":" Same as a Url in `elm/url`, but slightly more structured. The path portion of the URL is parsed into a `List String` representing each segment, and\nthe query params are parsed into a `Dict String (List String)`.\n\n@docs PageUrl, toUrl\n\n@docs parseQueryParams\n\n","unions":[],"aliases":[{"name":"PageUrl","comment":" ","args":[],"type":"{ protocol : Url.Protocol, host : String.String, port_ : Maybe.Maybe Basics.Int, path : UrlPath.UrlPath, query : Dict.Dict String.String (List.List String.String), fragment : Maybe.Maybe String.String }"}],"values":[{"name":"parseQueryParams","comment":" ","type":"String.String -> Dict.Dict String.String (List.List String.String)"},{"name":"toUrl","comment":" ","type":"Pages.PageUrl.PageUrl -> Url.Url"}],"binops":[]},{"name":"Pages.Script","comment":" An elm-pages Script is a way to execute an `elm-pages` `BackendTask`.\n\nRead more about using the `elm-pages` CLI to run (or bundle) scripts, plus a brief tutorial, at <https://elm-pages.com/docs/elm-pages-scripts>.\n\n@docs Script\n\n\n## Defining Scripts\n\n@docs withCliOptions, withoutCliOptions\n\n\n## File System Utilities\n\n@docs writeFile\n\n\n## Shell Commands\n\n@docs command, exec\n\n\n## Utilities\n\n@docs log, sleep, doThen, which, expectWhich, question\n\n\n## Errors\n\n@docs Error\n\n","unions":[{"name":"Error","comment":" The recoverable error type for file writes. You can use `BackendTask.allowFatal` if you want to allow the program to crash\nwith an error message if a file write is unsuccessful.\n","args":[],"cases":[["FileWriteError",[]]]}],"aliases":[{"name":"Script","comment":" The type for your `run` function that can be executed by `elm-pages run`.\n","args":[],"type":"Pages.Internal.Script.Script"}],"values":[{"name":"command","comment":" Run a single command and return stderr and stdout combined as a single String.\n\nIf you want to do more advanced things like piping together multiple commands in a pipeline, or piping in a file to a command, etc., see the [`Stream`](BackendTask-Stream) module.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Script.command \"ls\" []\n |> BackendTask.andThen\n (\\files ->\n Script.log (\"Files: \" ++ files)\n )\n )\n\n","type":"String.String -> List.List String.String -> BackendTask.BackendTask FatalError.FatalError String.String"},{"name":"doThen","comment":" Run a command with no output, then run another command.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.log \"Hello!\"\n |> Script.doThen\n (Script.log \"World!\")\n )\n\n","type":"BackendTask.BackendTask error value -> BackendTask.BackendTask error () -> BackendTask.BackendTask error value"},{"name":"exec","comment":" Like [`command`](#command), but prints stderr and stdout to the console as the command runs instead of capturing them.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script exposing (Script)\n\n run : Script\n run =\n Script.withoutCliOptions\n (Script.exec \"ls\" [])\n\n","type":"String.String -> List.List String.String -> BackendTask.BackendTask FatalError.FatalError ()"},{"name":"expectWhich","comment":" Check if a command is available on the system. If it is, return the full path to the command, otherwise fail with a [`FatalError`](FatalError).\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run : Script\n run =\n Script.withoutCliOptions\n (Script.expectWhich \"elm-review\"\n |> BackendTask.andThen\n (\\path ->\n Script.log (\"The path to `elm-review` is: \" ++ path)\n )\n )\n\nIf you run it with a command that is not available, you will see an error like this:\n\n Script.expectWhich \"hype-script\"\n\n```shell\n-- COMMAND NOT FOUND ---------------\nI expected to find `hype-script`, but it was not on your PATH. Make sure it is installed and included in your PATH.\n```\n\n","type":"String.String -> BackendTask.BackendTask FatalError.FatalError String.String"},{"name":"log","comment":" Log to stdout.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.log \"Hello!\"\n |> BackendTask.allowFatal\n )\n\n","type":"String.String -> BackendTask.BackendTask error ()"},{"name":"question","comment":"\n\n module QuestionDemo exposing (run)\n\n import BackendTask\n\n run : Script\n run =\n Script.withoutCliOptions\n (Script.question \"What is your name? \"\n |> BackendTask.andThen\n (\\name ->\n Script.log (\"Hello, \" ++ name ++ \"!\")\n )\n )\n\n","type":"String.String -> BackendTask.BackendTask error String.String"},{"name":"sleep","comment":" Sleep for a number of milliseconds.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.log \"Hello...\"\n |> Script.doThen\n (Script.sleep 1000)\n |> Script.doThen\n (Script.log \"World!\")\n )\n\n","type":"Basics.Int -> BackendTask.BackendTask error ()"},{"name":"which","comment":" Same as [`expectWhich`](#expectWhich), but returns `Nothing` if the command is not found instead of failing with a [`FatalError`](FatalError).\n","type":"String.String -> BackendTask.BackendTask error (Maybe.Maybe String.String)"},{"name":"withCliOptions","comment":" Same as [`withoutCliOptions`](#withoutCliOptions), but allows you to define a CLI Options Parser so the user can\npass in additional options for the script.\n\nUses <https://package.elm-lang.org/packages/dillonkearns/elm-cli-options-parser/latest/>.\n\nRead more at <https://elm-pages.com/docs/elm-pages-scripts/#adding-command-line-options>.\n\n","type":"Cli.Program.Config cliOptions -> (cliOptions -> BackendTask.BackendTask FatalError.FatalError ()) -> Pages.Script.Script"},{"name":"withoutCliOptions","comment":" Define a simple Script (no CLI Options).\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.log \"Hello!\"\n |> BackendTask.allowFatal\n )\n\n","type":"BackendTask.BackendTask FatalError.FatalError () -> Pages.Script.Script"},{"name":"writeFile","comment":" Write a file to the file system.\n\nFile paths are relative to the root of your `elm-pages` project (next to the `elm.json` file and `src/` directory), or you can pass in absolute paths beginning with a `/`.\n\n module MyScript exposing (run)\n\n import BackendTask\n import Pages.Script as Script\n\n run =\n Script.withoutCliOptions\n (Script.writeFile\n { path = \"hello.json\"\n , body = \"\"\"{ \"message\": \"Hello, World!\" }\"\"\"\n }\n |> BackendTask.allowFatal\n )\n\n","type":"{ path : String.String, body : String.String } -> BackendTask.BackendTask { fatal : FatalError.FatalError, recoverable : Pages.Script.Error } ()"}],"binops":[]},{"name":"Pages.Script.Spinner","comment":"\n\n\n## Running Steps\n\nThe easiest way to use spinners is to define a series of [`Steps`](#Steps) and then run them with [`runSteps`](#runSteps).\n\nSteps are a sequential series of `BackendTask`s that are run one after the other. If a step fails (has a [`FatalError`](FatalError)),\nits spinner will show a failure, and the remaining steps will not be run and will be displayed as cancelled (the step name in gray).\n\n module StepsDemo exposing (run)\n\n import BackendTask exposing (BackendTask)\n import Pages.Script as Script exposing (Script)\n import Pages.Script.Spinner as Spinner\n\n run : Script\n run =\n Script.withoutCliOptions\n (Spinner.steps\n |> Spinner.withStep \"Compile Main.elm\" (\\() -> Script.exec \"elm\" [ \"make\", \"src/Main.elm\", \"--output=/dev/null\" ])\n |> Spinner.withStep \"Verify formatting\" (\\() -> Script.exec \"elm-format\" [ \"--validate\", \"src/\" ])\n |> Spinner.withStep \"elm-review\" (\\() -> Script.exec \"elm-review\" [])\n |> Spinner.runSteps\n )\n\n@docs Steps, steps, withStep\n\n@docs withStepWithOptions\n\n@docs runSteps\n\n\n## Configuring Steps\n\n@docs Options, options\n\n@docs CompletionIcon\n\n@docs withOnCompletion\n\n\n## Running with BackendTask\n\n@docs runTask, runTaskWithOptions\n\n\n## Low-Level\n\n@docs showStep, runSpinnerWithTask, Spinner\n\n","unions":[{"name":"CompletionIcon","comment":" An icon used to indicate the completion status of a step. Set by using [`withOnCompletion`](#withOnCompletion).\n","args":[],"cases":[["Succeed",[]],["Fail",[]],["Warn",[]],["Info",[]]]},{"name":"Options","comment":" Configuration that can be used with [`runTaskWithOptions`](#runTaskWithOptions) and [`withStepWithOptions`](#withStepWithOptions).\n","args":["error","value"],"cases":[]},{"name":"Spinner","comment":" ","args":["error","value"],"cases":[]},{"name":"Steps","comment":" The definition of a series of `BackendTask`s to run, with a spinner for each step.\n","args":["error","value"],"cases":[["Steps",["BackendTask.BackendTask error value"]]]}],"aliases":[],"values":[{"name":"options","comment":" The default options for a spinner. The spinner `text` is a required argument and will be displayed as the step name.\n\n import Pages.Script.Spinner as Spinner\n\n example =\n Spinner.options \"Compile Main.elm\"\n\n","type":"String.String -> Pages.Script.Spinner.Options error value"},{"name":"runSpinnerWithTask","comment":" After calling `showStep` to get a reference to a `Spinner`, use `runSpinnerWithTask` to run a `BackendTask` and show a failure or success\ncompletion status once it is done.\n","type":"Pages.Script.Spinner.Spinner error value -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"runSteps","comment":" Perform the `Steps` in sequence.\n","type":"Pages.Script.Spinner.Steps FatalError.FatalError value -> BackendTask.BackendTask FatalError.FatalError value"},{"name":"runTask","comment":" Run a `BackendTask` with a spinner. The spinner will show a success icon if the task succeeds, and a failure icon if the task fails.\n\nIt's often easier to use [`steps`](#steps) when possible.\n\n module SequentialSteps exposing (run)\n\n import Pages.Script as Script exposing (Script, doThen, sleep)\n import Pages.Script.Spinner as Spinner\n\n\n run : Script\n run =\n Script.withoutCliOptions\n (sleep 3000\n |> Spinner.runTask \"Step 1...\"\n |> doThen\n (sleep 3000\n |> Spinner.runTask \"Step 2...\"\n |> doThen\n (sleep 3000\n |> Spinner.runTask \"Step 3...\"\n )\n )\n )\n\n","type":"String.String -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"runTaskWithOptions","comment":" ","type":"Pages.Script.Spinner.Options error value -> BackendTask.BackendTask error value -> BackendTask.BackendTask error value"},{"name":"showStep","comment":" `showStep` gives you a `Spinner` reference which you can use to start the spinner later with `runSpinnerWithTask`.\n\nMost use cases can be achieved more easily using more high-level helpers, like [`runTask`](#runTask) or [`steps`](#steps).\n`showStep` can be useful if you have more dynamic steps that you want to reveal over time.\n\n module ShowStepDemo exposing (run)\n\n import BackendTask exposing (BackendTask)\n import Pages.Script as Script exposing (Script, doThen, sleep)\n import Pages.Script.Spinner as Spinner\n\n run : Script\n run =\n Script.withoutCliOptions\n (BackendTask.succeed\n (\\spinner1 spinner2 spinner3 ->\n sleep 3000\n |> Spinner.runSpinnerWithTask spinner1\n |> doThen\n (sleep 3000\n |> Spinner.runSpinnerWithTask spinner2\n |> doThen\n (sleep 3000\n |> Spinner.runSpinnerWithTask spinner3\n )\n )\n )\n |> BackendTask.andMap\n (Spinner.options \"Step 1\" |> Spinner.showStep)\n |> BackendTask.andMap\n (Spinner.options \"Step 2\" |> Spinner.showStep)\n |> BackendTask.andMap\n (Spinner.options \"Step 3\" |> Spinner.showStep)\n |> BackendTask.andThen identity\n )\n\n","type":"Pages.Script.Spinner.Options error value -> BackendTask.BackendTask error (Pages.Script.Spinner.Spinner error value)"},{"name":"steps","comment":" Initialize an empty series of `Steps`.\n","type":"Pages.Script.Spinner.Steps FatalError.FatalError ()"},{"name":"withOnCompletion","comment":" Set the completion icon and text based on the result of the task.\n\n import Pages.Script.Spinner as Spinner\n\n example =\n Spinner.options \"Fetching data\"\n |> Spinner.withOnCompletion\n (\\result ->\n case result of\n Ok _ ->\n ( Spinner.Succeed, \"Fetched data!\" )\n\n Err _ ->\n ( Spinner.Fail\n , Just \"Could not fetch data.\"\n )\n )\n\n","type":"(Result.Result error value -> ( Pages.Script.Spinner.CompletionIcon, Maybe.Maybe String.String )) -> Pages.Script.Spinner.Options error value -> Pages.Script.Spinner.Options error value"},{"name":"withStep","comment":" Add a `Step`. See [`withStepWithOptions`](#withStepWithOptions) to configure the step's spinner.\n","type":"String.String -> (oldValue -> BackendTask.BackendTask FatalError.FatalError newValue) -> Pages.Script.Spinner.Steps FatalError.FatalError oldValue -> Pages.Script.Spinner.Steps FatalError.FatalError newValue"},{"name":"withStepWithOptions","comment":" Add a step with custom [`Options`](#Options).\n","type":"Pages.Script.Spinner.Options FatalError.FatalError newValue -> (oldValue -> BackendTask.BackendTask FatalError.FatalError newValue) -> Pages.Script.Spinner.Steps FatalError.FatalError oldValue -> Pages.Script.Spinner.Steps FatalError.FatalError newValue"}],"binops":[]},{"name":"Pages.Url","comment":" Some of the `elm-pages` APIs will take internal URLs and ensure that they have the `canonicalSiteUrl` prepended.\n\nThat's the purpose for this type. If you have an external URL, like `Pages.Url.external \"https://google.com\"`,\nthen the canonicalUrl will not be prepended when it is used in a head tag.\n\nIf you refer to a local page, like `Route.Index |> Route.toPath |> Pages.Url.fromPath`, or `Pages.Url.fromPath`\n\n@docs Url, external, fromPath, toAbsoluteUrl, toString\n\n","unions":[{"name":"Url","comment":" ","args":[],"cases":[]}],"aliases":[],"values":[{"name":"external","comment":" ","type":"String.String -> Pages.Url.Url"},{"name":"fromPath","comment":" ","type":"UrlPath.UrlPath -> Pages.Url.Url"},{"name":"toAbsoluteUrl","comment":" ","type":"String.String -> Pages.Url.Url -> String.String"},{"name":"toString","comment":" ","type":"Pages.Url.Url -> String.String"}],"binops":[]},{"name":"PagesMsg","comment":" In `elm-pages`, Route modules have their own `Msg` type which can be used like a normal TEA (The Elm Architecture) app.\nBut the `Msg` defined in a `Route` module is wrapped in the `PagesMsg` type.\n\n@docs PagesMsg\n\nYou can wrap your Route Module's `Msg` using `fromMsg`.\n\n@docs fromMsg\n\n@docs map, noOp\n\n","unions":[],"aliases":[{"name":"PagesMsg","comment":" ","args":["userMsg"],"type":"Pages.Internal.Msg.Msg userMsg"}],"values":[{"name":"fromMsg","comment":"\n\n import Form\n import Pages.Form\n import PagesMsg exposing (PagesMsg)\n\n type Msg\n = ToggleMenu\n\n view :\n Maybe PageUrl\n -> Shared.Model\n -> Model\n -> App Data ActionData RouteParams\n -> View (PagesMsg Msg)\n view maybeUrl sharedModel model app =\n { title = \"My Page\"\n , view =\n [ button\n -- we need to wrap our Route module's `Msg` here so we have a `PagesMsg Msg`\n [ onClick (PagesMsg.fromMsg ToggleMenu) ]\n []\n\n -- `Pages.Form.renderHtml` gives us `Html (PagesMsg msg)`, so we don't need to wrap its Msg type\n , logoutForm\n |> Pages.Form.renderHtml []\n Pages.Form.Serial\n (Form.options \"logout\"\n |> Form.withOnSubmit (\\_ -> NewItemSubmitted)\n )\n app\n ]\n }\n\n","type":"userMsg -> PagesMsg.PagesMsg userMsg"},{"name":"map","comment":" ","type":"(a -> b) -> PagesMsg.PagesMsg a -> PagesMsg.PagesMsg b"},{"name":"noOp","comment":" A Msg that is handled by the elm-pages framework and does nothing. Helpful for when you don't want to register a callback.\n\n import Browser.Dom as Dom\n import PagesMsg exposing (PagesMsg)\n import Task\n\n resetViewport : Cmd (PagesMsg msg)\n resetViewport =\n Dom.setViewport 0 0\n |> Task.perform (\\() -> PagesMsg.noOp)\n\n","type":"PagesMsg.PagesMsg userMsg"}],"binops":[]},{"name":"Scaffold.Form","comment":" This module helps you with scaffolding a form in `elm-pages`, similar to how rails generators are used to scaffold out forms to\nget up and running quickly with the starting point for a form with different field types. See also [`Scaffold.Route`](Scaffold-Route).\n\nSee the `AddRoute` script in the starter template for an example. It's usually easiest to modify that script as a starting\npoint rather than using this API from scratch.\n\nUsing the `AddRoute` script from the default starter template, you can run a command like this:\n\n`npx elm-pages run AddRoute Profile.Username_.Edit first last bio:textarea dob:date` to generate a Route module `app/Route/Profile/Username_/Edit.elm`\nwith the wiring form a `Form`.\n\n[Learn more about writing and running elm-pages Scripts for scaffolding](https://elm-pages.com/docs/elm-pages-scripts#scaffolding-a-route-module).\n\n@docs Kind, provide, restArgsParser\n\n@docs Context\n\n@docs recordEncoder, fieldEncoder\n\n","unions":[{"name":"Kind","comment":" ","args":[],"cases":[["FieldInt",[]],["FieldText",[]],["FieldTextarea",[]],["FieldFloat",[]],["FieldTime",[]],["FieldDate",[]],["FieldCheckbox",[]]]}],"aliases":[{"name":"Context","comment":" ","args":[],"type":"{ errors : Elm.Expression, submitting : Elm.Expression, submitAttempted : Elm.Expression, data : Elm.Expression, expression : Elm.Expression }"}],"values":[{"name":"fieldEncoder","comment":" A lower-level, more granular version of `recordEncoder` - lets you generate a JSON Encoder `Expression` for an individual Field rather than a group of Fields.\n","type":"Elm.Expression -> String.String -> Scaffold.Form.Kind -> Elm.Expression"},{"name":"provide","comment":" ","type":"{ fields : List.List ( String.String, Scaffold.Form.Kind ), elmCssView : Basics.Bool, view : { formState : Scaffold.Form.Context, params : List.List { name : String.String, kind : Scaffold.Form.Kind, param : Elm.Expression } } -> Elm.Expression } -> Maybe.Maybe { formHandlers : Elm.Expression, form : Elm.Expression, declarations : List.List Elm.Declaration }"},{"name":"recordEncoder","comment":" Generate a JSON Encoder for the form fields. This can be helpful for sending the validated form data through a\nBackendTask.Custom or to an external API from your scaffolded Route Module code.\n","type":"Elm.Expression -> List.List ( String.String, Scaffold.Form.Kind ) -> Elm.Expression"},{"name":"restArgsParser","comment":" This parser handles the following field types (or `text` if none is provided):\n\n - `text`\n - `textarea`\n - `checkbox`\n - `time`\n - `date`\n\nThe naming convention follows the same naming as the HTML form field elements or attributes that are used to represent them.\nIn addition to using the appropriate field type, this will also give you an Elm type with the corresponding base type (like `Date` for `date` or `Bool` for `checkbox`).\n\n","type":"Cli.Option.Option (List.List String.String) (List.List ( String.String, Scaffold.Form.Kind )) Cli.Option.RestArgsOption"}],"binops":[]},{"name":"Scaffold.Route","comment":" This module provides some functions for scaffolding code for a new Route Module. It uses [`elm-codegen`'s API](https://package.elm-lang.org/packages/mdgriffith/elm-codegen/latest/) for generating code.\n\nTypically you'll want to use this via the `elm-pages run` CLI command. The default starter template includes a Script that uses these functions, which you can tweak to customize your scaffolding commands.\n[Learn more about writing and running elm-pages Scripts for scaffolding](https://elm-pages.com/docs/elm-pages-scripts#scaffolding-a-route-module).\n\nIt's typically easiest to modify the `AddRoute` script from the starter template and adjust it to your needs rather than writing one from scratch.\n\n\n## Initializing the Generator Builder\n\nThese functions mirror the `RouteBuilder` API that you use in your Route modules to define your route. The difference is that\ninstead of defining a route, this is defining a code generator for a Route module.\n\n@docs buildWithLocalState, buildWithSharedState, buildNoState, Builder\n\n@docs Type\n\n\n## Generating Server-Rendered Pages\n\n@docs serverRender\n\n\n## Generating pre-rendered pages\n\n@docs preRender, single\n\n\n## Including Additional elm-codegen Declarations\n\n@docs addDeclarations\n\n\n## CLI Options Parsing Helpers\n\n@docs moduleNameCliArg\n\n","unions":[{"name":"Builder","comment":" ","args":[],"cases":[]},{"name":"Type","comment":" ","args":[],"cases":[["Alias",["Elm.Annotation.Annotation"]],["Custom",["List.List Elm.Variant"]]]}],"aliases":[],"values":[{"name":"addDeclarations","comment":" The helpers in this module help you generate a Route module file with the core boilerplate abstracted away.\n\nYou can also define additional top-level declarations in the generated Route module using this helper.\n\n","type":"List.List Elm.Declaration -> Scaffold.Route.Builder -> Scaffold.Route.Builder"},{"name":"buildNoState","comment":" ","type":"{ view : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"buildWithLocalState","comment":" ","type":"{ view : { shared : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { shared : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { routeParams : Elm.Expression, path : Elm.Expression, shared : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Scaffold.Route.Type, model : Scaffold.Route.Type } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"buildWithSharedState","comment":" ","type":"{ view : { shared : Elm.Expression, model : Elm.Expression, app : Elm.Expression } -> Elm.Expression, update : { shared : Elm.Expression, app : Elm.Expression, msg : Elm.Expression, model : Elm.Expression } -> Elm.Expression, init : { shared : Elm.Expression, app : Elm.Expression } -> Elm.Expression, subscriptions : { routeParams : Elm.Expression, path : Elm.Expression, shared : Elm.Expression, model : Elm.Expression } -> Elm.Expression, msg : Scaffold.Route.Type, model : Scaffold.Route.Type } -> Scaffold.Route.Builder -> { path : String.String, body : String.String }"},{"name":"moduleNameCliArg","comment":" A positional argument for elm-cli-options-parser that does a Regex validation to check that the module name is a valid Elm Route module name.\n","type":"Cli.Option.Option from String.String builderState -> Cli.Option.Option from (List.List String.String) builderState"},{"name":"preRender","comment":" Will scaffold using `RouteBuilder.preRender` if there are any dynamic segments (as in `Company.Team.Name_`),\nor using `RouteBuilder.single` if there are no dynamic segments (as in `Company.AboutUs`).\n\nWhen there are no dynamic segments, the `pages` field will be ignored as it is only relevant for Routes with dynamic segments.\n\nFor dynamic segments, the `routeParams` parameter in the `data` function will be an `Elm.Expression` with the `RouteParams` parameter in the `data` function.\nFor static segments, it will be a hardcoded empty record (`{}`).\n\n","type":"{ data : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression ), pages : Elm.Expression, head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"},{"name":"serverRender","comment":" ","type":"{ data : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression -> Elm.Expression ), action : ( Scaffold.Route.Type, Elm.Expression -> Elm.Expression -> Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"},{"name":"single","comment":" @depreacted. This is obsolete and will be removed in a future release. Use [`preRender`](#preRender) instead.\n\nIf you pass in only static route segments as the `moduleName` to `preRender` it will yield the same result as `single`.\n\n","type":"{ data : ( Scaffold.Route.Type, Elm.Expression ), head : Elm.Expression -> Elm.Expression, moduleName : List.List String.String } -> Scaffold.Route.Builder"}],"binops":[]},{"name":"Server.Request","comment":" Server-rendered Route modules and [server-rendered API Routes](ApiRoute#serverRender) give you access to a `Server.Request.Request` argument.\n\n@docs Request\n\nFor example, in a server-rendered route,\nyou could check a session cookie to decide whether to respond by rendering a page\nfor the logged-in user, or else respond with an HTTP redirect response (see the [`Server.Response` docs](Server-Response)).\n\nYou can access the incoming HTTP request's:\n\n - [Headers](#headers)\n - [Cookies](#cookies)\n - [`method`](#method)\n - [`rawUrl`](#rawUrl)\n - [`requestTime`](#requestTime) (as a `Time.Posix`)\n\nThere are also some high-level helpers that take the low-level Request data and let you parse it into Elm types:\n\n - [`jsonBody`](#jsonBody)\n - [Form Helpers](#forms)\n - [URL query parameters](#queryParam)\n - [Content Type](#content-type)\n\nNote that this data is not available for pre-rendered pages or pre-rendered API Routes, only for server-rendered pages.\nThis is because when a page is pre-rendered, there _is_ no incoming HTTP request to respond to, it is rendered before a user\nrequests the page and then the pre-rendered page is served as a plain file (without running your Route Module).\n\nThat's why `RouteBuilder.preRender` does not have a `Server.Request.Request` argument.\n\n import BackendTask exposing (BackendTask)\n import RouteBuilder exposing (StatelessRoute)\n\n type alias Data =\n {}\n\n data : RouteParams -> BackendTask Data\n data routeParams =\n BackendTask.succeed Data\n\n route : StatelessRoute RouteParams Data ActionData\n route =\n RouteBuilder.preRender\n { data = data\n , head = head\n , pages = pages\n }\n |> RouteBuilder.buildNoState { view = view }\n\nA server-rendered Route Module _does_ have access to a user's incoming HTTP request because it runs every time the page\nis loaded. That's why `data` has a `Server.Request.Request` argument in server-rendered Route Modules. Since you have an incoming HTTP request for server-rendered routes,\n`RouteBuilder.serverRender` has `data : RouteParams -> Request -> BackendTask (Response Data)`. That means that you\ncan use the incoming HTTP request data to choose how to respond. For example, you could check for a dark-mode preference\ncookie and render a light- or dark-themed page and render a different page.\n\n@docs requestTime\n\n\n## Request Headers\n\n@docs header, headers\n\n\n## Request Method\n\n@docs method, Method, methodToString\n\n\n## Request Body\n\n@docs body, jsonBody\n\n\n## Forms\n\n@docs formData, formDataWithServerValidation\n\n@docs rawFormData\n\n\n## URL\n\n@docs rawUrl\n\n@docs queryParam, queryParams\n\n\n## Content Type\n\n@docs matchesContentType\n\n\n## Using Cookies\n\n@docs cookie, cookies\n\n","unions":[{"name":"Method","comment":" An [Incoming HTTP Request Method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).\n","args":[],"cases":[["Connect",[]],["Delete",[]],["Get",[]],["Head",[]],["Options",[]],["Patch",[]],["Post",[]],["Put",[]],["Trace",[]],["NonStandard",["String.String"]]]}],"aliases":[{"name":"Request","comment":" A value that lets you access data from the incoming HTTP request.\n","args":[],"type":"Internal.Request.Request"}],"values":[{"name":"body","comment":" The Request body, if present (or `Nothing` if there is no request body).\n","type":"Server.Request.Request -> Maybe.Maybe String.String"},{"name":"cookie","comment":" Get a cookie from the request. For a more high-level API, see [`Server.Session`](Server-Session).\n","type":"String.String -> Server.Request.Request -> Maybe.Maybe String.String"},{"name":"cookies","comment":" Get all of the cookies from the incoming HTTP request. For a more high-level API, see [`Server.Session`](Server-Session).\n","type":"Server.Request.Request -> Dict.Dict String.String String.String"},{"name":"formData","comment":" Takes a [`Form.Handler.Handler`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form-Handler) and\nparses the raw form data into a [`Form.Validated`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Validated) value.\n\nThis is the standard pattern for dealing with form data in `elm-pages`. You can share your code for your [`Form`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#Form)\ndefinitions between your client and server code, using this function to parse the raw form data into a `Form.Validated` value for the backend,\nand [`Pages.Form`](Pages-Form) to render the `Form` on the client.\n\nSince we are sharing the `Form` definition between frontend and backend, we get to re-use the same validation logic so we gain confidence that\nthe validation errors that the user sees on the client are protected on our backend, and vice versa.\n\n import BackendTask exposing (BackendTask)\n import FatalError exposing (FatalError)\n import Form\n import Server.Request as Request exposing (Request)\n import Server.Response as Response exposing (Response)\n\n type Action\n = Delete\n | CreateOrUpdate Post\n\n formHandlers : Form.Handler.Handler String Action\n formHandlers =\n deleteForm\n |> Form.Handler.init (\\() -> Delete)\n |> Form.Handler.with CreateOrUpdate createOrUpdateForm\n\n deleteForm : Form.HtmlForm String () input msg\n\n createOrUpdateForm : Form.HtmlForm String Post Post msg\n\n action :\n RouteParams\n -> Request\n -> BackendTask FatalError (Response ActionData ErrorPage)\n action routeParams request =\n case request |> Server.Request.formData formHandlers of\n Nothing ->\n BackendTask.fail (FatalError.fromString \"Missing form data\")\n\n Just ( formResponse, parsedForm ) ->\n case parsedForm of\n Form.Valid Delete ->\n deletePostBySlug routeParams.slug\n |> BackendTask.map\n (\\() -> Route.redirectTo Route.Index)\n\n Form.Valid (CreateOrUpdate post) ->\n let\n createPost : Bool\n createPost =\n okForm.slug == \"new\"\n in\n createOrUpdatePost post\n |> BackendTask.map\n (\\() ->\n Route.redirectTo\n (Route.Admin__Slug_ { slug = okForm.slug })\n )\n\n Form.Invalid _ invalidForm ->\n BackendTask.succeed\n (Server.Response.render\n { errors = formResponse }\n )\n\nYou can handle form submissions as either GET or POST requests. Note that for security reasons, it's important to performing mutations with care from GET requests,\nsince a GET request can be performed from an outside origin by embedding an image that points to the given URL. So a logout submission should be protected by\nusing `POST` to ensure that you can't log users out by embedding an image with a logout URL in it.\n\nIf the request has HTTP method `GET`, the form data will come from the query parameters.\n\nIf the request has the HTTP method `POST` _and_ the `Content-Type` is `application/x-www-form-urlencoded`, it will return the\ndecoded form data from the body of the request.\n\nOtherwise, this `Parser` will not match.\n\nNote that in server-rendered Route modules, your `data` function will handle `GET` requests (and will _not_ receive any `POST` requests),\nwhile your `action` will receive POST (and other non-GET) requests.\n\nBy default, [`Form`]'s are rendered with a `POST` method, and you can configure them to submit `GET` requests using [`withGetMethod`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#withGetMethod).\nSo you will want to handle any `Form`'s rendered using `withGetMethod` in your Route's `data` function, or otherwise handle forms in `action`.\n\n","type":"Form.Handler.Handler error combined -> Server.Request.Request -> Maybe.Maybe ( Form.ServerResponse error, Form.Validated error combined )"},{"name":"formDataWithServerValidation","comment":" ","type":"Pages.Form.Handler error combined -> Server.Request.Request -> Maybe.Maybe (BackendTask.BackendTask FatalError.FatalError (Result.Result (Form.ServerResponse error) ( Form.ServerResponse error, combined )))"},{"name":"header","comment":" Get a header from the request. The header name is case-insensitive.\n\nHeader: Accept-Language: en-US,en;q=0.5\n\n request |> Request.header \"Accept-Language\"\n -- Just \"Accept-Language: en-US,en;q=0.5\"\n\n","type":"String.String -> Server.Request.Request -> Maybe.Maybe String.String"},{"name":"headers","comment":" ","type":"Server.Request.Request -> Dict.Dict String.String String.String"},{"name":"jsonBody","comment":" If the request has a body and its `Content-Type` matches JSON, then\ntry running a JSON decoder on the body of the request. Otherwise, return `Nothing`.\n\nExample:\n\n Body: { \"name\": \"John\" }\n Headers:\n Content-Type: application/json\n request |> jsonBody (Json.Decode.field \"name\" Json.Decode.string)\n -- Just (Ok \"John\")\n\n Body: { \"name\": \"John\" }\n No Headers\n jsonBody (Json.Decode.field \"name\" Json.Decode.string) request\n -- Nothing\n\n No Body\n No Headers\n jsonBody (Json.Decode.field \"name\" Json.Decode.string) request\n -- Nothing\n\n","type":"Json.Decode.Decoder value -> Server.Request.Request -> Maybe.Maybe (Result.Result Json.Decode.Error value)"},{"name":"matchesContentType","comment":" True if the `content-type` header is present AND matches the given argument.\n\nExamples:\n\n Content-Type: application/json; charset=utf-8\n request |> matchesContentType \"application/json\"\n -- True\n\n Content-Type: application/json\n request |> matchesContentType \"application/json\"\n -- True\n\n Content-Type: application/json\n request |> matchesContentType \"application/xml\"\n -- False\n\n","type":"String.String -> Server.Request.Request -> Basics.Bool"},{"name":"method","comment":" The [HTTP request method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) of the incoming request.\n\nNote that Route modules `data` is run for `GET` requests, and `action` is run for other request methods (including `POST`, `PUT`, `DELETE`).\nSo you don't need to check the `method` in your Route Module's `data` function, though you can choose to do so in its `action`.\n\n","type":"Server.Request.Request -> Server.Request.Method"},{"name":"methodToString","comment":" Gets the HTTP Method as an uppercase String.\n\nExamples:\n\n Get\n |> methodToString\n -- \"GET\"\n\n","type":"Server.Request.Method -> String.String"},{"name":"queryParam","comment":" Get `Nothing` if the query param with the given name is missing, or `Just` the value if it is present.\n\nIf there are multiple query params with the same name, the first one is returned.\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc\n -- parses into: Just \"abc\"\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc&coupon=xyz\n -- parses into: Just \"abc\"\n\n queryParam \"coupon\"\n\n -- url: http://example.com\n -- parses into: Nothing\n\nSee also [`queryParams`](#queryParams), or [`rawUrl`](#rawUrl) if you need something more low-level.\n\n","type":"String.String -> Server.Request.Request -> Maybe.Maybe String.String"},{"name":"queryParams","comment":" Gives all query params from the URL.\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc\n -- parses into: Dict.fromList [(\"coupon\", [\"abc\"])]\n\n queryParam \"coupon\"\n\n -- url: http://example.com?coupon=abc&coupon=xyz\n -- parses into: Dict.fromList [(\"coupon\", [\"abc\", \"xyz\"])]\n\n","type":"Server.Request.Request -> Dict.Dict String.String (List.List String.String)"},{"name":"rawFormData","comment":" Get the raw key-value pairs from a form submission.\n\nIf the request has the HTTP method `GET`, it will return the query parameters.\n\nIf the request has the HTTP method `POST` _and_ the `Content-Type` is `application/x-www-form-urlencoded`, it will return the\ndecoded form data from the body of the request.\n\nOtherwise, this `Parser` will not match.\n\nNote that in server-rendered Route modules, your `data` function will handle `GET` requests (and will _not_ receive any `POST` requests),\nwhile your `action` will receive POST (and other non-GET) requests.\n\nBy default, [`Form`]'s are rendered with a `POST` method, and you can configure them to submit `GET` requests using [`withGetMethod`](https://package.elm-lang.org/packages/dillonkearns/elm-form/latest/Form#withGetMethod).\nSo you will want to handle any `Form`'s rendered using `withGetMethod` in your Route's `data` function, or otherwise handle forms in `action`.\n\n","type":"Server.Request.Request -> Maybe.Maybe (List.List ( String.String, String.String ))"},{"name":"rawUrl","comment":" The full URL of the incoming HTTP request, including the query params.\n\nNote that the fragment is not included because this is client-only (not sent to the server).\n\n rawUrl request\n\n -- url: http://example.com?coupon=abc\n -- parses into: \"http://example.com?coupon=abc\"\n\n rawUrl request\n\n -- url: https://example.com?coupon=abc&coupon=xyz\n -- parses into: \"https://example.com?coupon=abc&coupon=xyz\"\n\n","type":"Server.Request.Request -> String.String"},{"name":"requestTime","comment":" Get the `Time.Posix` when the incoming HTTP request was received.\n","type":"Server.Request.Request -> Time.Posix"}],"binops":[]},{"name":"Server.Response","comment":"\n\n\n## Responses\n\n@docs Response\n\n\n## Response's for Route Modules\n\nIn a server-rendered Route Module, you return a [`Response`](#Response). You'll typically want to return one of 3 types of Responses\nfrom your Route Modules:\n\n - [`Server.Response.render`](#render) to render the current Route Module\n - [`Server.Response.errorPage`](#errorPage) to render an ErrorPage\n - [`Server.Response.temporaryRedirect`](#temporaryRedirect) to redirect to another page (the easiest way to build a redirect response is with `Route.redirectTo : Route -> Response data error`).\n\n```\n import Server.Response as Response\n import Route\n\n data routeParams request =\n case loggedInUser request of\n Just user ->\n findProjectById routeParams.id user\n |> BackendTask.map\n (\\maybeProject ->\n case maybeProject of\n Just project ->\n Response.render project\n\n Nothing ->\n Response.errorPage ErrorPage.notFound\n )\n Nothing ->\n -- the generated module `Route` contains a high-level helper for returning a redirect `Response`\n Route.redirectTo Route.Login\n```\n\n\n## Render Responses\n\n@docs render\n\n@docs map\n\n\n## Rendering Error Pages\n\n@docs errorPage, mapError\n\n\n## Redirects\n\n@docs temporaryRedirect, permanentRedirect\n\n\n## Response's for Server-Rendered ApiRoutes\n\nWhen defining your [server-rendered `ApiRoute`'s (`ApiRoute.serverRender`)](ApiRoute#serverRender) in your `app/Api.elm` module,\nyou can send a low-level server Response. You can set a String body,\na list of headers, the status code, etc. The Server Response helpers like `json` and `temporaryRedirect` are just helpers for\nbuilding up those low-level Server Responses.\n\nRender Responses are a little more special in the way they are connected to your elm-pages app. They allow you to render\nthe current Route Module. To do that, you'll need to pass along the `data` for your Route Module.\n\nYou can use `withHeader` and `withStatusCode` to customize either type of Response (Server Responses or Render Responses).\n\n\n## Response Body\n\n@docs json, plainText, emptyBody, body, bytesBody, base64Body\n\n\n## Amending Responses\n\n@docs withHeader, withHeaders, withStatusCode, withSetCookieHeader\n\n\n## Internals\n\n@docs toJson\n\n","unions":[],"aliases":[{"name":"Response","comment":" ","args":["data","error"],"type":"PageServerResponse.PageServerResponse data error"}],"values":[{"name":"base64Body","comment":" Build a `Response` with a String that should represent a base64 encoded value.\n\nYour adapter will need to handle `isBase64Encoded` to turn it into the appropriate response.\n\n Response.base64Body \"SGVsbG8gV29ybGQ=\"\n\n","type":"String.String -> Server.Response.Response data error"},{"name":"body","comment":" Same as [`plainText`](#plainText), but doesn't set a `Content-Type`.\n","type":"String.String -> Server.Response.Response data error"},{"name":"bytesBody","comment":" Build a `Response` with a `Bytes`.\n\nUnder the hood, it will be converted to a base64 encoded String with `isBase64Encoded = True`.\nYour adapter will need to handle `isBase64Encoded` to turn it into the appropriate response.\n\n","type":"Bytes.Bytes -> Server.Response.Response data error"},{"name":"emptyBody","comment":" Build a `Response` with no HTTP response body.\n","type":"Server.Response.Response data error"},{"name":"errorPage","comment":" Instead of rendering the current Route Module, you can render an `ErrorPage` such as a 404 page or a 500 error page.\n\n[Read more about Error Pages](https://elm-pages.com/docs/error-pages) to learn about\ndefining and rendering your custom ErrorPage type.\n\n","type":"errorPage -> Server.Response.Response data errorPage"},{"name":"json","comment":" Build a JSON body from a `Json.Encode.Value`.\n\n Json.Encode.object\n [ ( \"message\", Json.Encode.string \"Hello\" ) ]\n |> Response.json\n\nSets the `Content-Type` to `application/json`.\n\n","type":"Json.Encode.Value -> Server.Response.Response data error"},{"name":"map","comment":" Maps the `data` for a Render response. Usually not needed, but always good to have the option.\n","type":"(data -> mappedData) -> Server.Response.Response data error -> Server.Response.Response mappedData error"},{"name":"mapError","comment":" Maps the `error` for an ErrorPage response. Usually not needed, but always good to have the option.\n","type":"(errorPage -> mappedErrorPage) -> Server.Response.Response data errorPage -> Server.Response.Response data mappedErrorPage"},{"name":"permanentRedirect","comment":" Build a 308 permanent redirect response.\n\nPermanent redirects tell the browser that a resource has permanently moved. If you redirect because a user is not logged in,\nthen you **do not** want to use a permanent redirect because the page they are looking for hasn't changed, you are just\ntemporarily pointing them to a new page since they need to authenticate.\n\nPermanent redirects are aggressively cached so be careful not to use them when you mean to use temporary redirects instead.\n\nIf you need to specifically rely on a 301 permanent redirect (see <https://stackoverflow.com/a/42138726> on the difference between 301 and 308),\nuse `customResponse` instead.\n\n","type":"String.String -> Server.Response.Response data error"},{"name":"plainText","comment":" Build a `Response` with a String body. Sets the `Content-Type` to `text/plain`.\n\n Response.plainText \"Hello\"\n\n","type":"String.String -> Server.Response.Response data error"},{"name":"render","comment":" Render the Route Module with the supplied data. Used for both the `data` and `action` functions in a server-rendered Route Module.\n\n Response.render project\n\n","type":"data -> Server.Response.Response data error"},{"name":"temporaryRedirect","comment":" ","type":"String.String -> Server.Response.Response data error"},{"name":"toJson","comment":" For internal use or more advanced use cases for meta frameworks.\n","type":"Server.Response.Response Basics.Never Basics.Never -> Json.Encode.Value"},{"name":"withHeader","comment":" Add a header to the response.\n\n Response.plainText \"Hello!\"\n -- allow CORS requests\n |> Response.withHeader \"Access-Control-Allow-Origin\" \"*\"\n |> Response.withHeader \"Access-Control-Allow-Methods\" \"GET, POST, OPTIONS\"\n\n","type":"String.String -> String.String -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withHeaders","comment":" Same as [`withHeader`](#withHeader), but allows you to add multiple headers at once.\n\n Response.plainText \"Hello!\"\n -- allow CORS requests\n |> Response.withHeaders\n [ ( \"Access-Control-Allow-Origin\", \"*\" )\n , ( \"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\" )\n ]\n\n","type":"List.List ( String.String, String.String ) -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withSetCookieHeader","comment":" Set a [`Server.SetCookie`](Server-SetCookie) value on the response.\n\nThe easiest way to manage cookies in your Routes is through the [`Server.Session`](Server-Session) API, but this\nprovides a more granular way to set cookies.\n\n","type":"Server.SetCookie.SetCookie -> Server.Response.Response data error -> Server.Response.Response data error"},{"name":"withStatusCode","comment":" Set the [HTTP Response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) for the `Response`.\n\n Response.plainText \"Not Authorized\"\n |> Response.withStatusCode 401\n\n","type":"Basics.Int -> Server.Response.Response data Basics.Never -> Server.Response.Response data Basics.Never"}],"binops":[]},{"name":"Server.Session","comment":" You can manage server state with HTTP cookies using this Server.Session API. Server-rendered routes have a `Server.Request.Request`\nargument that lets you inspect the incoming HTTP request, and return a response using the `Server.Response.Response` type.\n\nThis API provides a higher-level abstraction for extracting data from the HTTP request, and setting data in the HTTP response.\nIt manages the session through key-value data stored in cookies, and lets you [`insert`](#insert), [`update`](#update), and [`remove`](#remove)\nvalues from the Session. It also provides an abstraction for flash session values through [`withFlash`](#withFlash).\n\n\n## Using Sessions\n\nUsing these functions, you can store and read session data in cookies to maintain state between requests.\n\n import Server.Session as Session\n\n secrets : BackendTask FatalError (List String)\n secrets =\n Env.expect \"SESSION_SECRET\"\n |> BackendTask.allowFatal\n |> BackendTask.map List.singleton\n\n type alias Data =\n { darkMode : Bool }\n\n data : RouteParams -> Request -> BackendTask FatalError (Response Data ErrorPage)\n data routeParams request =\n request\n |> Session.withSession\n { name = \"mysession\"\n , secrets = secrets\n , options = Nothing\n }\n (\\session ->\n let\n darkMode : Bool\n darkMode =\n (session |> Session.get \"mode\" |> Maybe.withDefault \"light\")\n == \"dark\"\n in\n BackendTask.succeed\n ( session\n , Response.render\n { darkMode = darkMode\n }\n )\n )\n\nThe elm-pages framework will manage signing these cookies using the `secrets : BackendTask FatalError (List String)` you pass in.\nThat means that the values you set in your session will be directly visible to anyone who has access to the cookie\n(so don't directly store sensitive data in your session). Since the session cookie is signed using the secret you provide,\nthe cookie will be invalidated if it is tampered with because it won't match when elm-pages verifies that it has been\nsigned with your secrets. Of course you need to provide secure secrets and treat your secrets with care.\n\n\n### Rotating Secrets\n\nThe first String in `secrets : BackendTask FatalError (List String)` will be used to sign sessions, while the remaining String's will\nstill be used to attempt to \"unsign\" the cookies. So if you have a single secret:\n\n Session.withSession\n { name = \"mysession\"\n , secrets =\n BackendTask.map List.singleton\n (Env.expect \"SESSION_SECRET2022-09-01\")\n , options = Nothing\n }\n\nThen you add a second secret\n\n Session.withSession\n { name = \"mysession\"\n , secrets =\n BackendTask.map2\n (\\newSecret oldSecret -> [ newSecret, oldSecret ])\n (Env.expect \"SESSION_SECRET2022-12-01\")\n (Env.expect \"SESSION_SECRET2022-09-01\")\n , options = Nothing\n }\n\nThe new secret (`2022-12-01`) will be used to sign all requests. This API always re-signs using the newest secret in the list\nwhenever a new request comes in (even if the Session key-value pairs are unchanged), so these cookies get \"refreshed\" with the latest\nsigning secret when a new request comes in.\n\nHowever, incoming requests with a cookie signed using the old secret (`2022-09-01`) will still successfully be unsigned\nbecause they are still in the rotation (and then subsequently \"refreshed\" and signed using the new secret).\n\nThis allows you to rotate your session secrets (for security purposes). When a secret goes out of the rotation,\nit will invalidate all cookies signed with that. For example, if we remove our old secret from the rotation:\n\n Session.withSession\n { name = \"mysession\"\n , secrets =\n BackendTask.map List.singleton\n (Env.expect \"SESSION_SECRET2022-12-01\")\n , options = Nothing\n }\n\nAnd then a user makes a request but had a session signed with our old secret (`2022-09-01`), the session will be invalid\n(so `withSession` would parse the session for that request as `Nothing`). It's standard for cookies to have an expiration date,\nso there's nothing wrong with an old session expiring (and the browser will eventually delete old cookies), just be aware of that when rotating secrets.\n\n@docs withSession, withSessionResult\n\n@docs NotLoadedReason\n\n\n## Creating and Updating Sessions\n\n@docs Session, empty, get, insert, remove, update, withFlash\n\n","unions":[{"name":"NotLoadedReason","comment":" [`withSessionResult`](#withSessionResult) will return a `Result` with this type if it can't load a session.\n","args":[],"cases":[["NoSessionCookie",[]],["InvalidSessionCookie",[]]]},{"name":"Session","comment":" Represents a Session with key-value Strings.\n\nUse with `withSession` to read in the `Session`, and encode any changes you make to the `Session` back through cookie storage\nvia the outgoing HTTP response.\n\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"empty","comment":" An empty `Session` with no key-value pairs.\n","type":"Server.Session.Session"},{"name":"get","comment":" Retrieve a String value from the session for the given key (or `Nothing` if the key is not present).\n\n (session\n |> Session.get \"mode\"\n |> Maybe.withDefault \"light\"\n )\n == \"dark\"\n\n","type":"String.String -> Server.Session.Session -> Maybe.Maybe String.String"},{"name":"insert","comment":" Insert a value under the given key in the `Session`.\n\n session\n |> Session.insert \"mode\" \"dark\"\n\n","type":"String.String -> String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"remove","comment":" Remove a key from the `Session`.\n","type":"String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"update","comment":" Update the `Session`, given a `Maybe String` of the current value for the given key, and returning a `Maybe String`.\n\nIf you return `Nothing`, the key-value pair will be removed from the `Session` (or left out if it didn't exist in the first place).\n\n session\n |> Session.update \"mode\"\n (\\mode ->\n case mode of\n Just \"dark\" ->\n Just \"light\"\n\n Just \"light\" ->\n Just \"dark\"\n\n Nothing ->\n Just \"dark\"\n )\n\n","type":"String.String -> (Maybe.Maybe String.String -> Maybe.Maybe String.String) -> Server.Session.Session -> Server.Session.Session"},{"name":"withFlash","comment":" Flash session values are values that are only available for the next request.\n\n session\n |> Session.withFlash \"message\" \"Your payment was successful!\"\n\n","type":"String.String -> String.String -> Server.Session.Session -> Server.Session.Session"},{"name":"withSession","comment":" The main function for using sessions. If you need more fine-grained control over cases where a session can't be loaded, see\n[`withSessionResult`](#withSessionResult).\n","type":"{ name : String.String, secrets : BackendTask.BackendTask error (List.List String.String), options : Maybe.Maybe Server.SetCookie.Options } -> (Server.Session.Session -> BackendTask.BackendTask error ( Server.Session.Session, Server.Response.Response data errorPage )) -> Server.Request.Request -> BackendTask.BackendTask error (Server.Response.Response data errorPage)"},{"name":"withSessionResult","comment":" Same as `withSession`, but gives you an `Err` with the reason why the Session couldn't be loaded instead of\nusing `Session.empty` as a default in the cases where there is an error loading the session.\n\nA session won't load if there is no session, or if it cannot be unsigned with your secrets. This could be because the cookie was tampered with\nor otherwise corrupted, or because the cookie was signed with a secret that is no longer in the rotation.\n\n","type":"{ name : String.String, secrets : BackendTask.BackendTask error (List.List String.String), options : Maybe.Maybe Server.SetCookie.Options } -> (Result.Result Server.Session.NotLoadedReason Server.Session.Session -> BackendTask.BackendTask error ( Server.Session.Session, Server.Response.Response data errorPage )) -> Server.Request.Request -> BackendTask.BackendTask error (Server.Response.Response data errorPage)"}],"binops":[]},{"name":"Server.SetCookie","comment":" Server-rendered pages in your `elm-pages` can set cookies. `elm-pages` provides two high-level ways to work with cookies:\n\n - [`Server.Session.withSession`](Server-Session#withSession)\n - [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader)\n\n[`Server.Session.withSession`](Server-Session#withSession) provides a high-level way to manage key-value pairs of data using cookie storage,\nwhereas `Server.Response.withSetCookieHeader` gives a more low-level tool for setting cookies. It's often best to use the\nmost high-level tool that will fit your use case.\n\nYou can learn more about the basics of cookies in the Web Platform in these helpful MDN documentation pages:\n\n - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>\n - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>\n\n@docs SetCookie, setCookie\n\n\n## Building Options\n\nUsually you'll want to start by creating default `Options` with `options` and then overriding defaults using the `with...` helpers.\n\n import Server.SetCookie as SetCookie\n\n options : SetCookie.Options\n options =\n SetCookie.options\n |> SetCookie.nonSecure\n |> SetCookie.withMaxAge 123\n |> SetCookie.makeVisibleToJavaScript\n |> SetCookie.withoutPath\n |> SetCookie.setCookie \"id\" \"a3fWa\"\n\n@docs Options, options\n\n@docs SameSite, withSameSite\n\n@docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, withDomain, withExpiration, withMaxAge, withPath, withoutPath\n\n\n## Internal\n\n@docs toString\n\n","unions":[{"name":"SameSite","comment":" Possible values for [the cookie's same-site value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value).\n\nThe default option is [`Lax`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#lax) (Lax does not send\ncookies in cross-origin requests so it is a good default for most cases, but [`Strict`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#strict)\nis even more restrictive).\n\nOverride the default option using [`withSameSite`](#withSameSite).\n\n","args":[],"cases":[["Strict",[]],["Lax",[]],["None",[]]]}],"aliases":[{"name":"Options","comment":" The set of possible configuration options. You can configure this record directly, or use the `with...` helpers.\n","args":[],"type":"{ expiration : Maybe.Maybe Time.Posix, visibleToJavaScript : Basics.Bool, maxAge : Maybe.Maybe Basics.Int, path : Maybe.Maybe String.String, domain : Maybe.Maybe String.String, secure : Basics.Bool, sameSite : Maybe.Maybe Server.SetCookie.SameSite }"},{"name":"SetCookie","comment":" ","args":[],"type":"{ name : String.String, value : String.String, options : Server.SetCookie.Options }"}],"values":[{"name":"makeVisibleToJavaScript","comment":" The default option in this API is for HttpOnly cookies <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#httponly>.\n\nCookies can be exposed so you can read them from JavaScript using `Document.cookie`. When this is intended and understood\nthen there's nothing unsafe about that (for example, if you are setting a `darkMode` cookie and what to access that\ndynamically). In this API you opt into exposing a cookie you set to JavaScript to ensure cookies aren't exposed to JS unintentionally.\n\nIn general if you can accomplish your goal using HttpOnly cookies (i.e. not using `makeVisibleToJavaScript`) then\nit's a good practice. With server-rendered `elm-pages` applications you can often manage your session state by pulling\nin session data from cookies in a `BackendTask` (which is resolved server-side before it ever reaches the browser).\n\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"nonSecure","comment":" Secure (only sent over https, or localhost on http) is the default. This overrides that and\nremoves the `Secure` attribute from the cookie.\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"options","comment":" Initialize the default `SetCookie` `Options`. Can be configured directly through a record update, or with `withExpiration`, etc.\n","type":"Server.SetCookie.Options"},{"name":"setCookie","comment":" Create a `SetCookie` record with the given name, value, and [`Options`](Options]. To add a `Set-Cookie` header, you can\npass this value with [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader). Or for more low-level\nuses you can stringify the value manually with [`toString`](#toString).\n","type":"String.String -> String.String -> Server.SetCookie.Options -> Server.SetCookie.SetCookie"},{"name":"toString","comment":" Usually you'll want to use [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader) instead.\n\nThis is a low-level helper that's there in case you want it but most users will never need this.\n\n","type":"Server.SetCookie.SetCookie -> String.String"},{"name":"withDomain","comment":" Sets the `Set-Cookie`'s [`Domain`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value).\n","type":"String.String -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withExpiration","comment":" ","type":"Time.Posix -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withImmediateExpiration","comment":" Sets [`Expires`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#expiresdate) to `Time.millisToPosix 0`,\nwhich effectively tells the browser to delete the cookie immediately (by giving it an expiration date in the past).\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withMaxAge","comment":" Sets the `Set-Cookie`'s [`Max-Age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#max-agenumber).\n","type":"Basics.Int -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withPath","comment":" Sets the `Set-Cookie`'s [`Path`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value).\n\nThe default value is `/`, which will match any sub-directories or the root directory. See also [\\`withoutPath](#withoutPath)\n\n","type":"String.String -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withSameSite","comment":" The default SameSite policy is Lax if one is not explicitly set. See the SameSite section in <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes>.\n","type":"Server.SetCookie.SameSite -> Server.SetCookie.Options -> Server.SetCookie.Options"},{"name":"withoutPath","comment":"\n\n> If the server omits the Path attribute, the user agent will use the \"directory\" of the request-uri's path component as the default value.\n\nSource: <https://www.rfc-editor.org/rfc/rfc6265>. See <https://stackoverflow.com/a/43336097>.\n\n","type":"Server.SetCookie.Options -> Server.SetCookie.Options"}],"binops":[]},{"name":"UrlPath","comment":" Represents the path portion of a URL (not query parameters, fragment, protocol, port, etc.).\n\nThis helper lets you combine together path parts without worrying about having too many or too few slashes.\nThese two examples will result in the same URL, even though the first example has trailing and leading slashes, and the\nsecond does not.\n\n UrlPath.join [ \"/blog/\", \"/post-1/\" ]\n |> UrlPath.toAbsolute\n --> \"/blog/post-1\"\n\n UrlPath.join [ \"blog\", \"post-1\" ]\n |> UrlPath.toAbsolute\n --> \"/blog/post-1\"\n\nWe can also safely join Strings that include multiple path parts, a single path part per string, or a mix of the two:\n\n UrlPath.join [ \"/articles/archive/\", \"1977\", \"06\", \"10\", \"post-1\" ]\n |> UrlPath.toAbsolute\n --> \"/articles/archive/1977/06/10/post-1\"\n\n\n## Creating UrlPaths\n\n@docs UrlPath, join, fromString\n\n\n## Turning UrlPaths to String\n\n@docs toAbsolute, toRelative, toSegments\n\n","unions":[],"aliases":[{"name":"UrlPath","comment":" The path portion of the URL, normalized to ensure that path segments are joined with `/`s in the right places (no doubled up or missing slashes).\n","args":[],"type":"List.List String.String"}],"values":[{"name":"fromString","comment":" Create a UrlPath from a path String.\n\n UrlPath.fromString \"blog/post-1/\"\n |> UrlPath.toAbsolute\n |> Expect.equal \"/blog/post-1\"\n\n","type":"String.String -> UrlPath.UrlPath"},{"name":"join","comment":" Turn a Path to a relative URL.\n","type":"UrlPath.UrlPath -> UrlPath.UrlPath"},{"name":"toAbsolute","comment":" Turn a UrlPath to an absolute URL (with no trailing slash).\n","type":"UrlPath.UrlPath -> String.String"},{"name":"toRelative","comment":" Turn a UrlPath to a relative URL.\n","type":"UrlPath.UrlPath -> String.String"},{"name":"toSegments","comment":" ","type":"String.String -> List.List String.String"}],"binops":[]}] |