@defra/forms-engine-plugin 1.4.1 → 2.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 (78) hide show
  1. package/.server/server/plugins/engine/README.md +2 -46
  2. package/.server/server/plugins/engine/configureEnginePlugin.d.ts +1 -1
  3. package/.server/server/plugins/engine/configureEnginePlugin.js +5 -2
  4. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  5. package/.server/server/plugins/engine/helpers.d.ts +11 -0
  6. package/.server/server/plugins/engine/helpers.js +7 -1
  7. package/.server/server/plugins/engine/helpers.js.map +1 -1
  8. package/.server/server/plugins/engine/index.js +1 -1
  9. package/.server/server/plugins/engine/index.js.map +1 -1
  10. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +1 -1
  11. package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
  12. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  13. package/.server/server/plugins/engine/options.js +5 -3
  14. package/.server/server/plugins/engine/options.js.map +1 -1
  15. package/.server/server/plugins/engine/options.test.js +18 -9
  16. package/.server/server/plugins/engine/options.test.js.map +1 -1
  17. package/.server/server/plugins/engine/pageControllers/PageController.d.ts +3 -1
  18. package/.server/server/plugins/engine/pageControllers/PageController.js +5 -1
  19. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  20. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -1
  21. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -4
  22. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  23. package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -2
  24. package/.server/server/plugins/engine/pageControllers/StartPageController.js +1 -3
  25. package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
  26. package/.server/server/plugins/engine/pageControllers/StatusPageController.d.ts +1 -0
  27. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +1 -0
  28. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  29. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +0 -1
  30. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +1 -4
  31. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  32. package/.server/server/plugins/engine/pageControllers/TerminalPageController.d.ts +1 -0
  33. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js +1 -0
  34. package/.server/server/plugins/engine/pageControllers/TerminalPageController.js.map +1 -1
  35. package/.server/server/plugins/engine/pageControllers/__stubs__/request.d.ts +4 -0
  36. package/.server/server/plugins/engine/pageControllers/__stubs__/request.js +14 -0
  37. package/.server/server/plugins/engine/pageControllers/__stubs__/request.js.map +1 -0
  38. package/.server/server/plugins/engine/pageControllers/__stubs__/server.d.ts +3 -0
  39. package/.server/server/plugins/engine/pageControllers/__stubs__/server.js +23 -0
  40. package/.server/server/plugins/engine/pageControllers/__stubs__/server.js.map +1 -0
  41. package/.server/server/plugins/engine/plugin.js +5 -6
  42. package/.server/server/plugins/engine/plugin.js.map +1 -1
  43. package/.server/server/plugins/engine/types.d.ts +7 -5
  44. package/.server/server/plugins/engine/types.js.map +1 -1
  45. package/.server/server/types.d.ts +2 -1
  46. package/.server/server/types.js.map +1 -1
  47. package/.server/typings/hapi/index.d.js.map +1 -1
  48. package/package.json +1 -1
  49. package/src/server/plugins/engine/README.md +2 -46
  50. package/src/server/plugins/engine/configureEnginePlugin.ts +4 -2
  51. package/src/server/plugins/engine/helpers.test.ts +3 -2
  52. package/src/server/plugins/engine/helpers.ts +9 -1
  53. package/src/server/plugins/engine/index.ts +3 -1
  54. package/src/server/plugins/engine/models/FormModel.test.ts +11 -10
  55. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +7 -5
  56. package/src/server/plugins/engine/models/SummaryViewModel.ts +1 -1
  57. package/src/server/plugins/engine/options.js +5 -3
  58. package/src/server/plugins/engine/options.test.js +22 -11
  59. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +3 -3
  60. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +12 -1
  61. package/src/server/plugins/engine/pageControllers/PageController.test.ts +9 -0
  62. package/src/server/plugins/engine/pageControllers/PageController.ts +10 -1
  63. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +34 -28
  64. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -5
  65. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +19 -4
  66. package/src/server/plugins/engine/pageControllers/StartPageController.test.ts +32 -0
  67. package/src/server/plugins/engine/pageControllers/StartPageController.ts +2 -4
  68. package/src/server/plugins/engine/pageControllers/StatusPageController.test.ts +32 -0
  69. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +1 -0
  70. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +1 -5
  71. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +9 -0
  72. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +1 -0
  73. package/src/server/plugins/engine/pageControllers/__stubs__/request.ts +21 -0
  74. package/src/server/plugins/engine/pageControllers/__stubs__/server.ts +27 -0
  75. package/src/server/plugins/engine/plugin.ts +6 -6
  76. package/src/server/plugins/engine/types.ts +14 -9
  77. package/src/server/types.ts +2 -0
  78. package/src/typings/hapi/index.d.ts +2 -0
@@ -86,53 +86,9 @@ There are a number of `LiquidJS` filters available to you from within the templa
86
86
  ]
87
87
  ```
88
88
 
89
- ## Session Rehydration
89
+ ### Save and return
90
90
 
91
- To support Save and Return functionality, this application now supports session rehydration. This allows user session state to be recovered across browser sessions or devices — even after the in-memory Redis session has expired.
92
-
93
- ### How it works
94
-
95
- To support session rehydration from a backend (e.g. for Save & Return), the consuming application must provide two functions when registering the DXT engine plugin:
96
-
97
- ```
98
- export interface PluginOptions {
99
- ...
100
- keyGenerator?: (request) => string
101
- sessionHydrator?: (request) => Promise<FormSubmissionState | null>
102
- ...
103
- }
104
-
105
- ```
106
-
107
- 1. `keyGenerator(request)`
108
-
109
- This generates a stable and consistent cache key used to store and retrieve user state. It should return a string based on persistent identifiers such as userId, businessId, and grantId — i.e., something like:
110
-
111
- ```
112
- const keyGenerator = request => {
113
- const { userId, businessId, grantId } = request.app.userContext
114
- return `${userId}:${businessId}:${grantId}`
115
- }
116
- ```
117
-
118
- 2. `sessionHydrator(request, key)`
119
-
120
- This function is called when no session state is found in Redis. It should fetch saved state (e.g., from an API) using the provided key and return it in the same structure expected by the form engine:
121
-
122
- ```
123
- const sessionHydrator = async (request, key) => {
124
- const response = await fetch(`https://backend.api/state/${key}`)
125
- if (!response.ok) return null
126
- return await response.json() // Must match form engine state shape
127
- }
128
- ```
129
-
130
- #### Session flow
131
-
132
- - When user resumes a journey and Redis session data is missing or expired, DXT will use `keyGenerator` and `sessionHydrator` to fetch the saved state from an external API (e.g. `/state` endpoint).
133
- - The fetched state is written back into Redis and used to continue the user journey.
134
- - The rehydrated state must include enough information to satisfy schema validation on the current or next page.
135
- - To properly resume a session, users should be redirected to the `/summary` page. This ensures the UI has all required answers preloaded and avoids invalid transitions from deep links.
91
+ See [our save and return feature page](/docs/features/code-based/SAVE_AND_RETURN.md).
136
92
 
137
93
  ### Additional notes
138
94
 
@@ -2,7 +2,7 @@ import { type FormDefinition } from '@defra/forms-model';
2
2
  import { plugin } from '~/src/server/plugins/engine/plugin.js';
3
3
  import { type PluginOptions } from '~/src/server/plugins/engine/types.js';
4
4
  import { type RouteConfig } from '~/src/server/types.js';
5
- export declare const configureEnginePlugin: ({ formFileName, formFilePath, services, controllers, preparePageEventRequestOptions, onRequest }?: RouteConfig) => Promise<{
5
+ export declare const configureEnginePlugin: ({ formFileName, formFilePath, services, controllers, preparePageEventRequestOptions, onRequest, saveAndReturn }?: RouteConfig) => Promise<{
6
6
  plugin: typeof plugin;
7
7
  options: PluginOptions;
8
8
  }>;
@@ -12,7 +12,8 @@ export const configureEnginePlugin = async ({
12
12
  services,
13
13
  controllers,
14
14
  preparePageEventRequestOptions,
15
- onRequest
15
+ onRequest,
16
+ saveAndReturn
16
17
  } = {}) => {
17
18
  let model;
18
19
  if (formFileName && formFilePath) {
@@ -43,7 +44,9 @@ export const configureEnginePlugin = async ({
43
44
  viewContext: devtoolContext,
44
45
  preparePageEventRequestOptions,
45
46
  onRequest,
46
- baseUrl: 'http://localhost:3009' // always runs locally
47
+ baseUrl: 'http://localhost:3009',
48
+ // always runs locally
49
+ saveAndReturn
47
50
  }
48
51
  };
49
52
  };
@@ -1 +1 @@
1
- {"version":3,"file":"configureEnginePlugin.js","names":["join","parse","FORM_PREFIX","FormModel","plugin","defaultServices","formsService","findPackageRoot","devtoolContext","configureEnginePlugin","formFileName","formFilePath","services","controllers","preparePageEventRequestOptions","onRequest","model","definition","getForm","name","initialBasePath","basePath","options","cacheName","nunjucks","baseLayoutPath","paths","viewContext","baseUrl","importPath","ext","attributes","type","formImport","with","default"],"sources":["../../../../src/server/plugins/engine/configureEnginePlugin.ts"],"sourcesContent":["import { join, parse } from 'node:path'\n\nimport { type FormDefinition } from '@defra/forms-model'\n\nimport { FORM_PREFIX } from '~/src/server/constants.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport { formsService } from '~/src/server/plugins/engine/services/localFormsService.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { findPackageRoot } from '~/src/server/plugins/engine/vision.js'\nimport { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'\nimport { type RouteConfig } from '~/src/server/types.js'\n\nexport const configureEnginePlugin = async ({\n formFileName,\n formFilePath,\n services,\n controllers,\n preparePageEventRequestOptions,\n onRequest\n}: RouteConfig = {}): Promise<{\n plugin: typeof plugin\n options: PluginOptions\n}> => {\n let model: FormModel | undefined\n\n if (formFileName && formFilePath) {\n const definition = await getForm(join(formFilePath, formFileName))\n const { name } = parse(formFileName)\n\n const initialBasePath = `${FORM_PREFIX}${name}`\n\n model = new FormModel(\n definition,\n { basePath: initialBasePath },\n services,\n controllers\n )\n }\n\n return {\n plugin,\n options: {\n model,\n services: services ?? {\n // services for testing, else use the disk loader option for running this service locally\n ...defaultServices,\n formsService: await formsService()\n },\n controllers,\n cacheName: 'session',\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: devtoolContext,\n preparePageEventRequestOptions,\n onRequest,\n baseUrl: 'http://localhost:3009' // always runs locally\n }\n }\n}\n\nexport async function getForm(importPath: string) {\n const { ext } = parse(importPath)\n\n const attributes: ImportAttributes = {\n type: ext === '.json' ? 'json' : 'module'\n }\n\n const formImport = import(importPath, { with: attributes }) as Promise<{\n default: FormDefinition\n }>\n\n const { default: definition } = await formImport\n return definition\n}\n"],"mappings":"AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,WAAW;AAIvC,SAASC,WAAW;AACpB,SAASC,SAAS;AAClB,SAASC,MAAM;AACf,OAAO,KAAKC,eAAe;AAC3B,SAASC,YAAY;AAErB,SAASC,eAAe;AACxB,SAASC,cAAc;AAGvB,OAAO,MAAMC,qBAAqB,GAAG,MAAAA,CAAO;EAC1CC,YAAY;EACZC,YAAY;EACZC,QAAQ;EACRC,WAAW;EACXC,8BAA8B;EAC9BC;AACW,CAAC,GAAG,CAAC,CAAC,KAGb;EACJ,IAAIC,KAA4B;EAEhC,IAAIN,YAAY,IAAIC,YAAY,EAAE;IAChC,MAAMM,UAAU,GAAG,MAAMC,OAAO,CAAClB,IAAI,CAACW,YAAY,EAAED,YAAY,CAAC,CAAC;IAClE,MAAM;MAAES;IAAK,CAAC,GAAGlB,KAAK,CAACS,YAAY,CAAC;IAEpC,MAAMU,eAAe,GAAG,GAAGlB,WAAW,GAAGiB,IAAI,EAAE;IAE/CH,KAAK,GAAG,IAAIb,SAAS,CACnBc,UAAU,EACV;MAAEI,QAAQ,EAAED;IAAgB,CAAC,EAC7BR,QAAQ,EACRC,WACF,CAAC;EACH;EAEA,OAAO;IACLT,MAAM;IACNkB,OAAO,EAAE;MACPN,KAAK;MACLJ,QAAQ,EAAEA,QAAQ,IAAI;QACpB;QACA,GAAGP,eAAe;QAClBC,YAAY,EAAE,MAAMA,YAAY,CAAC;MACnC,CAAC;MACDO,WAAW;MACXU,SAAS,EAAE,SAAS;MACpBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC1B,IAAI,CAACO,eAAe,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC;MAC3D,CAAC;MACDoB,WAAW,EAAEnB,cAAc;MAC3BM,8BAA8B;MAC9BC,SAAS;MACTa,OAAO,EAAE,uBAAuB,CAAC;IACnC;EACF,CAAC;AACH,CAAC;AAED,OAAO,eAAeV,OAAOA,CAACW,UAAkB,EAAE;EAChD,MAAM;IAAEC;EAAI,CAAC,GAAG7B,KAAK,CAAC4B,UAAU,CAAC;EAEjC,MAAME,UAA4B,GAAG;IACnCC,IAAI,EAAEF,GAAG,KAAK,OAAO,GAAG,MAAM,GAAG;EACnC,CAAC;EAED,MAAMG,UAAU,GAAG,MAAM,CAACJ,UAAU,EAAE;IAAEK,IAAI,EAAEH;EAAW,CAAC,CAExD;EAEF,MAAM;IAAEI,OAAO,EAAElB;EAAW,CAAC,GAAG,MAAMgB,UAAU;EAChD,OAAOhB,UAAU;AACnB","ignoreList":[]}
1
+ {"version":3,"file":"configureEnginePlugin.js","names":["join","parse","FORM_PREFIX","FormModel","plugin","defaultServices","formsService","findPackageRoot","devtoolContext","configureEnginePlugin","formFileName","formFilePath","services","controllers","preparePageEventRequestOptions","onRequest","saveAndReturn","model","definition","getForm","name","initialBasePath","basePath","options","cacheName","nunjucks","baseLayoutPath","paths","viewContext","baseUrl","importPath","ext","attributes","type","formImport","with","default"],"sources":["../../../../src/server/plugins/engine/configureEnginePlugin.ts"],"sourcesContent":["import { join, parse } from 'node:path'\n\nimport { type FormDefinition } from '@defra/forms-model'\n\nimport { FORM_PREFIX } from '~/src/server/constants.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport { formsService } from '~/src/server/plugins/engine/services/localFormsService.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { findPackageRoot } from '~/src/server/plugins/engine/vision.js'\nimport { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'\nimport { type RouteConfig } from '~/src/server/types.js'\n\nexport const configureEnginePlugin = async ({\n formFileName,\n formFilePath,\n services,\n controllers,\n preparePageEventRequestOptions,\n onRequest,\n saveAndReturn\n}: RouteConfig = {}): Promise<{\n plugin: typeof plugin\n options: PluginOptions\n}> => {\n let model: FormModel | undefined\n\n if (formFileName && formFilePath) {\n const definition = await getForm(join(formFilePath, formFileName))\n const { name } = parse(formFileName)\n\n const initialBasePath = `${FORM_PREFIX}${name}`\n\n model = new FormModel(\n definition,\n { basePath: initialBasePath },\n services,\n controllers\n )\n }\n\n return {\n plugin,\n options: {\n model,\n services: services ?? {\n // services for testing, else use the disk loader option for running this service locally\n ...defaultServices,\n formsService: await formsService()\n },\n controllers,\n cacheName: 'session',\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: devtoolContext,\n preparePageEventRequestOptions,\n onRequest,\n baseUrl: 'http://localhost:3009', // always runs locally\n saveAndReturn\n }\n }\n}\n\nexport async function getForm(importPath: string) {\n const { ext } = parse(importPath)\n\n const attributes: ImportAttributes = {\n type: ext === '.json' ? 'json' : 'module'\n }\n\n const formImport = import(importPath, { with: attributes }) as Promise<{\n default: FormDefinition\n }>\n\n const { default: definition } = await formImport\n return definition\n}\n"],"mappings":"AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,WAAW;AAIvC,SAASC,WAAW;AACpB,SAASC,SAAS;AAClB,SAASC,MAAM;AACf,OAAO,KAAKC,eAAe;AAC3B,SAASC,YAAY;AAErB,SAASC,eAAe;AACxB,SAASC,cAAc;AAGvB,OAAO,MAAMC,qBAAqB,GAAG,MAAAA,CAAO;EAC1CC,YAAY;EACZC,YAAY;EACZC,QAAQ;EACRC,WAAW;EACXC,8BAA8B;EAC9BC,SAAS;EACTC;AACW,CAAC,GAAG,CAAC,CAAC,KAGb;EACJ,IAAIC,KAA4B;EAEhC,IAAIP,YAAY,IAAIC,YAAY,EAAE;IAChC,MAAMO,UAAU,GAAG,MAAMC,OAAO,CAACnB,IAAI,CAACW,YAAY,EAAED,YAAY,CAAC,CAAC;IAClE,MAAM;MAAEU;IAAK,CAAC,GAAGnB,KAAK,CAACS,YAAY,CAAC;IAEpC,MAAMW,eAAe,GAAG,GAAGnB,WAAW,GAAGkB,IAAI,EAAE;IAE/CH,KAAK,GAAG,IAAId,SAAS,CACnBe,UAAU,EACV;MAAEI,QAAQ,EAAED;IAAgB,CAAC,EAC7BT,QAAQ,EACRC,WACF,CAAC;EACH;EAEA,OAAO;IACLT,MAAM;IACNmB,OAAO,EAAE;MACPN,KAAK;MACLL,QAAQ,EAAEA,QAAQ,IAAI;QACpB;QACA,GAAGP,eAAe;QAClBC,YAAY,EAAE,MAAMA,YAAY,CAAC;MACnC,CAAC;MACDO,WAAW;MACXW,SAAS,EAAE,SAAS;MACpBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC3B,IAAI,CAACO,eAAe,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC;MAC3D,CAAC;MACDqB,WAAW,EAAEpB,cAAc;MAC3BM,8BAA8B;MAC9BC,SAAS;MACTc,OAAO,EAAE,uBAAuB;MAAE;MAClCb;IACF;EACF,CAAC;AACH,CAAC;AAED,OAAO,eAAeG,OAAOA,CAACW,UAAkB,EAAE;EAChD,MAAM;IAAEC;EAAI,CAAC,GAAG9B,KAAK,CAAC6B,UAAU,CAAC;EAEjC,MAAME,UAA4B,GAAG;IACnCC,IAAI,EAAEF,GAAG,KAAK,OAAO,GAAG,MAAM,GAAG;EACnC,CAAC;EAED,MAAMG,UAAU,GAAG,MAAM,CAACJ,UAAU,EAAE;IAAEK,IAAI,EAAEH;EAAW,CAAC,CAExD;EAEF,MAAM;IAAEI,OAAO,EAAElB;EAAW,CAAC,GAAG,MAAMgB,UAAU;EAChD,OAAOhB,UAAU;AACnB","ignoreList":[]}
@@ -60,6 +60,17 @@ export declare function safeGenerateCrumb(request: FormRequest | FormRequestPayl
60
60
  export declare function getExponentialBackoffDelay(depth: number): number;
61
61
  export declare function evaluateTemplate(template: string, context: FormContext): string;
62
62
  export declare function getCacheService(server: Server): import("../../services/cacheService.js").CacheService;
63
+ export declare function getSaveAndReturnHelpers(server: Server): {
64
+ keyGenerator: (request: FormRequest | FormRequestPayload | import("@hapi/hapi").Request<import("@hapi/hapi").ReqRefDefaults>) => string;
65
+ sessionHydrator: (request: FormRequest | FormRequestPayload | import("@hapi/hapi").Request<import("@hapi/hapi").ReqRefDefaults>) => Promise<import("~/src/server/plugins/engine/types.js").FormSubmissionState>;
66
+ sessionPersister: (key: string, state: import("~/src/server/plugins/engine/types.js").FormSubmissionState, request: FormRequest | FormRequestPayload | import("@hapi/hapi").Request<import("@hapi/hapi").ReqRefDefaults>) => Promise<void>;
67
+ } | undefined;
68
+ export declare function getPluginOptions(server: Server): {
69
+ baseLayoutPath: string;
70
+ cacheService: import("../../services/cacheService.js").CacheService;
71
+ viewContext?: (request: FormRequest | FormRequestPayload | null) => Record<string, unknown> | Promise<Record<string, unknown>>;
72
+ saveAndReturn?: import("~/src/server/plugins/engine/types.js").PluginOptions["saveAndReturn"];
73
+ };
63
74
  /**
64
75
  * Handles logging and issuing a permanent redirect for legacy routes.
65
76
  * @param h - The Hapi response toolkit.
@@ -257,7 +257,13 @@ export function evaluateTemplate(template, context) {
257
257
  });
258
258
  }
259
259
  export function getCacheService(server) {
260
- return server.plugins['forms-engine-plugin'].cacheService;
260
+ return getPluginOptions(server).cacheService;
261
+ }
262
+ export function getSaveAndReturnHelpers(server) {
263
+ return getPluginOptions(server).saveAndReturn;
264
+ }
265
+ export function getPluginOptions(server) {
266
+ return server.plugins['forms-engine-plugin'];
261
267
  }
262
268
 
263
269
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","names":["ControllerPath","Engine","getErrorMessage","hasComponents","isFormType","Boom","format","parseISO","StatusCodes","Liquid","createLogger","getAnswer","FormAction","FormStatus","logger","engine","outputEscape","jsTruthy","ownPropertyOnly","registerFilter","template","globals","context","evaluated","evaluateTemplate","path","pageDef","pages","get","query","page","pageMap","undefined","getPageHref","name","componentDef","components","component","componentMap","isFormComponent","answer","relevantState","proceed","request","h","nextUrl","method","payload","returnUrl","isReturnAllowed","action","Continue","Validate","response","isPathRelative","redirect","redirectPath","code","SEE_OTHER","MOVED_TEMPORARILY","encodeUrl","link","URL","toString","err","errMsg","error","pathOrQuery","queryOnly","Error","getHref","isRelative","params","Object","entries","filter","url","value","searchParams","set","pathname","search","href","startsWith","normalisePath","trim","replace","getPage","model","findPage","notFound","findPath","find","getStartPath","V2","startPath","def","at","Start","startPage","checkFormStatus","isPreview","state","Live","Draft","checkEmailAddressForLiveFormSubmission","emailAddress","internal","getErrors","details","length","map","getError","detail","message","key","text","safeGenerateCrumb","server","plugins","crumb","generate","route","settings","getExponentialBackoffDelay","depth","BASE_DELAY_MS","CAP_DELAY_MS","delay","Math","min","pageDefMap","componentDefMap","parseAndRenderSync","getCacheService","cacheService","handleLegacyRedirect","targetUrl","permanent","takeover","setPageTitles","forEach","title","firstFormComponent","type","formNameMsg","info"],"sources":["../../../../src/server/plugins/engine/helpers.ts"],"sourcesContent":["import {\n ControllerPath,\n Engine,\n getErrorMessage,\n hasComponents,\n isFormType,\n type ComponentDef,\n type FormDefinition,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type Server } from '@hapi/hapi'\nimport { format, parseISO } from 'date-fns'\nimport { StatusCodes } from 'http-status-codes'\nimport { type Schema, type ValidationErrorItem } from 'joi'\nimport { Liquid } from 'liquidjs'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n FormStatus,\n type FormParams,\n type FormQuery,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst logger = createLogger()\n\nexport const engine = new Liquid({\n outputEscape: 'escape',\n jsTruthy: true,\n ownPropertyOnly: false\n})\n\nexport interface GlobalScope {\n context: FormContext\n pages: Map<string, Page>\n components: Map<string, ComponentDef>\n}\n\nengine.registerFilter('evaluate', function (template?: string) {\n if (typeof template !== 'string') {\n return template\n }\n\n const globals = this.context.globals as GlobalScope\n const evaluated = evaluateTemplate(template, globals.context)\n\n return evaluated\n})\n\nengine.registerFilter('page', function (path?: string) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const pageDef = globals.pages.get(path)\n\n return pageDef\n})\n\nengine.registerFilter('href', function (path: string, query?: FormQuery) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const page = globals.context.pageMap.get(path)\n\n if (page === undefined) {\n return\n }\n\n return getPageHref(page, query)\n})\n\nengine.registerFilter('field', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const componentDef = globals.components.get(name)\n\n return componentDef\n})\n\nengine.registerFilter('answer', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const component = globals.context.componentMap.get(name)\n\n if (!component?.isFormComponent) {\n return\n }\n\n const answer = getAnswer(component as Field, globals.context.relevantState)\n\n return answer\n})\n\nexport function proceed(\n request: Pick<FormContextRequest, 'method' | 'payload' | 'query'>,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextUrl: string\n) {\n const { method, payload, query } = request\n const { returnUrl } = query\n\n const isReturnAllowed =\n payload && 'action' in payload\n ? payload.action === FormAction.Continue ||\n payload.action === FormAction.Validate\n : false\n\n // Redirect to return location (optional)\n const response =\n isReturnAllowed && isPathRelative(returnUrl)\n ? h.redirect(returnUrl)\n : h.redirect(redirectPath(nextUrl))\n\n // Redirect POST to GET to avoid resubmission\n return method === 'post'\n ? response.code(StatusCodes.SEE_OTHER)\n : response.code(StatusCodes.MOVED_TEMPORARILY)\n}\n\n/**\n * Encodes a URL, returning undefined if the process fails.\n */\nexport function encodeUrl(link?: string) {\n if (link) {\n try {\n return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368\n } catch (err) {\n const errMsg = getErrorMessage(err)\n logger.error(\n errMsg,\n `[urlEncodingFailed] Failed to encode URL: ${link} - ${errMsg}`\n )\n throw err\n }\n }\n}\n\n/**\n * Get page href\n */\nexport function getPageHref(\n page: PageControllerClass,\n query?: FormQuery\n): string\n\n/**\n * Get page href by path\n */\nexport function getPageHref(\n page: PageControllerClass,\n path: string,\n query?: FormQuery\n): string\n\nexport function getPageHref(\n page: PageControllerClass,\n pathOrQuery?: string | FormQuery,\n queryOnly: FormQuery = {}\n) {\n const path = typeof pathOrQuery === 'string' ? pathOrQuery : page.path\n const query = typeof pathOrQuery === 'object' ? pathOrQuery : queryOnly\n\n if (!isPathRelative(path)) {\n throw Error(`Only relative URLs are allowed: ${path}`)\n }\n\n // Return path with page href as base\n return redirectPath(page.getHref(path), query)\n}\n\n/**\n * Get redirect path with optional query params\n */\nexport function redirectPath(nextUrl: string, query: FormQuery = {}) {\n const isRelative = isPathRelative(nextUrl)\n\n // Filter string query params only\n const params = Object.entries(query).filter(\n (query): query is [string, string] => typeof query[1] === 'string'\n )\n\n // Build URL with relative path support\n const url = isRelative\n ? new URL(nextUrl, 'http://example.com')\n : new URL(nextUrl)\n\n // Append query params\n for (const [name, value] of params) {\n url.searchParams.set(name, value)\n }\n\n if (isRelative) {\n return `${url.pathname}${url.search}`\n }\n\n return url.href\n}\n\nexport function isPathRelative(path?: string) {\n return (path ?? '').startsWith('/')\n}\n\nexport function normalisePath(path = '') {\n return path\n .trim() // Trim empty spaces\n .replace(/^\\//, '') // Remove leading slash\n .replace(/\\/$/, '') // Remove trailing slash\n}\n\nexport function getPage(\n model: FormModel | undefined,\n request: FormContextRequest\n) {\n const { params } = request\n\n const page = findPage(model, `/${params.path}`)\n\n if (!page) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page\n}\n\nexport function findPage(model: FormModel | undefined, path?: string) {\n const findPath = `/${normalisePath(path)}`\n return model?.pages.find(({ path }) => path === findPath)\n}\n\nexport function getStartPath(model?: FormModel) {\n if (model?.engine === Engine.V2) {\n const startPath = normalisePath(model.def.pages.at(0)?.path)\n return startPath ? `/${startPath}` : ControllerPath.Start\n }\n\n const startPath = normalisePath(model?.def.startPage)\n return startPath ? `/${startPath}` : ControllerPath.Start\n}\n\nexport function checkFormStatus(params?: FormParams) {\n const isPreview = !!params?.state\n\n let state = FormStatus.Live\n\n if (isPreview && params.state === FormStatus.Draft) {\n state = FormStatus.Draft\n }\n\n return {\n isPreview,\n state\n }\n}\n\nexport function checkEmailAddressForLiveFormSubmission(\n emailAddress: string | undefined,\n isPreview: boolean\n) {\n if (!emailAddress && !isPreview) {\n throw Boom.internal(\n 'An email address is required to complete the form submission'\n )\n }\n}\n\n/**\n * Parses the errors from {@link Schema.validate} so they can be rendered by govuk-frontend templates\n * @param [details] - provided by {@link Schema.validate}\n */\nexport function getErrors(\n details?: ValidationErrorItem[]\n): FormSubmissionError[] | undefined {\n if (!details?.length) {\n return\n }\n\n return details.map(getError)\n}\n\nexport function getError(detail: ValidationErrorItem): FormSubmissionError {\n const { context, message, path } = detail\n\n const name = context?.key ?? ''\n const href = `#${name}`\n\n const text = message.replace(\n /\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)/,\n (text) => format(parseISO(text), 'd MMMM yyyy')\n )\n\n return {\n path,\n href,\n name,\n text,\n context\n }\n}\n\n/**\n * A small helper to safely generate a crumb token.\n * Checks that the crumb plugin is available, that crumb\n * is not disabled on the current route, and that cookies/state are present.\n */\nexport function safeGenerateCrumb(\n request: FormRequest | FormRequestPayload | null\n): string | undefined {\n // no request or no .state\n if (!request?.state) {\n return undefined\n }\n\n // crumb plugin or its generate method doesn't exist\n if (!request.server.plugins.crumb.generate) {\n return undefined\n }\n\n // crumb is explicitly disabled for this route\n if (request.route.settings.plugins?.crumb === false) {\n return undefined\n }\n\n return request.server.plugins.crumb.generate(request)\n}\n\n/**\n * Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,\n * using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).\n * @param depth - The current retry depth (1, 2, 3, …)\n * @returns The calculated delay in milliseconds.\n */\nexport function getExponentialBackoffDelay(depth: number): number {\n const BASE_DELAY_MS = 2000 // 2 seconds initial delay\n const CAP_DELAY_MS = 25000 // cap each delay to 25 seconds\n const delay = BASE_DELAY_MS * 2 ** (depth - 1)\n return Math.min(delay, CAP_DELAY_MS)\n}\n\nexport function evaluateTemplate(\n template: string,\n context: FormContext\n): string {\n const globals: GlobalScope = {\n context,\n pages: context.pageDefMap,\n components: context.componentDefMap\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return engine.parseAndRenderSync(template, context.relevantState, {\n globals\n })\n}\n\nexport function getCacheService(server: Server) {\n return server.plugins['forms-engine-plugin'].cacheService\n}\n\n/**\n * Handles logging and issuing a permanent redirect for legacy routes.\n * @param h - The Hapi response toolkit.\n * @param targetUrl - The URL to redirect to.\n * @returns The Hapi response object configured for permanent redirect.\n */\nexport function handleLegacyRedirect(h: ResponseToolkit, targetUrl: string) {\n return h.redirect(targetUrl).permanent().takeover()\n}\n\n/**\n * If the page doesn't have a title, set it from the title of the first form component\n * @param def - the form definition\n */\nexport function setPageTitles(def: FormDefinition) {\n def.pages.forEach((page) => {\n if (!page.title) {\n if (hasComponents(page)) {\n // Set the page title from the first form component\n const firstFormComponent = page.components.find((component) =>\n isFormType(component.type)\n )\n\n page.title = firstFormComponent?.title ?? ''\n }\n\n if (!page.title) {\n const formNameMsg = def.name ? ` in form '${def.name}'` : ''\n\n logger.info(\n `[pageTitleMissing] Page '${page.path}' has no title${formNameMsg}`\n )\n }\n }\n })\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"helpers.js","names":["ControllerPath","Engine","getErrorMessage","hasComponents","isFormType","Boom","format","parseISO","StatusCodes","Liquid","createLogger","getAnswer","FormAction","FormStatus","logger","engine","outputEscape","jsTruthy","ownPropertyOnly","registerFilter","template","globals","context","evaluated","evaluateTemplate","path","pageDef","pages","get","query","page","pageMap","undefined","getPageHref","name","componentDef","components","component","componentMap","isFormComponent","answer","relevantState","proceed","request","h","nextUrl","method","payload","returnUrl","isReturnAllowed","action","Continue","Validate","response","isPathRelative","redirect","redirectPath","code","SEE_OTHER","MOVED_TEMPORARILY","encodeUrl","link","URL","toString","err","errMsg","error","pathOrQuery","queryOnly","Error","getHref","isRelative","params","Object","entries","filter","url","value","searchParams","set","pathname","search","href","startsWith","normalisePath","trim","replace","getPage","model","findPage","notFound","findPath","find","getStartPath","V2","startPath","def","at","Start","startPage","checkFormStatus","isPreview","state","Live","Draft","checkEmailAddressForLiveFormSubmission","emailAddress","internal","getErrors","details","length","map","getError","detail","message","key","text","safeGenerateCrumb","server","plugins","crumb","generate","route","settings","getExponentialBackoffDelay","depth","BASE_DELAY_MS","CAP_DELAY_MS","delay","Math","min","pageDefMap","componentDefMap","parseAndRenderSync","getCacheService","getPluginOptions","cacheService","getSaveAndReturnHelpers","saveAndReturn","handleLegacyRedirect","targetUrl","permanent","takeover","setPageTitles","forEach","title","firstFormComponent","type","formNameMsg","info"],"sources":["../../../../src/server/plugins/engine/helpers.ts"],"sourcesContent":["import {\n ControllerPath,\n Engine,\n getErrorMessage,\n hasComponents,\n isFormType,\n type ComponentDef,\n type FormDefinition,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type Server } from '@hapi/hapi'\nimport { format, parseISO } from 'date-fns'\nimport { StatusCodes } from 'http-status-codes'\nimport { type Schema, type ValidationErrorItem } from 'joi'\nimport { Liquid } from 'liquidjs'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n FormStatus,\n type FormParams,\n type FormQuery,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst logger = createLogger()\n\nexport const engine = new Liquid({\n outputEscape: 'escape',\n jsTruthy: true,\n ownPropertyOnly: false\n})\n\nexport interface GlobalScope {\n context: FormContext\n pages: Map<string, Page>\n components: Map<string, ComponentDef>\n}\n\nengine.registerFilter('evaluate', function (template?: string) {\n if (typeof template !== 'string') {\n return template\n }\n\n const globals = this.context.globals as GlobalScope\n const evaluated = evaluateTemplate(template, globals.context)\n\n return evaluated\n})\n\nengine.registerFilter('page', function (path?: string) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const pageDef = globals.pages.get(path)\n\n return pageDef\n})\n\nengine.registerFilter('href', function (path: string, query?: FormQuery) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const page = globals.context.pageMap.get(path)\n\n if (page === undefined) {\n return\n }\n\n return getPageHref(page, query)\n})\n\nengine.registerFilter('field', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const componentDef = globals.components.get(name)\n\n return componentDef\n})\n\nengine.registerFilter('answer', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const component = globals.context.componentMap.get(name)\n\n if (!component?.isFormComponent) {\n return\n }\n\n const answer = getAnswer(component as Field, globals.context.relevantState)\n\n return answer\n})\n\nexport function proceed(\n request: Pick<FormContextRequest, 'method' | 'payload' | 'query'>,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextUrl: string\n) {\n const { method, payload, query } = request\n const { returnUrl } = query\n\n const isReturnAllowed =\n payload && 'action' in payload\n ? payload.action === FormAction.Continue ||\n payload.action === FormAction.Validate\n : false\n\n // Redirect to return location (optional)\n const response =\n isReturnAllowed && isPathRelative(returnUrl)\n ? h.redirect(returnUrl)\n : h.redirect(redirectPath(nextUrl))\n\n // Redirect POST to GET to avoid resubmission\n return method === 'post'\n ? response.code(StatusCodes.SEE_OTHER)\n : response.code(StatusCodes.MOVED_TEMPORARILY)\n}\n\n/**\n * Encodes a URL, returning undefined if the process fails.\n */\nexport function encodeUrl(link?: string) {\n if (link) {\n try {\n return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368\n } catch (err) {\n const errMsg = getErrorMessage(err)\n logger.error(\n errMsg,\n `[urlEncodingFailed] Failed to encode URL: ${link} - ${errMsg}`\n )\n throw err\n }\n }\n}\n\n/**\n * Get page href\n */\nexport function getPageHref(\n page: PageControllerClass,\n query?: FormQuery\n): string\n\n/**\n * Get page href by path\n */\nexport function getPageHref(\n page: PageControllerClass,\n path: string,\n query?: FormQuery\n): string\n\nexport function getPageHref(\n page: PageControllerClass,\n pathOrQuery?: string | FormQuery,\n queryOnly: FormQuery = {}\n) {\n const path = typeof pathOrQuery === 'string' ? pathOrQuery : page.path\n const query = typeof pathOrQuery === 'object' ? pathOrQuery : queryOnly\n\n if (!isPathRelative(path)) {\n throw Error(`Only relative URLs are allowed: ${path}`)\n }\n\n // Return path with page href as base\n return redirectPath(page.getHref(path), query)\n}\n\n/**\n * Get redirect path with optional query params\n */\nexport function redirectPath(nextUrl: string, query: FormQuery = {}) {\n const isRelative = isPathRelative(nextUrl)\n\n // Filter string query params only\n const params = Object.entries(query).filter(\n (query): query is [string, string] => typeof query[1] === 'string'\n )\n\n // Build URL with relative path support\n const url = isRelative\n ? new URL(nextUrl, 'http://example.com')\n : new URL(nextUrl)\n\n // Append query params\n for (const [name, value] of params) {\n url.searchParams.set(name, value)\n }\n\n if (isRelative) {\n return `${url.pathname}${url.search}`\n }\n\n return url.href\n}\n\nexport function isPathRelative(path?: string) {\n return (path ?? '').startsWith('/')\n}\n\nexport function normalisePath(path = '') {\n return path\n .trim() // Trim empty spaces\n .replace(/^\\//, '') // Remove leading slash\n .replace(/\\/$/, '') // Remove trailing slash\n}\n\nexport function getPage(\n model: FormModel | undefined,\n request: FormContextRequest\n) {\n const { params } = request\n\n const page = findPage(model, `/${params.path}`)\n\n if (!page) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page\n}\n\nexport function findPage(model: FormModel | undefined, path?: string) {\n const findPath = `/${normalisePath(path)}`\n return model?.pages.find(({ path }) => path === findPath)\n}\n\nexport function getStartPath(model?: FormModel) {\n if (model?.engine === Engine.V2) {\n const startPath = normalisePath(model.def.pages.at(0)?.path)\n return startPath ? `/${startPath}` : ControllerPath.Start\n }\n\n const startPath = normalisePath(model?.def.startPage)\n return startPath ? `/${startPath}` : ControllerPath.Start\n}\n\nexport function checkFormStatus(params?: FormParams) {\n const isPreview = !!params?.state\n\n let state = FormStatus.Live\n\n if (isPreview && params.state === FormStatus.Draft) {\n state = FormStatus.Draft\n }\n\n return {\n isPreview,\n state\n }\n}\n\nexport function checkEmailAddressForLiveFormSubmission(\n emailAddress: string | undefined,\n isPreview: boolean\n) {\n if (!emailAddress && !isPreview) {\n throw Boom.internal(\n 'An email address is required to complete the form submission'\n )\n }\n}\n\n/**\n * Parses the errors from {@link Schema.validate} so they can be rendered by govuk-frontend templates\n * @param [details] - provided by {@link Schema.validate}\n */\nexport function getErrors(\n details?: ValidationErrorItem[]\n): FormSubmissionError[] | undefined {\n if (!details?.length) {\n return\n }\n\n return details.map(getError)\n}\n\nexport function getError(detail: ValidationErrorItem): FormSubmissionError {\n const { context, message, path } = detail\n\n const name = context?.key ?? ''\n const href = `#${name}`\n\n const text = message.replace(\n /\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)/,\n (text) => format(parseISO(text), 'd MMMM yyyy')\n )\n\n return {\n path,\n href,\n name,\n text,\n context\n }\n}\n\n/**\n * A small helper to safely generate a crumb token.\n * Checks that the crumb plugin is available, that crumb\n * is not disabled on the current route, and that cookies/state are present.\n */\nexport function safeGenerateCrumb(\n request: FormRequest | FormRequestPayload | null\n): string | undefined {\n // no request or no .state\n if (!request?.state) {\n return undefined\n }\n\n // crumb plugin or its generate method doesn't exist\n if (!request.server.plugins.crumb.generate) {\n return undefined\n }\n\n // crumb is explicitly disabled for this route\n if (request.route.settings.plugins?.crumb === false) {\n return undefined\n }\n\n return request.server.plugins.crumb.generate(request)\n}\n\n/**\n * Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,\n * using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).\n * @param depth - The current retry depth (1, 2, 3, …)\n * @returns The calculated delay in milliseconds.\n */\nexport function getExponentialBackoffDelay(depth: number): number {\n const BASE_DELAY_MS = 2000 // 2 seconds initial delay\n const CAP_DELAY_MS = 25000 // cap each delay to 25 seconds\n const delay = BASE_DELAY_MS * 2 ** (depth - 1)\n return Math.min(delay, CAP_DELAY_MS)\n}\n\nexport function evaluateTemplate(\n template: string,\n context: FormContext\n): string {\n const globals: GlobalScope = {\n context,\n pages: context.pageDefMap,\n components: context.componentDefMap\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return engine.parseAndRenderSync(template, context.relevantState, {\n globals\n })\n}\n\nexport function getCacheService(server: Server) {\n return getPluginOptions(server).cacheService\n}\n\nexport function getSaveAndReturnHelpers(server: Server) {\n return getPluginOptions(server).saveAndReturn\n}\n\nexport function getPluginOptions(server: Server) {\n return server.plugins['forms-engine-plugin']\n}\n\n/**\n * Handles logging and issuing a permanent redirect for legacy routes.\n * @param h - The Hapi response toolkit.\n * @param targetUrl - The URL to redirect to.\n * @returns The Hapi response object configured for permanent redirect.\n */\nexport function handleLegacyRedirect(h: ResponseToolkit, targetUrl: string) {\n return h.redirect(targetUrl).permanent().takeover()\n}\n\n/**\n * If the page doesn't have a title, set it from the title of the first form component\n * @param def - the form definition\n */\nexport function setPageTitles(def: FormDefinition) {\n def.pages.forEach((page) => {\n if (!page.title) {\n if (hasComponents(page)) {\n // Set the page title from the first form component\n const firstFormComponent = page.components.find((component) =>\n isFormType(component.type)\n )\n\n page.title = firstFormComponent?.title ?? ''\n }\n\n if (!page.title) {\n const formNameMsg = def.name ? ` in form '${def.name}'` : ''\n\n logger.info(\n `[pageTitleMissing] Page '${page.path}' has no title${formNameMsg}`\n )\n }\n }\n })\n}\n"],"mappings":"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","ignoreList":[]}
@@ -17,7 +17,7 @@ export const prepareNunjucksEnvironment = function (env, pluginOptions) {
17
17
  for (const [name, nunjucksFilter] of Object.entries(filters)) {
18
18
  env.addFilter(name, nunjucksFilter);
19
19
  }
20
- env.addFilter('markdown', text => markdownToHtml(text));
20
+ env.addFilter('markdown', text => markdownToHtml(text, pluginOptions.baseUrl));
21
21
  for (const [name, nunjucksGlobal] of Object.entries(globals)) {
22
22
  env.addGlobal(name, nunjucksGlobal);
23
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["markdownToHtml","engine","plugin","checkComponentTemplates","checkErrorTemplates","evaluate","govukRebrand","filters","getPageHref","context","globals","VIEW_PATH","PLUGIN_PATH","prepareNunjucksEnvironment","env","pluginOptions","name","nunjucksFilter","Object","entries","addFilter","text","nunjucksGlobal","addGlobal","filter","registerFilter","forEach","fn"],"sources":["../../../../src/server/plugins/engine/index.ts"],"sourcesContent":["import { markdownToHtml } from '@defra/forms-model'\nimport { type Environment } from 'nunjucks'\n\nimport { engine } from '~/src/server/plugins/engine/helpers.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n} from '~/src/server/plugins/nunjucks/environment.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nexport { getPageHref } from '~/src/server/plugins/engine/helpers.js'\nexport { context } from '~/src/server/plugins/nunjucks/context.js'\n\nconst globals = {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n}\n\nexport const VIEW_PATH = 'src/server/plugins/engine/views'\nexport const PLUGIN_PATH = 'node_modules/@defra/forms-engine-plugin'\n\nexport const prepareNunjucksEnvironment = function (\n env: Environment,\n pluginOptions: PluginOptions\n) {\n for (const [name, nunjucksFilter] of Object.entries(filters)) {\n env.addFilter(name, nunjucksFilter)\n }\n\n env.addFilter('markdown', (text: string) => markdownToHtml(text))\n\n for (const [name, nunjucksGlobal] of Object.entries(globals)) {\n env.addGlobal(name, nunjucksGlobal)\n }\n\n // Apply any additional filters to both the liquid and nunjucks engines\n if (pluginOptions.filters) {\n for (const [name, filter] of Object.entries(pluginOptions.filters)) {\n env.addFilter(name, filter)\n engine.registerFilter(name, filter)\n }\n }\n\n // Apply any additional globals to nunjucks engines\n if (pluginOptions.globals) {\n Object.entries(pluginOptions.globals).forEach(([name, fn]) => {\n env.addGlobal(name, fn)\n })\n }\n}\n\nexport default plugin\n"],"mappings":"AAAA,SAASA,cAAc,QAAQ,oBAAoB;AAGnD,SAASC,MAAM;AACf,SAASC,MAAM;AAEf,SACEC,uBAAuB,EACvBC,mBAAmB,EACnBC,QAAQ,EACRC,YAAY;AAEd,OAAO,KAAKC,OAAO;AAEnB,SAASC,WAAW;AACpB,SAASC,OAAO;AAEhB,MAAMC,OAAO,GAAG;EACdP,uBAAuB;EACvBC,mBAAmB;EACnBC,QAAQ;EACRC;AACF,CAAC;AAED,OAAO,MAAMK,SAAS,GAAG,iCAAiC;AAC1D,OAAO,MAAMC,WAAW,GAAG,yCAAyC;AAEpE,OAAO,MAAMC,0BAA0B,GAAG,SAAAA,CACxCC,GAAgB,EAChBC,aAA4B,EAC5B;EACA,KAAK,MAAM,CAACC,IAAI,EAAEC,cAAc,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACZ,OAAO,CAAC,EAAE;IAC5DO,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEC,cAAc,CAAC;EACrC;EAEAH,GAAG,CAACM,SAAS,CAAC,UAAU,EAAGC,IAAY,IAAKrB,cAAc,CAACqB,IAAI,CAAC,CAAC;EAEjE,KAAK,MAAM,CAACL,IAAI,EAAEM,cAAc,CAAC,IAAIJ,MAAM,CAACC,OAAO,CAACT,OAAO,CAAC,EAAE;IAC5DI,GAAG,CAACS,SAAS,CAACP,IAAI,EAAEM,cAAc,CAAC;EACrC;;EAEA;EACA,IAAIP,aAAa,CAACR,OAAO,EAAE;IACzB,KAAK,MAAM,CAACS,IAAI,EAAEQ,MAAM,CAAC,IAAIN,MAAM,CAACC,OAAO,CAACJ,aAAa,CAACR,OAAO,CAAC,EAAE;MAClEO,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEQ,MAAM,CAAC;MAC3BvB,MAAM,CAACwB,cAAc,CAACT,IAAI,EAAEQ,MAAM,CAAC;IACrC;EACF;;EAEA;EACA,IAAIT,aAAa,CAACL,OAAO,EAAE;IACzBQ,MAAM,CAACC,OAAO,CAACJ,aAAa,CAACL,OAAO,CAAC,CAACgB,OAAO,CAAC,CAAC,CAACV,IAAI,EAAEW,EAAE,CAAC,KAAK;MAC5Db,GAAG,CAACS,SAAS,CAACP,IAAI,EAAEW,EAAE,CAAC;IACzB,CAAC,CAAC;EACJ;AACF,CAAC;AAED,eAAezB,MAAM","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["markdownToHtml","engine","plugin","checkComponentTemplates","checkErrorTemplates","evaluate","govukRebrand","filters","getPageHref","context","globals","VIEW_PATH","PLUGIN_PATH","prepareNunjucksEnvironment","env","pluginOptions","name","nunjucksFilter","Object","entries","addFilter","text","baseUrl","nunjucksGlobal","addGlobal","filter","registerFilter","forEach","fn"],"sources":["../../../../src/server/plugins/engine/index.ts"],"sourcesContent":["import { markdownToHtml } from '@defra/forms-model'\nimport { type Environment } from 'nunjucks'\n\nimport { engine } from '~/src/server/plugins/engine/helpers.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n} from '~/src/server/plugins/nunjucks/environment.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nexport { getPageHref } from '~/src/server/plugins/engine/helpers.js'\nexport { context } from '~/src/server/plugins/nunjucks/context.js'\n\nconst globals = {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n}\n\nexport const VIEW_PATH = 'src/server/plugins/engine/views'\nexport const PLUGIN_PATH = 'node_modules/@defra/forms-engine-plugin'\n\nexport const prepareNunjucksEnvironment = function (\n env: Environment,\n pluginOptions: PluginOptions\n) {\n for (const [name, nunjucksFilter] of Object.entries(filters)) {\n env.addFilter(name, nunjucksFilter)\n }\n\n env.addFilter('markdown', (text: string) =>\n markdownToHtml(text, pluginOptions.baseUrl)\n )\n\n for (const [name, nunjucksGlobal] of Object.entries(globals)) {\n env.addGlobal(name, nunjucksGlobal)\n }\n\n // Apply any additional filters to both the liquid and nunjucks engines\n if (pluginOptions.filters) {\n for (const [name, filter] of Object.entries(pluginOptions.filters)) {\n env.addFilter(name, filter)\n engine.registerFilter(name, filter)\n }\n }\n\n // Apply any additional globals to nunjucks engines\n if (pluginOptions.globals) {\n Object.entries(pluginOptions.globals).forEach(([name, fn]) => {\n env.addGlobal(name, fn)\n })\n }\n}\n\nexport default plugin\n"],"mappings":"AAAA,SAASA,cAAc,QAAQ,oBAAoB;AAGnD,SAASC,MAAM;AACf,SAASC,MAAM;AAEf,SACEC,uBAAuB,EACvBC,mBAAmB,EACnBC,QAAQ,EACRC,YAAY;AAEd,OAAO,KAAKC,OAAO;AAEnB,SAASC,WAAW;AACpB,SAASC,OAAO;AAEhB,MAAMC,OAAO,GAAG;EACdP,uBAAuB;EACvBC,mBAAmB;EACnBC,QAAQ;EACRC;AACF,CAAC;AAED,OAAO,MAAMK,SAAS,GAAG,iCAAiC;AAC1D,OAAO,MAAMC,WAAW,GAAG,yCAAyC;AAEpE,OAAO,MAAMC,0BAA0B,GAAG,SAAAA,CACxCC,GAAgB,EAChBC,aAA4B,EAC5B;EACA,KAAK,MAAM,CAACC,IAAI,EAAEC,cAAc,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACZ,OAAO,CAAC,EAAE;IAC5DO,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEC,cAAc,CAAC;EACrC;EAEAH,GAAG,CAACM,SAAS,CAAC,UAAU,EAAGC,IAAY,IACrCrB,cAAc,CAACqB,IAAI,EAAEN,aAAa,CAACO,OAAO,CAC5C,CAAC;EAED,KAAK,MAAM,CAACN,IAAI,EAAEO,cAAc,CAAC,IAAIL,MAAM,CAACC,OAAO,CAACT,OAAO,CAAC,EAAE;IAC5DI,GAAG,CAACU,SAAS,CAACR,IAAI,EAAEO,cAAc,CAAC;EACrC;;EAEA;EACA,IAAIR,aAAa,CAACR,OAAO,EAAE;IACzB,KAAK,MAAM,CAACS,IAAI,EAAES,MAAM,CAAC,IAAIP,MAAM,CAACC,OAAO,CAACJ,aAAa,CAACR,OAAO,CAAC,EAAE;MAClEO,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAES,MAAM,CAAC;MAC3BxB,MAAM,CAACyB,cAAc,CAACV,IAAI,EAAES,MAAM,CAAC;IACrC;EACF;;EAEA;EACA,IAAIV,aAAa,CAACL,OAAO,EAAE;IACzBQ,MAAM,CAACC,OAAO,CAACJ,aAAa,CAACL,OAAO,CAAC,CAACiB,OAAO,CAAC,CAAC,CAACX,IAAI,EAAEY,EAAE,CAAC,KAAK;MAC5Dd,GAAG,CAACU,SAAS,CAACR,IAAI,EAAEY,EAAE,CAAC;IACzB,CAAC,CAAC;EACJ;AACF,CAAC;AAED,eAAe1B,MAAM","ignoreList":[]}
@@ -20,7 +20,7 @@ export declare class SummaryViewModel {
20
20
  serviceUrl: string;
21
21
  hasMissingNotificationEmail?: boolean;
22
22
  components?: ComponentViewModel[];
23
- allowSaveAndReturn?: boolean;
23
+ allowSaveAndReturn: boolean;
24
24
  constructor(request: FormContextRequest, page: PageControllerClass, context: FormContext);
25
25
  private summaryDetails;
26
26
  }
@@ -21,7 +21,7 @@ export class SummaryViewModel {
21
21
  serviceUrl;
22
22
  hasMissingNotificationEmail;
23
23
  components;
24
- allowSaveAndReturn;
24
+ allowSaveAndReturn = false;
25
25
  constructor(request, page, context) {
26
26
  const {
27
27
  model
@@ -1 +1 @@
1
- {"version":3,"file":"SummaryViewModel.js","names":["getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","components","allowSaveAndReturn","constructor","request","model","basePath","def","sections","isForceAccess","title","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState","getFirstError"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { type Section } from '@defra/forms-model'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n components?: ComponentViewModel[]\n allowSaveAndReturn?: boolean\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not supplied'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[undefined, ...sections].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? title : `${title}s`\n\n return {\n name,\n label: title,\n title: values.length ? `${unit} added` : unit,\n value: values.length ? `You added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nfunction ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title: field.label,\n error: field.getFirstError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"SummaryViewModel.js","names":["getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","components","allowSaveAndReturn","constructor","request","model","basePath","def","sections","isForceAccess","title","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState","getFirstError"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { type Section } from '@defra/forms-model'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n components?: ComponentViewModel[]\n allowSaveAndReturn = false\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not supplied'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[undefined, ...sections].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? title : `${title}s`\n\n return {\n name,\n label: title,\n title: values.length ? `${unit} added` : unit,\n value: values.length ? `You added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nfunction ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title: field.label,\n error: field.getFirstError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -17,9 +17,11 @@ const pluginRegistrationOptionsSchema = Joi.object({
17
17
  preparePageEventRequestOptions: Joi.function().optional(),
18
18
  onRequest: Joi.function().optional(),
19
19
  baseUrl: Joi.string().uri().required(),
20
- keyGenerator: Joi.function().optional(),
21
- sessionHydrator: Joi.function().optional(),
22
- sessionPersister: Joi.function().optional()
20
+ saveAndReturn: Joi.object({
21
+ keyGenerator: Joi.function(),
22
+ sessionHydrator: Joi.function(),
23
+ sessionPersister: Joi.function()
24
+ }).optional()
23
25
  });
24
26
 
25
27
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"options.js","names":["Joi","createLogger","logger","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cacheName","globals","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","preparePageEventRequestOptions","onRequest","baseUrl","uri","keyGenerator","sessionHydrator","sessionPersister","validatePluginOptions","options","result","validate","abortEarly","error","message","Error","value"],"sources":["../../../../src/server/plugins/engine/options.js"],"sourcesContent":["import Joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\n\nconst logger = createLogger()\n\nconst pluginRegistrationOptionsSchema = Joi.object({\n model: Joi.object().optional(),\n services: Joi.object().optional(),\n controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n cacheName: Joi.string().optional(),\n globals: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n pluginPath: Joi.string().optional(),\n nunjucks: Joi.object({\n baseLayoutPath: Joi.string().required(),\n paths: Joi.array().items(Joi.string()).required()\n }).required(),\n viewContext: Joi.function().required(),\n preparePageEventRequestOptions: Joi.function().optional(),\n onRequest: Joi.function().optional(),\n baseUrl: Joi.string().uri().required(),\n keyGenerator: Joi.function().optional(),\n sessionHydrator: Joi.function().optional(),\n sessionPersister: Joi.function().optional()\n})\n\n/**\n * Validates the plugin options against the schema and returns the validated value.\n * @param {PluginOptions} options\n * @returns {PluginOptions}\n */\nexport function validatePluginOptions(options) {\n const result = pluginRegistrationOptionsSchema.validate(options, {\n abortEarly: false\n })\n\n if (result.error) {\n logger.error(\n `Missing required properties in plugin options: ${result.error.message}`\n )\n throw new Error('Invalid plugin options', result.error)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return result.value\n}\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAErB,SAASC,YAAY;AAErB,MAAMC,MAAM,GAAGD,YAAY,CAAC,CAAC;AAE7B,MAAME,+BAA+B,GAAGH,GAAG,CAACI,MAAM,CAAC;EACjDC,KAAK,EAAEL,GAAG,CAACI,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EAC9BC,QAAQ,EAAEP,GAAG,CAACI,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EACjCE,WAAW,EAAER,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACrEM,SAAS,EAAEZ,GAAG,CAACU,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EAClCO,OAAO,EAAEb,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjEQ,OAAO,EAAEd,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjES,UAAU,EAAEf,GAAG,CAACU,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EACnCU,QAAQ,EAAEhB,GAAG,CAACI,MAAM,CAAC;IACnBa,cAAc,EAAEjB,GAAG,CAACU,MAAM,CAAC,CAAC,CAACQ,QAAQ,CAAC,CAAC;IACvCC,KAAK,EAAEnB,GAAG,CAACoB,KAAK,CAAC,CAAC,CAACC,KAAK,CAACrB,GAAG,CAACU,MAAM,CAAC,CAAC,CAAC,CAACQ,QAAQ,CAAC;EAClD,CAAC,CAAC,CAACA,QAAQ,CAAC,CAAC;EACbI,WAAW,EAAEtB,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACtCM,8BAA8B,EAAExB,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACjB,QAAQ,CAAC,CAAC;EACzDmB,SAAS,EAAEzB,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACjB,QAAQ,CAAC,CAAC;EACpCoB,OAAO,EAAE1B,GAAG,CAACU,MAAM,CAAC,CAAC,CAACiB,GAAG,CAAC,CAAC,CAACT,QAAQ,CAAC,CAAC;EACtCU,YAAY,EAAE5B,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACjB,QAAQ,CAAC,CAAC;EACvCuB,eAAe,EAAE7B,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACjB,QAAQ,CAAC,CAAC;EAC1CwB,gBAAgB,EAAE9B,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACjB,QAAQ,CAAC;AAC5C,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,SAASyB,qBAAqBA,CAACC,OAAO,EAAE;EAC7C,MAAMC,MAAM,GAAG9B,+BAA+B,CAAC+B,QAAQ,CAACF,OAAO,EAAE;IAC/DG,UAAU,EAAE;EACd,CAAC,CAAC;EAEF,IAAIF,MAAM,CAACG,KAAK,EAAE;IAChBlC,MAAM,CAACkC,KAAK,CACV,kDAAkDH,MAAM,CAACG,KAAK,CAACC,OAAO,EACxE,CAAC;IACD,MAAM,IAAIC,KAAK,CAAC,wBAAwB,EAAEL,MAAM,CAACG,KAAK,CAAC;EACzD;;EAEA;EACA,OAAOH,MAAM,CAACM,KAAK;AACrB;;AAEA;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"options.js","names":["Joi","createLogger","logger","pluginRegistrationOptionsSchema","object","model","optional","services","controllers","pattern","string","any","cacheName","globals","filters","pluginPath","nunjucks","baseLayoutPath","required","paths","array","items","viewContext","function","preparePageEventRequestOptions","onRequest","baseUrl","uri","saveAndReturn","keyGenerator","sessionHydrator","sessionPersister","validatePluginOptions","options","result","validate","abortEarly","error","message","Error","value"],"sources":["../../../../src/server/plugins/engine/options.js"],"sourcesContent":["import Joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\n\nconst logger = createLogger()\n\nconst pluginRegistrationOptionsSchema = Joi.object({\n model: Joi.object().optional(),\n services: Joi.object().optional(),\n controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n cacheName: Joi.string().optional(),\n globals: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),\n pluginPath: Joi.string().optional(),\n nunjucks: Joi.object({\n baseLayoutPath: Joi.string().required(),\n paths: Joi.array().items(Joi.string()).required()\n }).required(),\n viewContext: Joi.function().required(),\n preparePageEventRequestOptions: Joi.function().optional(),\n onRequest: Joi.function().optional(),\n baseUrl: Joi.string().uri().required(),\n saveAndReturn: Joi.object({\n keyGenerator: Joi.function(),\n sessionHydrator: Joi.function(),\n sessionPersister: Joi.function()\n }).optional()\n})\n\n/**\n * Validates the plugin options against the schema and returns the validated value.\n * @param {PluginOptions} options\n * @returns {PluginOptions}\n */\nexport function validatePluginOptions(options) {\n const result = pluginRegistrationOptionsSchema.validate(options, {\n abortEarly: false\n })\n\n if (result.error) {\n logger.error(\n `Missing required properties in plugin options: ${result.error.message}`\n )\n throw new Error('Invalid plugin options', result.error)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return result.value\n}\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,OAAOA,GAAG,MAAM,KAAK;AAErB,SAASC,YAAY;AAErB,MAAMC,MAAM,GAAGD,YAAY,CAAC,CAAC;AAE7B,MAAME,+BAA+B,GAAGH,GAAG,CAACI,MAAM,CAAC;EACjDC,KAAK,EAAEL,GAAG,CAACI,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EAC9BC,QAAQ,EAAEP,GAAG,CAACI,MAAM,CAAC,CAAC,CAACE,QAAQ,CAAC,CAAC;EACjCE,WAAW,EAAER,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACrEM,SAAS,EAAEZ,GAAG,CAACU,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EAClCO,OAAO,EAAEb,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjEQ,OAAO,EAAEd,GAAG,CAACI,MAAM,CAAC,CAAC,CAACK,OAAO,CAACT,GAAG,CAACU,MAAM,CAAC,CAAC,EAAEV,GAAG,CAACW,GAAG,CAAC,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACjES,UAAU,EAAEf,GAAG,CAACU,MAAM,CAAC,CAAC,CAACJ,QAAQ,CAAC,CAAC;EACnCU,QAAQ,EAAEhB,GAAG,CAACI,MAAM,CAAC;IACnBa,cAAc,EAAEjB,GAAG,CAACU,MAAM,CAAC,CAAC,CAACQ,QAAQ,CAAC,CAAC;IACvCC,KAAK,EAAEnB,GAAG,CAACoB,KAAK,CAAC,CAAC,CAACC,KAAK,CAACrB,GAAG,CAACU,MAAM,CAAC,CAAC,CAAC,CAACQ,QAAQ,CAAC;EAClD,CAAC,CAAC,CAACA,QAAQ,CAAC,CAAC;EACbI,WAAW,EAAEtB,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACtCM,8BAA8B,EAAExB,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACjB,QAAQ,CAAC,CAAC;EACzDmB,SAAS,EAAEzB,GAAG,CAACuB,QAAQ,CAAC,CAAC,CAACjB,QAAQ,CAAC,CAAC;EACpCoB,OAAO,EAAE1B,GAAG,CAACU,MAAM,CAAC,CAAC,CAACiB,GAAG,CAAC,CAAC,CAACT,QAAQ,CAAC,CAAC;EACtCU,aAAa,EAAE5B,GAAG,CAACI,MAAM,CAAC;IACxByB,YAAY,EAAE7B,GAAG,CAACuB,QAAQ,CAAC,CAAC;IAC5BO,eAAe,EAAE9B,GAAG,CAACuB,QAAQ,CAAC,CAAC;IAC/BQ,gBAAgB,EAAE/B,GAAG,CAACuB,QAAQ,CAAC;EACjC,CAAC,CAAC,CAACjB,QAAQ,CAAC;AACd,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS0B,qBAAqBA,CAACC,OAAO,EAAE;EAC7C,MAAMC,MAAM,GAAG/B,+BAA+B,CAACgC,QAAQ,CAACF,OAAO,EAAE;IAC/DG,UAAU,EAAE;EACd,CAAC,CAAC;EAEF,IAAIF,MAAM,CAACG,KAAK,EAAE;IAChBnC,MAAM,CAACmC,KAAK,CACV,kDAAkDH,MAAM,CAACG,KAAK,CAACC,OAAO,EACxE,CAAC;IACD,MAAM,IAAIC,KAAK,CAAC,wBAAwB,EAAEL,MAAM,CAACG,KAAK,CAAC;EACzD;;EAEA;EACA,OAAOH,MAAM,CAACM,KAAK;AACrB;;AAEA;AACA;AACA","ignoreList":[]}
@@ -1,6 +1,9 @@
1
1
  import { validatePluginOptions } from "./options.js";
2
2
  describe('validatePluginOptions', () => {
3
3
  it('returns the validated value for valid options', () => {
4
+ /**
5
+ * @type {PluginOptions}
6
+ */
4
7
  const validOptions = {
5
8
  nunjucks: {
6
9
  baseLayoutPath: 'dxt-devtool-baselayout.html',
@@ -16,6 +19,9 @@ describe('validatePluginOptions', () => {
16
19
  expect(validatePluginOptions(validOptions)).toEqual(validOptions);
17
20
  });
18
21
  it('accepts optional properties keyGenerator, sessionHydrator, and sessionPersister', () => {
22
+ /**
23
+ * @type {PluginOptions}
24
+ */
19
25
  const validOptionsWithOptionals = {
20
26
  nunjucks: {
21
27
  baseLayoutPath: 'dxt-devtool-baselayout.html',
@@ -27,11 +33,13 @@ describe('validatePluginOptions', () => {
27
33
  };
28
34
  },
29
35
  baseUrl: 'http://localhost:3009',
30
- keyGenerator: () => 'test-key',
31
- sessionHydrator: () => Promise.resolve({
32
- someState: 'value'
33
- }),
34
- sessionPersister: () => Promise.resolve(undefined)
36
+ saveAndReturn: {
37
+ keyGenerator: () => 'test-key',
38
+ sessionHydrator: () => Promise.resolve({
39
+ someState: 'value'
40
+ }),
41
+ sessionPersister: () => Promise.resolve(undefined)
42
+ }
35
43
  };
36
44
  expect(validatePluginOptions(validOptionsWithOptionals)).toEqual(validOptionsWithOptionals);
37
45
  });
@@ -40,16 +48,17 @@ describe('validatePluginOptions', () => {
40
48
  * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test
41
49
  */
42
50
  it('fails if a required attribute is missing', () => {
43
- // viewContext is missing
44
- const invalidOptions = {
51
+ const invalidOptions = /** @type {PluginOptions} testing without viewContext */{
45
52
  nunjucks: {
46
53
  baseLayoutPath: 'dxt-devtool-baselayout.html',
47
54
  paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner
48
55
  }
49
56
  };
50
-
51
- // @ts-expect-error -- add a test for JS users
52
57
  expect(() => validatePluginOptions(invalidOptions)).toThrow('Invalid plugin options');
53
58
  });
54
59
  });
60
+
61
+ /**
62
+ * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'
63
+ */
55
64
  //# sourceMappingURL=options.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"options.test.js","names":["validatePluginOptions","describe","it","validOptions","nunjucks","baseLayoutPath","paths","viewContext","hello","baseUrl","expect","toEqual","validOptionsWithOptionals","keyGenerator","sessionHydrator","Promise","resolve","someState","sessionPersister","undefined","invalidOptions","toThrow"],"sources":["../../../../src/server/plugins/engine/options.test.js"],"sourcesContent":["import { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\n\ndescribe('validatePluginOptions', () => {\n it('returns the validated value for valid options', () => {\n const validOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: () => {\n return { hello: 'world' }\n },\n baseUrl: 'http://localhost:3009'\n }\n\n expect(validatePluginOptions(validOptions)).toEqual(validOptions)\n })\n\n it('accepts optional properties keyGenerator, sessionHydrator, and sessionPersister', () => {\n const validOptionsWithOptionals = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver']\n },\n viewContext: () => {\n return { hello: 'world' }\n },\n baseUrl: 'http://localhost:3009',\n keyGenerator: () => 'test-key',\n sessionHydrator: () => Promise.resolve({ someState: 'value' }),\n sessionPersister: () => Promise.resolve(undefined)\n }\n\n expect(validatePluginOptions(validOptionsWithOptionals)).toEqual(\n validOptionsWithOptionals\n )\n })\n\n /**\n * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test\n */\n it('fails if a required attribute is missing', () => {\n // viewContext is missing\n const invalidOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n }\n }\n\n // @ts-expect-error -- add a test for JS users\n expect(() => validatePluginOptions(invalidOptions)).toThrow(\n 'Invalid plugin options'\n )\n })\n})\n"],"mappings":"AAAA,SAASA,qBAAqB;AAE9BC,QAAQ,CAAC,uBAAuB,EAAE,MAAM;EACtCC,EAAE,CAAC,+CAA+C,EAAE,MAAM;IACxD,MAAMC,YAAY,GAAG;MACnBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B,CAAC;MACDC,OAAO,EAAE;IACX,CAAC;IAEDC,MAAM,CAACV,qBAAqB,CAACG,YAAY,CAAC,CAAC,CAACQ,OAAO,CAACR,YAAY,CAAC;EACnE,CAAC,CAAC;EAEFD,EAAE,CAAC,iFAAiF,EAAE,MAAM;IAC1F,MAAMU,yBAAyB,GAAG;MAChCR,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB;MAChC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B,CAAC;MACDC,OAAO,EAAE,uBAAuB;MAChCI,YAAY,EAAEA,CAAA,KAAM,UAAU;MAC9BC,eAAe,EAAEA,CAAA,KAAMC,OAAO,CAACC,OAAO,CAAC;QAAEC,SAAS,EAAE;MAAQ,CAAC,CAAC;MAC9DC,gBAAgB,EAAEA,CAAA,KAAMH,OAAO,CAACC,OAAO,CAACG,SAAS;IACnD,CAAC;IAEDT,MAAM,CAACV,qBAAqB,CAACY,yBAAyB,CAAC,CAAC,CAACD,OAAO,CAC9DC,yBACF,CAAC;EACH,CAAC,CAAC;;EAEF;AACF;AACA;EACEV,EAAE,CAAC,0CAA0C,EAAE,MAAM;IACnD;IACA,MAAMkB,cAAc,GAAG;MACrBhB,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC;IACF,CAAC;;IAED;IACAI,MAAM,CAAC,MAAMV,qBAAqB,CAACoB,cAAc,CAAC,CAAC,CAACC,OAAO,CACzD,wBACF,CAAC;EACH,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"options.test.js","names":["validatePluginOptions","describe","it","validOptions","nunjucks","baseLayoutPath","paths","viewContext","hello","baseUrl","expect","toEqual","validOptionsWithOptionals","saveAndReturn","keyGenerator","sessionHydrator","Promise","resolve","someState","sessionPersister","undefined","invalidOptions","toThrow"],"sources":["../../../../src/server/plugins/engine/options.test.js"],"sourcesContent":["import { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\n\ndescribe('validatePluginOptions', () => {\n it('returns the validated value for valid options', () => {\n /**\n * @type {PluginOptions}\n */\n const validOptions = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n },\n viewContext: () => {\n return { hello: 'world' }\n },\n baseUrl: 'http://localhost:3009'\n }\n\n expect(validatePluginOptions(validOptions)).toEqual(validOptions)\n })\n\n it('accepts optional properties keyGenerator, sessionHydrator, and sessionPersister', () => {\n /**\n * @type {PluginOptions}\n */\n const validOptionsWithOptionals = {\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver']\n },\n viewContext: () => {\n return { hello: 'world' }\n },\n baseUrl: 'http://localhost:3009',\n saveAndReturn: {\n keyGenerator: () => 'test-key',\n sessionHydrator: () => Promise.resolve({ someState: 'value' }),\n sessionPersister: () => Promise.resolve(undefined)\n }\n }\n\n expect(validatePluginOptions(validOptionsWithOptionals)).toEqual(\n validOptionsWithOptionals\n )\n })\n\n /**\n * tsc would usually check compliance with the type, but given a user might be using plain JS we still want a test\n */\n it('fails if a required attribute is missing', () => {\n const invalidOptions =\n /** @type {PluginOptions} testing without viewContext */ ({\n nunjucks: {\n baseLayoutPath: 'dxt-devtool-baselayout.html',\n paths: ['src/server/devserver'] // custom layout to make it really clear this is not the same as the runner\n }\n })\n\n expect(() => validatePluginOptions(invalidOptions)).toThrow(\n 'Invalid plugin options'\n )\n })\n})\n\n/**\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,SAASA,qBAAqB;AAE9BC,QAAQ,CAAC,uBAAuB,EAAE,MAAM;EACtCC,EAAE,CAAC,+CAA+C,EAAE,MAAM;IACxD;AACJ;AACA;IACI,MAAMC,YAAY,GAAG;MACnBC,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B,CAAC;MACDC,OAAO,EAAE;IACX,CAAC;IAEDC,MAAM,CAACV,qBAAqB,CAACG,YAAY,CAAC,CAAC,CAACQ,OAAO,CAACR,YAAY,CAAC;EACnE,CAAC,CAAC;EAEFD,EAAE,CAAC,iFAAiF,EAAE,MAAM;IAC1F;AACJ;AACA;IACI,MAAMU,yBAAyB,GAAG;MAChCR,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB;MAChC,CAAC;MACDC,WAAW,EAAEA,CAAA,KAAM;QACjB,OAAO;UAAEC,KAAK,EAAE;QAAQ,CAAC;MAC3B,CAAC;MACDC,OAAO,EAAE,uBAAuB;MAChCI,aAAa,EAAE;QACbC,YAAY,EAAEA,CAAA,KAAM,UAAU;QAC9BC,eAAe,EAAEA,CAAA,KAAMC,OAAO,CAACC,OAAO,CAAC;UAAEC,SAAS,EAAE;QAAQ,CAAC,CAAC;QAC9DC,gBAAgB,EAAEA,CAAA,KAAMH,OAAO,CAACC,OAAO,CAACG,SAAS;MACnD;IACF,CAAC;IAEDV,MAAM,CAACV,qBAAqB,CAACY,yBAAyB,CAAC,CAAC,CAACD,OAAO,CAC9DC,yBACF,CAAC;EACH,CAAC,CAAC;;EAEF;AACF;AACA;EACEV,EAAE,CAAC,0CAA0C,EAAE,MAAM;IACnD,MAAMmB,cAAc,GAClB,wDAA0D;MACxDjB,QAAQ,EAAE;QACRC,cAAc,EAAE,6BAA6B;QAC7CC,KAAK,EAAE,CAAC,sBAAsB,CAAC,CAAC;MAClC;IACF,CAAE;IAEJI,MAAM,CAAC,MAAMV,qBAAqB,CAACqB,cAAc,CAAC,CAAC,CAACC,OAAO,CACzD,wBACF,CAAC;EACH,CAAC,CAAC;AACJ,CAAC,CAAC;;AAEF;AACA;AACA","ignoreList":[]}
@@ -1,5 +1,5 @@
1
1
  import { type Events, type FormDefinition, type Page, type Section } from '@defra/forms-model';
2
- import { type Lifecycle, type ResponseToolkit, type RouteOptions } from '@hapi/hapi';
2
+ import { type Lifecycle, type ResponseToolkit, type RouteOptions, type Server } from '@hapi/hapi';
3
3
  import { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js';
4
4
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
5
5
  import { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js';
@@ -19,6 +19,7 @@ export declare class PageController {
19
19
  events?: Events;
20
20
  collection?: ComponentCollection;
21
21
  viewName: string;
22
+ allowSaveAndReturn: boolean;
22
23
  constructor(model: FormModel, pageDef: Page);
23
24
  get path(): string;
24
25
  get href(): string;
@@ -40,4 +41,5 @@ export declare class PageController {
40
41
  getStatusPath(): string;
41
42
  makeGetRouteHandler(): (request: FormRequest, context: FormContext, h: Pick<ResponseToolkit, 'redirect' | 'view'>) => ReturnType<Lifecycle.Method<FormRequestRefs>>;
42
43
  makePostRouteHandler(): (request: FormRequestPayload, context: FormContext, h: Pick<ResponseToolkit, 'redirect' | 'view'>) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>>;
44
+ shouldShowSaveAndReturn(server: Server): boolean;
43
45
  }
@@ -1,6 +1,6 @@
1
1
  import { ControllerPath } from '@defra/forms-model';
2
2
  import Boom from '@hapi/boom';
3
- import { encodeUrl, getStartPath, normalisePath } from "../helpers.js";
3
+ import { encodeUrl, getSaveAndReturnHelpers, getStartPath, normalisePath } from "../helpers.js";
4
4
  export class PageController {
5
5
  /**
6
6
  * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.
@@ -15,6 +15,7 @@ export class PageController {
15
15
  events;
16
16
  collection;
17
17
  viewName = 'index';
18
+ allowSaveAndReturn = false;
18
19
  constructor(model, pageDef) {
19
20
  const {
20
21
  def
@@ -137,5 +138,8 @@ export class PageController {
137
138
  makePostRouteHandler() {
138
139
  throw Boom.badRequest('Unsupported POST route handler for this page');
139
140
  }
141
+ shouldShowSaveAndReturn(server) {
142
+ return getSaveAndReturnHelpers(server) !== undefined && this.allowSaveAndReturn;
143
+ }
140
144
  }
141
145
  //# sourceMappingURL=PageController.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PageController.js","names":["ControllerPath","Boom","encodeUrl","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","constructor","sections","find","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","feedback","emailAddress","url","phaseBanner","phase","basePath","relativeTargetPath","startsWith","substring","finalPath","replace","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type Lifecycle,\n type ResponseToolkit,\n type RouteOptions\n} from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n encodeUrl,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type PageViewModelBase\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n this.section = model.sections.find(\n (section) => section.name === pageDef.section\n )\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n const { def } = this\n\n // setting the feedbackLink to undefined here for feedback forms prevents the feedback link from being shown\n const feedbackLink = def.feedback?.emailAddress\n ? `mailto:${def.feedback.emailAddress}`\n : def.feedback?.url\n\n return encodeUrl(feedbackLink)\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string): string {\n const basePath = this.model.basePath\n\n if (path === '/') {\n return `/${basePath}`\n }\n\n // if ever the path is not prefixed with a slash, add it\n const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path\n let finalPath = `/${basePath}`\n if (relativeTargetPath) {\n finalPath += `/${relativeTargetPath}`\n }\n finalPath = finalPath.replace(/\\/{2,}/g, '/')\n\n return finalPath\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n return h.view(viewName, viewModel)\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"PageController.js","names":["ControllerPath","Boom","encodeUrl","getSaveAndReturnHelpers","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","allowSaveAndReturn","constructor","sections","find","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","feedback","emailAddress","url","phaseBanner","phase","basePath","relativeTargetPath","startsWith","substring","finalPath","replace","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest","shouldShowSaveAndReturn","server","undefined"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type Lifecycle,\n type ResponseToolkit,\n type RouteOptions,\n type Server\n} from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n encodeUrl,\n getSaveAndReturnHelpers,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type PageViewModelBase\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n allowSaveAndReturn = false\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n this.section = model.sections.find(\n (section) => section.name === pageDef.section\n )\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n const { def } = this\n\n // setting the feedbackLink to undefined here for feedback forms prevents the feedback link from being shown\n const feedbackLink = def.feedback?.emailAddress\n ? `mailto:${def.feedback.emailAddress}`\n : def.feedback?.url\n\n return encodeUrl(feedbackLink)\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string): string {\n const basePath = this.model.basePath\n\n if (path === '/') {\n return `/${basePath}`\n }\n\n // if ever the path is not prefixed with a slash, add it\n const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path\n let finalPath = `/${basePath}`\n if (relativeTargetPath) {\n finalPath += `/${relativeTargetPath}`\n }\n finalPath = finalPath.replace(/\\/{2,}/g, '/')\n\n return finalPath\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n return h.view(viewName, viewModel)\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n\n shouldShowSaveAndReturn(server: Server): boolean {\n return (\n getSaveAndReturnHelpers(server) !== undefined && this.allowSaveAndReturn\n )\n }\n}\n"],"mappings":"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","ignoreList":[]}