elm-pages 3.0.0-beta.8 → 3.0.0

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 (164) hide show
  1. package/README.md +11 -2
  2. package/adapter/netlify.js +207 -0
  3. package/codegen/{elm-pages-codegen.js → elm-pages-codegen.cjs} +2730 -2938
  4. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
  5. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
  6. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
  7. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  8. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  9. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  10. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +1 -1
  11. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1527 -422
  12. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +16840 -13653
  13. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
  14. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +2 -2
  15. package/generator/dead-code-review/elm.json +9 -7
  16. package/generator/dead-code-review/src/Pages/Review/DeadCodeEliminateData.elm +59 -10
  17. package/generator/dead-code-review/tests/Pages/Review/DeadCodeEliminateDataTest.elm +52 -36
  18. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmi +0 -0
  19. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmo +0 -0
  20. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmi +0 -0
  21. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmo +0 -0
  22. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  23. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  24. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  25. package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -1
  26. package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1527 -422
  27. package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +25118 -21832
  28. package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
  29. package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +2 -2
  30. package/generator/review/elm.json +10 -10
  31. package/generator/src/RouteBuilder.elm +93 -128
  32. package/generator/src/SharedTemplate.elm +8 -7
  33. package/generator/src/SiteConfig.elm +3 -2
  34. package/generator/src/basepath-middleware.js +3 -3
  35. package/generator/src/build.js +147 -63
  36. package/generator/src/cli.js +292 -88
  37. package/generator/src/codegen.js +29 -27
  38. package/generator/src/compatibility-key.js +3 -0
  39. package/generator/src/compile-elm.js +43 -26
  40. package/generator/src/config.js +2 -4
  41. package/generator/src/copy-dir.js +2 -2
  42. package/generator/src/dev-server.js +159 -92
  43. package/generator/src/dir-helpers.js +9 -26
  44. package/generator/src/elm-codegen.js +5 -4
  45. package/generator/src/elm-file-constants.js +2 -3
  46. package/generator/src/error-formatter.js +12 -11
  47. package/generator/src/file-helpers.js +3 -4
  48. package/generator/src/generate-template-module-connector.js +23 -23
  49. package/generator/src/init.js +9 -8
  50. package/generator/src/pre-render-html.js +10 -13
  51. package/generator/src/render-test.js +109 -0
  52. package/generator/src/render-worker.js +25 -28
  53. package/generator/src/render.js +321 -142
  54. package/generator/src/request-cache.js +265 -162
  55. package/generator/src/resolve-elm-module.js +64 -0
  56. package/generator/src/rewrite-client-elm-json.js +6 -5
  57. package/generator/src/rewrite-elm-json-help.js +56 -0
  58. package/generator/src/rewrite-elm-json.js +17 -7
  59. package/generator/src/route-codegen-helpers.js +16 -31
  60. package/generator/src/seo-renderer.js +12 -7
  61. package/generator/src/vite-utils.js +1 -2
  62. package/generator/static-code/elm-pages.js +10 -0
  63. package/generator/static-code/hmr.js +79 -13
  64. package/generator/template/app/Api.elm +3 -2
  65. package/generator/template/app/Effect.elm +155 -0
  66. package/generator/template/app/ErrorPage.elm +49 -6
  67. package/generator/template/app/Route/Blog/Slug_.elm +86 -0
  68. package/generator/template/app/Route/Greet.elm +107 -0
  69. package/generator/template/app/Route/Hello.elm +119 -0
  70. package/generator/template/app/Route/Index.elm +26 -25
  71. package/generator/template/app/Shared.elm +38 -39
  72. package/generator/template/app/Site.elm +4 -7
  73. package/generator/template/app/View.elm +9 -8
  74. package/generator/template/codegen/elm.codegen.json +18 -0
  75. package/generator/template/custom-backend-task.ts +3 -0
  76. package/generator/template/elm-pages.config.mjs +13 -0
  77. package/generator/template/elm-tooling.json +0 -3
  78. package/generator/template/elm.json +25 -20
  79. package/generator/template/index.ts +1 -2
  80. package/generator/template/netlify.toml +4 -1
  81. package/generator/template/package.json +10 -4
  82. package/generator/template/script/.elm-pages/compiled-ports/custom-backend-task.mjs +7 -0
  83. package/generator/template/script/custom-backend-task.ts +3 -0
  84. package/generator/template/script/elm.json +61 -0
  85. package/generator/template/script/src/AddRoute.elm +312 -0
  86. package/generator/template/script/src/Stars.elm +42 -0
  87. package/package.json +30 -27
  88. package/src/ApiRoute.elm +249 -85
  89. package/src/BackendTask/Custom.elm +325 -0
  90. package/src/BackendTask/Env.elm +90 -0
  91. package/src/{DataSource → BackendTask}/File.elm +171 -56
  92. package/src/{DataSource → BackendTask}/Glob.elm +136 -125
  93. package/src/BackendTask/Http.elm +679 -0
  94. package/src/{DataSource → BackendTask}/Internal/Glob.elm +1 -1
  95. package/src/BackendTask/Internal/Request.elm +69 -0
  96. package/src/BackendTask/Random.elm +79 -0
  97. package/src/BackendTask/Time.elm +47 -0
  98. package/src/BackendTask.elm +531 -0
  99. package/src/FatalError.elm +90 -0
  100. package/src/FormData.elm +21 -18
  101. package/src/Head/Seo.elm +4 -4
  102. package/src/Head.elm +237 -7
  103. package/src/Internal/ApiRoute.elm +7 -5
  104. package/src/Internal/Request.elm +84 -4
  105. package/src/PageServerResponse.elm +6 -1
  106. package/src/Pages/ConcurrentSubmission.elm +127 -0
  107. package/src/Pages/Form.elm +340 -0
  108. package/src/Pages/FormData.elm +19 -0
  109. package/src/Pages/GeneratorProgramConfig.elm +15 -0
  110. package/src/Pages/Internal/FatalError.elm +5 -0
  111. package/src/Pages/Internal/Msg.elm +93 -0
  112. package/src/Pages/Internal/NotFoundReason.elm +4 -4
  113. package/src/Pages/Internal/Platform/Cli.elm +629 -767
  114. package/src/Pages/Internal/Platform/CompatibilityKey.elm +6 -0
  115. package/src/Pages/Internal/Platform/Effect.elm +1 -2
  116. package/src/Pages/Internal/Platform/GeneratorApplication.elm +379 -0
  117. package/src/Pages/Internal/Platform/StaticResponses.elm +65 -276
  118. package/src/Pages/Internal/Platform/ToJsPayload.elm +6 -9
  119. package/src/Pages/Internal/Platform.elm +330 -203
  120. package/src/Pages/Internal/ResponseSketch.elm +2 -2
  121. package/src/Pages/Internal/Script.elm +17 -0
  122. package/src/Pages/Internal/StaticHttpBody.elm +35 -1
  123. package/src/Pages/Manifest.elm +52 -11
  124. package/src/Pages/Navigation.elm +85 -0
  125. package/src/Pages/PageUrl.elm +26 -12
  126. package/src/Pages/ProgramConfig.elm +32 -22
  127. package/src/Pages/Script.elm +166 -0
  128. package/src/Pages/SiteConfig.elm +3 -2
  129. package/src/Pages/StaticHttp/Request.elm +2 -2
  130. package/src/Pages/StaticHttpRequest.elm +23 -99
  131. package/src/Pages/Url.elm +3 -3
  132. package/src/PagesMsg.elm +88 -0
  133. package/src/QueryParams.elm +21 -172
  134. package/src/RenderRequest.elm +7 -7
  135. package/src/RequestsAndPending.elm +37 -20
  136. package/src/Result/Extra.elm +26 -0
  137. package/src/Scaffold/Form.elm +569 -0
  138. package/src/Scaffold/Route.elm +1431 -0
  139. package/src/Server/Request.elm +476 -1001
  140. package/src/Server/Response.elm +130 -36
  141. package/src/Server/Session.elm +181 -111
  142. package/src/Server/SetCookie.elm +80 -32
  143. package/src/Stub.elm +53 -0
  144. package/src/Test/Html/Internal/ElmHtml/ToString.elm +8 -9
  145. package/src/{Path.elm → UrlPath.elm} +33 -36
  146. package/generator/template/public/images/icon-png.png +0 -0
  147. package/src/DataSource/Env.elm +0 -38
  148. package/src/DataSource/Http.elm +0 -446
  149. package/src/DataSource/Internal/Request.elm +0 -20
  150. package/src/DataSource/Port.elm +0 -90
  151. package/src/DataSource.elm +0 -538
  152. package/src/Form/Field.elm +0 -717
  153. package/src/Form/FieldStatus.elm +0 -36
  154. package/src/Form/FieldView.elm +0 -417
  155. package/src/Form/FormData.elm +0 -22
  156. package/src/Form/Validation.elm +0 -391
  157. package/src/Form/Value.elm +0 -118
  158. package/src/Form.elm +0 -1683
  159. package/src/FormDecoder.elm +0 -102
  160. package/src/Pages/FormState.elm +0 -256
  161. package/src/Pages/Generate.elm +0 -1151
  162. package/src/Pages/Internal/Form.elm +0 -17
  163. package/src/Pages/Msg.elm +0 -79
  164. package/src/Pages/Transition.elm +0 -70
@@ -1,48 +1,53 @@
1
1
  module Server.Session exposing
2
- ( withSession
2
+ ( withSession, withSessionResult
3
3
  , NotLoadedReason(..)
4
4
  , Session, empty, get, insert, remove, update, withFlash
5
5
  )
6
6
 
7
- {-| You can manage server state with HTTP cookies using this Server.Session API. Server-rendered pages define a `Server.Request.Parser`
8
- to choose which requests to respond to and how to extract structured data from the incoming request.
7
+ {-| You can manage server state with HTTP cookies using this Server.Session API. Server-rendered routes have a `Server.Request.Request`
8
+ argument that lets you inspect the incoming HTTP request, and return a response using the `Server.Response.Response` type.
9
9
 
10
+ This API provides a higher-level abstraction for extracting data from the HTTP request, and setting data in the HTTP response.
11
+ It manages the session through key-value data stored in cookies, and lets you [`insert`](#insert), [`update`](#update), and [`remove`](#remove)
12
+ values from the Session. It also provides an abstraction for flash session values through [`withFlash`](#withFlash).
10
13
 
11
- ## Using Sessions in a Request.Parser
14
+
15
+ ## Using Sessions
12
16
 
13
17
  Using these functions, you can store and read session data in cookies to maintain state between requests.
14
- For example, TODO:
15
-
16
- action : RouteParams -> Request.Parser (DataSource (Response ActionData ErrorPage))
17
- action routeParams =
18
- MySession.withSession
19
- (Request.formDataWithServerValidation (form |> Form.initCombinedServer identity))
20
- (\nameResultData session ->
21
- nameResultData
22
- |> DataSource.map
23
- (\nameResult ->
24
- case nameResult of
25
- Err errors ->
26
- ( session
27
- |> Result.withDefault Nothing
28
- |> Maybe.withDefault Session.empty
29
- , Response.render
30
- { errors = errors
31
- }
32
- )
33
18
 
34
- Ok ( _, name ) ->
35
- ( session
36
- |> Result.withDefault Nothing
37
- |> Maybe.withDefault Session.empty
38
- |> Session.insert "name" name
39
- |> Session.withFlash "message" ("Welcome " ++ name ++ "!")
40
- , Route.redirectTo Route.Greet
41
- )
42
- )
43
- )
19
+ import Server.Session as Session
20
+
21
+ secrets : BackendTask FatalError (List String)
22
+ secrets =
23
+ Env.expect "SESSION_SECRET"
24
+ |> BackendTask.allowFatal
25
+ |> BackendTask.map List.singleton
26
+
27
+ type alias Data =
28
+ { darkMode : Bool }
29
+
30
+ data : RouteParams -> Request -> BackendTask (Response Data ErrorPage)
31
+ data routeParams request =
32
+ request
33
+ |> Session.withSession
34
+ { name = "mysession"
35
+ , secrets = secrets
36
+ , options = Nothing
37
+ }
38
+ (\session ->
39
+ let
40
+ darkMode : Bool
41
+ darkMode =
42
+ (session |> Session.get "mode" |> Maybe.withDefault "light")
43
+ == "dark"
44
+ in
45
+ ( session
46
+ , { darkMode = darkMode }
47
+ )
48
+ )
44
49
 
45
- The elm-pages framework will manage signing these cookies using the `secrets : DataSource (List String)` you pass in.
50
+ The elm-pages framework will manage signing these cookies using the `secrets : BackendTask (List String)` you pass in.
46
51
  That means that the values you set in your session will be directly visible to anyone who has access to the cookie
47
52
  (so don't directly store sensitive data in your session). Since the session cookie is signed using the secret you provide,
48
53
  the cookie will be invalidated if it is tampered with because it won't match when elm-pages verifies that it has been
@@ -51,15 +56,15 @@ signed with your secrets. Of course you need to provide secure secrets and treat
51
56
 
52
57
  ### Rotating Secrets
53
58
 
54
- The first String in `secrets : DataSource (List String)` will be used to sign sessions, while the remaining String's will
59
+ The first String in `secrets : BackendTask (List String)` will be used to sign sessions, while the remaining String's will
55
60
  still be used to attempt to "unsign" the cookies. So if you have a single secret:
56
61
 
57
62
  Session.withSession
58
63
  { name = "mysession"
59
64
  , secrets =
60
- DataSource.map List.singleton
65
+ BackendTask.map List.singleton
61
66
  (Env.expect "SESSION_SECRET2022-09-01")
62
- , options = cookieOptions
67
+ , options = Nothing
63
68
  }
64
69
 
65
70
  Then you add a second secret
@@ -67,11 +72,11 @@ Then you add a second secret
67
72
  Session.withSession
68
73
  { name = "mysession"
69
74
  , secrets =
70
- DataSource.map2
75
+ BackendTask.map2
71
76
  (\newSecret oldSecret -> [ newSecret, oldSecret ])
72
77
  (Env.expect "SESSION_SECRET2022-12-01")
73
78
  (Env.expect "SESSION_SECRET2022-09-01")
74
- , options = cookieOptions
79
+ , options = Nothing
75
80
  }
76
81
 
77
82
  The new secret (`2022-12-01`) will be used to sign all requests. This API always re-signs using the newest secret in the list
@@ -87,16 +92,16 @@ it will invalidate all cookies signed with that. For example, if we remove our o
87
92
  Session.withSession
88
93
  { name = "mysession"
89
94
  , secrets =
90
- DataSource.map List.singleton
95
+ BackendTask.map List.singleton
91
96
  (Env.expect "SESSION_SECRET2022-12-01")
92
- , options = cookieOptions
97
+ , options = Nothing
93
98
  }
94
99
 
95
100
  And then a user makes a request but had a session signed with our old secret (`2022-09-01`), the session will be invalid
96
101
  (so `withSession` would parse the session for that request as `Nothing`). It's standard for cookies to have an expiration date,
97
102
  so 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.
98
103
 
99
- @docs withSession
104
+ @docs withSession, withSessionResult
100
105
 
101
106
  @docs NotLoadedReason
102
107
 
@@ -107,18 +112,23 @@ so there's nothing wrong with an old session expiring (and the browser will even
107
112
 
108
113
  -}
109
114
 
110
- import DataSource exposing (DataSource)
111
- import DataSource.Http
112
- import DataSource.Internal.Request
115
+ import BackendTask exposing (BackendTask)
116
+ import BackendTask.Http
117
+ import BackendTask.Internal.Request
113
118
  import Dict exposing (Dict)
114
119
  import Json.Decode
115
120
  import Json.Encode
116
- import Server.Request
121
+ import Server.Request exposing (Request)
117
122
  import Server.Response exposing (Response)
118
123
  import Server.SetCookie as SetCookie
119
124
 
120
125
 
121
- {-| -}
126
+ {-| Represents a Session with key-value Strings.
127
+
128
+ Use with `withSession` to read in the `Session`, and encode any changes you make to the `Session` back through cookie storage
129
+ via the outgoing HTTP response.
130
+
131
+ -}
122
132
  type Session
123
133
  = Session (Dict String Value)
124
134
 
@@ -130,7 +140,12 @@ type Value
130
140
  | NewFlash String
131
141
 
132
142
 
133
- {-| -}
143
+ {-| Flash session values are values that are only available for the next request.
144
+
145
+ session
146
+ |> Session.withFlash "message" "Your payment was successful!"
147
+
148
+ -}
134
149
  withFlash : String -> String -> Session -> Session
135
150
  withFlash key value (Session session) =
136
151
  session
@@ -138,7 +153,12 @@ withFlash key value (Session session) =
138
153
  |> Session
139
154
 
140
155
 
141
- {-| -}
156
+ {-| Insert a value under the given key in the `Session`.
157
+
158
+ session
159
+ |> Session.insert "mode" "dark"
160
+
161
+ -}
142
162
  insert : String -> String -> Session -> Session
143
163
  insert key value (Session session) =
144
164
  session
@@ -146,7 +166,15 @@ insert key value (Session session) =
146
166
  |> Session
147
167
 
148
168
 
149
- {-| -}
169
+ {-| Retrieve a String value from the session for the given key (or `Nothing` if the key is not present).
170
+
171
+ (session
172
+ |> Session.get "mode"
173
+ |> Maybe.withDefault "light"
174
+ )
175
+ == "dark"
176
+
177
+ -}
150
178
  get : String -> Session -> Maybe String
151
179
  get key (Session session) =
152
180
  session
@@ -168,7 +196,25 @@ unwrap value =
168
196
  string
169
197
 
170
198
 
171
- {-| -}
199
+ {-| Update the `Session`, given a `Maybe String` of the current value for the given key, and returning a `Maybe String`.
200
+
201
+ If 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).
202
+
203
+ session
204
+ |> Session.update "mode"
205
+ (\mode ->
206
+ case mode of
207
+ Just "dark" ->
208
+ Just "light"
209
+
210
+ Just "light" ->
211
+ Just "dark"
212
+
213
+ Nothing ->
214
+ Just "dark"
215
+ )
216
+
217
+ -}
172
218
  update : String -> (Maybe String -> Maybe String) -> Session -> Session
173
219
  update key updateFn (Session session) =
174
220
  session
@@ -192,7 +238,8 @@ update key updateFn (Session session) =
192
238
  |> Session
193
239
 
194
240
 
195
- {-| -}
241
+ {-| Remove a key from the `Session`.
242
+ -}
196
243
  remove : String -> Session -> Session
197
244
  remove key (Session session) =
198
245
  session
@@ -200,13 +247,15 @@ remove key (Session session) =
200
247
  |> Session
201
248
 
202
249
 
203
- {-| -}
250
+ {-| An empty `Session` with no key-value pairs.
251
+ -}
204
252
  empty : Session
205
253
  empty =
206
254
  Session Dict.empty
207
255
 
208
256
 
209
- {-| -}
257
+ {-| [`withSessionResult`](#withSessionResult) will return a `Result` with this type if it can't load a session.
258
+ -}
210
259
  type NotLoadedReason
211
260
  = NoSessionCookie
212
261
  | InvalidSessionCookie
@@ -239,66 +288,87 @@ flashPrefix =
239
288
  "__flash__"
240
289
 
241
290
 
242
- {-| -}
291
+ {-| The main function for using sessions. If you need more fine-grained control over cases where a session can't be loaded, see
292
+ [`withSessionResult`](#withSessionResult).
293
+ -}
243
294
  withSession :
244
295
  { name : String
245
- , secrets : DataSource (List String)
246
- , options : SetCookie.Options
296
+ , secrets : BackendTask error (List String)
297
+ , options : Maybe SetCookie.Options
247
298
  }
248
- -> (request -> Result NotLoadedReason Session -> DataSource ( Session, Response data errorPage ))
249
- -> Server.Request.Parser request
250
- -> Server.Request.Parser (DataSource (Response data errorPage))
251
- withSession config toRequest userRequest =
252
- Server.Request.map2
253
- (\maybeSessionCookie userRequestData ->
254
- let
255
- unsigned : DataSource (Result NotLoadedReason Session)
256
- unsigned =
257
- case maybeSessionCookie of
258
- Just sessionCookie ->
259
- sessionCookie
260
- |> unsignCookie config
261
- |> DataSource.map
262
- (\unsignResult ->
263
- case unsignResult of
264
- Ok decoded ->
265
- Ok decoded
266
-
267
- Err () ->
268
- Err InvalidSessionCookie
269
- )
299
+ -> (Session -> BackendTask error ( Session, Response data errorPage ))
300
+ -> Request
301
+ -> BackendTask error (Response data errorPage)
302
+ withSession config toRequest request_ =
303
+ request_
304
+ |> withSessionResult config
305
+ (\session ->
306
+ session
307
+ |> Result.withDefault empty
308
+ |> toRequest
309
+ )
310
+
311
+
312
+ {-| Same as `withSession`, but gives you an `Err` with the reason why the Session couldn't be loaded instead of
313
+ using `Session.empty` as a default in the cases where there is an error loading the session.
314
+
315
+ A 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
316
+ or otherwise corrupted, or because the cookie was signed with a secret that is no longer in the rotation.
317
+
318
+ -}
319
+ withSessionResult :
320
+ { name : String
321
+ , secrets : BackendTask error (List String)
322
+ , options : Maybe SetCookie.Options
323
+ }
324
+ -> (Result NotLoadedReason Session -> BackendTask error ( Session, Response data errorPage ))
325
+ -> Request
326
+ -> BackendTask error (Response data errorPage)
327
+ withSessionResult config toTask request =
328
+ let
329
+ unsigned : BackendTask error (Result NotLoadedReason Session)
330
+ unsigned =
331
+ case Server.Request.cookie config.name request of
332
+ Just sessionCookie ->
333
+ sessionCookie
334
+ |> unsignCookie config
335
+ |> BackendTask.map
336
+ (\unsignResult ->
337
+ case unsignResult of
338
+ Ok decoded ->
339
+ Ok decoded
340
+
341
+ Err () ->
342
+ Err InvalidSessionCookie
343
+ )
270
344
 
271
- Nothing ->
272
- Err NoSessionCookie
273
- |> DataSource.succeed
274
- in
275
- unsigned
276
- |> DataSource.andThen
277
- (encodeSessionUpdate config toRequest userRequestData)
278
- )
279
- (Server.Request.cookie config.name)
280
- userRequest
345
+ Nothing ->
346
+ Err NoSessionCookie
347
+ |> BackendTask.succeed
348
+ in
349
+ unsigned
350
+ |> BackendTask.andThen
351
+ (encodeSessionUpdate config toTask)
281
352
 
282
353
 
283
354
  encodeSessionUpdate :
284
355
  { name : String
285
- , secrets : DataSource (List String)
286
- , options : SetCookie.Options
356
+ , secrets : BackendTask error (List String)
357
+ , options : Maybe SetCookie.Options
287
358
  }
288
- -> (c -> d -> DataSource ( Session, Response data errorPage ))
289
- -> c
359
+ -> (d -> BackendTask error ( Session, Response data errorPage ))
290
360
  -> d
291
- -> DataSource (Response data errorPage)
292
- encodeSessionUpdate config toRequest userRequestData sessionResult =
361
+ -> BackendTask error (Response data errorPage)
362
+ encodeSessionUpdate config toRequest sessionResult =
293
363
  sessionResult
294
- |> toRequest userRequestData
295
- |> DataSource.andThen
364
+ |> toRequest
365
+ |> BackendTask.andThen
296
366
  (\( sessionUpdate, response ) ->
297
- DataSource.map
367
+ BackendTask.map
298
368
  (\encoded ->
299
369
  response
300
370
  |> Server.Response.withSetCookieHeader
301
- (SetCookie.setCookie config.name encoded config.options)
371
+ (SetCookie.setCookie config.name encoded (config.options |> Maybe.withDefault SetCookie.options))
302
372
  )
303
373
  (sign config.secrets
304
374
  (encodeNonExpiringPairs sessionUpdate)
@@ -306,11 +376,11 @@ encodeSessionUpdate config toRequest userRequestData sessionResult =
306
376
  )
307
377
 
308
378
 
309
- unsignCookie : { a | secrets : DataSource (List String) } -> String -> DataSource (Result () Session)
379
+ unsignCookie : { a | secrets : BackendTask error (List String) } -> String -> BackendTask error (Result () Session)
310
380
  unsignCookie config sessionCookie =
311
381
  sessionCookie
312
382
  |> unsign config.secrets (Json.Decode.dict Json.Decode.string)
313
- |> DataSource.map
383
+ |> BackendTask.map
314
384
  (Result.map
315
385
  (\dict ->
316
386
  dict
@@ -331,15 +401,15 @@ unsignCookie config sessionCookie =
331
401
  )
332
402
 
333
403
 
334
- sign : DataSource (List String) -> Json.Encode.Value -> DataSource String
404
+ sign : BackendTask error (List String) -> Json.Encode.Value -> BackendTask error String
335
405
  sign getSecrets input =
336
406
  getSecrets
337
- |> DataSource.andThen
407
+ |> BackendTask.andThen
338
408
  (\secrets ->
339
- DataSource.Internal.Request.request
409
+ BackendTask.Internal.Request.request
340
410
  { name = "encrypt"
341
411
  , body =
342
- DataSource.Http.jsonBody
412
+ BackendTask.Http.jsonBody
343
413
  (Json.Encode.object
344
414
  [ ( "values", input )
345
415
  , ( "secret"
@@ -353,21 +423,21 @@ sign getSecrets input =
353
423
  ]
354
424
  )
355
425
  , expect =
356
- DataSource.Http.expectJson
426
+ BackendTask.Http.expectJson
357
427
  Json.Decode.string
358
428
  }
359
429
  )
360
430
 
361
431
 
362
- unsign : DataSource (List String) -> Json.Decode.Decoder a -> String -> DataSource (Result () a)
432
+ unsign : BackendTask error (List String) -> Json.Decode.Decoder a -> String -> BackendTask error (Result () a)
363
433
  unsign getSecrets decoder input =
364
434
  getSecrets
365
- |> DataSource.andThen
435
+ |> BackendTask.andThen
366
436
  (\secrets ->
367
- DataSource.Internal.Request.request
437
+ BackendTask.Internal.Request.request
368
438
  { name = "decrypt"
369
439
  , body =
370
- DataSource.Http.jsonBody
440
+ BackendTask.Http.jsonBody
371
441
  (Json.Encode.object
372
442
  [ ( "input", Json.Encode.string input )
373
443
  , ( "secrets", Json.Encode.list Json.Encode.string secrets )
@@ -377,6 +447,6 @@ unsign getSecrets decoder input =
377
447
  decoder
378
448
  |> Json.Decode.nullable
379
449
  |> Json.Decode.map (Result.fromMaybe ())
380
- |> DataSource.Http.expectJson
450
+ |> BackendTask.Http.expectJson
381
451
  }
382
452
  )
@@ -1,8 +1,8 @@
1
1
  module Server.SetCookie exposing
2
- ( SetCookie
3
- , SameSite(..)
4
- , Options, initOptions
5
- , withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
2
+ ( SetCookie, setCookie
3
+ , Options, options
4
+ , SameSite(..), withSameSite
5
+ , withImmediateExpiration, makeVisibleToJavaScript, nonSecure, withDomain, withExpiration, withMaxAge, withPath, withoutPath
6
6
  , toString
7
7
  )
8
8
 
@@ -20,16 +20,29 @@ You can learn more about the basics of cookies in the Web Platform in these help
20
20
  - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>
21
21
  - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>
22
22
 
23
- @docs SetCookie
23
+ @docs SetCookie, setCookie
24
24
 
25
- @docs SameSite
26
25
 
26
+ ## Building Options
27
27
 
28
- ## Options
28
+ Usually you'll want to start by creating default `Options` with `options` and then overriding defaults using the `with...` helpers.
29
29
 
30
- @docs Options, initOptions
30
+ import Server.SetCookie as SetCookie
31
31
 
32
- @docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, setCookie, withDomain, withExpiration, withMaxAge, withPath, withSameSite
32
+ options : SetCookie.Options
33
+ options =
34
+ SetCookie.options
35
+ |> SetCookie.nonSecure
36
+ |> SetCookie.withMaxAge 123
37
+ |> SetCookie.makeVisibleToJavaScript
38
+ |> SetCookie.withoutPath
39
+ |> SetCookie.setCookie "id" "a3fWa"
40
+
41
+ @docs Options, options
42
+
43
+ @docs SameSite, withSameSite
44
+
45
+ @docs withImmediateExpiration, makeVisibleToJavaScript, nonSecure, withDomain, withExpiration, withMaxAge, withPath, withoutPath
33
46
 
34
47
 
35
48
  ## Internal
@@ -51,7 +64,8 @@ type alias SetCookie =
51
64
  }
52
65
 
53
66
 
54
- {-| -}
67
+ {-| The set of possible configuration options. You can configure this record directly, or use the `with...` helpers.
68
+ -}
55
69
  type alias Options =
56
70
  { expiration : Maybe Time.Posix
57
71
  , visibleToJavaScript : Bool
@@ -63,7 +77,15 @@ type alias Options =
63
77
  }
64
78
 
65
79
 
66
- {-| -}
80
+ {-| Possible values for [the cookie's same-site value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value).
81
+
82
+ The default option is [`Lax`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#lax) (Lax does not send
83
+ cookies 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)
84
+ is even more restrictive).
85
+
86
+ Override the default option using [`withSameSite`](#withSameSite).
87
+
88
+ -}
67
89
  type SameSite
68
90
  = Strict
69
91
  | Lax
@@ -95,24 +117,24 @@ toString builder =
95
117
  else
96
118
  ""
97
119
 
98
- options : Options
99
- options =
120
+ options_ : Options
121
+ options_ =
100
122
  builder.options
101
123
 
102
124
  httpOnly : Bool
103
125
  httpOnly =
104
- not options.visibleToJavaScript
126
+ not options_.visibleToJavaScript
105
127
  in
106
128
  builder.name
107
129
  ++ "="
108
130
  ++ Url.percentEncode builder.value
109
- ++ option "Expires" (options.expiration |> Maybe.map Utc.fromTime)
110
- ++ option "Max-Age" (options.maxAge |> Maybe.map String.fromInt)
111
- ++ option "Path" options.path
112
- ++ option "Domain" options.domain
113
- ++ option "SameSite" (options.sameSite |> Maybe.map sameSiteToString)
131
+ ++ option "Expires" (options_.expiration |> Maybe.map Utc.fromTime)
132
+ ++ option "Max-Age" (options_.maxAge |> Maybe.map String.fromInt)
133
+ ++ option "Path" options_.path
134
+ ++ option "Domain" options_.domain
135
+ ++ option "SameSite" (options_.sameSite |> Maybe.map sameSiteToString)
114
136
  ++ boolOption "HttpOnly" httpOnly
115
- ++ boolOption "Secure" options.secure
137
+ ++ boolOption "Secure" options_.secure
116
138
 
117
139
 
118
140
  sameSiteToString : SameSite -> String
@@ -128,22 +150,26 @@ sameSiteToString sameSite =
128
150
  "None"
129
151
 
130
152
 
131
- {-| -}
153
+ {-| Create a `SetCookie` record with the given name, value, and [`Options`](Options]. To add a `Set-Cookie` header, you can
154
+ pass this value with [`Server.Response.withSetCookieHeader`](Server-Response#withSetCookieHeader). Or for more low-level
155
+ uses you can stringify the value manually with [`toString`](#toString).
156
+ -}
132
157
  setCookie : String -> String -> Options -> SetCookie
133
- setCookie name value options =
158
+ setCookie name value options_ =
134
159
  { name = name
135
160
  , value = value
136
- , options = options
161
+ , options = options_
137
162
  }
138
163
 
139
164
 
140
- {-| -}
141
- initOptions : Options
142
- initOptions =
165
+ {-| Initialize the default `SetCookie` `Options`. Can be configured directly through a record update, or with `withExpiration`, etc.
166
+ -}
167
+ options : Options
168
+ options =
143
169
  { expiration = Nothing
144
170
  , visibleToJavaScript = False
145
171
  , maxAge = Nothing
146
- , path = Nothing
172
+ , path = Just "/"
147
173
  , domain = Nothing
148
174
  , secure = True
149
175
  , sameSite = Nothing
@@ -158,7 +184,9 @@ withExpiration time builder =
158
184
  }
159
185
 
160
186
 
161
- {-| -}
187
+ {-| Sets [`Expires`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#expiresdate) to `Time.millisToPosix 0`,
188
+ which effectively tells the browser to delete the cookie immediately (by giving it an expiration date in the past).
189
+ -}
162
190
  withImmediateExpiration : Options -> Options
163
191
  withImmediateExpiration builder =
164
192
  { builder
@@ -174,7 +202,7 @@ dynamically). In this API you opt into exposing a cookie you set to JavaScript t
174
202
 
175
203
  In general if you can accomplish your goal using HttpOnly cookies (i.e. not using `makeVisibleToJavaScript`) then
176
204
  it's a good practice. With server-rendered `elm-pages` applications you can often manage your session state by pulling
177
- in session data from cookies in a `DataSource` (which is resolved server-side before it ever reaches the browser).
205
+ in session data from cookies in a `BackendTask` (which is resolved server-side before it ever reaches the browser).
178
206
 
179
207
  -}
180
208
  makeVisibleToJavaScript : Options -> Options
@@ -184,7 +212,8 @@ makeVisibleToJavaScript builder =
184
212
  }
185
213
 
186
214
 
187
- {-| -}
215
+ {-| Sets the `Set-Cookie`'s [`Max-Age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#max-agenumber).
216
+ -}
188
217
  withMaxAge : Int -> Options -> Options
189
218
  withMaxAge maxAge builder =
190
219
  { builder
@@ -192,7 +221,11 @@ withMaxAge maxAge builder =
192
221
  }
193
222
 
194
223
 
195
- {-| -}
224
+ {-| Sets the `Set-Cookie`'s [`Path`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value).
225
+
226
+ The default value is `/`, which will match any sub-directories or the root directory. See also [\`withoutPath](#withoutPath)
227
+
228
+ -}
196
229
  withPath : String -> Options -> Options
197
230
  withPath path builder =
198
231
  { builder
@@ -200,7 +233,22 @@ withPath path builder =
200
233
  }
201
234
 
202
235
 
203
- {-| -}
236
+ {-|
237
+
238
+ > 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.
239
+
240
+ Source: <https://www.rfc-editor.org/rfc/rfc6265>. See <https://stackoverflow.com/a/43336097>.
241
+
242
+ -}
243
+ withoutPath : Options -> Options
244
+ withoutPath builder =
245
+ { builder
246
+ | path = Nothing
247
+ }
248
+
249
+
250
+ {-| Sets the `Set-Cookie`'s [`Domain`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value).
251
+ -}
204
252
  withDomain : String -> Options -> Options
205
253
  withDomain domain builder =
206
254
  { builder