@defra/forms-engine-plugin 2.1.9 → 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 (126) hide show
  1. package/.server/server/index.js +2 -1
  2. package/.server/server/index.js.map +1 -1
  3. package/.server/server/plugins/engine/README.md +2 -2
  4. package/.server/server/plugins/engine/components/helpers/components.js +1 -1
  5. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  6. package/.server/server/plugins/engine/configureEnginePlugin.d.ts +2 -1
  7. package/.server/server/plugins/engine/configureEnginePlugin.js +4 -4
  8. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  9. package/.server/server/plugins/engine/helpers.d.ts +7 -11
  10. package/.server/server/plugins/engine/helpers.js +2 -2
  11. package/.server/server/plugins/engine/helpers.js.map +1 -1
  12. package/.server/server/plugins/engine/models/FormModel.js +1 -1
  13. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  14. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +1 -1
  15. package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
  16. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  17. package/.server/server/plugins/engine/options.js +3 -6
  18. package/.server/server/plugins/engine/options.js.map +1 -1
  19. package/.server/server/plugins/engine/options.test.js +2 -8
  20. package/.server/server/plugins/engine/options.test.js.map +1 -1
  21. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +5 -6
  22. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  23. package/.server/server/plugins/engine/pageControllers/PageController.d.ts +6 -6
  24. package/.server/server/plugins/engine/pageControllers/PageController.js +4 -4
  25. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  26. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +13 -13
  27. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +12 -20
  28. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  29. package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +7 -8
  30. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  31. package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -2
  32. package/.server/server/plugins/engine/pageControllers/StartPageController.js +1 -1
  33. package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
  34. package/.server/server/plugins/engine/pageControllers/StatusPageController.d.ts +3 -4
  35. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +1 -1
  36. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  37. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +5 -4
  38. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +12 -1
  39. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  40. package/.server/server/plugins/engine/pageControllers/TerminalPageController.d.ts +4 -4
  41. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js +1 -1
  42. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js.map +1 -1
  43. package/.server/server/plugins/engine/pageControllers/__stubs__/server.d.ts +1 -1
  44. package/.server/server/plugins/engine/pageControllers/__stubs__/server.js +2 -6
  45. package/.server/server/plugins/engine/pageControllers/__stubs__/server.js.map +1 -1
  46. package/.server/server/plugins/engine/plugin.js +7 -12
  47. package/.server/server/plugins/engine/plugin.js.map +1 -1
  48. package/.server/server/plugins/engine/routes/index.d.ts +5 -5
  49. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  50. package/.server/server/plugins/engine/routes/questions.d.ts +4 -4
  51. package/.server/server/plugins/engine/routes/questions.js.map +1 -1
  52. package/.server/server/plugins/engine/routes/repeaters/item-delete.js.map +1 -1
  53. package/.server/server/plugins/engine/routes/repeaters/summary.js.map +1 -1
  54. package/.server/server/plugins/engine/types/index.d.ts +2 -2
  55. package/.server/server/plugins/engine/types/index.js.map +1 -1
  56. package/.server/server/plugins/engine/types.d.ts +10 -11
  57. package/.server/server/plugins/engine/types.js.map +1 -1
  58. package/.server/server/plugins/engine/views/partials/form.html +3 -3
  59. package/.server/server/plugins/engine/views/summary.html +21 -5
  60. package/.server/server/plugins/nunjucks/context.d.ts +5 -6
  61. package/.server/server/plugins/nunjucks/context.js +3 -3
  62. package/.server/server/plugins/nunjucks/context.js.map +1 -1
  63. package/.server/server/routes/types.d.ts +3 -2
  64. package/.server/server/routes/types.js +1 -1
  65. package/.server/server/routes/types.js.map +1 -1
  66. package/.server/server/schemas/index.js +1 -1
  67. package/.server/server/schemas/index.js.map +1 -1
  68. package/.server/server/services/cacheService.d.ts +11 -19
  69. package/.server/server/services/cacheService.js +9 -30
  70. package/.server/server/services/cacheService.js.map +1 -1
  71. package/.server/server/types.d.ts +4 -1
  72. package/.server/server/types.js.map +1 -1
  73. package/.server/typings/hapi/index.d.js.map +1 -1
  74. package/package.json +3 -1
  75. package/src/server/index.test.ts +0 -39
  76. package/src/server/index.ts +4 -1
  77. package/src/server/plugins/engine/README.md +2 -2
  78. package/src/server/plugins/engine/components/MultilineTextField.test.ts +11 -0
  79. package/src/server/plugins/engine/components/helpers/components.ts +1 -1
  80. package/src/server/plugins/engine/components/helpers/helpers.test.ts +1 -1
  81. package/src/server/plugins/engine/configureEnginePlugin.ts +15 -11
  82. package/src/server/plugins/engine/helpers.test.ts +3 -2
  83. package/src/server/plugins/engine/helpers.ts +6 -6
  84. package/src/server/plugins/engine/models/FormModel.test.ts +2 -2
  85. package/src/server/plugins/engine/models/FormModel.ts +1 -2
  86. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +7 -7
  87. package/src/server/plugins/engine/models/SummaryViewModel.ts +1 -1
  88. package/src/server/plugins/engine/options.js +6 -6
  89. package/src/server/plugins/engine/options.test.js +2 -6
  90. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +8 -10
  91. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +9 -8
  92. package/src/server/plugins/engine/pageControllers/PageController.test.ts +11 -8
  93. package/src/server/plugins/engine/pageControllers/PageController.ts +9 -15
  94. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +35 -102
  95. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +24 -36
  96. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +4 -6
  97. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +8 -11
  98. package/src/server/plugins/engine/pageControllers/StartPageController.test.ts +4 -4
  99. package/src/server/plugins/engine/pageControllers/StartPageController.ts +1 -1
  100. package/src/server/plugins/engine/pageControllers/StatusPageController.test.ts +4 -4
  101. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +6 -4
  102. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -5
  103. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +4 -4
  104. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +7 -4
  105. package/src/server/plugins/engine/pageControllers/__stubs__/server.ts +5 -6
  106. package/src/server/plugins/engine/plugin.ts +7 -13
  107. package/src/server/plugins/engine/routes/index.ts +6 -11
  108. package/src/server/plugins/engine/routes/questions.test.ts +29 -53
  109. package/src/server/plugins/engine/routes/questions.ts +6 -8
  110. package/src/server/plugins/engine/routes/repeaters/item-delete.ts +5 -14
  111. package/src/server/plugins/engine/routes/repeaters/summary.ts +5 -14
  112. package/src/server/plugins/engine/types/index.ts +4 -1
  113. package/src/server/plugins/engine/types.ts +19 -13
  114. package/src/server/plugins/engine/views/partials/form.html +3 -3
  115. package/src/server/plugins/engine/views/summary.html +21 -5
  116. package/src/server/plugins/nunjucks/context.js +3 -3
  117. package/src/server/routes/types.ts +7 -2
  118. package/src/server/schemas/index.ts +1 -1
  119. package/src/server/services/cacheService.test.ts +1 -117
  120. package/src/server/services/cacheService.ts +22 -73
  121. package/src/server/types.ts +4 -1
  122. package/src/typings/hapi/index.d.ts +6 -7
  123. package/.server/server/plugins/engine/routes/exit.d.ts +0 -46
  124. package/.server/server/plugins/engine/routes/exit.js +0 -36
  125. package/.server/server/plugins/engine/routes/exit.js.map +0 -1
  126. package/src/server/plugins/engine/routes/exit.ts +0 -47
@@ -13,12 +13,12 @@
13
13
  preventDoubleClick: true
14
14
  }) }}
15
15
 
16
- {% if allowSaveAndReturn %}
16
+ {% if allowSaveAndExit %}
17
17
  {{ govukButton({
18
- text: "Save and return",
18
+ text: "Save and exit",
19
19
  classes: "govuk-button--secondary",
20
20
  name: "action",
21
- value: "save-and-return",
21
+ value: "save-and-exit",
22
22
  preventDoubleClick: true
23
23
  }) }}
24
24
  {% endif %}
@@ -2,6 +2,7 @@
2
2
 
3
3
  {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
4
4
  {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
5
+ {% from "govuk/components/button/macro.njk" import govukButton %}
5
6
  {% from "partials/components.html" import componentList with context %}
6
7
 
7
8
  {% block content %}
@@ -31,7 +32,6 @@
31
32
 
32
33
  <form method="post" novalidate>
33
34
  <input type="hidden" name="crumb" value="{{ crumb }}">
34
- <input type="hidden" name="action" value="send">
35
35
 
36
36
  {% if declaration %}
37
37
  <h2 class="govuk-heading-m" id="declaration">Declaration</h2>
@@ -42,10 +42,26 @@
42
42
 
43
43
  {{ componentList(components) }}
44
44
 
45
- {% set isDeclaration = declaration or components | length %}
46
- <button data-prevent-double-click="true" class="govuk-button" data-module="govuk-button">
47
- {{ "Accept and send" if isDeclaration else "Send" }}
48
- </button>
45
+ <div class="govuk-button-group">
46
+ {% set isDeclaration = declaration or components | length %}
47
+
48
+ {{ govukButton({
49
+ text: "Accept and send" if isDeclaration else "Send",
50
+ name: "action",
51
+ value: "send",
52
+ preventDoubleClick: true
53
+ }) }}
54
+
55
+ {% if allowSaveAndExit %}
56
+ {{ govukButton({
57
+ text: "Save and exit",
58
+ classes: "govuk-button--secondary",
59
+ name: "action",
60
+ value: "save-and-exit",
61
+ preventDoubleClick: true
62
+ }) }}
63
+ {% endif %}
64
+ </div>
49
65
  </form>
50
66
  </div>
51
67
  </div>
@@ -18,7 +18,7 @@ const logger = createLogger()
18
18
  let webpackManifest
19
19
 
20
20
  /**
21
- * @param {FormRequest | FormRequestPayload | null} request
21
+ * @param {AnyFormRequest | null} request
22
22
  */
23
23
  export async function context(request) {
24
24
  const { params, response } = request ?? {}
@@ -62,7 +62,7 @@ export async function context(request) {
62
62
 
63
63
  /**
64
64
  * Returns the context for the devtool. Consumers won't have access to this.
65
- * @param {FormRequest | FormRequestPayload | null} _request
65
+ * @param {AnyFormRequest | null} _request
66
66
  * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}
67
67
  */
68
68
  export function devtoolContext(_request) {
@@ -97,5 +97,5 @@ export function devtoolContext(_request) {
97
97
 
98
98
  /**
99
99
  * @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'
100
- * @import { FormRequest, FormRequestPayload } from '~/src/server/routes/types.js'
100
+ * @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'
101
101
  */
@@ -1,4 +1,8 @@
1
- import { type ReqRefDefaults, type Request } from '@hapi/hapi'
1
+ import {
2
+ type ReqRefDefaults,
3
+ type Request,
4
+ type ResponseToolkit
5
+ } from '@hapi/hapi'
2
6
 
3
7
  import { type FormPayload } from '~/src/server/plugins/engine/types.js'
4
8
 
@@ -33,6 +37,7 @@ export interface FormRequestPayloadRefs extends FormRequestRefs {
33
37
 
34
38
  export type FormRequest = Request<FormRequestRefs>
35
39
  export type FormRequestPayload = Request<FormRequestPayloadRefs>
40
+ export type FormResponseToolkit = Pick<ResponseToolkit, 'redirect' | 'view'>
36
41
 
37
42
  export enum FormAction {
38
43
  Continue = 'continue',
@@ -40,7 +45,7 @@ export enum FormAction {
40
45
  Delete = 'delete',
41
46
  AddAnother = 'add-another',
42
47
  Send = 'send',
43
- SaveAndReturn = 'save-and-return'
48
+ SaveAndExit = 'save-and-exit'
44
49
  }
45
50
 
46
51
  export enum FormStatus {
@@ -14,7 +14,7 @@ export const actionSchema = Joi.string<FormAction>()
14
14
  FormAction.Delete,
15
15
  FormAction.AddAnother,
16
16
  FormAction.Send,
17
- FormAction.SaveAndReturn
17
+ FormAction.SaveAndExit
18
18
  )
19
19
  .default(FormAction.Validate)
20
20
  .optional()
@@ -2,11 +2,7 @@ import { type Request, type Server } from '@hapi/hapi'
2
2
 
3
3
  import { config } from '~/src/config/index.js'
4
4
  import { type FormRequest } from '~/src/server/routes/types.js'
5
- import {
6
- ADDITIONAL_IDENTIFIER,
7
- CacheService,
8
- merge
9
- } from '~/src/server/services/cacheService.js'
5
+ import { CacheService, merge } from '~/src/server/services/cacheService.js'
10
6
 
11
7
  describe('CacheService', () => {
12
8
  let mockServer: Partial<Server>
@@ -76,63 +72,6 @@ describe('CacheService', () => {
76
72
  expect(result).toEqual({})
77
73
  })
78
74
  })
79
-
80
- it('should rehydrate state using custom fetcher when cache is missed', async () => {
81
- const rehydratedState = { rehydrated: true }
82
-
83
- const customFetcher = jest.fn().mockResolvedValue(rehydratedState)
84
-
85
- cacheService = new CacheService({
86
- server: mockServer as Server,
87
- cacheName: 'test-cache',
88
- options: { sessionHydrator: customFetcher }
89
- })
90
-
91
- const mockRequest = {
92
- yar: { id: 'session-id' },
93
- params: { state: 's', slug: 'p' }
94
- } as unknown as FormRequest
95
-
96
- mockCache.get
97
- .mockResolvedValueOnce(null)
98
- .mockResolvedValueOnce(rehydratedState)
99
-
100
- const result = await cacheService.getState(mockRequest)
101
-
102
- expect(customFetcher).toHaveBeenCalledWith(mockRequest)
103
- expect(mockCache.set).toHaveBeenCalledWith(
104
- expect.objectContaining({
105
- segment: 'cache',
106
- id: expect.stringContaining('session-id')
107
- }),
108
- rehydratedState,
109
- config.get('sessionTimeout')
110
- )
111
- expect(result).toEqual(rehydratedState)
112
- })
113
-
114
- it('should return empty object when custom fetcher returns null', async () => {
115
- const customFetcher = jest.fn().mockResolvedValue(null)
116
-
117
- cacheService = new CacheService({
118
- server: mockServer as Server,
119
- cacheName: 'test-cache',
120
- options: { sessionHydrator: customFetcher }
121
- })
122
-
123
- const mockRequest = {
124
- yar: { id: 'session-id' },
125
- params: { state: 's', slug: 'p' }
126
- } as unknown as FormRequest
127
-
128
- mockCache.get.mockResolvedValue(null)
129
-
130
- const result = await cacheService.getState(mockRequest)
131
-
132
- expect(customFetcher).toHaveBeenCalledWith(mockRequest)
133
- expect(mockCache.set).not.toHaveBeenCalled()
134
- expect(result).toEqual({})
135
- })
136
75
  })
137
76
 
138
77
  describe('setState', () => {
@@ -178,61 +117,6 @@ describe('CacheService', () => {
178
117
  )
179
118
  })
180
119
  })
181
-
182
- it('should use custom key generator if provided', async () => {
183
- const customKey = 'my-custom-key'
184
- const customKeyGenerator = jest.fn().mockReturnValue(customKey)
185
-
186
- cacheService = new CacheService({
187
- server: mockServer as Server,
188
- cacheName: 'test-cache',
189
- options: { keyGenerator: customKeyGenerator }
190
- })
191
-
192
- const mockRequest = {
193
- yar: { id: 'some-session' },
194
- params: { state: 'form1', slug: 'page1' }
195
- } as unknown as FormRequest
196
-
197
- await cacheService.setState(mockRequest, { test: 'value' })
198
-
199
- expect(mockCache.set).toHaveBeenCalledWith(
200
- {
201
- segment: 'cache',
202
- id: 'my-custom-key'
203
- },
204
- { test: 'value' },
205
- expect.any(Number)
206
- )
207
- })
208
-
209
- it('should append additionalIdentifier to custom key', () => {
210
- const customKey = 'custom:key:base:'
211
- const customKeyGenerator = jest.fn().mockReturnValue(customKey)
212
-
213
- cacheService = new CacheService({
214
- server: mockServer as Server,
215
- cacheName: 'test-cache',
216
- options: { keyGenerator: customKeyGenerator }
217
- })
218
-
219
- const mockRequest = {
220
- yar: { id: 'session-id' },
221
- params: { state: 'formA', slug: 'step1' }
222
- } as unknown as FormRequest
223
-
224
- const result = cacheService.Key(
225
- mockRequest,
226
- ADDITIONAL_IDENTIFIER.Confirmation
227
- )
228
-
229
- expect(result).toEqual({
230
- segment: 'cache',
231
- id: 'custom:key:base::confirmation'
232
- })
233
-
234
- expect(customKeyGenerator).toHaveBeenCalledWith(mockRequest)
235
- })
236
120
  })
237
121
 
238
122
  describe('merge', () => {
@@ -1,18 +1,16 @@
1
- import { type Request, type Server } from '@hapi/hapi'
1
+ import { type Server } from '@hapi/hapi'
2
2
  import * as Hoek from '@hapi/hoek'
3
3
 
4
4
  import { config } from '~/src/config/index.js'
5
5
  import { type createServer } from '~/src/server/index.js'
6
6
  import {
7
+ type AnyFormRequest,
8
+ type AnyRequest,
7
9
  type FormPayload,
8
10
  type FormState,
9
11
  type FormSubmissionError,
10
12
  type FormSubmissionState
11
13
  } from '~/src/server/plugins/engine/types.js'
12
- import {
13
- type FormRequest,
14
- type FormRequestPayload
15
- } from '~/src/server/routes/types.js'
16
14
 
17
15
  const partition = 'cache'
18
16
 
@@ -25,66 +23,28 @@ export class CacheService {
25
23
  * This service is responsible for getting, storing or deleting a user's session data in the cache. This service has been registered by {@link createServer}
26
24
  */
27
25
  cache
28
- generateKey?: (request: Request | FormRequest | FormRequestPayload) => string
29
- customFetcher?: (
30
- request: Request | FormRequest | FormRequestPayload
31
- ) => Promise<FormSubmissionState | null>
32
-
33
26
  logger: Server['logger']
34
27
 
35
- constructor({
36
- server,
37
- cacheName,
38
- options
39
- }: {
40
- server: Server
41
- cacheName?: string
42
- options?: {
43
- keyGenerator?: (
44
- request: Request | FormRequest | FormRequestPayload
45
- ) => string
46
- sessionHydrator?: (
47
- request: Request | FormRequest | FormRequestPayload
48
- ) => Promise<FormSubmissionState | null>
49
- }
50
- }) {
51
- const { keyGenerator, sessionHydrator } = options ?? {}
28
+ constructor({ server, cacheName }: { server: Server; cacheName?: string }) {
52
29
  if (!cacheName) {
53
30
  server.log(
54
31
  'warn',
55
32
  'You are using the default hapi cache. Please provide a cache name in plugin registration options.'
56
33
  )
57
34
  }
58
- this.generateKey = keyGenerator ?? this.defaultKeyGenerator.bind(this)
59
- this.customFetcher = sessionHydrator ?? undefined
35
+
60
36
  this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })
61
37
  this.logger = server.logger
62
38
  }
63
39
 
64
- async getState(
65
- request: Request | FormRequest | FormRequestPayload
66
- ): Promise<FormSubmissionState> {
40
+ async getState(request: AnyRequest): Promise<FormSubmissionState> {
67
41
  const key = this.Key(request)
68
-
69
- let cached = await this.cache.get(key)
70
-
71
- // If nothing in Redis, attempt to rehydrate from backend DB
72
- if (!cached && this.customFetcher) {
73
- const rehydrated = await this.customFetcher(request)
74
-
75
- if (rehydrated != null) {
76
- await this.cache.set(key, rehydrated, config.get('sessionTimeout'))
77
- cached = await this.getState(request)
78
- }
79
- }
42
+ const cached = await this.cache.get(key)
80
43
 
81
44
  return cached ?? {}
82
45
  }
83
46
 
84
- async setState(
85
- request: FormRequest | FormRequestPayload,
86
- state: FormSubmissionState
87
- ) {
47
+ async setState(request: AnyFormRequest, state: FormSubmissionState) {
88
48
  const key = this.Key(request)
89
49
  const ttl = config.get('sessionTimeout')
90
50
 
@@ -94,7 +54,7 @@ export class CacheService {
94
54
  }
95
55
 
96
56
  async getConfirmationState(
97
- request: FormRequest | FormRequestPayload
57
+ request: AnyFormRequest
98
58
  ): Promise<{ confirmed?: true }> {
99
59
  const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)
100
60
  const value = await this.cache.get(key)
@@ -103,7 +63,7 @@ export class CacheService {
103
63
  }
104
64
 
105
65
  async setConfirmationState(
106
- request: FormRequest | FormRequestPayload,
66
+ request: AnyFormRequest,
107
67
  confirmationState: { confirmed?: true }
108
68
  ) {
109
69
  const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)
@@ -112,14 +72,14 @@ export class CacheService {
112
72
  return this.cache.set(key, confirmationState, ttl)
113
73
  }
114
74
 
115
- async clearState(request: FormRequest | FormRequestPayload) {
75
+ async clearState(request: AnyFormRequest) {
116
76
  if (request.yar.id) {
117
77
  await this.cache.drop(this.Key(request))
118
78
  }
119
79
  }
120
80
 
121
81
  getFlash(
122
- request: FormRequest | FormRequestPayload
82
+ request: AnyFormRequest
123
83
  ): { errors: FormSubmissionError[] } | undefined {
124
84
  const key = this.Key(request)
125
85
  const messages = request.yar.flash(key.id)
@@ -130,7 +90,7 @@ export class CacheService {
130
90
  }
131
91
 
132
92
  setFlash(
133
- request: FormRequest | FormRequestPayload,
93
+ request: AnyFormRequest,
134
94
  message: { errors: FormSubmissionError[] }
135
95
  ) {
136
96
  const key = this.Key(request)
@@ -138,35 +98,24 @@ export class CacheService {
138
98
  request.yar.flash(key.id, message)
139
99
  }
140
100
 
141
- private defaultKeyGenerator(
142
- request: Request | FormRequest | FormRequestPayload
143
- ): string {
144
- if (!request.yar.id) {
145
- throw new Error('No session ID found')
146
- }
147
-
148
- const state = (request.params.state as string) || ''
149
- const slug = (request.params.slug as string) || ''
150
- return `${request.yar.id}:${state}:${slug}:`
151
- }
152
-
153
101
  /**
154
102
  * The key used to store user session data against.
155
103
  * If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`
156
104
  * @param request - hapi request object
157
105
  * @param additionalIdentifier - appended to the id
158
106
  */
159
- Key(
160
- request: Request | FormRequest | FormRequestPayload,
161
- additionalIdentifier?: ADDITIONAL_IDENTIFIER
162
- ) {
163
- const baseKey = this.generateKey
164
- ? this.generateKey(request)
165
- : this.defaultKeyGenerator(request)
107
+ Key(request: AnyRequest, additionalIdentifier?: ADDITIONAL_IDENTIFIER) {
108
+ if (!request.yar.id) {
109
+ throw new Error('No session ID found')
110
+ }
111
+
112
+ const state = (request.params.state as string) || ''
113
+ const slug = (request.params.slug as string) || ''
114
+ const key = `${request.yar.id}:${state}:${slug}:`
166
115
 
167
116
  return {
168
117
  segment: partition,
169
- id: `${baseKey}${additionalIdentifier ?? ''}`
118
+ id: `${key}${additionalIdentifier ?? ''}`
170
119
  }
171
120
  }
172
121
  }
@@ -4,6 +4,7 @@ import {
4
4
  type SubmitPayload,
5
5
  type SubmitResponsePayload
6
6
  } from '@defra/forms-model'
7
+ import { type Server } from '@hapi/hapi'
7
8
 
8
9
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
9
10
  import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
@@ -18,6 +19,7 @@ import {
18
19
  type FormRequestPayload,
19
20
  type FormStatus
20
21
  } from '~/src/server/routes/types.js'
22
+ import { type CacheService } from '~/src/server/services/cacheService.js'
21
23
 
22
24
  export interface FormsService {
23
25
  getFormMetadata: (slug: string) => Promise<FormMetadata>
@@ -49,7 +51,8 @@ export interface RouteConfig {
49
51
  controllers?: Record<string, typeof PageController>
50
52
  preparePageEventRequestOptions?: PreparePageEventRequestOptions
51
53
  onRequest?: OnRequestCallback
52
- saveAndReturn?: PluginOptions['saveAndReturn']
54
+ saveAndExit?: PluginOptions['saveAndExit']
55
+ cacheServiceCreator?: (server: Server) => CacheService
53
56
  }
54
57
 
55
58
  export interface OutputService {
@@ -5,11 +5,10 @@ import { type ServerYar, type Yar } from '@hapi/yar'
5
5
  import { type Logger } from 'pino'
6
6
 
7
7
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
8
- import { type PluginOptions } from '~/src/server/plugins/engine/types.ts'
9
8
  import {
10
- type FormRequest,
11
- type FormRequestPayload
12
- } from '~/src/server/routes/types.js'
9
+ type AnyFormRequest,
10
+ type PluginOptions
11
+ } from '~/src/server/plugins/engine/types.ts'
13
12
  import { type CacheService } from '~/src/server/services/index.js'
14
13
 
15
14
  declare module '@hapi/hapi' {
@@ -17,15 +16,15 @@ declare module '@hapi/hapi' {
17
16
  // props from plugins which doesn't export @types
18
17
  interface PluginProperties {
19
18
  crumb: {
20
- generate?: (request: Request | FormRequest | FormRequestPayload) => string
19
+ generate?: (request: AnyRequest) => string
21
20
  }
22
21
  'forms-engine-plugin': {
23
22
  baseLayoutPath: string
24
23
  cacheService: CacheService
25
24
  viewContext?: (
26
- request: FormRequest | FormRequestPayload | null
25
+ request: AnyFormRequest | null
27
26
  ) => Record<string, unknown> | Promise<Record<string, unknown>>
28
- saveAndReturn?: PluginOptions['saveAndReturn']
27
+ saveAndExit?: PluginOptions['saveAndExit']
29
28
  }
30
29
  }
31
30
 
@@ -1,46 +0,0 @@
1
- import { type ResponseToolkit, type RouteOptions } from '@hapi/hapi';
2
- import Joi from 'joi';
3
- import { type FormRequest, type FormRequestRefs } from '~/src/server/routes/types.js';
4
- export declare function getRoutes(getRouteOptions: RouteOptions<FormRequestRefs>): {
5
- method: string;
6
- path: string;
7
- handler: (request: FormRequest, h: Pick<ResponseToolkit, "redirect" | "view">) => import("@hapi/hapi").ResponseObject;
8
- options: {
9
- validate: {
10
- params: Joi.ObjectSchema<any>;
11
- };
12
- auth?: false | string | import("@hapi/hapi").RouteOptionsAccess | undefined;
13
- app?: import("@hapi/hapi").RouteOptionsApp | undefined;
14
- bind?: object | null | undefined;
15
- cache?: false | import("@hapi/hapi").RouteOptionsCache | undefined;
16
- compression?: { [P in keyof import("@hapi/hapi").ContentEncoders]?: Parameters<import("@hapi/hapi").ContentEncoders[P]>[0]; } | undefined;
17
- cors?: boolean | import("@hapi/hapi").RouteOptionsCors | undefined;
18
- description?: string | undefined;
19
- ext?: { [key in import("@hapi/hapi").RouteRequestExtType]?: import("@hapi/hapi").RouteExtObject | import("@hapi/hapi").RouteExtObject[] | undefined; } | undefined;
20
- files?: {
21
- relativeTo: string;
22
- } | undefined;
23
- handler?: object | import("@hapi/hapi").Lifecycle.Method<FormRequestRefs, import("@hapi/hapi").Lifecycle.ReturnValue<FormRequestRefs>> | undefined;
24
- id?: string | undefined;
25
- isInternal?: boolean | undefined;
26
- json?: import("@hapi/hapi").Json.StringifyArguments | undefined;
27
- log?: {
28
- collect: boolean;
29
- } | undefined;
30
- notes?: string | string[] | undefined;
31
- payload?: import("@hapi/hapi").RouteOptionsPayload | undefined;
32
- plugins?: import("@hapi/hapi").PluginSpecificConfiguration | undefined;
33
- pre?: import("@hapi/hapi").RouteOptionsPreArray<FormRequestRefs> | undefined;
34
- response?: import("@hapi/hapi").RouteOptionsResponse | undefined;
35
- security?: import("@hapi/hapi").RouteOptionsSecure | undefined;
36
- state?: {
37
- parse?: boolean | undefined;
38
- failAction?: import("@hapi/hapi").Lifecycle.FailAction | undefined;
39
- } | undefined;
40
- tags?: string[] | undefined;
41
- timeout?: {
42
- server?: boolean | number | undefined;
43
- socket?: boolean | number | undefined;
44
- } | undefined;
45
- };
46
- }[];
@@ -1,36 +0,0 @@
1
- import { slugSchema } from '@defra/forms-model';
2
- import Boom from '@hapi/boom';
3
- import Joi from 'joi';
4
- export function getRoutes(getRouteOptions) {
5
- return [{
6
- method: 'get',
7
- path: '/{slug}/exit',
8
- handler: (request, h) => {
9
- const {
10
- app
11
- } = request;
12
- const {
13
- model
14
- } = app;
15
- if (!model) {
16
- throw Boom.notFound('No model found for exit page');
17
- }
18
- const returnUrl = request.query.returnUrl;
19
- const exitViewModel = {
20
- pageTitle: 'Your progress has been saved',
21
- phaseTag: model.def.phaseBanner?.phase,
22
- returnUrl
23
- };
24
- return h.view('exit', exitViewModel);
25
- },
26
- options: {
27
- ...getRouteOptions,
28
- validate: {
29
- params: Joi.object().keys({
30
- slug: slugSchema
31
- })
32
- }
33
- }
34
- }];
35
- }
36
- //# sourceMappingURL=exit.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"exit.js","names":["slugSchema","Boom","Joi","getRoutes","getRouteOptions","method","path","handler","request","h","app","model","notFound","returnUrl","query","exitViewModel","pageTitle","phaseTag","def","phaseBanner","phase","view","options","validate","params","object","keys","slug"],"sources":["../../../../../src/server/plugins/engine/routes/exit.ts"],"sourcesContent":["import { slugSchema } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport {\n type FormRequest,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport function getRoutes(getRouteOptions: RouteOptions<FormRequestRefs>) {\n return [\n {\n method: 'get',\n path: '/{slug}/exit',\n handler: (\n request: FormRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { app } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound('No model found for exit page')\n }\n\n const returnUrl = request.query.returnUrl\n\n const exitViewModel = {\n pageTitle: 'Your progress has been saved',\n phaseTag: model.def.phaseBanner?.phase,\n returnUrl\n }\n\n return h.view('exit', exitViewModel)\n },\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n }\n ]\n}\n"],"mappings":"AAAA,SAASA,UAAU,QAAQ,oBAAoB;AAC/C,OAAOC,IAAI,MAAM,YAAY;AAE7B,OAAOC,GAAG,MAAM,KAAK;AAOrB,OAAO,SAASC,SAASA,CAACC,eAA8C,EAAE;EACxE,OAAO,CACL;IACEC,MAAM,EAAE,KAAK;IACbC,IAAI,EAAE,cAAc;IACpBC,OAAO,EAAEA,CACPC,OAAoB,EACpBC,CAA6C,KAC1C;MACH,MAAM;QAAEC;MAAI,CAAC,GAAGF,OAAO;MACvB,MAAM;QAAEG;MAAM,CAAC,GAAGD,GAAG;MAErB,IAAI,CAACC,KAAK,EAAE;QACV,MAAMV,IAAI,CAACW,QAAQ,CAAC,8BAA8B,CAAC;MACrD;MAEA,MAAMC,SAAS,GAAGL,OAAO,CAACM,KAAK,CAACD,SAAS;MAEzC,MAAME,aAAa,GAAG;QACpBC,SAAS,EAAE,8BAA8B;QACzCC,QAAQ,EAAEN,KAAK,CAACO,GAAG,CAACC,WAAW,EAAEC,KAAK;QACtCP;MACF,CAAC;MAED,OAAOJ,CAAC,CAACY,IAAI,CAAC,MAAM,EAAEN,aAAa,CAAC;IACtC,CAAC;IACDO,OAAO,EAAE;MACP,GAAGlB,eAAe;MAClBmB,QAAQ,EAAE;QACRC,MAAM,EAAEtB,GAAG,CAACuB,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBC,IAAI,EAAE3B;QACR,CAAC;MACH;IACF;EACF,CAAC,CACF;AACH","ignoreList":[]}
@@ -1,47 +0,0 @@
1
- import { slugSchema } from '@defra/forms-model'
2
- import Boom from '@hapi/boom'
3
- import { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'
4
- import Joi from 'joi'
5
-
6
- import {
7
- type FormRequest,
8
- type FormRequestRefs
9
- } from '~/src/server/routes/types.js'
10
-
11
- export function getRoutes(getRouteOptions: RouteOptions<FormRequestRefs>) {
12
- return [
13
- {
14
- method: 'get',
15
- path: '/{slug}/exit',
16
- handler: (
17
- request: FormRequest,
18
- h: Pick<ResponseToolkit, 'redirect' | 'view'>
19
- ) => {
20
- const { app } = request
21
- const { model } = app
22
-
23
- if (!model) {
24
- throw Boom.notFound('No model found for exit page')
25
- }
26
-
27
- const returnUrl = request.query.returnUrl
28
-
29
- const exitViewModel = {
30
- pageTitle: 'Your progress has been saved',
31
- phaseTag: model.def.phaseBanner?.phase,
32
- returnUrl
33
- }
34
-
35
- return h.view('exit', exitViewModel)
36
- },
37
- options: {
38
- ...getRouteOptions,
39
- validate: {
40
- params: Joi.object().keys({
41
- slug: slugSchema
42
- })
43
- }
44
- }
45
- }
46
- ]
47
- }