@defra/forms-engine-plugin 2.1.10 → 3.0.1

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 (136) 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/configureEnginePlugin.d.ts +2 -1
  5. package/.server/server/plugins/engine/configureEnginePlugin.js +4 -4
  6. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  7. package/.server/server/plugins/engine/helpers.d.ts +7 -11
  8. package/.server/server/plugins/engine/helpers.js +2 -2
  9. package/.server/server/plugins/engine/helpers.js.map +1 -1
  10. package/.server/server/plugins/engine/models/FormModel.d.ts +2 -0
  11. package/.server/server/plugins/engine/models/FormModel.js +5 -2
  12. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  13. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +1 -1
  14. package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
  15. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  16. package/.server/server/plugins/engine/options.js +3 -6
  17. package/.server/server/plugins/engine/options.js.map +1 -1
  18. package/.server/server/plugins/engine/options.test.js +2 -8
  19. package/.server/server/plugins/engine/options.test.js.map +1 -1
  20. package/.server/server/plugins/engine/outputFormatters/adapter/v1.d.ts +4 -0
  21. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +25 -0
  22. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
  23. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +7 -6
  24. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  25. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +5 -6
  26. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  27. package/.server/server/plugins/engine/pageControllers/PageController.d.ts +6 -6
  28. package/.server/server/plugins/engine/pageControllers/PageController.js +4 -4
  29. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  30. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +13 -13
  31. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +12 -20
  32. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  33. package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +7 -8
  34. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  35. package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -2
  36. package/.server/server/plugins/engine/pageControllers/StartPageController.js +1 -1
  37. package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
  38. package/.server/server/plugins/engine/pageControllers/StatusPageController.d.ts +3 -4
  39. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +1 -1
  40. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  41. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +5 -4
  42. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +12 -1
  43. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  44. package/.server/server/plugins/engine/pageControllers/TerminalPageController.d.ts +4 -4
  45. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js +1 -1
  46. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js.map +1 -1
  47. package/.server/server/plugins/engine/pageControllers/__stubs__/server.d.ts +1 -1
  48. package/.server/server/plugins/engine/pageControllers/__stubs__/server.js +2 -6
  49. package/.server/server/plugins/engine/pageControllers/__stubs__/server.js.map +1 -1
  50. package/.server/server/plugins/engine/plugin.js +7 -12
  51. package/.server/server/plugins/engine/plugin.js.map +1 -1
  52. package/.server/server/plugins/engine/routes/index.d.ts +5 -5
  53. package/.server/server/plugins/engine/routes/index.js +3 -1
  54. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  55. package/.server/server/plugins/engine/routes/questions.d.ts +4 -4
  56. package/.server/server/plugins/engine/routes/questions.js.map +1 -1
  57. package/.server/server/plugins/engine/routes/repeaters/item-delete.js.map +1 -1
  58. package/.server/server/plugins/engine/routes/repeaters/summary.js.map +1 -1
  59. package/.server/server/plugins/engine/types/index.d.ts +2 -2
  60. package/.server/server/plugins/engine/types/index.js.map +1 -1
  61. package/.server/server/plugins/engine/types/schema.js +3 -2
  62. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  63. package/.server/server/plugins/engine/types.d.ts +13 -12
  64. package/.server/server/plugins/engine/types.js.map +1 -1
  65. package/.server/server/plugins/engine/views/partials/form.html +3 -3
  66. package/.server/server/plugins/engine/views/summary.html +21 -5
  67. package/.server/server/plugins/nunjucks/context.d.ts +5 -6
  68. package/.server/server/plugins/nunjucks/context.js +3 -3
  69. package/.server/server/plugins/nunjucks/context.js.map +1 -1
  70. package/.server/server/routes/types.d.ts +3 -2
  71. package/.server/server/routes/types.js +1 -1
  72. package/.server/server/routes/types.js.map +1 -1
  73. package/.server/server/schemas/index.js +1 -1
  74. package/.server/server/schemas/index.js.map +1 -1
  75. package/.server/server/services/cacheService.d.ts +11 -19
  76. package/.server/server/services/cacheService.js +9 -30
  77. package/.server/server/services/cacheService.js.map +1 -1
  78. package/.server/server/types.d.ts +4 -1
  79. package/.server/server/types.js.map +1 -1
  80. package/.server/typings/hapi/index.d.js.map +1 -1
  81. package/package.json +4 -2
  82. package/src/server/index.test.ts +0 -39
  83. package/src/server/index.ts +4 -1
  84. package/src/server/plugins/engine/README.md +2 -2
  85. package/src/server/plugins/engine/components/helpers/helpers.test.ts +1 -1
  86. package/src/server/plugins/engine/configureEnginePlugin.ts +15 -11
  87. package/src/server/plugins/engine/helpers.test.ts +3 -2
  88. package/src/server/plugins/engine/helpers.ts +6 -6
  89. package/src/server/plugins/engine/models/FormModel.test.ts +66 -2
  90. package/src/server/plugins/engine/models/FormModel.ts +6 -4
  91. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +7 -7
  92. package/src/server/plugins/engine/models/SummaryViewModel.ts +1 -1
  93. package/src/server/plugins/engine/options.js +6 -6
  94. package/src/server/plugins/engine/options.test.js +2 -6
  95. package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +446 -13
  96. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +37 -0
  97. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +8 -6
  98. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +8 -10
  99. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +9 -8
  100. package/src/server/plugins/engine/pageControllers/PageController.test.ts +11 -8
  101. package/src/server/plugins/engine/pageControllers/PageController.ts +9 -15
  102. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +35 -102
  103. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +24 -36
  104. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +4 -6
  105. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +8 -11
  106. package/src/server/plugins/engine/pageControllers/StartPageController.test.ts +4 -4
  107. package/src/server/plugins/engine/pageControllers/StartPageController.ts +1 -1
  108. package/src/server/plugins/engine/pageControllers/StatusPageController.test.ts +4 -4
  109. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +6 -4
  110. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -5
  111. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +4 -4
  112. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +7 -4
  113. package/src/server/plugins/engine/pageControllers/__stubs__/server.ts +5 -6
  114. package/src/server/plugins/engine/plugin.ts +7 -13
  115. package/src/server/plugins/engine/routes/index.ts +9 -12
  116. package/src/server/plugins/engine/routes/questions.test.ts +29 -53
  117. package/src/server/plugins/engine/routes/questions.ts +6 -8
  118. package/src/server/plugins/engine/routes/repeaters/item-delete.ts +5 -14
  119. package/src/server/plugins/engine/routes/repeaters/summary.ts +5 -14
  120. package/src/server/plugins/engine/types/index.ts +4 -1
  121. package/src/server/plugins/engine/types/schema.test.ts +40 -0
  122. package/src/server/plugins/engine/types/schema.ts +3 -1
  123. package/src/server/plugins/engine/types.ts +22 -13
  124. package/src/server/plugins/engine/views/partials/form.html +3 -3
  125. package/src/server/plugins/engine/views/summary.html +21 -5
  126. package/src/server/plugins/nunjucks/context.js +3 -3
  127. package/src/server/routes/types.ts +7 -2
  128. package/src/server/schemas/index.ts +1 -1
  129. package/src/server/services/cacheService.test.ts +1 -117
  130. package/src/server/services/cacheService.ts +22 -73
  131. package/src/server/types.ts +4 -1
  132. package/src/typings/hapi/index.d.ts +6 -7
  133. package/.server/server/plugins/engine/routes/exit.d.ts +0 -46
  134. package/.server/server/plugins/engine/routes/exit.js +0 -36
  135. package/.server/server/plugins/engine/routes/exit.js.map +0 -1
  136. package/src/server/plugins/engine/routes/exit.ts +0 -47
@@ -1,4 +1,4 @@
1
- import { FormStatus, idSchema, notificationEmailAddressSchema, slugSchema, titleSchema } from '@defra/forms-model';
1
+ import { FormStatus, formVersionMetadataSchema, idSchema, notificationEmailAddressSchema, slugSchema, titleSchema } from '@defra/forms-model';
2
2
  import Joi from 'joi';
3
3
  import { FormAdapterSubmissionSchemaVersion } from "./enums.js";
4
4
  export const formAdapterSubmissionMessageMetaSchema = Joi.object().keys({
@@ -10,7 +10,8 @@ export const formAdapterSubmissionMessageMetaSchema = Joi.object().keys({
10
10
  formSlug: slugSchema,
11
11
  status: Joi.string().valid(...Object.values(FormStatus)).required(),
12
12
  isPreview: Joi.boolean().required(),
13
- notificationEmail: notificationEmailAddressSchema.required()
13
+ notificationEmail: notificationEmailAddressSchema.required(),
14
+ versionMetadata: formVersionMetadataSchema.optional()
14
15
  });
15
16
  export const formAdapterSubmissionMessageDataSchema = Joi.object().keys({
16
17
  main: Joi.object(),
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","names":["FormStatus","idSchema","notificationEmailAddressSchema","slugSchema","titleSchema","Joi","FormAdapterSubmissionSchemaVersion","formAdapterSubmissionMessageMetaSchema","object","keys","schemaVersion","string","valid","Object","values","timestamp","date","required","referenceNumber","formName","formId","formSlug","status","isPreview","boolean","notificationEmail","formAdapterSubmissionMessageDataSchema","main","repeaters","files","pattern","array","items","fileName","fileId","userDownloadLink","formAdapterSubmissionMessageResultSchema","formAdapterSubmissionMessagePayloadSchema","meta","data","result"],"sources":["../../../../../src/server/plugins/engine/types/schema.ts"],"sourcesContent":["import {\n FormStatus,\n idSchema,\n notificationEmailAddressSchema,\n slugSchema,\n titleSchema\n} from '@defra/forms-model'\nimport Joi from 'joi'\n\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessageMeta,\n type FormAdapterSubmissionMessagePayload,\n type FormAdapterSubmissionMessageResult\n} from '~/src/server/plugins/engine/types.js'\n\nexport const formAdapterSubmissionMessageMetaSchema =\n Joi.object<FormAdapterSubmissionMessageMeta>().keys({\n schemaVersion: Joi.string().valid(\n ...Object.values(FormAdapterSubmissionSchemaVersion)\n ),\n timestamp: Joi.date().required(),\n referenceNumber: Joi.string().required(),\n formName: titleSchema,\n formId: idSchema,\n formSlug: slugSchema,\n status: Joi.string()\n .valid(...Object.values(FormStatus))\n .required(),\n isPreview: Joi.boolean().required(),\n notificationEmail: notificationEmailAddressSchema.required()\n })\n\nexport const formAdapterSubmissionMessageDataSchema =\n Joi.object<FormAdapterSubmissionMessageData>().keys({\n main: Joi.object(),\n repeaters: Joi.object(),\n files: Joi.object().pattern(\n Joi.string(),\n Joi.array().items(\n Joi.object().keys({\n fileName: Joi.string().required(),\n fileId: Joi.string().required(),\n userDownloadLink: Joi.string().required()\n })\n )\n )\n })\n\nexport const formAdapterSubmissionMessageResultSchema =\n Joi.object<FormAdapterSubmissionMessageResult>().keys({\n files: Joi.object()\n .keys({\n main: Joi.string().required(),\n repeaters: Joi.object()\n })\n .required()\n })\n\nexport const formAdapterSubmissionMessagePayloadSchema =\n Joi.object<FormAdapterSubmissionMessagePayload>().keys({\n meta: formAdapterSubmissionMessageMetaSchema.required(),\n data: formAdapterSubmissionMessageDataSchema.required(),\n result: formAdapterSubmissionMessageResultSchema.required()\n })\n"],"mappings":"AAAA,SACEA,UAAU,EACVC,QAAQ,EACRC,8BAA8B,EAC9BC,UAAU,EACVC,WAAW,QACN,oBAAoB;AAC3B,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,kCAAkC;AAQ3C,OAAO,MAAMC,sCAAsC,GACjDF,GAAG,CAACG,MAAM,CAAmC,CAAC,CAACC,IAAI,CAAC;EAClDC,aAAa,EAAEL,GAAG,CAACM,MAAM,CAAC,CAAC,CAACC,KAAK,CAC/B,GAAGC,MAAM,CAACC,MAAM,CAACR,kCAAkC,CACrD,CAAC;EACDS,SAAS,EAAEV,GAAG,CAACW,IAAI,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC;EAChCC,eAAe,EAAEb,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;EACxCE,QAAQ,EAAEf,WAAW;EACrBgB,MAAM,EAAEnB,QAAQ;EAChBoB,QAAQ,EAAElB,UAAU;EACpBmB,MAAM,EAAEjB,GAAG,CAACM,MAAM,CAAC,CAAC,CACjBC,KAAK,CAAC,GAAGC,MAAM,CAACC,MAAM,CAACd,UAAU,CAAC,CAAC,CACnCiB,QAAQ,CAAC,CAAC;EACbM,SAAS,EAAElB,GAAG,CAACmB,OAAO,CAAC,CAAC,CAACP,QAAQ,CAAC,CAAC;EACnCQ,iBAAiB,EAAEvB,8BAA8B,CAACe,QAAQ,CAAC;AAC7D,CAAC,CAAC;AAEJ,OAAO,MAAMS,sCAAsC,GACjDrB,GAAG,CAACG,MAAM,CAAmC,CAAC,CAACC,IAAI,CAAC;EAClDkB,IAAI,EAAEtB,GAAG,CAACG,MAAM,CAAC,CAAC;EAClBoB,SAAS,EAAEvB,GAAG,CAACG,MAAM,CAAC,CAAC;EACvBqB,KAAK,EAAExB,GAAG,CAACG,MAAM,CAAC,CAAC,CAACsB,OAAO,CACzBzB,GAAG,CAACM,MAAM,CAAC,CAAC,EACZN,GAAG,CAAC0B,KAAK,CAAC,CAAC,CAACC,KAAK,CACf3B,GAAG,CAACG,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;IAChBwB,QAAQ,EAAE5B,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;IACjCiB,MAAM,EAAE7B,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;IAC/BkB,gBAAgB,EAAE9B,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC;EAC1C,CAAC,CACH,CACF;AACF,CAAC,CAAC;AAEJ,OAAO,MAAMmB,wCAAwC,GACnD/B,GAAG,CAACG,MAAM,CAAqC,CAAC,CAACC,IAAI,CAAC;EACpDoB,KAAK,EAAExB,GAAG,CAACG,MAAM,CAAC,CAAC,CAChBC,IAAI,CAAC;IACJkB,IAAI,EAAEtB,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;IAC7BW,SAAS,EAAEvB,GAAG,CAACG,MAAM,CAAC;EACxB,CAAC,CAAC,CACDS,QAAQ,CAAC;AACd,CAAC,CAAC;AAEJ,OAAO,MAAMoB,yCAAyC,GACpDhC,GAAG,CAACG,MAAM,CAAsC,CAAC,CAACC,IAAI,CAAC;EACrD6B,IAAI,EAAE/B,sCAAsC,CAACU,QAAQ,CAAC,CAAC;EACvDsB,IAAI,EAAEb,sCAAsC,CAACT,QAAQ,CAAC,CAAC;EACvDuB,MAAM,EAAEJ,wCAAwC,CAACnB,QAAQ,CAAC;AAC5D,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"schema.js","names":["FormStatus","formVersionMetadataSchema","idSchema","notificationEmailAddressSchema","slugSchema","titleSchema","Joi","FormAdapterSubmissionSchemaVersion","formAdapterSubmissionMessageMetaSchema","object","keys","schemaVersion","string","valid","Object","values","timestamp","date","required","referenceNumber","formName","formId","formSlug","status","isPreview","boolean","notificationEmail","versionMetadata","optional","formAdapterSubmissionMessageDataSchema","main","repeaters","files","pattern","array","items","fileName","fileId","userDownloadLink","formAdapterSubmissionMessageResultSchema","formAdapterSubmissionMessagePayloadSchema","meta","data","result"],"sources":["../../../../../src/server/plugins/engine/types/schema.ts"],"sourcesContent":["import {\n FormStatus,\n formVersionMetadataSchema,\n idSchema,\n notificationEmailAddressSchema,\n slugSchema,\n titleSchema\n} from '@defra/forms-model'\nimport Joi from 'joi'\n\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessageMeta,\n type FormAdapterSubmissionMessagePayload,\n type FormAdapterSubmissionMessageResult\n} from '~/src/server/plugins/engine/types.js'\n\nexport const formAdapterSubmissionMessageMetaSchema =\n Joi.object<FormAdapterSubmissionMessageMeta>().keys({\n schemaVersion: Joi.string().valid(\n ...Object.values(FormAdapterSubmissionSchemaVersion)\n ),\n timestamp: Joi.date().required(),\n referenceNumber: Joi.string().required(),\n formName: titleSchema,\n formId: idSchema,\n formSlug: slugSchema,\n status: Joi.string()\n .valid(...Object.values(FormStatus))\n .required(),\n isPreview: Joi.boolean().required(),\n notificationEmail: notificationEmailAddressSchema.required(),\n versionMetadata: formVersionMetadataSchema.optional()\n })\n\nexport const formAdapterSubmissionMessageDataSchema =\n Joi.object<FormAdapterSubmissionMessageData>().keys({\n main: Joi.object(),\n repeaters: Joi.object(),\n files: Joi.object().pattern(\n Joi.string(),\n Joi.array().items(\n Joi.object().keys({\n fileName: Joi.string().required(),\n fileId: Joi.string().required(),\n userDownloadLink: Joi.string().required()\n })\n )\n )\n })\n\nexport const formAdapterSubmissionMessageResultSchema =\n Joi.object<FormAdapterSubmissionMessageResult>().keys({\n files: Joi.object()\n .keys({\n main: Joi.string().required(),\n repeaters: Joi.object()\n })\n .required()\n })\n\nexport const formAdapterSubmissionMessagePayloadSchema =\n Joi.object<FormAdapterSubmissionMessagePayload>().keys({\n meta: formAdapterSubmissionMessageMetaSchema.required(),\n data: formAdapterSubmissionMessageDataSchema.required(),\n result: formAdapterSubmissionMessageResultSchema.required()\n })\n"],"mappings":"AAAA,SACEA,UAAU,EACVC,yBAAyB,EACzBC,QAAQ,EACRC,8BAA8B,EAC9BC,UAAU,EACVC,WAAW,QACN,oBAAoB;AAC3B,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,kCAAkC;AAQ3C,OAAO,MAAMC,sCAAsC,GACjDF,GAAG,CAACG,MAAM,CAAmC,CAAC,CAACC,IAAI,CAAC;EAClDC,aAAa,EAAEL,GAAG,CAACM,MAAM,CAAC,CAAC,CAACC,KAAK,CAC/B,GAAGC,MAAM,CAACC,MAAM,CAACR,kCAAkC,CACrD,CAAC;EACDS,SAAS,EAAEV,GAAG,CAACW,IAAI,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC;EAChCC,eAAe,EAAEb,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;EACxCE,QAAQ,EAAEf,WAAW;EACrBgB,MAAM,EAAEnB,QAAQ;EAChBoB,QAAQ,EAAElB,UAAU;EACpBmB,MAAM,EAAEjB,GAAG,CAACM,MAAM,CAAC,CAAC,CACjBC,KAAK,CAAC,GAAGC,MAAM,CAACC,MAAM,CAACf,UAAU,CAAC,CAAC,CACnCkB,QAAQ,CAAC,CAAC;EACbM,SAAS,EAAElB,GAAG,CAACmB,OAAO,CAAC,CAAC,CAACP,QAAQ,CAAC,CAAC;EACnCQ,iBAAiB,EAAEvB,8BAA8B,CAACe,QAAQ,CAAC,CAAC;EAC5DS,eAAe,EAAE1B,yBAAyB,CAAC2B,QAAQ,CAAC;AACtD,CAAC,CAAC;AAEJ,OAAO,MAAMC,sCAAsC,GACjDvB,GAAG,CAACG,MAAM,CAAmC,CAAC,CAACC,IAAI,CAAC;EAClDoB,IAAI,EAAExB,GAAG,CAACG,MAAM,CAAC,CAAC;EAClBsB,SAAS,EAAEzB,GAAG,CAACG,MAAM,CAAC,CAAC;EACvBuB,KAAK,EAAE1B,GAAG,CAACG,MAAM,CAAC,CAAC,CAACwB,OAAO,CACzB3B,GAAG,CAACM,MAAM,CAAC,CAAC,EACZN,GAAG,CAAC4B,KAAK,CAAC,CAAC,CAACC,KAAK,CACf7B,GAAG,CAACG,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;IAChB0B,QAAQ,EAAE9B,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;IACjCmB,MAAM,EAAE/B,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;IAC/BoB,gBAAgB,EAAEhC,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC;EAC1C,CAAC,CACH,CACF;AACF,CAAC,CAAC;AAEJ,OAAO,MAAMqB,wCAAwC,GACnDjC,GAAG,CAACG,MAAM,CAAqC,CAAC,CAACC,IAAI,CAAC;EACpDsB,KAAK,EAAE1B,GAAG,CAACG,MAAM,CAAC,CAAC,CAChBC,IAAI,CAAC;IACJoB,IAAI,EAAExB,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;IAC7Ba,SAAS,EAAEzB,GAAG,CAACG,MAAM,CAAC;EACxB,CAAC,CAAC,CACDS,QAAQ,CAAC;AACd,CAAC,CAAC;AAEJ,OAAO,MAAMsB,yCAAyC,GACpDlC,GAAG,CAACG,MAAM,CAAsC,CAAC,CAACC,IAAI,CAAC;EACrD+B,IAAI,EAAEjC,sCAAsC,CAACU,QAAQ,CAAC,CAAC;EACvDwB,IAAI,EAAEb,sCAAsC,CAACX,QAAQ,CAAC,CAAC;EACvDyB,MAAM,EAAEJ,wCAAwC,CAACrB,QAAQ,CAAC;AAC5D,CAAC,CAAC","ignoreList":[]}
@@ -1,5 +1,5 @@
1
- import { type ComponentDef, type Event, type FormDefinition, type FormMetadata, type Item, type List, type Page } from '@defra/forms-model';
2
- import { type PluginProperties, type Request } from '@hapi/hapi';
1
+ import { type ComponentDef, type Event, type FormDefinition, type FormMetadata, type FormVersionMetadata, type Item, type List, type Page } from '@defra/forms-model';
2
+ import { type PluginProperties, type Request, type ResponseObject } from '@hapi/hapi';
3
3
  import { type JoiExpression, type ValidationErrorItem } from 'joi';
4
4
  import { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js';
5
5
  import { type Component } from '~/src/server/plugins/engine/components/helpers/components.js';
@@ -11,10 +11,12 @@ import { type PageController } from '~/src/server/plugins/engine/pageControllers
11
11
  import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js';
12
12
  import { type FileStatus, type FormAdapterSubmissionSchemaVersion, type UploadStatus } from '~/src/server/plugins/engine/types/enums.js';
13
13
  import { type ViewContext } from '~/src/server/plugins/nunjucks/types.js';
14
- import { type FormAction, type FormParams, type FormRequest, type FormRequestPayload, type FormStatus } from '~/src/server/routes/types.js';
14
+ import { type FormAction, type FormParams, type FormRequest, type FormRequestPayload, type FormResponseToolkit, type FormStatus } from '~/src/server/routes/types.js';
15
+ import { type CacheService } from '~/src/server/services/cacheService.js';
15
16
  import { type RequestOptions } from '~/src/server/services/httpService.js';
16
17
  import { type Services } from '~/src/server/types.js';
17
- type RequestType = Request | FormRequest | FormRequestPayload;
18
+ export type AnyFormRequest = FormRequest | FormRequestPayload;
19
+ export type AnyRequest = Request | AnyFormRequest;
18
20
  /**
19
21
  * Form submission state stores the following in Redis:
20
22
  * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`
@@ -119,6 +121,7 @@ export interface FormContext {
119
121
  pageMap: Map<string, PageControllerClass>;
120
122
  componentMap: Map<string, Component>;
121
123
  referenceNumber: string;
124
+ submittedVersionNumber?: number;
122
125
  }
123
126
  export type FormContextRequest = ({
124
127
  method: 'get';
@@ -230,7 +233,7 @@ export interface FormPageViewModel extends PageViewModelBase {
230
233
  context: FormContext;
231
234
  errors?: FormSubmissionError[];
232
235
  hasMissingNotificationEmail?: boolean;
233
- allowSaveAndReturn: boolean;
236
+ allowSaveAndExit: boolean;
234
237
  }
235
238
  export interface RepeaterSummaryPageViewModel extends PageViewModelBase {
236
239
  context: FormContext;
@@ -257,19 +260,16 @@ export interface ErrorMessageTemplateList {
257
260
  advancedSettingsErrors: ErrorMessageTemplate[];
258
261
  }
259
262
  export type PreparePageEventRequestOptions = (options: RequestOptions, event: Event, page: PageControllerClass, context: FormContext) => void;
260
- export type OnRequestCallback = (request: FormRequest | FormRequestPayload, params: FormParams, definition: FormDefinition, metadata: FormMetadata) => void;
263
+ export type OnRequestCallback = (request: AnyFormRequest, params: FormParams, definition: FormDefinition, metadata: FormMetadata) => void;
264
+ export type SaveAndExitHandler = (request: FormRequestPayload, h: FormResponseToolkit, context: FormContext) => ResponseObject;
261
265
  export interface PluginOptions {
262
266
  model?: FormModel;
263
267
  services?: Services;
264
268
  controllers?: Record<string, typeof PageController>;
265
- cacheName?: string;
269
+ cache?: CacheService | string;
266
270
  globals?: Record<string, GlobalFunction>;
267
271
  filters?: Record<string, FilterFunction>;
268
- saveAndReturn?: {
269
- keyGenerator: (request: RequestType) => string;
270
- sessionHydrator: (request: RequestType) => Promise<FormSubmissionState>;
271
- sessionPersister: (state: FormSubmissionState, request: RequestType) => Promise<void>;
272
- };
272
+ saveAndExit?: SaveAndExitHandler;
273
273
  pluginPath?: string;
274
274
  nunjucks: {
275
275
  baseLayoutPath: string;
@@ -290,6 +290,7 @@ export interface FormAdapterSubmissionMessageMeta {
290
290
  status: FormStatus;
291
291
  isPreview: boolean;
292
292
  notificationEmail: string;
293
+ versionMetadata?: FormVersionMetadata;
293
294
  }
294
295
  export type FormAdapterSubmissionMessageMetaSerialised = Omit<FormAdapterSubmissionMessageMeta, 'schemaVersion' | 'timestamp' | 'status'> & {
295
296
  schemaVersion: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\ntype RequestType = Request | FormRequest | FormRequestPayload\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndReturn: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndReturn?: {\n keyGenerator: (request: RequestType) => string\n sessionHydrator: (request: RequestType) => Promise<FormSubmissionState>\n sessionPersister: (\n state: FormSubmissionState,\n request: RequestType\n ) => Promise<void>\n }\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AA6CA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAqGA,SACEA,UAAU,EACVC,YAAY;;AA6Nd;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n submittedVersionNumber?: number\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAqDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AA6Nd;AACA;AACA","ignoreList":[]}
@@ -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>
@@ -1,16 +1,15 @@
1
1
  /**
2
- * @param {FormRequest | FormRequestPayload | null} request
2
+ * @param {AnyFormRequest | null} request
3
3
  */
4
- export function context(request: FormRequest | FormRequestPayload | null): Promise<ViewContext>;
4
+ export function context(request: AnyFormRequest | null): Promise<ViewContext>;
5
5
  /**
6
6
  * Returns the context for the devtool. Consumers won't have access to this.
7
- * @param {FormRequest | FormRequestPayload | null} _request
7
+ * @param {AnyFormRequest | null} _request
8
8
  * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}
9
9
  */
10
- export function devtoolContext(_request: FormRequest | FormRequestPayload | null): Record<string, unknown> & {
10
+ export function devtoolContext(_request: AnyFormRequest | null): Record<string, unknown> & {
11
11
  assetPath: string;
12
12
  getDxtAssetPath: (asset: string) => string;
13
13
  };
14
- import type { FormRequest } from '~/src/server/routes/types.js';
15
- import type { FormRequestPayload } from '~/src/server/routes/types.js';
14
+ import type { AnyFormRequest } from '~/src/server/plugins/engine/types.js';
16
15
  import type { ViewContext } from '~/src/server/plugins/nunjucks/types.js';
@@ -11,7 +11,7 @@ const logger = createLogger();
11
11
  let webpackManifest;
12
12
 
13
13
  /**
14
- * @param {FormRequest | FormRequestPayload | null} request
14
+ * @param {AnyFormRequest | null} request
15
15
  */
16
16
  export async function context(request) {
17
17
  const {
@@ -53,7 +53,7 @@ export async function context(request) {
53
53
 
54
54
  /**
55
55
  * Returns the context for the devtool. Consumers won't have access to this.
56
- * @param {FormRequest | FormRequestPayload | null} _request
56
+ * @param {AnyFormRequest | null} _request
57
57
  * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}
58
58
  */
59
59
  export function devtoolContext(_request) {
@@ -84,6 +84,6 @@ export function devtoolContext(_request) {
84
84
 
85
85
  /**
86
86
  * @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'
87
- * @import { FormRequest, FormRequestPayload } from '~/src/server/routes/types.js'
87
+ * @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'
88
88
  */
89
89
  //# sourceMappingURL=context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","safeGenerateCrumb","logger","webpackManifest","context","request","params","response","isPreview","isPreviewMode","state","formState","isResponseOK","isBoom","statusCode","OK","pluginStorage","server","plugins","consumerViewContext","Error","baseLayoutPath","viewContext","ctx","crumb","currentPath","path","url","search","previewMode","undefined","slug","devtoolContext","_request","manifestPath","get","JSON","parse","info","cdpEnvironment","designerUrl","feedbackLink","phaseTag","serviceName","serviceVersion","assetPath","getDxtAssetPath","asset"],"sources":["../../../../src/server/plugins/nunjucks/context.js"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport Boom from '@hapi/boom'\nimport { StatusCodes } from 'http-status-codes'\n\nimport { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n checkFormStatus,\n encodeUrl,\n safeGenerateCrumb\n} from '~/src/server/plugins/engine/helpers.js'\n\nconst logger = createLogger()\n\n/** @type {Record<string, string> | undefined} */\nlet webpackManifest\n\n/**\n * @param {FormRequest | FormRequestPayload | null} request\n */\nexport async function context(request) {\n const { params, response } = request ?? {}\n\n const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params)\n\n // Only add the slug in to the context if the response is OK.\n // Footer meta links are not rendered when the slug is missing.\n const isResponseOK =\n !Boom.isBoom(response) && response?.statusCode === StatusCodes.OK\n\n const pluginStorage = request?.server.plugins['forms-engine-plugin']\n\n let consumerViewContext = {}\n\n if (!pluginStorage) {\n throw Error('context called before plugin registered')\n }\n\n if (!pluginStorage.baseLayoutPath) {\n throw Error('Missing baseLayoutPath in plugin.options.nunjucks')\n }\n\n if (typeof pluginStorage.viewContext === 'function') {\n consumerViewContext = await pluginStorage.viewContext(request)\n }\n\n /** @type {ViewContext} */\n const ctx = {\n // take consumers props first so we can override it\n ...consumerViewContext,\n baseLayoutPath: pluginStorage.baseLayoutPath,\n crumb: safeGenerateCrumb(request),\n currentPath: `${request.path}${request.url.search}`,\n previewMode: isPreviewMode ? formState : undefined,\n slug: isResponseOK ? params?.slug : undefined\n }\n\n return ctx\n}\n\n/**\n * Returns the context for the devtool. Consumers won't have access to this.\n * @param {FormRequest | FormRequestPayload | null} _request\n * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}\n */\nexport function devtoolContext(_request) {\n const manifestPath = join(config.get('publicDir'), 'assets-manifest.json')\n\n if (!webpackManifest) {\n try {\n // eslint-disable-next-line -- Allow JSON type 'any'\n webpackManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))\n } catch {\n logger.info(\n `[webpackManifestMissing] Webpack ${basename(manifestPath)} not found - running without asset manifest`\n )\n }\n }\n\n return {\n config: {\n cdpEnvironment: config.get('cdpEnvironment'),\n designerUrl: config.get('designerUrl'),\n feedbackLink: encodeUrl(config.get('feedbackLink')),\n phaseTag: config.get('phaseTag'),\n serviceName: config.get('serviceName'),\n serviceVersion: config.get('serviceVersion')\n },\n assetPath: '/assets',\n getDxtAssetPath: (asset = '') => {\n return `/${webpackManifest?.[asset] ?? asset}`\n }\n }\n}\n\n/**\n * @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { FormRequest, FormRequestPayload } from '~/src/server/routes/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","safeGenerateCrumb","logger","webpackManifest","context","request","params","response","isPreview","isPreviewMode","state","formState","isResponseOK","isBoom","statusCode","OK","pluginStorage","server","plugins","consumerViewContext","Error","baseLayoutPath","viewContext","ctx","crumb","currentPath","path","url","search","previewMode","undefined","slug","devtoolContext","_request","manifestPath","get","JSON","parse","info","cdpEnvironment","designerUrl","feedbackLink","phaseTag","serviceName","serviceVersion","assetPath","getDxtAssetPath","asset"],"sources":["../../../../src/server/plugins/nunjucks/context.js"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport Boom from '@hapi/boom'\nimport { StatusCodes } from 'http-status-codes'\n\nimport { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n checkFormStatus,\n encodeUrl,\n safeGenerateCrumb\n} from '~/src/server/plugins/engine/helpers.js'\n\nconst logger = createLogger()\n\n/** @type {Record<string, string> | undefined} */\nlet webpackManifest\n\n/**\n * @param {AnyFormRequest | null} request\n */\nexport async function context(request) {\n const { params, response } = request ?? {}\n\n const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params)\n\n // Only add the slug in to the context if the response is OK.\n // Footer meta links are not rendered when the slug is missing.\n const isResponseOK =\n !Boom.isBoom(response) && response?.statusCode === StatusCodes.OK\n\n const pluginStorage = request?.server.plugins['forms-engine-plugin']\n\n let consumerViewContext = {}\n\n if (!pluginStorage) {\n throw Error('context called before plugin registered')\n }\n\n if (!pluginStorage.baseLayoutPath) {\n throw Error('Missing baseLayoutPath in plugin.options.nunjucks')\n }\n\n if (typeof pluginStorage.viewContext === 'function') {\n consumerViewContext = await pluginStorage.viewContext(request)\n }\n\n /** @type {ViewContext} */\n const ctx = {\n // take consumers props first so we can override it\n ...consumerViewContext,\n baseLayoutPath: pluginStorage.baseLayoutPath,\n crumb: safeGenerateCrumb(request),\n currentPath: `${request.path}${request.url.search}`,\n previewMode: isPreviewMode ? formState : undefined,\n slug: isResponseOK ? params?.slug : undefined\n }\n\n return ctx\n}\n\n/**\n * Returns the context for the devtool. Consumers won't have access to this.\n * @param {AnyFormRequest | null} _request\n * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}\n */\nexport function devtoolContext(_request) {\n const manifestPath = join(config.get('publicDir'), 'assets-manifest.json')\n\n if (!webpackManifest) {\n try {\n // eslint-disable-next-line -- Allow JSON type 'any'\n webpackManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))\n } catch {\n logger.info(\n `[webpackManifestMissing] Webpack ${basename(manifestPath)} not found - running without asset manifest`\n )\n }\n }\n\n return {\n config: {\n cdpEnvironment: config.get('cdpEnvironment'),\n designerUrl: config.get('designerUrl'),\n feedbackLink: encodeUrl(config.get('feedbackLink')),\n phaseTag: config.get('phaseTag'),\n serviceName: config.get('serviceName'),\n serviceVersion: config.get('serviceVersion')\n },\n assetPath: '/assets',\n getDxtAssetPath: (asset = '') => {\n return `/${webpackManifest?.[asset] ?? asset}`\n }\n }\n}\n\n/**\n * @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
@@ -1,4 +1,4 @@
1
- import { type ReqRefDefaults, type Request } from '@hapi/hapi';
1
+ import { type ReqRefDefaults, type Request, type ResponseToolkit } from '@hapi/hapi';
2
2
  import { type FormPayload } from '~/src/server/plugins/engine/types.js';
3
3
  export interface FormQuery extends Partial<Record<string, string>> {
4
4
  /**
@@ -25,13 +25,14 @@ export interface FormRequestPayloadRefs extends FormRequestRefs {
25
25
  }
26
26
  export type FormRequest = Request<FormRequestRefs>;
27
27
  export type FormRequestPayload = Request<FormRequestPayloadRefs>;
28
+ export type FormResponseToolkit = Pick<ResponseToolkit, 'redirect' | 'view'>;
28
29
  export declare enum FormAction {
29
30
  Continue = "continue",
30
31
  Validate = "validate",
31
32
  Delete = "delete",
32
33
  AddAnother = "add-another",
33
34
  Send = "send",
34
- SaveAndReturn = "save-and-return"
35
+ SaveAndExit = "save-and-exit"
35
36
  }
36
37
  export declare enum FormStatus {
37
38
  Draft = "draft",
@@ -4,7 +4,7 @@ export let FormAction = /*#__PURE__*/function (FormAction) {
4
4
  FormAction["Delete"] = "delete";
5
5
  FormAction["AddAnother"] = "add-another";
6
6
  FormAction["Send"] = "send";
7
- FormAction["SaveAndReturn"] = "save-and-return";
7
+ FormAction["SaveAndExit"] = "save-and-exit";
8
8
  return FormAction;
9
9
  }({});
10
10
  export let FormStatus = /*#__PURE__*/function (FormStatus) {
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["FormAction","FormStatus"],"sources":["../../../src/server/routes/types.ts"],"sourcesContent":["import { type ReqRefDefaults, type Request } from '@hapi/hapi'\n\nimport { type FormPayload } from '~/src/server/plugins/engine/types.js'\n\nexport interface FormQuery extends Partial<Record<string, string>> {\n /**\n * Allow preview URL direct access without relevant page checks\n */\n force?: string\n\n /**\n * Redirect location after 'continue' form action\n */\n returnUrl?: string\n}\n\nexport interface FormParams extends Partial<Record<string, string>> {\n path: string\n slug: string\n state?: FormStatus\n}\n\nexport interface FormRequestRefs\n extends Omit<ReqRefDefaults, 'Params' | 'Payload' | 'Query'> {\n Params: FormParams\n Payload: object | undefined\n Query: FormQuery\n}\n\nexport interface FormRequestPayloadRefs extends FormRequestRefs {\n Payload: FormPayload\n}\n\nexport type FormRequest = Request<FormRequestRefs>\nexport type FormRequestPayload = Request<FormRequestPayloadRefs>\n\nexport enum FormAction {\n Continue = 'continue',\n Validate = 'validate',\n Delete = 'delete',\n AddAnother = 'add-another',\n Send = 'send',\n SaveAndReturn = 'save-and-return'\n}\n\nexport enum FormStatus {\n Draft = 'draft',\n Live = 'live'\n}\n"],"mappings":"AAoCA,WAAYA,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA;AAStB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":["FormAction","FormStatus"],"sources":["../../../src/server/routes/types.ts"],"sourcesContent":["import {\n type ReqRefDefaults,\n type Request,\n type ResponseToolkit\n} from '@hapi/hapi'\n\nimport { type FormPayload } from '~/src/server/plugins/engine/types.js'\n\nexport interface FormQuery extends Partial<Record<string, string>> {\n /**\n * Allow preview URL direct access without relevant page checks\n */\n force?: string\n\n /**\n * Redirect location after 'continue' form action\n */\n returnUrl?: string\n}\n\nexport interface FormParams extends Partial<Record<string, string>> {\n path: string\n slug: string\n state?: FormStatus\n}\n\nexport interface FormRequestRefs\n extends Omit<ReqRefDefaults, 'Params' | 'Payload' | 'Query'> {\n Params: FormParams\n Payload: object | undefined\n Query: FormQuery\n}\n\nexport interface FormRequestPayloadRefs extends FormRequestRefs {\n Payload: FormPayload\n}\n\nexport type FormRequest = Request<FormRequestRefs>\nexport type FormRequestPayload = Request<FormRequestPayloadRefs>\nexport type FormResponseToolkit = Pick<ResponseToolkit, 'redirect' | 'view'>\n\nexport enum FormAction {\n Continue = 'continue',\n Validate = 'validate',\n Delete = 'delete',\n AddAnother = 'add-another',\n Send = 'send',\n SaveAndExit = 'save-and-exit'\n}\n\nexport enum FormStatus {\n Draft = 'draft',\n Live = 'live'\n}\n"],"mappings":"AAyCA,WAAYA,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA;AAStB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
@@ -1,7 +1,7 @@
1
1
  import Joi from 'joi';
2
2
  import { FormAction, FormStatus } from "../routes/types.js";
3
3
  export const stateSchema = Joi.string().valid(FormStatus.Draft, FormStatus.Live).required();
4
- export const actionSchema = Joi.string().valid(FormAction.Continue, FormAction.Validate, FormAction.Delete, FormAction.AddAnother, FormAction.Send, FormAction.SaveAndReturn).default(FormAction.Validate).optional();
4
+ export const actionSchema = Joi.string().valid(FormAction.Continue, FormAction.Validate, FormAction.Delete, FormAction.AddAnother, FormAction.Send, FormAction.SaveAndExit).default(FormAction.Validate).optional();
5
5
  export const pathSchema = Joi.string().required();
6
6
  export const itemIdSchema = Joi.string().uuid().required();
7
7
  export const crumbSchema = Joi.string().optional().allow('');
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Joi","FormAction","FormStatus","stateSchema","string","valid","Draft","Live","required","actionSchema","Continue","Validate","Delete","AddAnother","Send","SaveAndReturn","default","optional","pathSchema","itemIdSchema","uuid","crumbSchema","allow","confirmSchema","boolean","empty","paramsSchema","object","keys","action","confirm","crumb","itemId"],"sources":["../../../src/server/schemas/index.ts"],"sourcesContent":["import Joi from 'joi'\n\nimport { type FormPayloadParams } from '~/src/server/plugins/engine/types.js'\nimport { FormAction, FormStatus } from '~/src/server/routes/types.js'\n\nexport const stateSchema = Joi.string<FormStatus>()\n .valid(FormStatus.Draft, FormStatus.Live)\n .required()\n\nexport const actionSchema = Joi.string<FormAction>()\n .valid(\n FormAction.Continue,\n FormAction.Validate,\n FormAction.Delete,\n FormAction.AddAnother,\n FormAction.Send,\n FormAction.SaveAndReturn\n )\n .default(FormAction.Validate)\n .optional()\n\nexport const pathSchema = Joi.string().required()\nexport const itemIdSchema = Joi.string().uuid().required()\nexport const crumbSchema = Joi.string().optional().allow('')\nexport const confirmSchema = Joi.boolean().empty(false)\n\nexport const paramsSchema = Joi.object<FormPayloadParams>()\n .keys({\n action: actionSchema,\n confirm: confirmSchema,\n crumb: crumbSchema,\n itemId: itemIdSchema.optional()\n })\n .default({})\n .optional()\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAGrB,SAASC,UAAU,EAAEC,UAAU;AAE/B,OAAO,MAAMC,WAAW,GAAGH,GAAG,CAACI,MAAM,CAAa,CAAC,CAChDC,KAAK,CAACH,UAAU,CAACI,KAAK,EAAEJ,UAAU,CAACK,IAAI,CAAC,CACxCC,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,YAAY,GAAGT,GAAG,CAACI,MAAM,CAAa,CAAC,CACjDC,KAAK,CACJJ,UAAU,CAACS,QAAQ,EACnBT,UAAU,CAACU,QAAQ,EACnBV,UAAU,CAACW,MAAM,EACjBX,UAAU,CAACY,UAAU,EACrBZ,UAAU,CAACa,IAAI,EACfb,UAAU,CAACc,aACb,CAAC,CACAC,OAAO,CAACf,UAAU,CAACU,QAAQ,CAAC,CAC5BM,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,UAAU,GAAGlB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACI,QAAQ,CAAC,CAAC;AACjD,OAAO,MAAMW,YAAY,GAAGnB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACgB,IAAI,CAAC,CAAC,CAACZ,QAAQ,CAAC,CAAC;AAC1D,OAAO,MAAMa,WAAW,GAAGrB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACa,QAAQ,CAAC,CAAC,CAACK,KAAK,CAAC,EAAE,CAAC;AAC5D,OAAO,MAAMC,aAAa,GAAGvB,GAAG,CAACwB,OAAO,CAAC,CAAC,CAACC,KAAK,CAAC,KAAK,CAAC;AAEvD,OAAO,MAAMC,YAAY,GAAG1B,GAAG,CAAC2B,MAAM,CAAoB,CAAC,CACxDC,IAAI,CAAC;EACJC,MAAM,EAAEpB,YAAY;EACpBqB,OAAO,EAAEP,aAAa;EACtBQ,KAAK,EAAEV,WAAW;EAClBW,MAAM,EAAEb,YAAY,CAACF,QAAQ,CAAC;AAChC,CAAC,CAAC,CACDD,OAAO,CAAC,CAAC,CAAC,CAAC,CACXC,QAAQ,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["Joi","FormAction","FormStatus","stateSchema","string","valid","Draft","Live","required","actionSchema","Continue","Validate","Delete","AddAnother","Send","SaveAndExit","default","optional","pathSchema","itemIdSchema","uuid","crumbSchema","allow","confirmSchema","boolean","empty","paramsSchema","object","keys","action","confirm","crumb","itemId"],"sources":["../../../src/server/schemas/index.ts"],"sourcesContent":["import Joi from 'joi'\n\nimport { type FormPayloadParams } from '~/src/server/plugins/engine/types.js'\nimport { FormAction, FormStatus } from '~/src/server/routes/types.js'\n\nexport const stateSchema = Joi.string<FormStatus>()\n .valid(FormStatus.Draft, FormStatus.Live)\n .required()\n\nexport const actionSchema = Joi.string<FormAction>()\n .valid(\n FormAction.Continue,\n FormAction.Validate,\n FormAction.Delete,\n FormAction.AddAnother,\n FormAction.Send,\n FormAction.SaveAndExit\n )\n .default(FormAction.Validate)\n .optional()\n\nexport const pathSchema = Joi.string().required()\nexport const itemIdSchema = Joi.string().uuid().required()\nexport const crumbSchema = Joi.string().optional().allow('')\nexport const confirmSchema = Joi.boolean().empty(false)\n\nexport const paramsSchema = Joi.object<FormPayloadParams>()\n .keys({\n action: actionSchema,\n confirm: confirmSchema,\n crumb: crumbSchema,\n itemId: itemIdSchema.optional()\n })\n .default({})\n .optional()\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAGrB,SAASC,UAAU,EAAEC,UAAU;AAE/B,OAAO,MAAMC,WAAW,GAAGH,GAAG,CAACI,MAAM,CAAa,CAAC,CAChDC,KAAK,CAACH,UAAU,CAACI,KAAK,EAAEJ,UAAU,CAACK,IAAI,CAAC,CACxCC,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,YAAY,GAAGT,GAAG,CAACI,MAAM,CAAa,CAAC,CACjDC,KAAK,CACJJ,UAAU,CAACS,QAAQ,EACnBT,UAAU,CAACU,QAAQ,EACnBV,UAAU,CAACW,MAAM,EACjBX,UAAU,CAACY,UAAU,EACrBZ,UAAU,CAACa,IAAI,EACfb,UAAU,CAACc,WACb,CAAC,CACAC,OAAO,CAACf,UAAU,CAACU,QAAQ,CAAC,CAC5BM,QAAQ,CAAC,CAAC;AAEb,OAAO,MAAMC,UAAU,GAAGlB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACI,QAAQ,CAAC,CAAC;AACjD,OAAO,MAAMW,YAAY,GAAGnB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACgB,IAAI,CAAC,CAAC,CAACZ,QAAQ,CAAC,CAAC;AAC1D,OAAO,MAAMa,WAAW,GAAGrB,GAAG,CAACI,MAAM,CAAC,CAAC,CAACa,QAAQ,CAAC,CAAC,CAACK,KAAK,CAAC,EAAE,CAAC;AAC5D,OAAO,MAAMC,aAAa,GAAGvB,GAAG,CAACwB,OAAO,CAAC,CAAC,CAACC,KAAK,CAAC,KAAK,CAAC;AAEvD,OAAO,MAAMC,YAAY,GAAG1B,GAAG,CAAC2B,MAAM,CAAoB,CAAC,CACxDC,IAAI,CAAC;EACJC,MAAM,EAAEpB,YAAY;EACpBqB,OAAO,EAAEP,aAAa;EACtBQ,KAAK,EAAEV,WAAW;EAClBW,MAAM,EAAEb,YAAY,CAACF,QAAQ,CAAC;AAChC,CAAC,CAAC,CACDD,OAAO,CAAC,CAAC,CAAC,CAAC,CACXC,QAAQ,CAAC,CAAC","ignoreList":[]}
@@ -1,6 +1,5 @@
1
- import { type Request, type Server } from '@hapi/hapi';
2
- import { type FormPayload, type FormState, type FormSubmissionError, type FormSubmissionState } from '~/src/server/plugins/engine/types.js';
3
- import { type FormRequest, type FormRequestPayload } from '~/src/server/routes/types.js';
1
+ import { type Server } from '@hapi/hapi';
2
+ import { type AnyFormRequest, type AnyRequest, type FormPayload, type FormState, type FormSubmissionError, type FormSubmissionState } from '~/src/server/plugins/engine/types.js';
4
3
  export declare enum ADDITIONAL_IDENTIFIER {
5
4
  Confirmation = ":confirmation"
6
5
  }
@@ -12,40 +11,33 @@ export declare class CacheService {
12
11
  cache: string | undefined;
13
12
  segment: string;
14
13
  }>;
15
- generateKey?: (request: Request | FormRequest | FormRequestPayload) => string;
16
- customFetcher?: (request: Request | FormRequest | FormRequestPayload) => Promise<FormSubmissionState | null>;
17
14
  logger: Server['logger'];
18
- constructor({ server, cacheName, options }: {
15
+ constructor({ server, cacheName }: {
19
16
  server: Server;
20
17
  cacheName?: string;
21
- options?: {
22
- keyGenerator?: (request: Request | FormRequest | FormRequestPayload) => string;
23
- sessionHydrator?: (request: Request | FormRequest | FormRequestPayload) => Promise<FormSubmissionState | null>;
24
- };
25
18
  });
26
- getState(request: Request | FormRequest | FormRequestPayload): Promise<FormSubmissionState>;
27
- setState(request: FormRequest | FormRequestPayload, state: FormSubmissionState): Promise<FormSubmissionState>;
28
- getConfirmationState(request: FormRequest | FormRequestPayload): Promise<{
19
+ getState(request: AnyRequest): Promise<FormSubmissionState>;
20
+ setState(request: AnyFormRequest, state: FormSubmissionState): Promise<FormSubmissionState>;
21
+ getConfirmationState(request: AnyFormRequest): Promise<{
29
22
  confirmed?: true;
30
23
  }>;
31
- setConfirmationState(request: FormRequest | FormRequestPayload, confirmationState: {
24
+ setConfirmationState(request: AnyFormRequest, confirmationState: {
32
25
  confirmed?: true;
33
26
  }): Promise<void>;
34
- clearState(request: FormRequest | FormRequestPayload): Promise<void>;
35
- getFlash(request: FormRequest | FormRequestPayload): {
27
+ clearState(request: AnyFormRequest): Promise<void>;
28
+ getFlash(request: AnyFormRequest): {
36
29
  errors: FormSubmissionError[];
37
30
  } | undefined;
38
- setFlash(request: FormRequest | FormRequestPayload, message: {
31
+ setFlash(request: AnyFormRequest, message: {
39
32
  errors: FormSubmissionError[];
40
33
  }): void;
41
- private defaultKeyGenerator;
42
34
  /**
43
35
  * The key used to store user session data against.
44
36
  * 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`
45
37
  * @param request - hapi request object
46
38
  * @param additionalIdentifier - appended to the id
47
39
  */
48
- Key(request: Request | FormRequest | FormRequestPayload, additionalIdentifier?: ADDITIONAL_IDENTIFIER): {
40
+ Key(request: AnyRequest, additionalIdentifier?: ADDITIONAL_IDENTIFIER): {
49
41
  segment: string;
50
42
  id: string;
51
43
  };
@@ -10,23 +10,14 @@ export class CacheService {
10
10
  * 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}
11
11
  */
12
12
  cache;
13
- generateKey;
14
- customFetcher;
15
13
  logger;
16
14
  constructor({
17
15
  server,
18
- cacheName,
19
- options
16
+ cacheName
20
17
  }) {
21
- const {
22
- keyGenerator,
23
- sessionHydrator
24
- } = options ?? {};
25
18
  if (!cacheName) {
26
19
  server.log('warn', 'You are using the default hapi cache. Please provide a cache name in plugin registration options.');
27
20
  }
28
- this.generateKey = keyGenerator ?? this.defaultKeyGenerator.bind(this);
29
- this.customFetcher = sessionHydrator ?? undefined;
30
21
  this.cache = server.cache({
31
22
  cache: cacheName,
32
23
  segment: 'formSubmission'
@@ -35,16 +26,7 @@ export class CacheService {
35
26
  }
36
27
  async getState(request) {
37
28
  const key = this.Key(request);
38
- let cached = await this.cache.get(key);
39
-
40
- // If nothing in Redis, attempt to rehydrate from backend DB
41
- if (!cached && this.customFetcher) {
42
- const rehydrated = await this.customFetcher(request);
43
- if (rehydrated != null) {
44
- await this.cache.set(key, rehydrated, config.get('sessionTimeout'));
45
- cached = await this.getState(request);
46
- }
47
- }
29
+ const cached = await this.cache.get(key);
48
30
  return cached ?? {};
49
31
  }
50
32
  async setState(request, state) {
@@ -79,14 +61,6 @@ export class CacheService {
79
61
  const key = this.Key(request);
80
62
  request.yar.flash(key.id, message);
81
63
  }
82
- defaultKeyGenerator(request) {
83
- if (!request.yar.id) {
84
- throw new Error('No session ID found');
85
- }
86
- const state = request.params.state || '';
87
- const slug = request.params.slug || '';
88
- return `${request.yar.id}:${state}:${slug}:`;
89
- }
90
64
 
91
65
  /**
92
66
  * The key used to store user session data against.
@@ -95,10 +69,15 @@ export class CacheService {
95
69
  * @param additionalIdentifier - appended to the id
96
70
  */
97
71
  Key(request, additionalIdentifier) {
98
- const baseKey = this.generateKey ? this.generateKey(request) : this.defaultKeyGenerator(request);
72
+ if (!request.yar.id) {
73
+ throw new Error('No session ID found');
74
+ }
75
+ const state = request.params.state || '';
76
+ const slug = request.params.slug || '';
77
+ const key = `${request.yar.id}:${state}:${slug}:`;
99
78
  return {
100
79
  segment: partition,
101
- id: `${baseKey}${additionalIdentifier ?? ''}`
80
+ id: `${key}${additionalIdentifier ?? ''}`
102
81
  };
103
82
  }
104
83
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","generateKey","customFetcher","logger","constructor","server","cacheName","options","keyGenerator","sessionHydrator","log","defaultKeyGenerator","bind","undefined","segment","getState","request","key","Key","cached","get","rehydrated","set","setState","state","ttl","getConfirmationState","Confirmation","value","setConfirmationState","confirmationState","clearState","yar","id","drop","getFlash","messages","flash","Array","isArray","length","at","setFlash","message","Error","params","slug","additionalIdentifier","baseKey","merge","update","mergeArrays"],"sources":["../../../src/server/services/cacheService.ts"],"sourcesContent":["import { type Request, type Server } from '@hapi/hapi'\nimport * as Hoek from '@hapi/hoek'\n\nimport { config } from '~/src/config/index.js'\nimport { type createServer } from '~/src/server/index.js'\nimport {\n type FormPayload,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst partition = 'cache'\n\nexport enum ADDITIONAL_IDENTIFIER {\n Confirmation = ':confirmation'\n}\n\nexport class CacheService {\n /**\n * 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}\n */\n cache\n generateKey?: (request: Request | FormRequest | FormRequestPayload) => string\n customFetcher?: (\n request: Request | FormRequest | FormRequestPayload\n ) => Promise<FormSubmissionState | null>\n\n logger: Server['logger']\n\n constructor({\n server,\n cacheName,\n options\n }: {\n server: Server\n cacheName?: string\n options?: {\n keyGenerator?: (\n request: Request | FormRequest | FormRequestPayload\n ) => string\n sessionHydrator?: (\n request: Request | FormRequest | FormRequestPayload\n ) => Promise<FormSubmissionState | null>\n }\n }) {\n const { keyGenerator, sessionHydrator } = options ?? {}\n if (!cacheName) {\n server.log(\n 'warn',\n 'You are using the default hapi cache. Please provide a cache name in plugin registration options.'\n )\n }\n this.generateKey = keyGenerator ?? this.defaultKeyGenerator.bind(this)\n this.customFetcher = sessionHydrator ?? undefined\n this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })\n this.logger = server.logger\n }\n\n async getState(\n request: Request | FormRequest | FormRequestPayload\n ): Promise<FormSubmissionState> {\n const key = this.Key(request)\n\n let cached = await this.cache.get(key)\n\n // If nothing in Redis, attempt to rehydrate from backend DB\n if (!cached && this.customFetcher) {\n const rehydrated = await this.customFetcher(request)\n\n if (rehydrated != null) {\n await this.cache.set(key, rehydrated, config.get('sessionTimeout'))\n cached = await this.getState(request)\n }\n }\n\n return cached ?? {}\n }\n\n async setState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const key = this.Key(request)\n const ttl = config.get('sessionTimeout')\n\n await this.cache.set(key, state, ttl)\n\n return this.getState(request)\n }\n\n async getConfirmationState(\n request: FormRequest | FormRequestPayload\n ): Promise<{ confirmed?: true }> {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const value = await this.cache.get(key)\n\n return value ?? {}\n }\n\n async setConfirmationState(\n request: FormRequest | FormRequestPayload,\n confirmationState: { confirmed?: true }\n ) {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const ttl = config.get('confirmationSessionTimeout')\n\n return this.cache.set(key, confirmationState, ttl)\n }\n\n async clearState(request: FormRequest | FormRequestPayload) {\n if (request.yar.id) {\n await this.cache.drop(this.Key(request))\n }\n }\n\n getFlash(\n request: FormRequest | FormRequestPayload\n ): { errors: FormSubmissionError[] } | undefined {\n const key = this.Key(request)\n const messages = request.yar.flash(key.id)\n\n if (Array.isArray(messages) && messages.length) {\n return messages.at(0) as { errors: FormSubmissionError[] }\n }\n }\n\n setFlash(\n request: FormRequest | FormRequestPayload,\n message: { errors: FormSubmissionError[] }\n ) {\n const key = this.Key(request)\n\n request.yar.flash(key.id, message)\n }\n\n private defaultKeyGenerator(\n request: Request | FormRequest | FormRequestPayload\n ): string {\n if (!request.yar.id) {\n throw new Error('No session ID found')\n }\n\n const state = (request.params.state as string) || ''\n const slug = (request.params.slug as string) || ''\n return `${request.yar.id}:${state}:${slug}:`\n }\n\n /**\n * The key used to store user session data against.\n * 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`\n * @param request - hapi request object\n * @param additionalIdentifier - appended to the id\n */\n Key(\n request: Request | FormRequest | FormRequestPayload,\n additionalIdentifier?: ADDITIONAL_IDENTIFIER\n ) {\n const baseKey = this.generateKey\n ? this.generateKey(request)\n : this.defaultKeyGenerator(request)\n\n return {\n segment: partition,\n id: `${baseKey}${additionalIdentifier ?? ''}`\n }\n }\n}\n\n/**\n * State merge helper\n * 1. Merges objects (form fields)\n * 2. Overwrites arrays\n */\nexport function merge<StateType extends FormState | FormPayload>(\n state: StateType,\n update: object\n): StateType {\n return Hoek.merge(state, update, {\n mergeArrays: false\n })\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","logger","constructor","server","cacheName","log","segment","getState","request","key","Key","cached","get","setState","state","ttl","set","getConfirmationState","Confirmation","value","setConfirmationState","confirmationState","clearState","yar","id","drop","getFlash","messages","flash","Array","isArray","length","at","setFlash","message","additionalIdentifier","Error","params","slug","merge","update","mergeArrays"],"sources":["../../../src/server/services/cacheService.ts"],"sourcesContent":["import { type Server } from '@hapi/hapi'\nimport * as Hoek from '@hapi/hoek'\n\nimport { config } from '~/src/config/index.js'\nimport { type createServer } from '~/src/server/index.js'\nimport {\n type AnyFormRequest,\n type AnyRequest,\n type FormPayload,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nconst partition = 'cache'\n\nexport enum ADDITIONAL_IDENTIFIER {\n Confirmation = ':confirmation'\n}\n\nexport class CacheService {\n /**\n * 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}\n */\n cache\n logger: Server['logger']\n\n constructor({ server, cacheName }: { server: Server; cacheName?: string }) {\n if (!cacheName) {\n server.log(\n 'warn',\n 'You are using the default hapi cache. Please provide a cache name in plugin registration options.'\n )\n }\n\n this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })\n this.logger = server.logger\n }\n\n async getState(request: AnyRequest): Promise<FormSubmissionState> {\n const key = this.Key(request)\n const cached = await this.cache.get(key)\n\n return cached ?? {}\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const key = this.Key(request)\n const ttl = config.get('sessionTimeout')\n\n await this.cache.set(key, state, ttl)\n\n return this.getState(request)\n }\n\n async getConfirmationState(\n request: AnyFormRequest\n ): Promise<{ confirmed?: true }> {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const value = await this.cache.get(key)\n\n return value ?? {}\n }\n\n async setConfirmationState(\n request: AnyFormRequest,\n confirmationState: { confirmed?: true }\n ) {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const ttl = config.get('confirmationSessionTimeout')\n\n return this.cache.set(key, confirmationState, ttl)\n }\n\n async clearState(request: AnyFormRequest) {\n if (request.yar.id) {\n await this.cache.drop(this.Key(request))\n }\n }\n\n getFlash(\n request: AnyFormRequest\n ): { errors: FormSubmissionError[] } | undefined {\n const key = this.Key(request)\n const messages = request.yar.flash(key.id)\n\n if (Array.isArray(messages) && messages.length) {\n return messages.at(0) as { errors: FormSubmissionError[] }\n }\n }\n\n setFlash(\n request: AnyFormRequest,\n message: { errors: FormSubmissionError[] }\n ) {\n const key = this.Key(request)\n\n request.yar.flash(key.id, message)\n }\n\n /**\n * The key used to store user session data against.\n * 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`\n * @param request - hapi request object\n * @param additionalIdentifier - appended to the id\n */\n Key(request: AnyRequest, additionalIdentifier?: ADDITIONAL_IDENTIFIER) {\n if (!request.yar.id) {\n throw new Error('No session ID found')\n }\n\n const state = (request.params.state as string) || ''\n const slug = (request.params.slug as string) || ''\n const key = `${request.yar.id}:${state}:${slug}:`\n\n return {\n segment: partition,\n id: `${key}${additionalIdentifier ?? ''}`\n }\n }\n}\n\n/**\n * State merge helper\n * 1. Merges objects (form fields)\n * 2. Overwrites arrays\n */\nexport function merge<StateType extends FormState | FormPayload>(\n state: StateType,\n update: object\n): StateType {\n return Hoek.merge(state, update, {\n mergeArrays: false\n })\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,9 +1,11 @@
1
1
  import { type FormDefinition, type FormMetadata, type SubmitPayload, type SubmitResponsePayload } from '@defra/forms-model';
2
+ import { type Server } from '@hapi/hapi';
2
3
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
3
4
  import { type DetailItem } from '~/src/server/plugins/engine/models/types.js';
4
5
  import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
5
6
  import { type FormContext, type OnRequestCallback, type PluginOptions, type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js';
6
7
  import { type FormRequestPayload, type FormStatus } from '~/src/server/routes/types.js';
8
+ import { type CacheService } from '~/src/server/services/cacheService.js';
7
9
  export interface FormsService {
8
10
  getFormMetadata: (slug: string) => Promise<FormMetadata>;
9
11
  getFormDefinition: (id: string, state: FormStatus) => Promise<FormDefinition | undefined>;
@@ -28,7 +30,8 @@ export interface RouteConfig {
28
30
  controllers?: Record<string, typeof PageController>;
29
31
  preparePageEventRequestOptions?: PreparePageEventRequestOptions;
30
32
  onRequest?: OnRequestCallback;
31
- saveAndReturn?: PluginOptions['saveAndReturn'];
33
+ saveAndExit?: PluginOptions['saveAndExit'];
34
+ cacheServiceCreator?: (server: Server) => CacheService;
32
35
  }
33
36
  export interface OutputService {
34
37
  submit: (context: FormContext, request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload, formMetadata?: FormMetadata) => Promise<void>;