elm-pages 2.1.7 → 2.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generator/review/elm.json +34 -0
- package/generator/review/src/ReviewConfig.elm +10 -0
- package/generator/src/basepath-middleware.js +15 -9
- package/generator/src/build.js +77 -4
- package/generator/src/cli.js +13 -9
- package/generator/src/compile-elm.js +43 -0
- package/generator/src/dev-server.js +63 -11
- package/generator/src/error-formatter.js +62 -9
- package/generator/src/generate-template-module-connector.js +17 -4
- package/generator/src/init.js +4 -0
- package/generator/src/pre-render-html.js +19 -12
- package/generator/src/render-worker.js +0 -1
- package/generator/src/render.js +1 -2
- package/generator/src/seo-renderer.js +21 -2
- package/generator/static-code/hmr.js +43 -6
- package/generator/template/elm.json +13 -5
- package/generator/template/package.json +3 -2
- package/package.json +14 -8
- package/src/ApiRoute.elm +178 -0
- package/src/AriaLiveAnnouncer.elm +36 -0
- package/src/BuildError.elm +60 -0
- package/src/DataSource/File.elm +288 -0
- package/src/DataSource/Glob.elm +1050 -0
- package/src/DataSource/Http.elm +467 -0
- package/src/DataSource/Internal/Glob.elm +74 -0
- package/src/DataSource/Port.elm +87 -0
- package/src/DataSource/ServerRequest.elm +60 -0
- package/src/DataSource.elm +801 -0
- package/src/Head/Seo.elm +516 -0
- package/src/Head/Twitter.elm +109 -0
- package/src/Head.elm +452 -0
- package/src/HtmlPrinter.elm +27 -0
- package/src/Internal/ApiRoute.elm +89 -0
- package/src/Internal/OptimizedDecoder.elm +18 -0
- package/src/KeepOrDiscard.elm +6 -0
- package/src/OptimizedDecoder/Pipeline.elm +335 -0
- package/src/OptimizedDecoder.elm +818 -0
- package/src/Pages/ContentCache.elm +248 -0
- package/src/Pages/Flags.elm +26 -0
- package/src/Pages/Http.elm +10 -0
- package/src/Pages/Internal/ApplicationType.elm +6 -0
- package/src/Pages/Internal/NotFoundReason.elm +256 -0
- package/src/Pages/Internal/Platform/Cli.elm +1015 -0
- package/src/Pages/Internal/Platform/Effect.elm +14 -0
- package/src/Pages/Internal/Platform/StaticResponses.elm +540 -0
- package/src/Pages/Internal/Platform/ToJsPayload.elm +138 -0
- package/src/Pages/Internal/Platform.elm +745 -0
- package/src/Pages/Internal/RoutePattern.elm +122 -0
- package/src/Pages/Internal/Router.elm +116 -0
- package/src/Pages/Internal/StaticHttpBody.elm +54 -0
- package/src/Pages/Internal/String.elm +39 -0
- package/src/Pages/Manifest/Category.elm +240 -0
- package/src/Pages/Manifest.elm +412 -0
- package/src/Pages/PageUrl.elm +38 -0
- package/src/Pages/ProgramConfig.elm +73 -0
- package/src/Pages/Review/NoContractViolations.elm +397 -0
- package/src/Pages/Secrets.elm +83 -0
- package/src/Pages/SiteConfig.elm +13 -0
- package/src/Pages/StaticHttp/Request.elm +42 -0
- package/src/Pages/StaticHttpRequest.elm +320 -0
- package/src/Pages/Url.elm +60 -0
- package/src/Path.elm +96 -0
- package/src/QueryParams.elm +216 -0
- package/src/RenderRequest.elm +163 -0
- package/src/RequestsAndPending.elm +20 -0
- package/src/Secrets.elm +111 -0
- package/src/SecretsDict.elm +45 -0
- package/src/StructuredData.elm +236 -0
- package/src/TerminalText.elm +242 -0
- package/src/Test/Html/Internal/ElmHtml/Constants.elm +53 -0
- package/src/Test/Html/Internal/ElmHtml/Helpers.elm +17 -0
- package/src/Test/Html/Internal/ElmHtml/InternalTypes.elm +529 -0
- package/src/Test/Html/Internal/ElmHtml/Markdown.elm +56 -0
- package/src/Test/Html/Internal/ElmHtml/ToString.elm +197 -0
- package/src/Test/Internal/KernelConstants.elm +34 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
module DataSource.Http exposing
|
|
2
|
+
( RequestDetails
|
|
3
|
+
, get, request
|
|
4
|
+
, Body, emptyBody, stringBody, jsonBody
|
|
5
|
+
, unoptimizedRequest
|
|
6
|
+
, Expect, expectString, expectUnoptimizedJson
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
{-| `DataSource.Http` requests are an alternative to doing Elm HTTP requests the traditional way using the `elm/http` package.
|
|
10
|
+
|
|
11
|
+
The key differences are:
|
|
12
|
+
|
|
13
|
+
- `DataSource.Http.Request`s are performed once at build time (`Http.Request`s are performed at runtime, at whenever point you perform them)
|
|
14
|
+
- `DataSource.Http.Request`s strip out unused JSON data from the data your decoder doesn't touch to minimize the JSON payload
|
|
15
|
+
- `DataSource.Http.Request`s can use [`Pages.Secrets`](Pages.Secrets) to securely use credentials from your environment variables which are completely masked in the production assets.
|
|
16
|
+
- `DataSource.Http.Request`s have a built-in `DataSource.andThen` that allows you to perform follow-up requests without using tasks
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Scenarios where DataSource.Http is a good fit
|
|
20
|
+
|
|
21
|
+
If you need data that is refreshed often you may want to do a traditional HTTP request with the `elm/http` package.
|
|
22
|
+
The kinds of situations that are served well by static HTTP are with data that updates moderately frequently or infrequently (or never).
|
|
23
|
+
A common pattern is to trigger a new build when data changes. Many JAMstack services
|
|
24
|
+
allow you to send a WebHook to your host (for example, Netlify is a good static file host that supports triggering builds with webhooks). So
|
|
25
|
+
you may want to have your site rebuild everytime your calendar feed has an event added, or whenever a page or article is added
|
|
26
|
+
or updated on a CMS service like Contentful.
|
|
27
|
+
|
|
28
|
+
In 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
|
|
29
|
+
gains of using `DataSource.Http.Request`s as well as the simplicity and robustness that comes with it. Read more about these benefits
|
|
30
|
+
in [this article introducing DataSource.Http requests and some concepts around it](https://elm-pages.com/blog/static-http).
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Scenarios where DataSource.Http is not a good fit
|
|
34
|
+
|
|
35
|
+
- Data that is specific to the logged-in user
|
|
36
|
+
- Data that needs to be the very latest and changes often (for example, sports scores)
|
|
37
|
+
|
|
38
|
+
@docs RequestDetails
|
|
39
|
+
@docs get, request
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Building a DataSource.Http Request Body
|
|
43
|
+
|
|
44
|
+
The way you build a body is analogous to the `elm/http` package. Currently, only `emptyBody` and
|
|
45
|
+
`stringBody` are supported. If you have a use case that calls for a different body type, please open a Github issue
|
|
46
|
+
and describe your use case!
|
|
47
|
+
|
|
48
|
+
@docs Body, emptyBody, stringBody, jsonBody
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Unoptimized Requests
|
|
52
|
+
|
|
53
|
+
Warning - use these at your own risk! It's highly recommended that you use the other request functions that make use of
|
|
54
|
+
`zwilias/json-decode-exploration` in order to allow you to reduce down your JSON to only the values that are used by
|
|
55
|
+
your decoders. This can significantly reduce download sizes for your DataSource.Http requests.
|
|
56
|
+
|
|
57
|
+
@docs unoptimizedRequest
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Expect for unoptimized requests
|
|
61
|
+
|
|
62
|
+
@docs Expect, expectString, expectUnoptimizedJson
|
|
63
|
+
|
|
64
|
+
-}
|
|
65
|
+
|
|
66
|
+
import DataSource exposing (DataSource)
|
|
67
|
+
import Dict
|
|
68
|
+
import Internal.OptimizedDecoder
|
|
69
|
+
import Json.Decode
|
|
70
|
+
import Json.Decode.Exploration
|
|
71
|
+
import Json.Encode as Encode
|
|
72
|
+
import KeepOrDiscard
|
|
73
|
+
import OptimizedDecoder as Decode exposing (Decoder)
|
|
74
|
+
import Pages.Internal.ApplicationType as ApplicationType
|
|
75
|
+
import Pages.Internal.StaticHttpBody as Body
|
|
76
|
+
import Pages.Secrets
|
|
77
|
+
import Pages.StaticHttp.Request as HashRequest
|
|
78
|
+
import Pages.StaticHttpRequest exposing (RawRequest(..))
|
|
79
|
+
import RequestsAndPending
|
|
80
|
+
import Secrets
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
{-| Build an empty body for a DataSource.Http request. See [elm/http's `Http.emptyBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#emptyBody).
|
|
84
|
+
-}
|
|
85
|
+
emptyBody : Body
|
|
86
|
+
emptyBody =
|
|
87
|
+
Body.EmptyBody
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
{-| Builds a string body for a DataSource.Http request. See [elm/http's `Http.stringBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#stringBody).
|
|
91
|
+
|
|
92
|
+
Note from the `elm/http` docs:
|
|
93
|
+
|
|
94
|
+
> The first argument is a [MIME type](https://en.wikipedia.org/wiki/Media_type) of the body. Some servers are strict about this!
|
|
95
|
+
|
|
96
|
+
-}
|
|
97
|
+
stringBody : String -> String -> Body
|
|
98
|
+
stringBody contentType content =
|
|
99
|
+
Body.StringBody contentType content
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
{-| Builds a JSON body for a DataSource.Http request. See [elm/http's `Http.jsonBody`](https://package.elm-lang.org/packages/elm/http/latest/Http#jsonBody).
|
|
103
|
+
-}
|
|
104
|
+
jsonBody : Encode.Value -> Body
|
|
105
|
+
jsonBody content =
|
|
106
|
+
Body.JsonBody content
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
{-| A body for a DataSource.Http request.
|
|
110
|
+
-}
|
|
111
|
+
type alias Body =
|
|
112
|
+
Body.Body
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
{-| A simplified helper around [`DataSource.Http.request`](#request), which builds up a DataSource.Http GET request.
|
|
116
|
+
|
|
117
|
+
import DataSource
|
|
118
|
+
import DataSource.Http
|
|
119
|
+
import Json.Decode as Decode exposing (Decoder)
|
|
120
|
+
|
|
121
|
+
getRequest : DataSource Int
|
|
122
|
+
getRequest =
|
|
123
|
+
DataSource.Http.get
|
|
124
|
+
(Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
|
|
125
|
+
(Decode.field "stargazers_count" Decode.int)
|
|
126
|
+
|
|
127
|
+
-}
|
|
128
|
+
get :
|
|
129
|
+
Pages.Secrets.Value String
|
|
130
|
+
-> Decoder a
|
|
131
|
+
-> DataSource a
|
|
132
|
+
get url decoder =
|
|
133
|
+
request
|
|
134
|
+
(Secrets.map
|
|
135
|
+
(\okUrl ->
|
|
136
|
+
-- wrap in new variant
|
|
137
|
+
{ url = okUrl
|
|
138
|
+
, method = "GET"
|
|
139
|
+
, headers = []
|
|
140
|
+
, body = emptyBody
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
url
|
|
144
|
+
)
|
|
145
|
+
decoder
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
{-| The full details to perform a DataSource.Http request.
|
|
149
|
+
-}
|
|
150
|
+
type alias RequestDetails =
|
|
151
|
+
{ url : String
|
|
152
|
+
, method : String
|
|
153
|
+
, headers : List ( String, String )
|
|
154
|
+
, body : Body
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
requestToString : RequestDetails -> String
|
|
159
|
+
requestToString requestDetails =
|
|
160
|
+
requestDetails.url
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
{-| Build a `DataSource.Http` request (analagous to [Http.request](https://package.elm-lang.org/packages/elm/http/latest/Http#request)).
|
|
164
|
+
This function takes in all the details to build a `DataSource.Http` request, but you can build your own simplified helper functions
|
|
165
|
+
with this as a low-level detail, or you can use functions like [DataSource.Http.get](#get).
|
|
166
|
+
-}
|
|
167
|
+
request :
|
|
168
|
+
Pages.Secrets.Value RequestDetails
|
|
169
|
+
-> Decoder a
|
|
170
|
+
-> DataSource a
|
|
171
|
+
request urlWithSecrets decoder =
|
|
172
|
+
unoptimizedRequest urlWithSecrets (ExpectJson decoder)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
{-| Analgous to the `Expect` type in the `elm/http` package. This represents how you will process the data that comes
|
|
176
|
+
back in your DataSource.Http request.
|
|
177
|
+
|
|
178
|
+
You can derive `ExpectUnoptimizedJson` from `ExpectString`. Or you could build your own helper to process the String
|
|
179
|
+
as XML, for example, or give an `elm-pages` build error if the response can't be parsed as XML.
|
|
180
|
+
|
|
181
|
+
-}
|
|
182
|
+
type Expect value
|
|
183
|
+
= ExpectUnoptimizedJson (Json.Decode.Decoder value)
|
|
184
|
+
| ExpectJson (Decoder value)
|
|
185
|
+
| ExpectString (String -> Result String value)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
{-| Request a raw String. You can validate the String if you need to check the formatting, or try to parse it
|
|
189
|
+
in something besides JSON. Be sure to use the `DataSource.Http.request` function if you want an optimized request that
|
|
190
|
+
strips out unused JSON to optimize your asset size.
|
|
191
|
+
|
|
192
|
+
If the function you pass to `expectString` yields an `Err`, then you will get a build error that will
|
|
193
|
+
fail your `elm-pages` build and print out the String from the `Err`.
|
|
194
|
+
|
|
195
|
+
request =
|
|
196
|
+
DataSource.Http.unoptimizedRequest
|
|
197
|
+
(Secrets.succeed
|
|
198
|
+
{ url = "https://example.com/file.txt"
|
|
199
|
+
, method = "GET"
|
|
200
|
+
, headers = []
|
|
201
|
+
, body = DataSource.Http.emptyBody
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
(DataSource.Http.expectString
|
|
205
|
+
(\string ->
|
|
206
|
+
if String.toUpper string == string then
|
|
207
|
+
Ok string
|
|
208
|
+
|
|
209
|
+
else
|
|
210
|
+
Err "String was not uppercased"
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
-}
|
|
215
|
+
expectString : (String -> Result String value) -> Expect value
|
|
216
|
+
expectString =
|
|
217
|
+
ExpectString
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
{-| Handle the incoming response as JSON and don't optimize the asset and strip out unused values.
|
|
221
|
+
Be sure to use the `DataSource.Http.request` function if you want an optimized request that
|
|
222
|
+
strips out unused JSON to optimize your asset size. This function makes sense to use for things like a GraphQL request
|
|
223
|
+
where the JSON payload is already trimmed down to the data you explicitly requested.
|
|
224
|
+
|
|
225
|
+
If the function you pass to `expectString` yields an `Err`, then you will get a build error that will
|
|
226
|
+
fail your `elm-pages` build and print out the String from the `Err`.
|
|
227
|
+
|
|
228
|
+
-}
|
|
229
|
+
expectUnoptimizedJson : Json.Decode.Decoder value -> Expect value
|
|
230
|
+
expectUnoptimizedJson =
|
|
231
|
+
ExpectUnoptimizedJson
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
{-| This is an alternative to the other request functions in this module that doesn't perform any optimizations on the
|
|
235
|
+
asset. Be sure to use the optimized versions, like `DataSource.Http.request`, if you can. Using those can significantly reduce
|
|
236
|
+
your asset sizes by removing all unused fields from your JSON.
|
|
237
|
+
|
|
238
|
+
You may want to use this function instead if you need XML data or plaintext. Or maybe you're hitting a GraphQL API,
|
|
239
|
+
so you don't need any additional optimization as the payload is already reduced down to exactly what you requested.
|
|
240
|
+
|
|
241
|
+
-}
|
|
242
|
+
unoptimizedRequest :
|
|
243
|
+
Pages.Secrets.Value RequestDetails
|
|
244
|
+
-> Expect a
|
|
245
|
+
-> DataSource a
|
|
246
|
+
unoptimizedRequest requestWithSecrets expect =
|
|
247
|
+
case expect of
|
|
248
|
+
ExpectJson decoder ->
|
|
249
|
+
Request Dict.empty
|
|
250
|
+
( [ requestWithSecrets ]
|
|
251
|
+
, \keepOrDiscard appType rawResponseDict ->
|
|
252
|
+
case appType of
|
|
253
|
+
ApplicationType.Cli ->
|
|
254
|
+
rawResponseDict
|
|
255
|
+
|> RequestsAndPending.get (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
256
|
+
|> (\maybeResponse ->
|
|
257
|
+
case maybeResponse of
|
|
258
|
+
Just rawResponse ->
|
|
259
|
+
Ok
|
|
260
|
+
( Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
261
|
+
(case keepOrDiscard of
|
|
262
|
+
KeepOrDiscard.Keep ->
|
|
263
|
+
Pages.StaticHttpRequest.StripResponse
|
|
264
|
+
(Decode.map (\_ -> ()) decoder)
|
|
265
|
+
|
|
266
|
+
KeepOrDiscard.Discard ->
|
|
267
|
+
Pages.StaticHttpRequest.CliOnly
|
|
268
|
+
)
|
|
269
|
+
, rawResponse
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
Nothing ->
|
|
273
|
+
Err
|
|
274
|
+
(Pages.StaticHttpRequest.MissingHttpResponse
|
|
275
|
+
(requestToString (Secrets.maskedLookup requestWithSecrets))
|
|
276
|
+
[ requestWithSecrets ]
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|> Result.andThen
|
|
280
|
+
(\( strippedResponses, rawResponse ) ->
|
|
281
|
+
rawResponse
|
|
282
|
+
|> Json.Decode.Exploration.decodeString (decoder |> Internal.OptimizedDecoder.jde)
|
|
283
|
+
|> (\decodeResult ->
|
|
284
|
+
case decodeResult of
|
|
285
|
+
Json.Decode.Exploration.BadJson ->
|
|
286
|
+
Pages.StaticHttpRequest.DecoderError ("Payload sent back invalid JSON\n" ++ rawResponse) |> Err
|
|
287
|
+
|
|
288
|
+
Json.Decode.Exploration.Errors errors ->
|
|
289
|
+
errors
|
|
290
|
+
|> Json.Decode.Exploration.errorsToString
|
|
291
|
+
|> Pages.StaticHttpRequest.DecoderError
|
|
292
|
+
|> Err
|
|
293
|
+
|
|
294
|
+
Json.Decode.Exploration.WithWarnings _ a ->
|
|
295
|
+
Ok a
|
|
296
|
+
|
|
297
|
+
Json.Decode.Exploration.Success a ->
|
|
298
|
+
Ok a
|
|
299
|
+
)
|
|
300
|
+
|> Result.map
|
|
301
|
+
(\finalRequest ->
|
|
302
|
+
( case keepOrDiscard of
|
|
303
|
+
KeepOrDiscard.Keep ->
|
|
304
|
+
strippedResponses
|
|
305
|
+
|> Dict.insert
|
|
306
|
+
(Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
307
|
+
(Pages.StaticHttpRequest.StripResponse
|
|
308
|
+
(Decode.map (\_ -> ()) decoder)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
KeepOrDiscard.Discard ->
|
|
312
|
+
strippedResponses
|
|
313
|
+
|> Dict.insert
|
|
314
|
+
(Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
315
|
+
Pages.StaticHttpRequest.CliOnly
|
|
316
|
+
, finalRequest
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
|> toResult
|
|
321
|
+
|
|
322
|
+
ApplicationType.Browser ->
|
|
323
|
+
rawResponseDict
|
|
324
|
+
|> RequestsAndPending.get (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
325
|
+
|> (\maybeResponse ->
|
|
326
|
+
case maybeResponse of
|
|
327
|
+
Just rawResponse ->
|
|
328
|
+
Ok
|
|
329
|
+
( -- TODO should this be an empty Dict? Shouldn't matter in the browser.
|
|
330
|
+
Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
331
|
+
Pages.StaticHttpRequest.UseRawResponse
|
|
332
|
+
, rawResponse
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
Nothing ->
|
|
336
|
+
Err
|
|
337
|
+
(Pages.StaticHttpRequest.MissingHttpResponse (requestToString (Secrets.maskedLookup requestWithSecrets))
|
|
338
|
+
[ requestWithSecrets ]
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
|> Result.andThen
|
|
342
|
+
(\( strippedResponses, rawResponse ) ->
|
|
343
|
+
rawResponse
|
|
344
|
+
|> Json.Decode.decodeString (decoder |> Internal.OptimizedDecoder.jd)
|
|
345
|
+
|> (\decodeResult ->
|
|
346
|
+
case decodeResult of
|
|
347
|
+
Err error ->
|
|
348
|
+
Pages.StaticHttpRequest.DecoderError
|
|
349
|
+
("Payload sent back invalid JSON\n"
|
|
350
|
+
++ rawResponse
|
|
351
|
+
++ "\n KEYS"
|
|
352
|
+
++ (Dict.keys strippedResponses |> String.join " - ")
|
|
353
|
+
++ Json.Decode.errorToString error
|
|
354
|
+
)
|
|
355
|
+
|> Err
|
|
356
|
+
|
|
357
|
+
Ok a ->
|
|
358
|
+
Ok a
|
|
359
|
+
)
|
|
360
|
+
|> Result.map
|
|
361
|
+
(\finalRequest ->
|
|
362
|
+
( strippedResponses
|
|
363
|
+
, finalRequest
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
|> toResult
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
ExpectUnoptimizedJson decoder ->
|
|
371
|
+
Request Dict.empty
|
|
372
|
+
( [ requestWithSecrets ]
|
|
373
|
+
, \_ _ rawResponseDict ->
|
|
374
|
+
rawResponseDict
|
|
375
|
+
|> RequestsAndPending.get (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
376
|
+
|> (\maybeResponse ->
|
|
377
|
+
case maybeResponse of
|
|
378
|
+
Just rawResponse ->
|
|
379
|
+
Ok
|
|
380
|
+
( -- TODO check keepOrDiscard
|
|
381
|
+
Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
382
|
+
Pages.StaticHttpRequest.UseRawResponse
|
|
383
|
+
, rawResponse
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
Nothing ->
|
|
387
|
+
Err
|
|
388
|
+
(Pages.StaticHttpRequest.MissingHttpResponse (requestToString (Secrets.maskedLookup requestWithSecrets))
|
|
389
|
+
[ requestWithSecrets ]
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
|> Result.andThen
|
|
393
|
+
(\( strippedResponses, rawResponse ) ->
|
|
394
|
+
rawResponse
|
|
395
|
+
|> Json.Decode.decodeString decoder
|
|
396
|
+
|> (\decodeResult ->
|
|
397
|
+
case decodeResult of
|
|
398
|
+
Err error ->
|
|
399
|
+
error
|
|
400
|
+
|> Decode.errorToString
|
|
401
|
+
|> Pages.StaticHttpRequest.DecoderError
|
|
402
|
+
|> Err
|
|
403
|
+
|
|
404
|
+
Ok a ->
|
|
405
|
+
Ok a
|
|
406
|
+
)
|
|
407
|
+
|> Result.map
|
|
408
|
+
(\finalRequest ->
|
|
409
|
+
( -- TODO check keepOrDiscard
|
|
410
|
+
strippedResponses
|
|
411
|
+
|> Dict.insert
|
|
412
|
+
(Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
413
|
+
Pages.StaticHttpRequest.UseRawResponse
|
|
414
|
+
, finalRequest
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
)
|
|
418
|
+
|> toResult
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
ExpectString mapStringFn ->
|
|
422
|
+
Request Dict.empty
|
|
423
|
+
( [ requestWithSecrets ]
|
|
424
|
+
, \_ _ rawResponseDict ->
|
|
425
|
+
rawResponseDict
|
|
426
|
+
|> RequestsAndPending.get (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash)
|
|
427
|
+
|> (\maybeResponse ->
|
|
428
|
+
case maybeResponse of
|
|
429
|
+
Just rawResponse ->
|
|
430
|
+
Ok
|
|
431
|
+
( -- TODO check keepOrDiscard
|
|
432
|
+
Dict.singleton (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash) Pages.StaticHttpRequest.UseRawResponse
|
|
433
|
+
, rawResponse
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
Nothing ->
|
|
437
|
+
Err
|
|
438
|
+
(Pages.StaticHttpRequest.MissingHttpResponse (requestToString (Secrets.maskedLookup requestWithSecrets))
|
|
439
|
+
[ requestWithSecrets ]
|
|
440
|
+
)
|
|
441
|
+
)
|
|
442
|
+
|> Result.andThen
|
|
443
|
+
(\( strippedResponses, rawResponse ) ->
|
|
444
|
+
rawResponse
|
|
445
|
+
|> mapStringFn
|
|
446
|
+
|> Result.mapError Pages.StaticHttpRequest.DecoderError
|
|
447
|
+
|> Result.map
|
|
448
|
+
(\finalRequest ->
|
|
449
|
+
( -- TODO check keepOrDiscard
|
|
450
|
+
strippedResponses
|
|
451
|
+
|> Dict.insert (Secrets.maskedLookup requestWithSecrets |> HashRequest.hash) Pages.StaticHttpRequest.UseRawResponse
|
|
452
|
+
, finalRequest
|
|
453
|
+
)
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
|> toResult
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
toResult : Result Pages.StaticHttpRequest.Error ( Dict.Dict String Pages.StaticHttpRequest.WhatToDo, b ) -> RawRequest b
|
|
461
|
+
toResult result =
|
|
462
|
+
case result of
|
|
463
|
+
Err error ->
|
|
464
|
+
RequestError error
|
|
465
|
+
|
|
466
|
+
Ok ( stripped, okValue ) ->
|
|
467
|
+
ApiRoute stripped okValue
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module DataSource.Internal.Glob exposing
|
|
2
|
+
( Glob(..)
|
|
3
|
+
, extractMatches
|
|
4
|
+
, run
|
|
5
|
+
, toPattern
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
import List.Extra
|
|
9
|
+
import Regex exposing (Regex)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
{-| -}
|
|
13
|
+
type Glob a
|
|
14
|
+
= Glob String String (String -> List String -> ( a, List String ))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
run : String -> Glob a -> { match : a, pattern : String }
|
|
18
|
+
run rawInput (Glob pattern regex applyCapture) =
|
|
19
|
+
let
|
|
20
|
+
fullRegex : String
|
|
21
|
+
fullRegex =
|
|
22
|
+
"^" ++ regex ++ "$"
|
|
23
|
+
|
|
24
|
+
regexCaptures : List String
|
|
25
|
+
regexCaptures =
|
|
26
|
+
Regex.find parsedRegex rawInput
|
|
27
|
+
|> List.concatMap .submatches
|
|
28
|
+
|> List.map (Maybe.withDefault "")
|
|
29
|
+
|
|
30
|
+
parsedRegex : Regex
|
|
31
|
+
parsedRegex =
|
|
32
|
+
Regex.fromString fullRegex |> Maybe.withDefault Regex.never
|
|
33
|
+
in
|
|
34
|
+
{ match =
|
|
35
|
+
regexCaptures
|
|
36
|
+
|> List.reverse
|
|
37
|
+
|> applyCapture rawInput
|
|
38
|
+
|> Tuple.first
|
|
39
|
+
, pattern = pattern
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
{-| -}
|
|
44
|
+
toPattern : Glob a -> String
|
|
45
|
+
toPattern (Glob pattern _ _) =
|
|
46
|
+
pattern
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
{-| -}
|
|
50
|
+
extractMatches : a -> List ( String, a ) -> String -> List a
|
|
51
|
+
extractMatches defaultValue list string =
|
|
52
|
+
extractMatchesHelp defaultValue list string []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
extractMatchesHelp : a -> List ( String, a ) -> String -> List a -> List a
|
|
56
|
+
extractMatchesHelp defaultValue list string soFar =
|
|
57
|
+
if string == "" then
|
|
58
|
+
List.reverse soFar
|
|
59
|
+
|
|
60
|
+
else
|
|
61
|
+
let
|
|
62
|
+
( matchedValue, updatedString ) =
|
|
63
|
+
List.Extra.findMap
|
|
64
|
+
(\( literalString, value ) ->
|
|
65
|
+
if string |> String.startsWith literalString then
|
|
66
|
+
Just ( value, string |> String.dropLeft (String.length literalString) )
|
|
67
|
+
|
|
68
|
+
else
|
|
69
|
+
Nothing
|
|
70
|
+
)
|
|
71
|
+
list
|
|
72
|
+
|> Maybe.withDefault ( defaultValue, "" )
|
|
73
|
+
in
|
|
74
|
+
extractMatchesHelp defaultValue list updatedString (matchedValue :: soFar)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module DataSource.Port exposing (get)
|
|
2
|
+
|
|
3
|
+
{-|
|
|
4
|
+
|
|
5
|
+
@docs get
|
|
6
|
+
|
|
7
|
+
-}
|
|
8
|
+
|
|
9
|
+
import DataSource
|
|
10
|
+
import DataSource.Http
|
|
11
|
+
import Json.Encode
|
|
12
|
+
import OptimizedDecoder exposing (Decoder)
|
|
13
|
+
import Secrets
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
{-| 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.
|
|
17
|
+
|
|
18
|
+
With `DataSource.Port`, you send and receive JSON to JavaScript running in NodeJS during build-time. This means that you can call shell scripts, or run NPM packages that are installed, or anything else you could do with NodeJS.
|
|
19
|
+
|
|
20
|
+
A `DataSource.Port` will call an async JavaScript function with the given name. The function receives the input JSON value, and the Decoder is used to decode the return value of the async function.
|
|
21
|
+
|
|
22
|
+
Here is the Elm code and corresponding JavaScript definition for getting an environment variable (or a build error if it isn't found).
|
|
23
|
+
|
|
24
|
+
import DataSource exposing (DataSource)
|
|
25
|
+
import DataSource.Port
|
|
26
|
+
import Json.Encode
|
|
27
|
+
import OptimizedDecoder as Decode
|
|
28
|
+
|
|
29
|
+
data : DataSource String
|
|
30
|
+
data =
|
|
31
|
+
DataSource.Port.get "environmentVariable"
|
|
32
|
+
(Json.Encode.string "EDITOR")
|
|
33
|
+
Decode.string
|
|
34
|
+
|
|
35
|
+
-- will resolve to "VIM" if you run `EDITOR=vim elm-pages dev`
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
const kleur = require("kleur");
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
module.exports =
|
|
42
|
+
/**
|
|
43
|
+
* @param { unknown } fromElm
|
|
44
|
+
* @returns { Promise<unknown> }
|
|
45
|
+
*/
|
|
46
|
+
{
|
|
47
|
+
environmentVariable: async function (name) {
|
|
48
|
+
const result = process.env[name];
|
|
49
|
+
if (result) {
|
|
50
|
+
return result;
|
|
51
|
+
} else {
|
|
52
|
+
throw `No environment variable called ${kleur
|
|
53
|
+
.yellow()
|
|
54
|
+
.underline(name)}\n\nAvailable:\n\n${Object.keys(process.env).join(
|
|
55
|
+
"\n"
|
|
56
|
+
)}`;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Error Handling
|
|
64
|
+
|
|
65
|
+
`port-data-source.js`
|
|
66
|
+
|
|
67
|
+
Any time you throw an exception from a DataaSource.Port definition, it will result in a build error in your `elm-pages build` or dev server. In the example above, if the environment variable
|
|
68
|
+
is not found it will result in a build failure. Notice that the NPM package `kleur` is being used in this example to add color to the output for that build error. You can use any tool you
|
|
69
|
+
prefer to add ANSI color codes within the error string in an exception and it will show up with color output in the build output and dev server.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
## Performance
|
|
73
|
+
|
|
74
|
+
As 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.
|
|
75
|
+
|
|
76
|
+
-}
|
|
77
|
+
get : String -> Json.Encode.Value -> Decoder b -> DataSource.DataSource b
|
|
78
|
+
get portName input decoder =
|
|
79
|
+
DataSource.Http.request
|
|
80
|
+
(Secrets.succeed
|
|
81
|
+
{ url = "port://" ++ portName
|
|
82
|
+
, method = "GET"
|
|
83
|
+
, headers = []
|
|
84
|
+
, body = DataSource.Http.jsonBody input
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
decoder
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module DataSource.ServerRequest exposing (ServerRequest, expectHeader, init, optionalHeader, staticData, toStaticHttp)
|
|
2
|
+
|
|
3
|
+
{-|
|
|
4
|
+
|
|
5
|
+
@docs ServerRequest, expectHeader, init, optionalHeader, staticData, toStaticHttp
|
|
6
|
+
|
|
7
|
+
-}
|
|
8
|
+
|
|
9
|
+
import DataSource
|
|
10
|
+
import DataSource.Http
|
|
11
|
+
import OptimizedDecoder
|
|
12
|
+
import Secrets
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
{-| -}
|
|
16
|
+
type ServerRequest decodesTo
|
|
17
|
+
= ServerRequest (OptimizedDecoder.Decoder decodesTo)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
{-| -}
|
|
21
|
+
init : constructor -> ServerRequest constructor
|
|
22
|
+
init constructor =
|
|
23
|
+
ServerRequest (OptimizedDecoder.succeed constructor)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
{-| -}
|
|
27
|
+
staticData : DataSource.DataSource String
|
|
28
|
+
staticData =
|
|
29
|
+
DataSource.Http.get (Secrets.succeed "$$elm-pages$$headers")
|
|
30
|
+
(OptimizedDecoder.field "headers"
|
|
31
|
+
(OptimizedDecoder.field "accept-language" OptimizedDecoder.string)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
{-| -}
|
|
36
|
+
toStaticHttp : ServerRequest decodesTo -> DataSource.DataSource decodesTo
|
|
37
|
+
toStaticHttp (ServerRequest decoder) =
|
|
38
|
+
DataSource.Http.get (Secrets.succeed "$$elm-pages$$headers") decoder
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
{-| -}
|
|
42
|
+
expectHeader : String -> ServerRequest (String -> value) -> ServerRequest value
|
|
43
|
+
expectHeader headerName (ServerRequest decoder) =
|
|
44
|
+
decoder
|
|
45
|
+
|> OptimizedDecoder.andMap
|
|
46
|
+
(OptimizedDecoder.field headerName OptimizedDecoder.string
|
|
47
|
+
|> OptimizedDecoder.field "headers"
|
|
48
|
+
)
|
|
49
|
+
|> ServerRequest
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
{-| -}
|
|
53
|
+
optionalHeader : String -> ServerRequest (Maybe String -> value) -> ServerRequest value
|
|
54
|
+
optionalHeader headerName (ServerRequest decoder) =
|
|
55
|
+
decoder
|
|
56
|
+
|> OptimizedDecoder.andMap
|
|
57
|
+
(OptimizedDecoder.optionalField headerName OptimizedDecoder.string
|
|
58
|
+
|> OptimizedDecoder.field "headers"
|
|
59
|
+
)
|
|
60
|
+
|> ServerRequest
|