elm-pages 3.0.0-beta.12 → 3.0.0-beta.14

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.
Files changed (77) hide show
  1. package/README.md +1 -1
  2. package/codegen/elm-pages-codegen.js +1496 -1126
  3. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
  4. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
  5. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
  6. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  7. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  8. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  9. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +1 -1
  10. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +151 -39
  11. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
  12. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
  13. package/generator/dead-code-review/elm.json +3 -2
  14. package/generator/dead-code-review/src/Pages/Review/DeadCodeEliminateData.elm +58 -10
  15. package/generator/dead-code-review/tests/Pages/Review/DeadCodeEliminateDataTest.elm +45 -29
  16. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  17. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  18. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  19. package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -1
  20. package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +45 -4
  21. package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
  22. package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
  23. package/generator/review/elm.json +3 -3
  24. package/generator/src/RouteBuilder.elm +66 -52
  25. package/generator/src/SharedTemplate.elm +3 -2
  26. package/generator/src/SiteConfig.elm +3 -2
  27. package/generator/src/build.js +6 -6
  28. package/generator/src/cli.js +12 -7
  29. package/generator/src/compatibility-key.js +1 -1
  30. package/generator/src/dev-server.js +7 -7
  31. package/generator/src/render-test.js +109 -0
  32. package/generator/src/render.js +77 -51
  33. package/generator/src/request-cache.js +149 -158
  34. package/generator/template/app/Api.elm +2 -2
  35. package/generator/template/app/Route/Index.elm +3 -3
  36. package/generator/template/app/Shared.elm +3 -3
  37. package/generator/template/app/Site.elm +3 -3
  38. package/package.json +11 -11
  39. package/src/ApiRoute.elm +63 -57
  40. package/src/BackendTask/Env.elm +87 -0
  41. package/src/{DataSource → BackendTask}/File.elm +89 -43
  42. package/src/{DataSource → BackendTask}/Glob.elm +134 -125
  43. package/src/BackendTask/Http.elm +637 -0
  44. package/src/{DataSource → BackendTask}/Internal/Glob.elm +1 -1
  45. package/src/BackendTask/Internal/Request.elm +28 -0
  46. package/src/BackendTask/Port.elm +202 -0
  47. package/src/{DataSource.elm → BackendTask.elm} +223 -207
  48. package/src/Exception.elm +37 -0
  49. package/src/Form.elm +20 -20
  50. package/src/Head.elm +7 -7
  51. package/src/Internal/ApiRoute.elm +7 -5
  52. package/src/PageServerResponse.elm +6 -1
  53. package/src/Pages/Generate.elm +35 -63
  54. package/src/Pages/Internal/Platform/Cli.elm +422 -731
  55. package/src/Pages/Internal/Platform/Cli.elm.bak +22 -22
  56. package/src/Pages/Internal/Platform/CompatibilityKey.elm +1 -1
  57. package/src/Pages/Internal/Platform/Effect.elm +0 -1
  58. package/src/Pages/Internal/Platform/GeneratorApplication.elm +53 -113
  59. package/src/Pages/Internal/Platform/StaticResponses.elm +72 -256
  60. package/src/Pages/Internal/Platform/ToJsPayload.elm +4 -4
  61. package/src/Pages/Internal/Platform.elm +25 -31
  62. package/src/Pages/Internal/Script.elm +17 -0
  63. package/src/Pages/Internal/StaticHttpBody.elm +35 -1
  64. package/src/Pages/Manifest.elm +5 -4
  65. package/src/Pages/ProgramConfig.elm +8 -7
  66. package/src/Pages/Script.elm +34 -25
  67. package/src/Pages/SiteConfig.elm +3 -2
  68. package/src/Pages/StaticHttp/Request.elm +2 -2
  69. package/src/Pages/StaticHttpRequest.elm +37 -90
  70. package/src/RequestsAndPending.elm +8 -19
  71. package/src/Server/Request.elm +14 -14
  72. package/src/Server/Session.elm +34 -34
  73. package/src/Server/SetCookie.elm +1 -1
  74. package/src/DataSource/Env.elm +0 -62
  75. package/src/DataSource/Http.elm +0 -446
  76. package/src/DataSource/Internal/Request.elm +0 -20
  77. package/src/DataSource/Port.elm +0 -90
package/src/ApiRoute.elm CHANGED
@@ -5,12 +5,12 @@ module ApiRoute exposing
5
5
  , ApiRoute, ApiRouteBuilder, Response
6
6
  , capture, literal, slash, succeed
7
7
  , withGlobalHeadTags
8
- , toJson, getBuildTimeRoutes, getGlobalHeadTagsDataSource
8
+ , toJson, getBuildTimeRoutes, getGlobalHeadTagsBackendTask
9
9
  )
10
10
 
11
11
  {-| ApiRoute's are defined in `src/Api.elm` and are a way to generate files, like RSS feeds, sitemaps, or any text-based file that you output with an Elm function! You get access
12
- to a DataSource so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in
13
- the DataSource for your ApiRoutes, and it won't effect the payload size. Instead, the size of an ApiRoute is just the content you output for that route.
12
+ to a BackendTask so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in
13
+ the 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.
14
14
 
15
15
  Similar 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
16
16
  use cases they support.
@@ -25,7 +25,7 @@ A pre-rendered ApiRoute is just a generated file. For example:
25
25
  - A redirect file for a hosting provider like Netlify
26
26
 
27
27
  You 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!
28
- The beauty is that you have a way to 1) pull in type-safe data using DataSource's, and 2) write those files, and all in pure Elm!
28
+ The 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!
29
29
 
30
30
  @docs single, preRender
31
31
 
@@ -62,11 +62,11 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
62
62
  module Api exposing (routes)
63
63
 
64
64
  import ApiRoute
65
- import DataSource exposing (DataSource)
65
+ import BackendTask exposing (BackendTask)
66
66
  import Server.Request
67
67
 
68
68
  routes :
69
- DataSource (List Route)
69
+ BackendTask (List Route)
70
70
  -> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)
71
71
  -> List (ApiRoute.ApiRoute ApiRoute.Response)
72
72
  routes getStaticRoutes htmlToString =
@@ -89,7 +89,7 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
89
89
  preRenderedExample =
90
90
  ApiRoute.succeed
91
91
  (\userId ->
92
- DataSource.succeed
92
+ BackendTask.succeed
93
93
  (Json.Encode.object
94
94
  [ ( "id", Json.Encode.string userId )
95
95
  , ( "name", "Data for user " ++ userId |> Json.Encode.string )
@@ -103,7 +103,7 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
103
103
  |> ApiRoute.literal ".json"
104
104
  |> ApiRoute.preRender
105
105
  (\route ->
106
- DataSource.succeed
106
+ BackendTask.succeed
107
107
  [ route "1"
108
108
  , route "2"
109
109
  , route "3"
@@ -144,7 +144,7 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
144
144
  )
145
145
  ]
146
146
  |> Response.json
147
- |> DataSource.succeed
147
+ |> BackendTask.succeed
148
148
  )
149
149
  Server.Request.rawBody
150
150
  Server.Request.method
@@ -168,12 +168,12 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
168
168
 
169
169
  ## Internals
170
170
 
171
- @docs toJson, getBuildTimeRoutes, getGlobalHeadTagsDataSource
171
+ @docs toJson, getBuildTimeRoutes, getGlobalHeadTagsBackendTask
172
172
 
173
173
  -}
174
174
 
175
- import DataSource exposing (DataSource)
176
- import DataSource.Http
175
+ import BackendTask exposing (BackendTask)
176
+ import Exception exposing (Throwable)
177
177
  import Head
178
178
  import Internal.ApiRoute exposing (ApiRoute(..), ApiRouteBuilder(..))
179
179
  import Json.Decode as Decode
@@ -190,53 +190,65 @@ type alias ApiRoute response =
190
190
 
191
191
 
192
192
  {-| Same as [`preRender`](#preRender), but for an ApiRoute that has no dynamic segments. This is just a bit simpler because
193
- since there are no dynamic segments, you don't need to provide a DataSource with the list of dynamic segments to pre-render because there is only a single possible route.
193
+ since 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.
194
194
  -}
195
- single : ApiRouteBuilder (DataSource String) (List String) -> ApiRoute Response
195
+ single : ApiRouteBuilder (BackendTask Throwable String) (List String) -> ApiRoute Response
196
196
  single handler =
197
197
  handler
198
- |> preRender (\constructor -> DataSource.succeed [ constructor ])
198
+ |> preRender (\constructor -> BackendTask.succeed [ constructor ])
199
199
 
200
200
 
201
201
  {-| -}
202
- serverRender : ApiRouteBuilder (Server.Request.Parser (DataSource (Server.Response.Response Never Never))) constructor -> ApiRoute Response
202
+ serverRender : ApiRouteBuilder (Server.Request.Parser (BackendTask Never (Server.Response.Response Never Never))) constructor -> ApiRoute Response
203
203
  serverRender ((ApiRouteBuilder patterns pattern _ _ _) as fullHandler) =
204
204
  ApiRoute
205
205
  { regex = Regex.fromString ("^" ++ pattern ++ "$") |> Maybe.withDefault Regex.never
206
206
  , matchesToResponse =
207
- \path ->
207
+ \serverRequest path ->
208
208
  Internal.ApiRoute.tryMatch path fullHandler
209
209
  |> Maybe.map
210
- (\toDataSource ->
211
- DataSource.Http.get
212
- "$$elm-pages$$headers"
213
- (toDataSource |> Server.Request.getDecoder |> Decode.map Just)
214
- |> DataSource.andThen
210
+ (\toBackendTask ->
211
+ Server.Request.getDecoder toBackendTask
212
+ |> (\decoder ->
213
+ Decode.decodeValue decoder serverRequest
214
+ |> Result.mapError Decode.errorToString
215
+ |> BackendTask.fromResult
216
+ |> BackendTask.map Just
217
+ )
218
+ |> BackendTask.onError
219
+ (\stringError ->
220
+ -- TODO make error with title and better context/formatting
221
+ Exception.fromString stringError |> BackendTask.fail
222
+ )
223
+ |> BackendTask.andThen
215
224
  (\rendered ->
216
225
  case rendered of
217
226
  Just (Ok okRendered) ->
218
227
  okRendered
228
+ |> BackendTask.onError never
219
229
 
220
230
  Just (Err errors) ->
221
231
  errors
222
232
  |> Server.Request.errorsToString
223
233
  |> Server.Response.plainText
224
234
  |> Server.Response.withStatusCode 400
225
- |> DataSource.succeed
235
+ |> BackendTask.succeed
236
+ |> BackendTask.onError never
226
237
 
227
238
  Nothing ->
228
239
  Server.Response.plainText "No matching request handler"
229
240
  |> Server.Response.withStatusCode 400
230
- |> DataSource.succeed
241
+ |> BackendTask.succeed
242
+ |> BackendTask.onError never
231
243
  )
232
244
  )
233
- |> Maybe.map (DataSource.map (Server.Response.toJson >> Just))
245
+ |> Maybe.map (BackendTask.map (Server.Response.toJson >> Just))
234
246
  |> Maybe.withDefault
235
- (DataSource.succeed Nothing)
236
- , buildTimeRoutes = DataSource.succeed []
247
+ (BackendTask.succeed Nothing)
248
+ , buildTimeRoutes = BackendTask.succeed []
237
249
  , handleRoute =
238
250
  \path ->
239
- DataSource.succeed
251
+ BackendTask.succeed
240
252
  (case Internal.ApiRoute.tryMatch path fullHandler of
241
253
  Just _ ->
242
254
  True
@@ -251,26 +263,26 @@ serverRender ((ApiRouteBuilder patterns pattern _ _ _) as fullHandler) =
251
263
 
252
264
 
253
265
  {-| -}
254
- preRenderWithFallback : (constructor -> DataSource (List (List String))) -> ApiRouteBuilder (DataSource (Server.Response.Response Never Never)) constructor -> ApiRoute Response
266
+ preRenderWithFallback : (constructor -> BackendTask Throwable (List (List String))) -> ApiRouteBuilder (BackendTask Throwable (Server.Response.Response Never Never)) constructor -> ApiRoute Response
255
267
  preRenderWithFallback buildUrls ((ApiRouteBuilder patterns pattern _ toString constructor) as fullHandler) =
256
268
  let
257
- buildTimeRoutes__ : DataSource (List String)
269
+ buildTimeRoutes__ : BackendTask Throwable (List String)
258
270
  buildTimeRoutes__ =
259
271
  buildUrls (constructor [])
260
- |> DataSource.map (List.map toString)
272
+ |> BackendTask.map (List.map toString)
261
273
  in
262
274
  ApiRoute
263
275
  { regex = Regex.fromString ("^" ++ pattern ++ "$") |> Maybe.withDefault Regex.never
264
276
  , matchesToResponse =
265
- \path ->
277
+ \_ path ->
266
278
  Internal.ApiRoute.tryMatch path fullHandler
267
- |> Maybe.map (DataSource.map (Server.Response.toJson >> Just))
279
+ |> Maybe.map (BackendTask.map (Server.Response.toJson >> Just))
268
280
  |> Maybe.withDefault
269
- (DataSource.succeed Nothing)
281
+ (BackendTask.succeed Nothing)
270
282
  , buildTimeRoutes = buildTimeRoutes__
271
283
  , handleRoute =
272
284
  \path ->
273
- DataSource.succeed
285
+ BackendTask.succeed
274
286
  (case Internal.ApiRoute.tryMatch path fullHandler of
275
287
  Just _ ->
276
288
  True
@@ -293,42 +305,42 @@ encodeStaticFileBody fileBody =
293
305
 
294
306
 
295
307
  {-| -}
296
- preRender : (constructor -> DataSource (List (List String))) -> ApiRouteBuilder (DataSource String) constructor -> ApiRoute Response
308
+ preRender : (constructor -> BackendTask Throwable (List (List String))) -> ApiRouteBuilder (BackendTask Throwable String) constructor -> ApiRoute Response
297
309
  preRender buildUrls ((ApiRouteBuilder patterns pattern _ toString constructor) as fullHandler) =
298
310
  let
299
- buildTimeRoutes__ : DataSource (List String)
311
+ buildTimeRoutes__ : BackendTask Throwable (List String)
300
312
  buildTimeRoutes__ =
301
313
  buildUrls (constructor [])
302
- |> DataSource.map (List.map toString)
314
+ |> BackendTask.map (List.map toString)
303
315
 
304
- preBuiltMatches : DataSource (List (List String))
316
+ preBuiltMatches : BackendTask Throwable (List (List String))
305
317
  preBuiltMatches =
306
318
  buildUrls (constructor [])
307
319
  in
308
320
  ApiRoute
309
321
  { regex = Regex.fromString ("^" ++ pattern ++ "$") |> Maybe.withDefault Regex.never
310
322
  , matchesToResponse =
311
- \path ->
323
+ \_ path ->
312
324
  let
313
325
  matches : List String
314
326
  matches =
315
327
  Internal.ApiRoute.pathToMatches path fullHandler
316
328
 
317
- routeFound : DataSource Bool
329
+ routeFound : BackendTask Throwable Bool
318
330
  routeFound =
319
331
  preBuiltMatches
320
- |> DataSource.map (List.member matches)
332
+ |> BackendTask.map (List.member matches)
321
333
  in
322
334
  routeFound
323
- |> DataSource.andThen
335
+ |> BackendTask.andThen
324
336
  (\found ->
325
337
  if found then
326
338
  Internal.ApiRoute.tryMatch path fullHandler
327
- |> Maybe.map (DataSource.map (encodeStaticFileBody >> Just))
328
- |> Maybe.withDefault (DataSource.succeed Nothing)
339
+ |> Maybe.map (BackendTask.map (encodeStaticFileBody >> Just))
340
+ |> Maybe.withDefault (BackendTask.succeed Nothing)
329
341
 
330
342
  else
331
- DataSource.succeed Nothing
343
+ BackendTask.succeed Nothing
332
344
  )
333
345
  , buildTimeRoutes = buildTimeRoutes__
334
346
  , handleRoute =
@@ -339,7 +351,7 @@ preRender buildUrls ((ApiRouteBuilder patterns pattern _ toString constructor) a
339
351
  Internal.ApiRoute.pathToMatches path fullHandler
340
352
  in
341
353
  preBuiltMatches
342
- |> DataSource.map (List.member matches)
354
+ |> BackendTask.map (List.member matches)
343
355
  , pattern = patterns
344
356
  , kind = "prerender"
345
357
  , globalHeadTags = Nothing
@@ -422,25 +434,19 @@ capture (ApiRouteBuilder patterns pattern previousHandler toString constructor)
422
434
 
423
435
  {-| For internal use by generated code. Not so useful in user-land.
424
436
  -}
425
- getBuildTimeRoutes : ApiRoute response -> DataSource (List String)
437
+ getBuildTimeRoutes : ApiRoute response -> BackendTask Throwable (List String)
426
438
  getBuildTimeRoutes (ApiRoute handler) =
427
439
  handler.buildTimeRoutes
428
440
 
429
441
 
430
442
  {-| Include head tags on every page's HTML.
431
443
  -}
432
- withGlobalHeadTags : DataSource (List Head.Tag) -> ApiRoute response -> ApiRoute response
444
+ withGlobalHeadTags : BackendTask Throwable (List Head.Tag) -> ApiRoute response -> ApiRoute response
433
445
  withGlobalHeadTags globalHeadTags (ApiRoute handler) =
434
446
  ApiRoute { handler | globalHeadTags = Just globalHeadTags }
435
447
 
436
448
 
437
449
  {-| -}
438
- getGlobalHeadTagsDataSource : ApiRoute response -> Maybe (DataSource (List Head.Tag))
439
- getGlobalHeadTagsDataSource (ApiRoute handler) =
450
+ getGlobalHeadTagsBackendTask : ApiRoute response -> Maybe (BackendTask Throwable (List Head.Tag))
451
+ getGlobalHeadTagsBackendTask (ApiRoute handler) =
440
452
  handler.globalHeadTags
441
-
442
-
443
-
444
- --captureRest : ApiRouteBuilder (List String -> a) b -> ApiRouteBuilder a b
445
- --captureRest previousHandler =
446
- -- Debug.todo ""
@@ -0,0 +1,87 @@
1
+ module BackendTask.Env exposing
2
+ ( get, expect
3
+ , Error(..)
4
+ )
5
+
6
+ {-| 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
7
+ down into the final `Data` value, it won't end up in the client!
8
+
9
+ import BackendTask exposing (BackendTask)
10
+ import BackendTask.Env
11
+
12
+ type alias EnvVariables =
13
+ { sendGridKey : String
14
+ , siteUrl : String
15
+ }
16
+
17
+ sendEmail : Email -> BackendTask ()
18
+ sendEmail email =
19
+ BackendTask.map2 EnvVariables
20
+ (BackendTask.Env.expect "SEND_GRID_KEY")
21
+ (BackendTask.Env.get "BASE_URL"
22
+ |> BackendTask.map (Maybe.withDefault "http://localhost:1234")
23
+ )
24
+ |> BackendTask.andThen (sendEmailBackendTask email)
25
+
26
+ sendEmailBackendTask : Email -> EnvVariables -> BackendTask ()
27
+ sendEmailBackendTask email envVariables =
28
+ Debug.todo "Not defined here"
29
+
30
+ @docs get, expect
31
+
32
+
33
+ ## Errors
34
+
35
+ @docs Error
36
+
37
+ -}
38
+
39
+ import BackendTask exposing (BackendTask)
40
+ import BackendTask.Http
41
+ import BackendTask.Internal.Request
42
+ import Exception exposing (Catchable)
43
+ import Json.Decode as Decode
44
+ import Json.Encode as Encode
45
+ import TerminalText
46
+
47
+
48
+ {-| -}
49
+ type Error
50
+ = MissingEnvVariable String
51
+
52
+
53
+ {-| Get an environment variable, or Nothing if there is no environment variable matching that name.
54
+ -}
55
+ get : String -> BackendTask error (Maybe String)
56
+ get envVariableName =
57
+ BackendTask.Internal.Request.request
58
+ { name = "env"
59
+ , body = BackendTask.Http.jsonBody (Encode.string envVariableName)
60
+ , expect =
61
+ BackendTask.Http.expectJson
62
+ (Decode.nullable Decode.string)
63
+ }
64
+
65
+
66
+ {-| Get an environment variable, or a BackendTask failure if there is no environment variable matching that name.
67
+ -}
68
+ expect : String -> BackendTask (Catchable Error) String
69
+ expect envVariableName =
70
+ envVariableName
71
+ |> get
72
+ |> BackendTask.andThen
73
+ (\maybeValue ->
74
+ maybeValue
75
+ |> Result.fromMaybe
76
+ (Exception.Catchable (MissingEnvVariable envVariableName)
77
+ { title = "Missing Env Variable"
78
+ , body =
79
+ [ TerminalText.text "BackendTask.Env.expect was expecting a variable `"
80
+ , TerminalText.yellow envVariableName
81
+ , TerminalText.text "` but couldn't find a variable with that name."
82
+ ]
83
+ |> TerminalText.toString
84
+ }
85
+ )
86
+ |> BackendTask.fromResult
87
+ )
@@ -1,9 +1,10 @@
1
- module DataSource.File exposing
1
+ module BackendTask.File exposing
2
2
  ( bodyWithFrontmatter, bodyWithoutFrontmatter, onlyFrontmatter
3
3
  , jsonFile, rawFile
4
+ , FileReadError(..)
4
5
  )
5
6
 
6
- {-| This module lets you read files from the local filesystem as a [`DataSource`](DataSource#DataSource).
7
+ {-| This module lets you read files from the local filesystem as a [`BackendTask`](BackendTask#BackendTask).
7
8
  File paths are relative to the root of your `elm-pages` project (next to the `elm.json` file and `src/` directory).
8
9
 
9
10
 
@@ -40,12 +41,19 @@ plain old JSON in Elm.
40
41
 
41
42
  @docs jsonFile, rawFile
42
43
 
44
+
45
+ ## Exceptions
46
+
47
+ @docs FileReadError
48
+
43
49
  -}
44
50
 
45
- import DataSource exposing (DataSource)
46
- import DataSource.Http
47
- import DataSource.Internal.Request
51
+ import BackendTask exposing (BackendTask)
52
+ import BackendTask.Http
53
+ import BackendTask.Internal.Request
54
+ import Exception exposing (Catchable)
48
55
  import Json.Decode as Decode exposing (Decoder)
56
+ import TerminalText
49
57
 
50
58
 
51
59
  frontmatter : Decoder frontmatter -> Decoder frontmatter
@@ -55,11 +63,11 @@ frontmatter frontmatterDecoder =
55
63
 
56
64
  {-|
57
65
 
58
- import DataSource exposing (DataSource)
59
- import DataSource.File as File
66
+ import BackendTask exposing (BackendTask)
67
+ import BackendTask.File as File
60
68
  import Decode as Decode exposing (Decoder)
61
69
 
62
- blogPost : DataSource BlogPostMetadata
70
+ blogPost : BackendTask BlogPostMetadata
63
71
  blogPost =
64
72
  File.bodyWithFrontmatter blogPostDecoder
65
73
  "blog/hello-world.md"
@@ -81,7 +89,7 @@ frontmatter frontmatterDecoder =
81
89
  Decode.map (String.split " ")
82
90
  Decode.string
83
91
 
84
- This will give us a DataSource that results in the following value:
92
+ This will give us a BackendTask that results in the following value:
85
93
 
86
94
  value =
87
95
  { body = "Hey there! This is my first post :)"
@@ -91,13 +99,13 @@ This will give us a DataSource that results in the following value:
91
99
 
92
100
  It's common to parse the body with a markdown parser or other format.
93
101
 
94
- import DataSource exposing (DataSource)
95
- import DataSource.File as File
102
+ import BackendTask exposing (BackendTask)
103
+ import BackendTask.File as File
96
104
  import Decode as Decode exposing (Decoder)
97
105
  import Html exposing (Html)
98
106
 
99
107
  example :
100
- DataSource
108
+ BackendTask
101
109
  { title : String
102
110
  , body : List (Html msg)
103
111
  }
@@ -133,7 +141,7 @@ It's common to parse the body with a markdown parser or other format.
133
141
  )
134
142
 
135
143
  -}
136
- bodyWithFrontmatter : (String -> Decoder frontmatter) -> String -> DataSource frontmatter
144
+ bodyWithFrontmatter : (String -> Decoder frontmatter) -> String -> BackendTask (Catchable (FileReadError Decode.Error)) frontmatter
137
145
  bodyWithFrontmatter frontmatterDecoder filePath =
138
146
  read filePath
139
147
  (body
@@ -144,16 +152,23 @@ bodyWithFrontmatter frontmatterDecoder filePath =
144
152
  )
145
153
 
146
154
 
155
+ {-| -}
156
+ type FileReadError decoding
157
+ = FileDoesntExist
158
+ | FileReadError String
159
+ | DecodingError decoding
160
+
161
+
147
162
  {-| Same as `bodyWithFrontmatter` except it doesn't include the body.
148
163
 
149
164
  This is often useful when you're aggregating data, for example getting a listing of blog posts and need to extract
150
165
  just the metadata.
151
166
 
152
- import DataSource exposing (DataSource)
153
- import DataSource.File as File
167
+ import BackendTask exposing (BackendTask)
168
+ import BackendTask.File as File
154
169
  import Decode as Decode exposing (Decoder)
155
170
 
156
- blogPost : DataSource BlogPostMetadata
171
+ blogPost : BackendTask BlogPostMetadata
157
172
  blogPost =
158
173
  File.onlyFrontmatter
159
174
  blogPostDecoder
@@ -171,34 +186,34 @@ just the metadata.
171
186
  (Decode.field "tags" (Decode.list Decode.string))
172
187
 
173
188
  If you wanted to use this to get this metadata for all blog posts in a folder, you could use
174
- the [`DataSource`](DataSource) API along with [`DataSource.Glob`](DataSource-Glob).
189
+ the [`BackendTask`](BackendTask) API along with [`BackendTask.Glob`](BackendTask-Glob).
175
190
 
176
- import DataSource exposing (DataSource)
177
- import DataSource.File as File
191
+ import BackendTask exposing (BackendTask)
192
+ import BackendTask.File as File
178
193
  import Decode as Decode exposing (Decoder)
179
194
 
180
- blogPostFiles : DataSource (List String)
195
+ blogPostFiles : BackendTask (List String)
181
196
  blogPostFiles =
182
197
  Glob.succeed identity
183
198
  |> Glob.captureFilePath
184
199
  |> Glob.match (Glob.literal "content/blog/")
185
200
  |> Glob.match Glob.wildcard
186
201
  |> Glob.match (Glob.literal ".md")
187
- |> Glob.toDataSource
202
+ |> Glob.toBackendTask
188
203
 
189
- allMetadata : DataSource (List BlogPostMetadata)
204
+ allMetadata : BackendTask (List BlogPostMetadata)
190
205
  allMetadata =
191
206
  blogPostFiles
192
- |> DataSource.map
207
+ |> BackendTask.map
193
208
  (List.map
194
209
  (File.onlyFrontmatter
195
210
  blogPostDecoder
196
211
  )
197
212
  )
198
- |> DataSource.resolve
213
+ |> BackendTask.resolve
199
214
 
200
215
  -}
201
- onlyFrontmatter : Decoder frontmatter -> String -> DataSource frontmatter
216
+ onlyFrontmatter : Decoder frontmatter -> String -> BackendTask (Catchable (FileReadError Decode.Error)) frontmatter
202
217
  onlyFrontmatter frontmatterDecoder filePath =
203
218
  read filePath
204
219
  (frontmatter frontmatterDecoder)
@@ -216,16 +231,16 @@ tags: elm
216
231
  Hey there! This is my first post :)
217
232
  ```
218
233
 
219
- import DataSource exposing (DataSource)
234
+ import BackendTask exposing (BackendTask)
220
235
 
221
- data : DataSource String
236
+ data : BackendTask String
222
237
  data =
223
238
  bodyWithoutFrontmatter "blog/hello-world.md"
224
239
 
225
240
  Then data will yield the value `"Hey there! This is my first post :)"`.
226
241
 
227
242
  -}
228
- bodyWithoutFrontmatter : String -> DataSource String
243
+ bodyWithoutFrontmatter : String -> BackendTask (Catchable (FileReadError decoderError)) String
229
244
  bodyWithoutFrontmatter filePath =
230
245
  read filePath
231
246
  body
@@ -241,15 +256,15 @@ use `jsonFile` to get the benefits of the `Decode` here.
241
256
 
242
257
  You could read a file called `hello.txt` in your root project directory like this:
243
258
 
244
- import DataSource exposing (DataSource)
245
- import DataSource.File as File
259
+ import BackendTask exposing (BackendTask)
260
+ import BackendTask.File as File
246
261
 
247
- elmJsonFile : DataSource String
262
+ elmJsonFile : BackendTask String
248
263
  elmJsonFile =
249
264
  File.rawFile "hello.txt"
250
265
 
251
266
  -}
252
- rawFile : String -> DataSource String
267
+ rawFile : String -> BackendTask (Catchable (FileReadError decoderError)) String
253
268
  rawFile filePath =
254
269
  read filePath (Decode.field "rawFile" Decode.string)
255
270
 
@@ -258,10 +273,10 @@ rawFile filePath =
258
273
 
259
274
  The Decode will strip off any unused JSON data.
260
275
 
261
- import DataSource exposing (DataSource)
262
- import DataSource.File as File
276
+ import BackendTask exposing (BackendTask)
277
+ import BackendTask.File as File
263
278
 
264
- sourceDirectories : DataSource (List String)
279
+ sourceDirectories : BackendTask (List String)
265
280
  sourceDirectories =
266
281
  File.jsonFile
267
282
  (Decode.field
@@ -271,15 +286,24 @@ The Decode will strip off any unused JSON data.
271
286
  "elm.json"
272
287
 
273
288
  -}
274
- jsonFile : Decoder a -> String -> DataSource a
289
+ jsonFile : Decoder a -> String -> BackendTask (Catchable (FileReadError Decode.Error)) a
275
290
  jsonFile jsonFileDecoder filePath =
276
291
  rawFile filePath
277
- |> DataSource.andThen
292
+ |> BackendTask.andThen
278
293
  (\jsonString ->
279
294
  jsonString
280
295
  |> Decode.decodeString jsonFileDecoder
281
- |> Result.mapError Decode.errorToString
282
- |> DataSource.fromResult
296
+ |> Result.mapError
297
+ (\jsonDecodeError ->
298
+ Exception.Catchable (DecodingError jsonDecodeError)
299
+ { title = "JSON Decoding Error"
300
+ , body =
301
+ [ TerminalText.text (Decode.errorToString jsonDecodeError)
302
+ ]
303
+ |> TerminalText.toString
304
+ }
305
+ )
306
+ |> BackendTask.fromResult
283
307
  )
284
308
 
285
309
 
@@ -290,10 +314,32 @@ body =
290
314
  Decode.field "withoutFrontmatter" Decode.string
291
315
 
292
316
 
293
- read : String -> Decoder a -> DataSource a
317
+ read : String -> Decoder a -> BackendTask (Catchable (FileReadError error)) a
294
318
  read filePath decoder =
295
- DataSource.Internal.Request.request
319
+ BackendTask.Internal.Request.request
296
320
  { name = "read-file"
297
- , body = DataSource.Http.stringBody "" filePath
298
- , expect = decoder |> DataSource.Http.expectJson
321
+ , body = BackendTask.Http.stringBody "" filePath
322
+ , expect =
323
+ Decode.oneOf
324
+ [ Decode.field "errorCode"
325
+ (Decode.map Err (errorDecoder filePath))
326
+ , decoder |> Decode.map Ok
327
+ ]
328
+ |> BackendTask.Http.expectJson
299
329
  }
330
+ |> BackendTask.andThen BackendTask.fromResult
331
+
332
+
333
+ errorDecoder : String -> Decoder (Catchable (FileReadError decoding))
334
+ errorDecoder filePath =
335
+ Decode.succeed
336
+ (Exception.Catchable FileDoesntExist
337
+ { title = "File Doesn't Exist"
338
+ , body =
339
+ [ TerminalText.text "Couldn't find file at path `"
340
+ , TerminalText.yellow filePath
341
+ , TerminalText.text "`"
342
+ ]
343
+ |> TerminalText.toString
344
+ }
345
+ )