@defra/forms-engine-plugin 2.1.3 → 2.1.5

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 (52) hide show
  1. package/.server/server/plugins/engine/models/FormModel.js +1 -1
  2. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  3. package/.server/server/plugins/engine/outputFormatters/adapter/v1.d.ts +6 -0
  4. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +23 -0
  5. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -0
  6. package/.server/server/plugins/engine/outputFormatters/human/v1.d.ts +2 -2
  7. package/.server/server/plugins/engine/outputFormatters/human/v1.js +1 -1
  8. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  9. package/.server/server/plugins/engine/outputFormatters/index.d.ts +4 -3
  10. package/.server/server/plugins/engine/outputFormatters/index.js +4 -0
  11. package/.server/server/plugins/engine/outputFormatters/index.js.map +1 -1
  12. package/.server/server/plugins/engine/outputFormatters/machine/v1.d.ts +2 -2
  13. package/.server/server/plugins/engine/outputFormatters/machine/v1.js +1 -1
  14. package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -1
  15. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +4 -1
  16. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  17. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +5 -4
  18. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  19. package/.server/server/plugins/engine/services/localFormsService.d.ts +1 -1
  20. package/.server/server/plugins/engine/services/notifyService.d.ts +7 -2
  21. package/.server/server/plugins/engine/services/notifyService.js +11 -2
  22. package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
  23. package/.server/server/plugins/engine/types/index.d.ts +10 -0
  24. package/.server/server/plugins/engine/types/index.js +4 -0
  25. package/.server/server/plugins/engine/types/index.js.map +1 -0
  26. package/.server/server/plugins/engine/types/schema.d.ts +5 -0
  27. package/.server/server/plugins/engine/types/schema.js +24 -0
  28. package/.server/server/plugins/engine/types/schema.js.map +1 -0
  29. package/.server/server/plugins/engine/types.d.ts +37 -1
  30. package/.server/server/plugins/engine/types.js +4 -0
  31. package/.server/server/plugins/engine/types.js.map +1 -1
  32. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  33. package/.server/server/plugins/nunjucks/filters/page.d.ts +1 -1
  34. package/.server/server/types.d.ts +1 -1
  35. package/.server/server/types.js.map +1 -1
  36. package/package.json +6 -1
  37. package/src/server/plugins/engine/models/FormModel.test.ts +64 -0
  38. package/src/server/plugins/engine/models/FormModel.ts +5 -1
  39. package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +506 -0
  40. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +53 -0
  41. package/src/server/plugins/engine/outputFormatters/human/v1.ts +6 -2
  42. package/src/server/plugins/engine/outputFormatters/index.ts +11 -3
  43. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +6 -2
  44. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +1 -1
  45. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -4
  46. package/src/server/plugins/engine/services/notifyService.test.ts +156 -1
  47. package/src/server/plugins/engine/services/notifyService.ts +24 -3
  48. package/src/server/plugins/engine/types/index.ts +96 -0
  49. package/src/server/plugins/engine/types/schema.test.ts +152 -0
  50. package/src/server/plugins/engine/types/schema.ts +45 -0
  51. package/src/server/plugins/engine/types.ts +51 -1
  52. package/src/server/types.ts +2 -1
@@ -1,3 +1,6 @@
1
+ import { type FormMetadata } from '@defra/forms-model'
2
+
3
+ import { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers.js'
1
4
  import { checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
2
5
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
3
6
  import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
@@ -13,6 +16,7 @@ import { sendNotification } from '~/src/server/utils/notify.js'
13
16
  jest.mock('~/src/server/utils/notify')
14
17
  jest.mock('~/src/server/plugins/engine/helpers')
15
18
  jest.mock('~/src/server/plugins/engine/outputFormatters/index')
19
+ jest.mock('~/src/server/plugins/engine/components/helpers')
16
20
 
17
21
  describe('notifyService', () => {
18
22
  const submitResponse = {
@@ -32,7 +36,8 @@ describe('notifyService', () => {
32
36
  const mockRequest: FormRequestPayload = jest.mocked<FormRequestPayload>({
33
37
  path: 'test',
34
38
  logger: {
35
- info: jest.fn()
39
+ info: jest.fn(),
40
+ error: jest.fn()
36
41
  }
37
42
  } as unknown as FormRequestPayload)
38
43
  let model: FormModel
@@ -41,6 +46,7 @@ describe('notifyService', () => {
41
46
 
42
47
  beforeEach(() => {
43
48
  jest.resetAllMocks()
49
+ jest.mocked(escapeMarkdown).mockImplementation((text) => text)
44
50
  })
45
51
 
46
52
  it('creates a subject line for real forms', async () => {
@@ -152,4 +158,153 @@ describe('notifyService', () => {
152
158
  })
153
159
  )
154
160
  })
161
+
162
+ it('calls outputFormatter with all correct arguments', async () => {
163
+ const mockFormatter = jest.fn().mockReturnValue('formatted-output')
164
+ jest.mocked(getFormatter).mockReturnValue(mockFormatter)
165
+
166
+ const formMetadata: FormMetadata = {
167
+ id: 'form-id',
168
+ slug: 'form-slug',
169
+ title: 'Form Title'
170
+ } as FormMetadata
171
+
172
+ const formStatus = {
173
+ isPreview: false,
174
+ state: FormStatus.Live
175
+ }
176
+
177
+ jest.mocked(checkFormStatus).mockReturnValue(formStatus)
178
+
179
+ model = {
180
+ name: 'foobar',
181
+ def: {
182
+ output: {
183
+ audience: 'human',
184
+ version: '1'
185
+ }
186
+ }
187
+ } as FormModel
188
+
189
+ await submit(
190
+ formContext,
191
+ mockRequest,
192
+ model,
193
+ 'test@defra.gov.uk',
194
+ items,
195
+ submitResponse,
196
+ formMetadata
197
+ )
198
+
199
+ expect(getFormatter).toHaveBeenCalledWith('human', '1')
200
+
201
+ expect(mockFormatter).toHaveBeenCalledWith(
202
+ formContext,
203
+ items,
204
+ model,
205
+ submitResponse,
206
+ formStatus,
207
+ formMetadata
208
+ )
209
+
210
+ expect(sendNotificationMock).toHaveBeenCalledWith(
211
+ expect.objectContaining({
212
+ personalisation: {
213
+ subject: 'Form submission: foobar',
214
+ body: 'formatted-output'
215
+ }
216
+ })
217
+ )
218
+ })
219
+
220
+ it('calls outputFormatter without formMetadata when not provided', async () => {
221
+ const mockFormatter = jest.fn().mockReturnValue('formatted-output')
222
+ jest.mocked(getFormatter).mockReturnValue(mockFormatter)
223
+
224
+ const formStatus = {
225
+ isPreview: true,
226
+ state: FormStatus.Draft
227
+ }
228
+
229
+ jest.mocked(checkFormStatus).mockReturnValue(formStatus)
230
+
231
+ model = {
232
+ name: 'foobar',
233
+ def: {
234
+ output: {
235
+ audience: 'machine',
236
+ version: '2'
237
+ }
238
+ }
239
+ } as FormModel
240
+
241
+ await submit(
242
+ formContext,
243
+ mockRequest,
244
+ model,
245
+ 'test@defra.gov.uk',
246
+ items,
247
+ submitResponse
248
+ )
249
+
250
+ expect(getFormatter).toHaveBeenCalledWith('machine', '2')
251
+
252
+ expect(mockFormatter).toHaveBeenCalledWith(
253
+ formContext,
254
+ items,
255
+ model,
256
+ submitResponse,
257
+ formStatus,
258
+ undefined
259
+ )
260
+
261
+ expect(sendNotificationMock).toHaveBeenCalledWith(
262
+ expect.objectContaining({
263
+ personalisation: {
264
+ subject: 'TEST FORM SUBMISSION: foobar',
265
+ body: Buffer.from('formatted-output').toString('base64')
266
+ }
267
+ })
268
+ )
269
+ })
270
+
271
+ it('should handle sendNotification errors and rethrow', async () => {
272
+ const mockFormatter = jest.fn().mockReturnValue('formatted-output')
273
+ jest.mocked(getFormatter).mockReturnValue(mockFormatter)
274
+ jest.mocked(checkFormStatus).mockReturnValue({
275
+ isPreview: false,
276
+ state: FormStatus.Live
277
+ })
278
+
279
+ const testError = new Error('Notification service unavailable')
280
+ sendNotificationMock.mockRejectedValue(testError)
281
+
282
+ model = {
283
+ name: 'foobar',
284
+ def: {
285
+ output: {
286
+ audience: 'human',
287
+ version: '1'
288
+ }
289
+ }
290
+ } as FormModel
291
+
292
+ await expect(
293
+ submit(
294
+ formContext,
295
+ mockRequest,
296
+ model,
297
+ 'test@defra.gov.uk',
298
+ items,
299
+ submitResponse
300
+ )
301
+ ).rejects.toThrow('Notification service unavailable')
302
+
303
+ expect(mockRequest.logger.error).toHaveBeenCalledWith(
304
+ 'Notification service unavailable',
305
+ expect.stringContaining(
306
+ '[emailSendFailed] Error sending notification email'
307
+ )
308
+ )
309
+ })
155
310
  })
@@ -1,4 +1,8 @@
1
- import { getErrorMessage, type SubmitResponsePayload } from '@defra/forms-model'
1
+ import {
2
+ getErrorMessage,
3
+ type FormMetadata,
4
+ type SubmitResponsePayload
5
+ } from '@defra/forms-model'
2
6
 
3
7
  import { config } from '~/src/config/index.js'
4
8
  import { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers.js'
@@ -12,14 +16,24 @@ import { sendNotification } from '~/src/server/utils/notify.js'
12
16
 
13
17
  const templateId = config.get('notifyTemplateId')
14
18
 
19
+ /**
20
+ * Optional GOV.UK Notify service for consumers who want email notifications
21
+ * Can be disabled by not providing notifyTemplateId in config
22
+ * Can be overridden by providing a custom outputService in the services config
23
+ */
15
24
  export async function submit(
16
25
  context: FormContext,
17
26
  request: FormRequestPayload,
18
27
  model: FormModel,
19
28
  emailAddress: string,
20
29
  items: DetailItem[],
21
- submitResponse: SubmitResponsePayload
30
+ submitResponse: SubmitResponsePayload,
31
+ formMetadata?: FormMetadata
22
32
  ) {
33
+ if (!templateId) {
34
+ return Promise.resolve()
35
+ }
36
+
23
37
  const logTags = ['submit', 'email']
24
38
  const formStatus = checkFormStatus(request.params)
25
39
 
@@ -35,7 +49,14 @@ export async function submit(
35
49
  const outputVersion = model.def.output?.version ?? '1'
36
50
 
37
51
  const outputFormatter = getFormatter(outputAudience, outputVersion)
38
- let body = outputFormatter(context, items, model, submitResponse, formStatus)
52
+ let body = outputFormatter(
53
+ context,
54
+ items,
55
+ model,
56
+ submitResponse,
57
+ formStatus,
58
+ formMetadata
59
+ )
39
60
 
40
61
  // GOV.UK Notify transforms quotes into curly quotes, so we can't just send the raw payload
41
62
  // This is logic specific to Notify, so we include the logic here rather than in the formatter
@@ -0,0 +1,96 @@
1
+ export type {
2
+ CheckAnswers,
3
+ ErrorMessageTemplate,
4
+ ErrorMessageTemplateList,
5
+ FeaturedFormPageViewModel,
6
+ FileState,
7
+ FilterFunction,
8
+ FormAdapterSubmissionMessage,
9
+ FormAdapterSubmissionMessageData,
10
+ FormAdapterSubmissionMessageMeta,
11
+ FormAdapterSubmissionMessageMetaSerialised,
12
+ FormAdapterSubmissionMessagePayload,
13
+ FormAdapterSubmissionService,
14
+ FormContext,
15
+ FormContextRequest,
16
+ FormPageViewModel,
17
+ FormPayload,
18
+ FormPayloadParams,
19
+ FormState,
20
+ FormStateValue,
21
+ FormSubmissionError,
22
+ FormSubmissionState,
23
+ FormValidationResult,
24
+ FormValue,
25
+ GlobalFunction,
26
+ ItemDeletePageViewModel,
27
+ OnRequestCallback,
28
+ PageViewModel,
29
+ PageViewModelBase,
30
+ PluginOptions,
31
+ PreparePageEventRequestOptions,
32
+ RepeatItemState,
33
+ RepeatListState,
34
+ RepeaterSummaryPageViewModel,
35
+ SummaryList,
36
+ SummaryListAction,
37
+ SummaryListRow,
38
+ TempFileState,
39
+ UploadInitiateResponse,
40
+ UploadStatusFileResponse,
41
+ UploadStatusResponse
42
+ } from '~/src/server/plugins/engine/types.js'
43
+
44
+ export {
45
+ FileStatus,
46
+ FormAdapterSubmissionSchemaVersion,
47
+ UploadStatus
48
+ } from '~/src/server/plugins/engine/types.js'
49
+
50
+ export type {
51
+ Detail,
52
+ DetailItem,
53
+ DetailItemBase,
54
+ DetailItemField,
55
+ DetailItemRepeat,
56
+ ExecutableCondition
57
+ } from '~/src/server/plugins/engine/models/types.js'
58
+
59
+ export type {
60
+ BackLink,
61
+ ComponentText,
62
+ ComponentViewModel,
63
+ Content,
64
+ DateInputItem,
65
+ DatePartsState,
66
+ Label,
67
+ ListItem,
68
+ ListItemLabel,
69
+ MonthYearState,
70
+ ViewModel
71
+ } from '~/src/server/plugins/engine/components/types.js'
72
+
73
+ export type { UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'
74
+
75
+ export type {
76
+ FormParams,
77
+ FormQuery,
78
+ FormRequest,
79
+ FormRequestPayload,
80
+ FormRequestPayloadRefs,
81
+ FormRequestRefs
82
+ } from '~/src/server/routes/types.js'
83
+
84
+ export { FormAction, FormStatus } from '~/src/server/routes/types.js'
85
+
86
+ export type {
87
+ FormSubmissionService,
88
+ FormsService,
89
+ OutputService,
90
+ RouteConfig,
91
+ Services
92
+ } from '~/src/server/types.js'
93
+
94
+ export type { RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
95
+
96
+ export * from '~/src/server/plugins/engine/types/schema.js'
@@ -0,0 +1,152 @@
1
+ import { FormStatus } from '@defra/forms-model'
2
+
3
+ import {
4
+ formAdapterSubmissionMessageDataSchema,
5
+ formAdapterSubmissionMessageMetaSchema,
6
+ formAdapterSubmissionMessagePayloadSchema
7
+ } from '~/src/server/plugins/engine/types/schema.js'
8
+ import {
9
+ FormAdapterSubmissionSchemaVersion,
10
+ type FormAdapterSubmissionMessageData,
11
+ type FormAdapterSubmissionMessageMeta,
12
+ type FormAdapterSubmissionMessagePayload
13
+ } from '~/src/server/plugins/engine/types.js'
14
+
15
+ describe('Schema validation', () => {
16
+ describe('formAdapterSubmissionMessageMetaSchema', () => {
17
+ const validMeta: FormAdapterSubmissionMessageMeta = {
18
+ schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
19
+ timestamp: new Date('2025-08-22T18:15:10.785Z'),
20
+ referenceNumber: '576-225-943',
21
+ formName: 'Order a pizza',
22
+ formId: '68a8b0449ab460290c28940a',
23
+ formSlug: 'order-a-pizza',
24
+ status: FormStatus.Live,
25
+ isPreview: false,
26
+ notificationEmail: 'info@example.com'
27
+ }
28
+
29
+ it('should validate valid meta object', () => {
30
+ const { error } =
31
+ formAdapterSubmissionMessageMetaSchema.validate(validMeta)
32
+ expect(error).toBeUndefined()
33
+ })
34
+
35
+ it('should reject invalid schema version', () => {
36
+ const invalidMeta = { ...validMeta, schemaVersion: 'invalid' }
37
+ const { error } =
38
+ formAdapterSubmissionMessageMetaSchema.validate(invalidMeta)
39
+ expect(error).toBeDefined()
40
+ })
41
+
42
+ it('should reject missing required fields', () => {
43
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
44
+ const { timestamp: _, ...metaWithoutTimestamp } = validMeta
45
+ const { error } =
46
+ formAdapterSubmissionMessageMetaSchema.validate(metaWithoutTimestamp)
47
+ expect(error).toBeDefined()
48
+ })
49
+ })
50
+
51
+ describe('formAdapterSubmissionMessageDataSchema', () => {
52
+ const validData: FormAdapterSubmissionMessageData = {
53
+ main: {
54
+ QMwMir: 'Roman Pizza',
55
+ duOEvZ: 'Small',
56
+ DzEODf: ['Mozzarella'],
57
+ juiCfC: ['Pepperoni', 'Sausage', 'Onions', 'Basil'],
58
+ YEpypP: 'None',
59
+ JumNVc: 'Joe Bloggs',
60
+ ALNehP: '+441234567890',
61
+ vAqTmg: {
62
+ addressLine1: '1 Anywhere Street',
63
+ town: 'Anywhereville',
64
+ postcode: 'AN1 2WH'
65
+ },
66
+ IbXVGY: {
67
+ day: 22,
68
+ month: 8,
69
+ year: 2025
70
+ },
71
+ HGBWLt: ['Garlic sauce']
72
+ },
73
+ repeaters: {},
74
+ files: {}
75
+ }
76
+
77
+ it('should validate valid data object', () => {
78
+ const { error } =
79
+ formAdapterSubmissionMessageDataSchema.validate(validData)
80
+ expect(error).toBeUndefined()
81
+ })
82
+
83
+ it('should validate empty objects', () => {
84
+ const emptyData = { main: {}, repeaters: {}, files: {} }
85
+ const { error } =
86
+ formAdapterSubmissionMessageDataSchema.validate(emptyData)
87
+ expect(error).toBeUndefined()
88
+ })
89
+ })
90
+
91
+ describe('formAdapterSubmissionMessagePayloadSchema', () => {
92
+ const validPayload: FormAdapterSubmissionMessagePayload = {
93
+ meta: {
94
+ schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
95
+ timestamp: new Date('2025-08-22T18:15:10.785Z'),
96
+ referenceNumber: '576-225-943',
97
+ formName: 'Order a pizza',
98
+ formId: '68a8b0449ab460290c28940a',
99
+ formSlug: 'order-a-pizza',
100
+ status: FormStatus.Live,
101
+ isPreview: false,
102
+ notificationEmail: 'info@example.com'
103
+ },
104
+ data: {
105
+ main: {
106
+ QMwMir: 'Roman Pizza',
107
+ duOEvZ: 'Small',
108
+ DzEODf: ['Mozzarella'],
109
+ juiCfC: ['Pepperoni', 'Sausage', 'Onions', 'Basil'],
110
+ YEpypP: 'None',
111
+ JumNVc: 'Joe Bloggs',
112
+ ALNehP: '+441234567890',
113
+ vAqTmg: {
114
+ addressLine1: '1 Anywhere Street',
115
+ town: 'Anywhereville',
116
+ postcode: 'AN1 2WH'
117
+ },
118
+ IbXVGY: {
119
+ day: 22,
120
+ month: 8,
121
+ year: 2025
122
+ },
123
+ HGBWLt: ['Garlic sauce']
124
+ },
125
+ repeaters: {},
126
+ files: {}
127
+ }
128
+ }
129
+
130
+ it('should validate complete payload', () => {
131
+ const { error } =
132
+ formAdapterSubmissionMessagePayloadSchema.validate(validPayload)
133
+ expect(error).toBeUndefined()
134
+ })
135
+
136
+ it('should reject payload without meta', () => {
137
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
138
+ const { meta: _, ...payloadWithoutMeta } = validPayload
139
+ const { error } =
140
+ formAdapterSubmissionMessagePayloadSchema.validate(payloadWithoutMeta)
141
+ expect(error).toBeDefined()
142
+ })
143
+
144
+ it('should reject payload without data', () => {
145
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
146
+ const { data: _, ...payloadWithoutData } = validPayload
147
+ const { error } =
148
+ formAdapterSubmissionMessagePayloadSchema.validate(payloadWithoutData)
149
+ expect(error).toBeDefined()
150
+ })
151
+ })
152
+ })
@@ -0,0 +1,45 @@
1
+ import {
2
+ FormStatus,
3
+ idSchema,
4
+ notificationEmailAddressSchema,
5
+ slugSchema,
6
+ titleSchema
7
+ } from '@defra/forms-model'
8
+ import Joi from 'joi'
9
+
10
+ import {
11
+ FormAdapterSubmissionSchemaVersion,
12
+ type FormAdapterSubmissionMessageData,
13
+ type FormAdapterSubmissionMessageMeta,
14
+ type FormAdapterSubmissionMessagePayload
15
+ } from '~/src/server/plugins/engine/types.js'
16
+
17
+ export const formAdapterSubmissionMessageMetaSchema =
18
+ Joi.object<FormAdapterSubmissionMessageMeta>().keys({
19
+ schemaVersion: Joi.string().valid(
20
+ ...Object.values(FormAdapterSubmissionSchemaVersion)
21
+ ),
22
+ timestamp: Joi.date().required(),
23
+ referenceNumber: Joi.string().required(),
24
+ formName: titleSchema,
25
+ formId: idSchema,
26
+ formSlug: slugSchema,
27
+ status: Joi.string()
28
+ .valid(...Object.values(FormStatus))
29
+ .required(),
30
+ isPreview: Joi.boolean().required(),
31
+ notificationEmail: notificationEmailAddressSchema.required()
32
+ })
33
+
34
+ export const formAdapterSubmissionMessageDataSchema =
35
+ Joi.object<FormAdapterSubmissionMessageData>().keys({
36
+ main: Joi.object(),
37
+ repeaters: Joi.object(),
38
+ files: Joi.object()
39
+ })
40
+
41
+ export const formAdapterSubmissionMessagePayloadSchema =
42
+ Joi.object<FormAdapterSubmissionMessagePayload>().keys({
43
+ meta: formAdapterSubmissionMessageMetaSchema.required(),
44
+ data: formAdapterSubmissionMessageDataSchema.required()
45
+ })
@@ -18,6 +18,7 @@ import {
18
18
  type ComponentViewModel
19
19
  } from '~/src/server/plugins/engine/components/types.js'
20
20
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
21
+ import { type RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
21
22
  import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
22
23
  import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'
23
24
  import { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'
@@ -25,7 +26,8 @@ import {
25
26
  type FormAction,
26
27
  type FormParams,
27
28
  type FormRequest,
28
- type FormRequestPayload
29
+ type FormRequestPayload,
30
+ type FormStatus
29
31
  } from '~/src/server/routes/types.js'
30
32
  import { type RequestOptions } from '~/src/server/services/httpService.js'
31
33
  import { type Services } from '~/src/server/types.js'
@@ -384,3 +386,51 @@ export interface PluginOptions {
384
386
  onRequest?: OnRequestCallback
385
387
  baseUrl: string // base URL of the application, protocol and hostname e.g. "https://myapp.com"
386
388
  }
389
+
390
+ export interface FormAdapterSubmissionMessageMeta {
391
+ schemaVersion: FormAdapterSubmissionSchemaVersion
392
+ timestamp: Date
393
+ referenceNumber: string
394
+ formName: string
395
+ formId: string
396
+ formSlug: string
397
+ status: FormStatus
398
+ isPreview: boolean
399
+ notificationEmail: string
400
+ }
401
+
402
+ export type FormAdapterSubmissionMessageMetaSerialised = Omit<
403
+ FormAdapterSubmissionMessageMeta,
404
+ 'schemaVersion' | 'timestamp' | 'status'
405
+ > & {
406
+ schemaVersion: string
407
+ status: string
408
+ timestamp: string
409
+ }
410
+
411
+ export interface FormAdapterSubmissionMessageData {
412
+ main: Record<string, RichFormValue>
413
+ repeaters: Record<string, Record<string, RichFormValue>[]>
414
+ files: Record<string, Record<string, string>[]>
415
+ }
416
+
417
+ export enum FormAdapterSubmissionSchemaVersion {
418
+ V1 = 1
419
+ }
420
+
421
+ export interface FormAdapterSubmissionMessagePayload {
422
+ meta: FormAdapterSubmissionMessageMeta
423
+ data: FormAdapterSubmissionMessageData
424
+ }
425
+
426
+ export interface FormAdapterSubmissionMessage
427
+ extends FormAdapterSubmissionMessagePayload {
428
+ messageId: string
429
+ recordCreatedAt: Date
430
+ }
431
+
432
+ export interface FormAdapterSubmissionService {
433
+ handleFormSubmission: (
434
+ submissionMessage: FormAdapterSubmissionMessage
435
+ ) => unknown
436
+ }
@@ -59,6 +59,7 @@ export interface OutputService {
59
59
  model: FormModel,
60
60
  emailAddress: string,
61
61
  items: DetailItem[],
62
- submitResponse: SubmitResponsePayload
62
+ submitResponse: SubmitResponsePayload,
63
+ formMetadata?: FormMetadata
63
64
  ) => Promise<void>
64
65
  }