@defra/forms-engine-plugin 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.server/server/plugins/engine/README.md +2 -46
- package/.server/server/plugins/engine/configureEnginePlugin.d.ts +1 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +5 -2
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +11 -0
- package/.server/server/plugins/engine/helpers.js +7 -1
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.js +2 -2
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/options.js +5 -3
- package/.server/server/plugins/engine/options.js.map +1 -1
- package/.server/server/plugins/engine/options.test.js +18 -9
- package/.server/server/plugins/engine/options.test.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.d.ts +3 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js +5 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -4
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -2
- package/.server/server/plugins/engine/pageControllers/StartPageController.js +1 -3
- package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StatusPageController.d.ts +1 -0
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js +1 -0
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +0 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +1 -4
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/TerminalPageController.d.ts +1 -0
- package/.server/server/plugins/engine/pageControllers/TerminalPageController.js +1 -0
- package/.server/server/plugins/engine/pageControllers/TerminalPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/__stubs__/request.d.ts +4 -0
- package/.server/server/plugins/engine/pageControllers/__stubs__/request.js +14 -0
- package/.server/server/plugins/engine/pageControllers/__stubs__/request.js.map +1 -0
- package/.server/server/plugins/engine/pageControllers/__stubs__/server.d.ts +3 -0
- package/.server/server/plugins/engine/pageControllers/__stubs__/server.js +23 -0
- package/.server/server/plugins/engine/pageControllers/__stubs__/server.js.map +1 -0
- package/.server/server/plugins/engine/plugin.js +5 -6
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +7 -5
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/types.d.ts +2 -1
- package/.server/server/types.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/package.json +1 -1
- package/src/server/plugins/engine/README.md +2 -46
- package/src/server/plugins/engine/configureEnginePlugin.ts +4 -2
- package/src/server/plugins/engine/helpers.test.ts +3 -2
- package/src/server/plugins/engine/helpers.ts +9 -1
- package/src/server/plugins/engine/models/FormModel.test.ts +96 -21
- package/src/server/plugins/engine/models/FormModel.ts +5 -2
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +7 -5
- package/src/server/plugins/engine/models/SummaryViewModel.ts +1 -1
- package/src/server/plugins/engine/options.js +5 -3
- package/src/server/plugins/engine/options.test.js +22 -11
- package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +3 -3
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +12 -1
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +9 -0
- package/src/server/plugins/engine/pageControllers/PageController.ts +10 -1
- package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +34 -28
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -5
- package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +19 -4
- package/src/server/plugins/engine/pageControllers/StartPageController.test.ts +32 -0
- package/src/server/plugins/engine/pageControllers/StartPageController.ts +2 -4
- package/src/server/plugins/engine/pageControllers/StatusPageController.test.ts +32 -0
- package/src/server/plugins/engine/pageControllers/StatusPageController.ts +1 -0
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +1 -5
- package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +9 -0
- package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +1 -0
- package/src/server/plugins/engine/pageControllers/__stubs__/request.ts +21 -0
- package/src/server/plugins/engine/pageControllers/__stubs__/server.ts +27 -0
- package/src/server/plugins/engine/plugin.ts +6 -6
- package/src/server/plugins/engine/types.ts +14 -9
- package/src/server/types.ts +2 -0
- package/src/typings/hapi/index.d.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TerminalPageController.js","names":["Boom","QuestionPageController","TerminalPageController","makePostRouteHandler","methodNotAllowed"],"sources":["../../../../../src/server/plugins/engine/pageControllers/TerminalPageController.ts"],"sourcesContent":["import { type PageTerminal } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseObject, type ResponseToolkit } from '@hapi/hapi'\n\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport { type FormRequestPayload } from '~/src/server/routes/types.js'\n\nexport class TerminalPageController extends QuestionPageController {\n declare pageDef: PageTerminal\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => Promise<ResponseObject> {\n throw Boom.methodNotAllowed('POST method not allowed for terminal pages')\n }\n}\n"],"mappings":"AACA,OAAOA,IAAI,MAAM,YAAY;AAG7B,SAASC,sBAAsB;AAI/B,OAAO,MAAMC,sBAAsB,SAASD,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"TerminalPageController.js","names":["Boom","QuestionPageController","TerminalPageController","allowSaveAndReturn","makePostRouteHandler","methodNotAllowed"],"sources":["../../../../../src/server/plugins/engine/pageControllers/TerminalPageController.ts"],"sourcesContent":["import { type PageTerminal } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseObject, type ResponseToolkit } from '@hapi/hapi'\n\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport { type FormRequestPayload } from '~/src/server/routes/types.js'\n\nexport class TerminalPageController extends QuestionPageController {\n declare pageDef: PageTerminal\n allowSaveAndReturn = false\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => Promise<ResponseObject> {\n throw Boom.methodNotAllowed('POST method not allowed for terminal pages')\n }\n}\n"],"mappings":"AACA,OAAOA,IAAI,MAAM,YAAY;AAG7B,SAASC,sBAAsB;AAI/B,OAAO,MAAMC,sBAAsB,SAASD,sBAAsB,CAAC;EAEjEE,kBAAkB,GAAG,KAAK;EAE1BC,oBAAoBA,CAAA,EAIS;IAC3B,MAAMJ,IAAI,CAACK,gBAAgB,CAAC,4CAA4C,CAAC;EAC3E;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type FormContextRequest } from '~/src/server/plugins/engine/types.js';
|
|
2
|
+
import { type FormRequest } from '~/src/server/routes/types.js';
|
|
3
|
+
export declare function buildFormRequest(request: Omit<FormRequest, 'server'>): FormRequest;
|
|
4
|
+
export declare function buildFormContextRequest(request: Omit<FormContextRequest, 'server'>): FormContextRequest;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { server } from "./server.js";
|
|
2
|
+
export function buildFormRequest(request) {
|
|
3
|
+
return {
|
|
4
|
+
...request,
|
|
5
|
+
server
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function buildFormContextRequest(request) {
|
|
9
|
+
return {
|
|
10
|
+
...request,
|
|
11
|
+
server
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","names":["server","buildFormRequest","request","buildFormContextRequest"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/__stubs__/request.ts"],"sourcesContent":["import { server } from '~/src/server/plugins/engine/pageControllers/__stubs__/server.js'\nimport { type FormContextRequest } from '~/src/server/plugins/engine/types.js'\nimport { type FormRequest } from '~/src/server/routes/types.js'\n\nexport function buildFormRequest(\n request: Omit<FormRequest, 'server'>\n): FormRequest {\n return {\n ...request,\n server\n } as FormRequest\n}\n\nexport function buildFormContextRequest(\n request: Omit<FormContextRequest, 'server'>\n): FormContextRequest {\n return {\n ...request,\n server\n } as FormContextRequest\n}\n"],"mappings":"AAAA,SAASA,MAAM;AAIf,OAAO,SAASC,gBAAgBA,CAC9BC,OAAoC,EACvB;EACb,OAAO;IACL,GAAGA,OAAO;IACVF;EACF,CAAC;AACH;AAEA,OAAO,SAASG,uBAAuBA,CACrCD,OAA2C,EACvB;EACpB,OAAO;IACL,GAAGA,OAAO;IACVF;EACF,CAAC;AACH","ignoreList":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const server = {
|
|
2
|
+
plugins: {
|
|
3
|
+
'forms-engine-plugin': {
|
|
4
|
+
baseLayoutPath: '',
|
|
5
|
+
cacheService: {}
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}; // only mocking out properties we care about;
|
|
9
|
+
|
|
10
|
+
export const serverWithSaveAndReturn = {
|
|
11
|
+
plugins: {
|
|
12
|
+
...server.plugins,
|
|
13
|
+
'forms-engine-plugin': {
|
|
14
|
+
...server.plugins['forms-engine-plugin'],
|
|
15
|
+
saveAndReturn: {
|
|
16
|
+
keyGenerator: jest.fn().mockReturnValue('foobar'),
|
|
17
|
+
sessionHydrator: jest.fn().mockReturnValue({}),
|
|
18
|
+
sessionPersister: jest.fn().mockImplementation(() => Promise.resolve())
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}; // only mocking out properties we care about
|
|
23
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":["server","plugins","baseLayoutPath","cacheService","serverWithSaveAndReturn","saveAndReturn","keyGenerator","jest","fn","mockReturnValue","sessionHydrator","sessionPersister","mockImplementation","Promise","resolve"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/__stubs__/server.ts"],"sourcesContent":["import { type Server } from '@hapi/hapi'\n\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { type CacheService } from '~/src/server/services/index.js'\n\nexport const server: Server = {\n plugins: {\n 'forms-engine-plugin': {\n baseLayoutPath: '',\n cacheService: {} as CacheService\n }\n }\n} as Server // only mocking out properties we care about;\n\nexport const serverWithSaveAndReturn: Server = {\n plugins: {\n ...server.plugins,\n 'forms-engine-plugin': {\n ...server.plugins['forms-engine-plugin'],\n saveAndReturn: {\n keyGenerator: jest.fn().mockReturnValue('foobar'),\n sessionHydrator: jest.fn().mockReturnValue({}),\n sessionPersister: jest.fn().mockImplementation(() => Promise.resolve())\n } as Pick<PluginOptions, 'saveAndReturn'>\n }\n }\n} as Server // only mocking out properties we care about\n"],"mappings":"AAKA,OAAO,MAAMA,MAAc,GAAG;EAC5BC,OAAO,EAAE;IACP,qBAAqB,EAAE;MACrBC,cAAc,EAAE,EAAE;MAClBC,YAAY,EAAE,CAAC;IACjB;EACF;AACF,CAAW,EAAC;;AAEZ,OAAO,MAAMC,uBAA+B,GAAG;EAC7CH,OAAO,EAAE;IACP,GAAGD,MAAM,CAACC,OAAO;IACjB,qBAAqB,EAAE;MACrB,GAAGD,MAAM,CAACC,OAAO,CAAC,qBAAqB,CAAC;MACxCI,aAAa,EAAE;QACbC,YAAY,EAAEC,IAAI,CAACC,EAAE,CAAC,CAAC,CAACC,eAAe,CAAC,QAAQ,CAAC;QACjDC,eAAe,EAAEH,IAAI,CAACC,EAAE,CAAC,CAAC,CAACC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC9CE,gBAAgB,EAAEJ,IAAI,CAACC,EAAE,CAAC,CAAC,CAACI,kBAAkB,CAAC,MAAMC,OAAO,CAACC,OAAO,CAAC,CAAC;MACxE;IACF;EACF;AACF,CAAW,EAAC","ignoreList":[]}
|
|
@@ -16,9 +16,7 @@ export const plugin = {
|
|
|
16
16
|
const {
|
|
17
17
|
model,
|
|
18
18
|
cacheName,
|
|
19
|
-
|
|
20
|
-
sessionHydrator,
|
|
21
|
-
sessionPersister,
|
|
19
|
+
saveAndReturn,
|
|
22
20
|
nunjucks: nunjucksOptions,
|
|
23
21
|
viewContext,
|
|
24
22
|
preparePageEventRequestOptions
|
|
@@ -27,15 +25,16 @@ export const plugin = {
|
|
|
27
25
|
server,
|
|
28
26
|
cacheName,
|
|
29
27
|
options: {
|
|
30
|
-
keyGenerator,
|
|
31
|
-
sessionHydrator,
|
|
32
|
-
sessionPersister
|
|
28
|
+
keyGenerator: saveAndReturn?.keyGenerator,
|
|
29
|
+
sessionHydrator: saveAndReturn?.sessionHydrator,
|
|
30
|
+
sessionPersister: saveAndReturn?.sessionPersister
|
|
33
31
|
}
|
|
34
32
|
});
|
|
35
33
|
await registerVision(server, options);
|
|
36
34
|
server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath);
|
|
37
35
|
server.expose('viewContext', viewContext);
|
|
38
36
|
server.expose('cacheService', cacheService);
|
|
37
|
+
server.expose('saveAndReturn', saveAndReturn);
|
|
39
38
|
server.app.model = model;
|
|
40
39
|
|
|
41
40
|
// In-memory cache of FormModel items, exposed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["validatePluginOptions","getRoutes","getSaveAndReturnExitRoutes","getFileUploadStatusRoutes","makeLoadFormPreHandler","getQuestionRoutes","getRepeaterItemDeleteRoutes","getRepeaterSummaryRoutes","registerVision","CacheService","plugin","name","dependencies","multiple","register","server","options","model","cacheName","
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["validatePluginOptions","getRoutes","getSaveAndReturnExitRoutes","getFileUploadStatusRoutes","makeLoadFormPreHandler","getQuestionRoutes","getRepeaterItemDeleteRoutes","getRepeaterSummaryRoutes","registerVision","CacheService","plugin","name","dependencies","multiple","register","server","options","model","cacheName","saveAndReturn","nunjucks","nunjucksOptions","viewContext","preparePageEventRequestOptions","cacheService","keyGenerator","sessionHydrator","sessionPersister","expose","baseLayoutPath","app","itemCache","Map","models","loadFormPreHandler","getRouteOptions","pre","method","postRouteOptions","payload","parse","routes","route"],"sources":["../../../../src/server/plugins/engine/plugin.ts"],"sourcesContent":["import {\n type Lifecycle,\n type Plugin,\n type RouteOptions,\n type Server,\n type ServerRoute\n} from '@hapi/hapi'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\nimport { getRoutes as getSaveAndReturnExitRoutes } from '~/src/server/plugins/engine/routes/exit.js'\nimport { getRoutes as getFileUploadStatusRoutes } from '~/src/server/plugins/engine/routes/file-upload.js'\nimport { makeLoadFormPreHandler } from '~/src/server/plugins/engine/routes/index.js'\nimport { getRoutes as getQuestionRoutes } from '~/src/server/plugins/engine/routes/questions.js'\nimport { getRoutes as getRepeaterItemDeleteRoutes } from '~/src/server/plugins/engine/routes/repeaters/item-delete.js'\nimport { getRoutes as getRepeaterSummaryRoutes } from '~/src/server/plugins/engine/routes/repeaters/summary.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { registerVision } from '~/src/server/plugins/engine/vision.js'\nimport {\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport { CacheService } from '~/src/server/services/index.js'\n\nexport const plugin = {\n name: '@defra/forms-engine-plugin',\n dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],\n multiple: true,\n async register(server: Server, options: PluginOptions) {\n options = validatePluginOptions(options)\n\n const {\n model,\n cacheName,\n saveAndReturn,\n nunjucks: nunjucksOptions,\n viewContext,\n preparePageEventRequestOptions\n } = options\n\n const cacheService = new CacheService({\n server,\n cacheName,\n options: {\n keyGenerator: saveAndReturn?.keyGenerator,\n sessionHydrator: saveAndReturn?.sessionHydrator,\n sessionPersister: saveAndReturn?.sessionPersister\n }\n })\n\n await registerVision(server, options)\n\n server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath)\n server.expose('viewContext', viewContext)\n server.expose('cacheService', cacheService)\n server.expose('saveAndReturn', saveAndReturn)\n\n server.app.model = model\n\n // In-memory cache of FormModel items, exposed\n // (for testing purposes) through `server.app.models`\n const itemCache = new Map<string, { model: FormModel; updatedAt: Date }>()\n server.app.models = itemCache\n\n const loadFormPreHandler = makeLoadFormPreHandler(server, options)\n\n const getRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestRefs>\n }\n ]\n }\n\n const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {\n payload: {\n parse: true\n },\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestPayloadRefs>\n }\n ]\n }\n\n const routes = [\n ...getQuestionRoutes(\n getRouteOptions,\n postRouteOptions,\n preparePageEventRequestOptions\n ),\n ...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions),\n ...getRepeaterItemDeleteRoutes(getRouteOptions, postRouteOptions),\n ...getSaveAndReturnExitRoutes(getRouteOptions),\n ...getFileUploadStatusRoutes()\n ]\n\n server.route(routes as unknown as ServerRoute[]) // TODO\n }\n} satisfies Plugin<PluginOptions>\n"],"mappings":"AASA,SAASA,qBAAqB;AAC9B,SAASC,SAAS,IAAIC,0BAA0B;AAChD,SAASD,SAAS,IAAIE,yBAAyB;AAC/C,SAASC,sBAAsB;AAC/B,SAASH,SAAS,IAAII,iBAAiB;AACvC,SAASJ,SAAS,IAAIK,2BAA2B;AACjD,SAASL,SAAS,IAAIM,wBAAwB;AAE9C,SAASC,cAAc;AAKvB,SAASC,YAAY;AAErB,OAAO,MAAMC,MAAM,GAAG;EACpBC,IAAI,EAAE,4BAA4B;EAClCC,YAAY,EAAE,CAAC,aAAa,EAAE,WAAW,EAAE,WAAW,CAAC;EACvDC,QAAQ,EAAE,IAAI;EACd,MAAMC,QAAQA,CAACC,MAAc,EAAEC,OAAsB,EAAE;IACrDA,OAAO,GAAGhB,qBAAqB,CAACgB,OAAO,CAAC;IAExC,MAAM;MACJC,KAAK;MACLC,SAAS;MACTC,aAAa;MACbC,QAAQ,EAAEC,eAAe;MACzBC,WAAW;MACXC;IACF,CAAC,GAAGP,OAAO;IAEX,MAAMQ,YAAY,GAAG,IAAIf,YAAY,CAAC;MACpCM,MAAM;MACNG,SAAS;MACTF,OAAO,EAAE;QACPS,YAAY,EAAEN,aAAa,EAAEM,YAAY;QACzCC,eAAe,EAAEP,aAAa,EAAEO,eAAe;QAC/CC,gBAAgB,EAAER,aAAa,EAAEQ;MACnC;IACF,CAAC,CAAC;IAEF,MAAMnB,cAAc,CAACO,MAAM,EAAEC,OAAO,CAAC;IAErCD,MAAM,CAACa,MAAM,CAAC,gBAAgB,EAAEP,eAAe,CAACQ,cAAc,CAAC;IAC/Dd,MAAM,CAACa,MAAM,CAAC,aAAa,EAAEN,WAAW,CAAC;IACzCP,MAAM,CAACa,MAAM,CAAC,cAAc,EAAEJ,YAAY,CAAC;IAC3CT,MAAM,CAACa,MAAM,CAAC,eAAe,EAAET,aAAa,CAAC;IAE7CJ,MAAM,CAACe,GAAG,CAACb,KAAK,GAAGA,KAAK;;IAExB;IACA;IACA,MAAMc,SAAS,GAAG,IAAIC,GAAG,CAAgD,CAAC;IAC1EjB,MAAM,CAACe,GAAG,CAACG,MAAM,GAAGF,SAAS;IAE7B,MAAMG,kBAAkB,GAAG9B,sBAAsB,CAACW,MAAM,EAAEC,OAAO,CAAC;IAElE,MAAMmB,eAA8C,GAAG;MACrDC,GAAG,EAAE,CACH;QACEC,MAAM,EACJH;MACJ,CAAC;IAEL,CAAC;IAED,MAAMI,gBAAsD,GAAG;MAC7DC,OAAO,EAAE;QACPC,KAAK,EAAE;MACT,CAAC;MACDJ,GAAG,EAAE,CACH;QACEC,MAAM,EACJH;MACJ,CAAC;IAEL,CAAC;IAED,MAAMO,MAAM,GAAG,CACb,GAAGpC,iBAAiB,CAClB8B,eAAe,EACfG,gBAAgB,EAChBf,8BACF,CAAC,EACD,GAAGhB,wBAAwB,CAAC4B,eAAe,EAAEG,gBAAgB,CAAC,EAC9D,GAAGhC,2BAA2B,CAAC6B,eAAe,EAAEG,gBAAgB,CAAC,EACjE,GAAGpC,0BAA0B,CAACiC,eAAe,CAAC,EAC9C,GAAGhC,yBAAyB,CAAC,CAAC,CAC/B;IAEDY,MAAM,CAAC2B,KAAK,CAACD,MAAkC,CAAC,EAAC;EACnD;AACF,CAAiC","ignoreList":[]}
|
|
@@ -125,7 +125,7 @@ export type FormContextRequest = ({
|
|
|
125
125
|
} | {
|
|
126
126
|
method: FormRequest['method'];
|
|
127
127
|
payload?: object | undefined;
|
|
128
|
-
}) & Pick<FormRequest, 'app' | 'method' | 'params' | 'path' | 'query' | 'url'>;
|
|
128
|
+
}) & Pick<FormRequest, 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'>;
|
|
129
129
|
export interface UploadInitiateResponse {
|
|
130
130
|
uploadId: string;
|
|
131
131
|
uploadUrl: string;
|
|
@@ -235,7 +235,7 @@ export interface FormPageViewModel extends PageViewModelBase {
|
|
|
235
235
|
context: FormContext;
|
|
236
236
|
errors?: FormSubmissionError[];
|
|
237
237
|
hasMissingNotificationEmail?: boolean;
|
|
238
|
-
allowSaveAndReturn
|
|
238
|
+
allowSaveAndReturn: boolean;
|
|
239
239
|
}
|
|
240
240
|
export interface RepeaterSummaryPageViewModel extends PageViewModelBase {
|
|
241
241
|
context: FormContext;
|
|
@@ -270,9 +270,11 @@ export interface PluginOptions {
|
|
|
270
270
|
cacheName?: string;
|
|
271
271
|
globals?: Record<string, GlobalFunction>;
|
|
272
272
|
filters?: Record<string, FilterFunction>;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
saveAndReturn?: {
|
|
274
|
+
keyGenerator: (request: RequestType) => string;
|
|
275
|
+
sessionHydrator: (request: RequestType) => Promise<FormSubmissionState>;
|
|
276
|
+
sessionPersister: (key: string, state: FormSubmissionState, request: RequestType) => Promise<void>;
|
|
277
|
+
};
|
|
276
278
|
pluginPath?: string;
|
|
277
279
|
nunjucks: {
|
|
278
280
|
baseLayoutPath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["UploadStatus","FileStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\ntype RequestType = Request | FormRequest | FormRequestPayload\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<FormRequest, 'app' | 'method' | 'params' | 'path' | 'query' | 'url'>\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport enum UploadStatus {\n initiated = 'initiated',\n pending = 'pending',\n ready = 'ready'\n}\n\nexport enum FileStatus {\n complete = 'complete',\n rejected = 'rejected',\n pending = 'pending'\n}\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndReturn?: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n keyGenerator?: (request: RequestType) => string\n sessionHydrator?: (request: RequestType) => Promise<FormSubmissionState>\n sessionPersister?: (\n key: string,\n state: FormSubmissionState,\n request: RequestType\n ) => Promise<void>\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n"],"mappings":"AAkCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAkGA,WAAYA,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAMxB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":["UploadStatus","FileStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\ntype RequestType = Request | FormRequest | FormRequestPayload\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport enum UploadStatus {\n initiated = 'initiated',\n pending = 'pending',\n ready = 'ready'\n}\n\nexport enum FileStatus {\n complete = 'complete',\n rejected = 'rejected',\n pending = 'pending'\n}\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndReturn: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndReturn?: {\n keyGenerator: (request: RequestType) => string\n sessionHydrator: (request: RequestType) => Promise<FormSubmissionState>\n sessionPersister: (\n key: string,\n state: FormSubmissionState,\n request: RequestType\n ) => Promise<void>\n }\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n"],"mappings":"AAkCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAqGA,WAAYA,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAMxB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
|
|
@@ -2,7 +2,7 @@ import { type FormDefinition, type FormMetadata, type SubmitPayload, type Submit
|
|
|
2
2
|
import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
|
|
3
3
|
import { type DetailItem } from '~/src/server/plugins/engine/models/types.js';
|
|
4
4
|
import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
|
|
5
|
-
import { type OnRequestCallback, type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js';
|
|
5
|
+
import { type OnRequestCallback, type PluginOptions, type PreparePageEventRequestOptions } from '~/src/server/plugins/engine/types.js';
|
|
6
6
|
import { type FormRequestPayload, type FormStatus } from '~/src/server/routes/types.js';
|
|
7
7
|
export interface FormsService {
|
|
8
8
|
getFormMetadata: (slug: string) => Promise<FormMetadata>;
|
|
@@ -28,6 +28,7 @@ export interface RouteConfig {
|
|
|
28
28
|
controllers?: Record<string, typeof PageController>;
|
|
29
29
|
preparePageEventRequestOptions?: PreparePageEventRequestOptions;
|
|
30
30
|
onRequest?: OnRequestCallback;
|
|
31
|
+
saveAndReturn?: PluginOptions['saveAndReturn'];
|
|
31
32
|
}
|
|
32
33
|
export interface OutputService {
|
|
33
34
|
submit: (request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type OnRequestCallback,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n}\n\nexport interface OutputService {\n submit: (\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type OnRequestCallback,\n type PluginOptions,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n saveAndReturn?: PluginOptions['saveAndReturn']\n}\n\nexport interface OutputService {\n submit: (\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: Request | FormRequest | FormRequestPayload) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: FormRequest | FormRequestPayload | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module '@hapi/scooter' {\n declare const hapiScooter: {\n plugin: Plugin\n }\n\n export = hapiScooter\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.ts'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: Request | FormRequest | FormRequestPayload) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: FormRequest | FormRequestPayload | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndReturn?: PluginOptions['saveAndReturn']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module '@hapi/scooter' {\n declare const hapiScooter: {\n plugin: Plugin\n }\n\n export = hapiScooter\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -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
|
-
|
|
89
|
+
### Save and return
|
|
90
90
|
|
|
91
|
-
|
|
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
|
|
|
@@ -18,7 +18,8 @@ export const configureEnginePlugin = async ({
|
|
|
18
18
|
services,
|
|
19
19
|
controllers,
|
|
20
20
|
preparePageEventRequestOptions,
|
|
21
|
-
onRequest
|
|
21
|
+
onRequest,
|
|
22
|
+
saveAndReturn
|
|
22
23
|
}: RouteConfig = {}): Promise<{
|
|
23
24
|
plugin: typeof plugin
|
|
24
25
|
options: PluginOptions
|
|
@@ -57,7 +58,8 @@ export const configureEnginePlugin = async ({
|
|
|
57
58
|
viewContext: devtoolContext,
|
|
58
59
|
preparePageEventRequestOptions,
|
|
59
60
|
onRequest,
|
|
60
|
-
baseUrl: 'http://localhost:3009' // always runs locally
|
|
61
|
+
baseUrl: 'http://localhost:3009', // always runs locally
|
|
62
|
+
saveAndReturn
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
19
19
|
import { handleLegacyRedirect } from '~/src/server/plugins/engine/helpers.js'
|
|
20
20
|
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
21
|
+
import { buildFormContextRequest } from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
|
|
21
22
|
import {
|
|
22
23
|
createPage,
|
|
23
24
|
type PageControllerClass
|
|
@@ -56,7 +57,7 @@ describe('Helpers', () => {
|
|
|
56
57
|
page = createPage(model, definition.pages[0])
|
|
57
58
|
const pageUrl = new URL(page.href, 'http://example.com')
|
|
58
59
|
|
|
59
|
-
request = {
|
|
60
|
+
request = buildFormContextRequest({
|
|
60
61
|
method: 'get',
|
|
61
62
|
url: pageUrl,
|
|
62
63
|
path: pageUrl.pathname,
|
|
@@ -66,7 +67,7 @@ describe('Helpers', () => {
|
|
|
66
67
|
},
|
|
67
68
|
query: {},
|
|
68
69
|
app: { model }
|
|
69
|
-
}
|
|
70
|
+
})
|
|
70
71
|
|
|
71
72
|
const response = {
|
|
72
73
|
code: jest.fn().mockImplementation(() => response)
|
|
@@ -377,7 +377,15 @@ export function evaluateTemplate(
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
export function getCacheService(server: Server) {
|
|
380
|
-
return server.
|
|
380
|
+
return getPluginOptions(server).cacheService
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function getSaveAndReturnHelpers(server: Server) {
|
|
384
|
+
return getPluginOptions(server).saveAndReturn
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function getPluginOptions(server: Server) {
|
|
388
|
+
return server.plugins['forms-engine-plugin']
|
|
381
389
|
}
|
|
382
390
|
|
|
383
391
|
/**
|