@dwp/govuk-casa 8.15.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/README.md +9 -9
  2. package/dist/assets/css/casa.css +2 -1
  3. package/dist/assets/css/casa.css.map +1 -0
  4. package/dist/casa.d.ts +122 -99
  5. package/dist/casa.js +120 -88
  6. package/dist/casa.js.map +1 -1
  7. package/dist/lib/CasaTemplateLoader.d.ts +4 -4
  8. package/dist/lib/CasaTemplateLoader.js +16 -16
  9. package/dist/lib/CasaTemplateLoader.js.map +1 -1
  10. package/dist/lib/JourneyContext.d.ts +38 -40
  11. package/dist/lib/JourneyContext.js +89 -75
  12. package/dist/lib/JourneyContext.js.map +1 -1
  13. package/dist/lib/MutableRouter.d.ts +40 -41
  14. package/dist/lib/MutableRouter.js +64 -71
  15. package/dist/lib/MutableRouter.js.map +1 -1
  16. package/dist/lib/Plan.d.ts +29 -26
  17. package/dist/lib/Plan.js +85 -71
  18. package/dist/lib/Plan.js.map +1 -1
  19. package/dist/lib/ValidationError.d.ts +16 -15
  20. package/dist/lib/ValidationError.js +21 -20
  21. package/dist/lib/ValidationError.js.map +1 -1
  22. package/dist/lib/ValidatorFactory.d.ts +15 -13
  23. package/dist/lib/ValidatorFactory.js +14 -12
  24. package/dist/lib/ValidatorFactory.js.map +1 -1
  25. package/dist/lib/configuration-ingestor.d.ts +37 -40
  26. package/dist/lib/configuration-ingestor.js +93 -93
  27. package/dist/lib/configuration-ingestor.js.map +1 -1
  28. package/dist/lib/configure.d.ts +6 -6
  29. package/dist/lib/configure.js +14 -12
  30. package/dist/lib/configure.js.map +1 -1
  31. package/dist/lib/constants.d.ts +1 -3
  32. package/dist/lib/constants.js +9 -11
  33. package/dist/lib/constants.js.map +1 -1
  34. package/dist/lib/context-id-generators.d.ts +3 -5
  35. package/dist/lib/context-id-generators.js +7 -6
  36. package/dist/lib/context-id-generators.js.map +1 -1
  37. package/dist/lib/end-session.d.ts +4 -4
  38. package/dist/lib/end-session.js +5 -5
  39. package/dist/lib/field.d.ts +24 -29
  40. package/dist/lib/field.js +41 -70
  41. package/dist/lib/field.js.map +1 -1
  42. package/dist/lib/index.d.ts +13 -13
  43. package/dist/lib/logger.d.ts +7 -6
  44. package/dist/lib/logger.js +7 -7
  45. package/dist/lib/logger.js.map +1 -1
  46. package/dist/lib/mount.d.ts +5 -5
  47. package/dist/lib/mount.js +12 -17
  48. package/dist/lib/mount.js.map +1 -1
  49. package/dist/lib/nunjucks-filters.d.ts +10 -12
  50. package/dist/lib/nunjucks-filters.js +35 -35
  51. package/dist/lib/nunjucks-filters.js.map +1 -1
  52. package/dist/lib/nunjucks.d.ts +7 -5
  53. package/dist/lib/nunjucks.js +10 -8
  54. package/dist/lib/nunjucks.js.map +1 -1
  55. package/dist/lib/utils.d.ts +19 -19
  56. package/dist/lib/utils.js +62 -55
  57. package/dist/lib/utils.js.map +1 -1
  58. package/dist/lib/validators/dateObject.d.ts +29 -22
  59. package/dist/lib/validators/dateObject.js +58 -49
  60. package/dist/lib/validators/dateObject.js.map +1 -1
  61. package/dist/lib/validators/email.d.ts +4 -4
  62. package/dist/lib/validators/email.js +4 -4
  63. package/dist/lib/validators/inArray.d.ts +4 -4
  64. package/dist/lib/validators/inArray.js +7 -8
  65. package/dist/lib/validators/inArray.js.map +1 -1
  66. package/dist/lib/validators/index.d.ts +10 -10
  67. package/dist/lib/validators/index.js +1 -3
  68. package/dist/lib/validators/index.js.map +1 -1
  69. package/dist/lib/validators/nino.d.ts +9 -8
  70. package/dist/lib/validators/nino.js +14 -10
  71. package/dist/lib/validators/nino.js.map +1 -1
  72. package/dist/lib/validators/postalAddressObject.d.ts +37 -24
  73. package/dist/lib/validators/postalAddressObject.js +65 -46
  74. package/dist/lib/validators/postalAddressObject.js.map +1 -1
  75. package/dist/lib/validators/range.d.ts +12 -8
  76. package/dist/lib/validators/range.js +11 -9
  77. package/dist/lib/validators/range.js.map +1 -1
  78. package/dist/lib/validators/regex.d.ts +4 -4
  79. package/dist/lib/validators/regex.js +5 -5
  80. package/dist/lib/validators/required.d.ts +6 -6
  81. package/dist/lib/validators/required.js +9 -11
  82. package/dist/lib/validators/required.js.map +1 -1
  83. package/dist/lib/validators/strlen.d.ts +12 -8
  84. package/dist/lib/validators/strlen.js +13 -11
  85. package/dist/lib/validators/strlen.js.map +1 -1
  86. package/dist/lib/validators/wordCount.d.ts +12 -8
  87. package/dist/lib/validators/wordCount.js +15 -11
  88. package/dist/lib/validators/wordCount.js.map +1 -1
  89. package/dist/lib/waypoint-url.d.ts +16 -13
  90. package/dist/lib/waypoint-url.js +39 -36
  91. package/dist/lib/waypoint-url.js.map +1 -1
  92. package/dist/middleware/body-parser.d.ts +1 -1
  93. package/dist/middleware/body-parser.js +6 -6
  94. package/dist/middleware/body-parser.js.map +1 -1
  95. package/dist/middleware/data.d.ts +1 -1
  96. package/dist/middleware/data.js +8 -7
  97. package/dist/middleware/data.js.map +1 -1
  98. package/dist/middleware/gather-fields.d.ts +2 -2
  99. package/dist/middleware/gather-fields.js +6 -4
  100. package/dist/middleware/gather-fields.js.map +1 -1
  101. package/dist/middleware/i18n.js +13 -15
  102. package/dist/middleware/i18n.js.map +1 -1
  103. package/dist/middleware/post.js +30 -18
  104. package/dist/middleware/post.js.map +1 -1
  105. package/dist/middleware/pre.d.ts +2 -2
  106. package/dist/middleware/pre.js +46 -26
  107. package/dist/middleware/pre.js.map +1 -1
  108. package/dist/middleware/progress-journey.d.ts +1 -1
  109. package/dist/middleware/progress-journey.js +5 -5
  110. package/dist/middleware/progress-journey.js.map +1 -1
  111. package/dist/middleware/sanitise-fields.d.ts +1 -1
  112. package/dist/middleware/sanitise-fields.js +13 -11
  113. package/dist/middleware/sanitise-fields.js.map +1 -1
  114. package/dist/middleware/serve-first-waypoint.d.ts +3 -3
  115. package/dist/middleware/serve-first-waypoint.js +8 -6
  116. package/dist/middleware/serve-first-waypoint.js.map +1 -1
  117. package/dist/middleware/session.js +14 -11
  118. package/dist/middleware/session.js.map +1 -1
  119. package/dist/middleware/skip-waypoint.d.ts +1 -1
  120. package/dist/middleware/skip-waypoint.js +3 -3
  121. package/dist/middleware/skip-waypoint.js.map +1 -1
  122. package/dist/middleware/steer-journey.d.ts +1 -1
  123. package/dist/middleware/steer-journey.js +16 -14
  124. package/dist/middleware/steer-journey.js.map +1 -1
  125. package/dist/middleware/strip-proxy-path.d.ts +1 -1
  126. package/dist/middleware/strip-proxy-path.js +3 -3
  127. package/dist/middleware/strip-proxy-path.js.map +1 -1
  128. package/dist/middleware/validate-fields.d.ts +1 -1
  129. package/dist/middleware/validate-fields.js +2 -5
  130. package/dist/middleware/validate-fields.js.map +1 -1
  131. package/dist/routes/ancillary.d.ts +3 -3
  132. package/dist/routes/ancillary.js +4 -4
  133. package/dist/routes/ancillary.js.map +1 -1
  134. package/dist/routes/journey.d.ts +2 -2
  135. package/dist/routes/journey.js +91 -39
  136. package/dist/routes/journey.js.map +1 -1
  137. package/dist/routes/static.d.ts +7 -5
  138. package/dist/routes/static.js +20 -20
  139. package/dist/routes/static.js.map +1 -1
  140. package/package.json +17 -16
  141. package/src/casa.js +134 -102
  142. package/src/lib/CasaTemplateLoader.js +24 -19
  143. package/src/lib/JourneyContext.js +147 -107
  144. package/src/lib/MutableRouter.js +72 -74
  145. package/src/lib/Plan.js +145 -97
  146. package/src/lib/ValidationError.js +25 -21
  147. package/src/lib/ValidatorFactory.js +17 -13
  148. package/src/lib/configuration-ingestor.js +147 -110
  149. package/src/lib/configure.js +34 -32
  150. package/src/lib/constants.js +9 -11
  151. package/src/lib/context-id-generators.js +40 -43
  152. package/src/lib/end-session.js +6 -6
  153. package/src/lib/field.js +74 -78
  154. package/src/lib/index.js +12 -12
  155. package/src/lib/logger.js +9 -9
  156. package/src/lib/mount.js +70 -80
  157. package/src/lib/nunjucks-filters.js +56 -59
  158. package/src/lib/nunjucks.js +23 -18
  159. package/src/lib/utils.js +78 -57
  160. package/src/lib/validators/dateObject.js +71 -60
  161. package/src/lib/validators/email.js +8 -8
  162. package/src/lib/validators/inArray.js +10 -11
  163. package/src/lib/validators/index.js +12 -14
  164. package/src/lib/validators/nino.js +29 -15
  165. package/src/lib/validators/postalAddressObject.js +87 -63
  166. package/src/lib/validators/range.js +14 -12
  167. package/src/lib/validators/regex.js +8 -8
  168. package/src/lib/validators/required.js +16 -16
  169. package/src/lib/validators/strlen.js +16 -14
  170. package/src/lib/validators/wordCount.js +22 -14
  171. package/src/lib/waypoint-url.js +64 -46
  172. package/src/middleware/body-parser.js +10 -10
  173. package/src/middleware/csrf.js +1 -1
  174. package/src/middleware/data.js +28 -24
  175. package/src/middleware/gather-fields.js +10 -9
  176. package/src/middleware/i18n.js +35 -37
  177. package/src/middleware/post.js +41 -21
  178. package/src/middleware/pre.js +62 -40
  179. package/src/middleware/progress-journey.js +32 -18
  180. package/src/middleware/sanitise-fields.js +43 -20
  181. package/src/middleware/serve-first-waypoint.js +14 -12
  182. package/src/middleware/session.js +74 -61
  183. package/src/middleware/skip-waypoint.js +7 -9
  184. package/src/middleware/steer-journey.js +40 -28
  185. package/src/middleware/strip-proxy-path.js +8 -7
  186. package/src/middleware/validate-fields.js +5 -12
  187. package/src/routes/ancillary.js +5 -7
  188. package/src/routes/journey.js +159 -85
  189. package/src/routes/static.js +62 -30
  190. package/views/casa/components/character-count/README.md +2 -2
  191. package/views/casa/components/checkboxes/README.md +6 -6
  192. package/views/casa/components/date-input/README.md +7 -7
  193. package/views/casa/components/input/README.md +2 -2
  194. package/views/casa/components/journey-form/README.md +33 -14
  195. package/views/casa/components/postal-address-object/README.md +4 -4
  196. package/views/casa/components/radios/README.md +6 -6
  197. package/views/casa/components/select/README.md +6 -6
  198. package/views/casa/components/textarea/README.md +2 -2
  199. package/views/casa/partials/scripts.njk +5 -3
  200. package/views/casa/partials/styles.njk +1 -4
  201. package/dist/assets/css/casa-ie8.css +0 -1
@@ -6,61 +6,61 @@
6
6
  * - Validation errors on that data
7
7
  * - Navigation information about how the user got where they are.
8
8
  */
9
- import lodash from 'lodash';
10
- import ValidationError from './ValidationError.js';
11
- import logger from './logger.js';
12
- import { notProto } from './utils.js';
13
- import { uuid as uuidGenerator } from './context-id-generators.js';
9
+ import lodash from "lodash";
10
+ import ValidationError from "./ValidationError.js";
11
+ import logger from "./logger.js";
12
+ import { notProto } from "./utils.js";
13
+ import { uuid as uuidGenerator } from "./context-id-generators.js";
14
14
 
15
- const {
16
- isPlainObject, isObject, has, isEqual,
17
- } = lodash; // CommonJS
15
+ const { isPlainObject, isObject, has, isEqual } = lodash; // CommonJS
18
16
 
19
- const log = logger('lib:journey-context');
17
+ const log = logger("lib:journey-context");
20
18
 
21
19
  const uuid = uuidGenerator();
22
20
 
23
21
  /**
22
+ * @typedef {import("../casa").ContextEventUserInfo} ContextEventUserInfo
24
23
  * @access private
25
- * @typedef {import('../casa').ContextEventUserInfo} ContextEventUserInfo
26
24
  */
27
25
 
28
26
  /**
27
+ * @typedef {import("../casa").Page} Page
29
28
  * @access private
30
- * @typedef {import('../casa').Page} Page
31
29
  */
32
30
 
33
31
  /**
32
+ * @typedef {import("../casa").ContextEventHandler} ContextEventHandler
34
33
  * @access private
35
- * @typedef {import('../casa').ContextEventHandler} ContextEventHandler
36
34
  */
37
35
 
38
36
  /**
37
+ * @typedef {import("../casa").ContextEvent} ContextEvent
39
38
  * @access private
40
- * @typedef {import('../casa').ContextEvent} ContextEvent
41
39
  */
42
40
 
43
41
  /**
42
+ * @typedef {import("../casa").JourneyContextObject} JourneyContextObject
44
43
  * @access private
45
- * @typedef {import('../casa').JourneyContextObject} JourneyContextObject
46
44
  */
47
45
 
48
46
  /**
47
+ * @typedef {import("express").Request} ExpressRequest
49
48
  * @access private
50
- * @typedef {import('express').Request} ExpressRequest
51
49
  */
52
50
 
53
- export function validateObjectKey(key = '') {
51
+ export function validateObjectKey(key = "") {
54
52
  const keyLower = String.prototype.toLowerCase.call(key);
55
- if (keyLower === 'prototype' || keyLower === '__proto__' || keyLower === 'constructor') {
53
+ if (
54
+ keyLower === "prototype" ||
55
+ keyLower === "__proto__" ||
56
+ keyLower === "constructor"
57
+ ) {
56
58
  throw new SyntaxError(`Invalid object key used, ${key}`);
57
59
  }
58
60
  return String(key);
59
61
  }
60
62
 
61
- /**
62
- * @memberof module:@dwp/govuk-casa
63
- */
63
+ /** @memberof module:@dwp/govuk-casa */
64
64
  export default class JourneyContext {
65
65
  // Private properties
66
66
  #data;
@@ -75,24 +75,20 @@ export default class JourneyContext {
75
75
 
76
76
  #eventListenerPreState;
77
77
 
78
- static DEFAULT_CONTEXT_ID = 'default';
78
+ static DEFAULT_CONTEXT_ID = "default";
79
79
 
80
- /**
81
- * @type {symbol}
82
- */
83
- static ID_GENERATOR_REQ_LOG = Symbol('generatedContextIds');
80
+ /** @type {symbol} */
81
+ static ID_GENERATOR_REQ_LOG = Symbol("generatedContextIds");
84
82
 
85
- /**
86
- * @type {symbol}
87
- */
88
- static ID_GENERATOR_REQ_KEY = Symbol('generateContextId');
83
+ /** @type {symbol} */
84
+ static ID_GENERATOR_REQ_KEY = Symbol("generateContextId");
89
85
 
90
86
  /**
91
87
  * Constructor.
92
88
  *
93
89
  * `data` is the "single source of truth" for all data gathered during the
94
90
  * user's journey. This is referred to as the "canonical data model".
95
- * Page-specific "views" of this data are generated at runtime in order to
91
+ * Page-specific "views" of this data are generated at runtime in order to
96
92
  * populate/validate specific form fields.
97
93
  *
98
94
  * `validation` holds the results of form field validation carried out when
@@ -105,10 +101,11 @@ export default class JourneyContext {
105
101
  * `identity` holds information that helps uniquely identify this context
106
102
  * among a group of contexts stored in the session.
107
103
  *
108
- * @param {Record<string,any>} data Entire journey data.
104
+ * @param {Record<string, any>} data Entire journey data.
109
105
  * @param {object} validation Page errors (indexed by waypoint id).
110
106
  * @param {object} nav Navigation context.
111
- * @param {object} identity Some metadata for identifying this context among others.
107
+ * @param {object} identity Some metadata for identifying this context among
108
+ * others.
112
109
  */
113
110
  constructor(data = {}, validation = {}, nav = {}, identity = {}) {
114
111
  this.#data = data;
@@ -152,7 +149,9 @@ export default class JourneyContext {
152
149
  let dErrors = errors;
153
150
 
154
151
  if (Array.isArray(errors)) {
155
- dErrors = errors.map((e) => (e instanceof ValidationError ? e : new ValidationError(e)));
152
+ dErrors = errors.map((e) =>
153
+ e instanceof ValidationError ? e : new ValidationError(e),
154
+ );
156
155
  }
157
156
 
158
157
  deserialisedValidation[notProto(waypoint)] = dErrors;
@@ -184,18 +183,20 @@ export default class JourneyContext {
184
183
  /**
185
184
  * Get data context for a specific a specific page.
186
185
  *
187
- * @param {string | Page} page Page waypoint ID, or Page object.
186
+ * @param {string | Page} page Page waypoint ID, or Page object.
188
187
  * @returns {object} Page data.
189
188
  * @throws {TypeError} When page is invalid.
190
189
  */
191
190
  getDataForPage(page) {
192
- if (typeof page === 'string') {
191
+ if (typeof page === "string") {
193
192
  return this.#data[validateObjectKey(page)];
194
193
  }
195
194
  if (isPlainObject(page)) {
196
195
  return this.#data[validateObjectKey(page.waypoint)];
197
196
  }
198
- throw new TypeError(`Page must be a string or Page object. Got ${typeof page}`);
197
+ throw new TypeError(
198
+ `Page must be a string or Page object. Got ${typeof page}`,
199
+ );
199
200
  }
200
201
 
201
202
  /**
@@ -227,12 +228,14 @@ export default class JourneyContext {
227
228
  * @throws {TypeError} When page is invalid.
228
229
  */
229
230
  setDataForPage(page, webFormData) {
230
- if (typeof page === 'string') {
231
+ if (typeof page === "string") {
231
232
  this.#data[validateObjectKey(page)] = webFormData;
232
233
  } else if (isPlainObject(page)) {
233
234
  this.#data[validateObjectKey(page.waypoint)] = webFormData;
234
235
  } else {
235
- throw new TypeError(`Page must be a string or Page object. Got ${typeof page}`)
236
+ throw new TypeError(
237
+ `Page must be a string or Page object. Got ${typeof page}`,
238
+ );
236
239
  }
237
240
 
238
241
  return this;
@@ -286,12 +289,14 @@ export default class JourneyContext {
286
289
  */
287
290
  setValidationErrorsForPage(pageId, errors = []) {
288
291
  if (!Array.isArray(errors)) {
289
- throw new SyntaxError(`Errors must be an Array. Received ${Object.prototype.toString.call(errors)}`);
292
+ throw new SyntaxError(
293
+ `Errors must be an Array. Received ${Object.prototype.toString.call(errors)}`,
294
+ );
290
295
  }
291
296
 
292
297
  errors.forEach((error) => {
293
298
  if (!(error instanceof ValidationError)) {
294
- throw new SyntaxError('Field errors must be a ValidationError');
299
+ throw new SyntaxError("Field errors must be a ValidationError");
295
300
  }
296
301
  });
297
302
 
@@ -312,12 +317,13 @@ export default class JourneyContext {
312
317
  }
313
318
 
314
319
  /**
315
- * Same as `getValidationErrorsForPage()`, but the return value is
316
- * an object whose keys are the field names, and values are the list of errors
320
+ * Same as `getValidationErrorsForPage()`, but the return value is an object
321
+ * whose keys are the field names, and values are the list of errors
317
322
  * associated with that particular field.
318
323
  *
319
324
  * @param {string} pageId Page ID.
320
- * @returns {object} Object indexed by field names; values containing list of errors
325
+ * @returns {object} Object indexed by field names; values containing list of
326
+ * errors
321
327
  */
322
328
  getValidationErrorsForPageByField(pageId) {
323
329
  const errors = this.getValidationErrorsForPage(pageId);
@@ -353,7 +359,7 @@ export default class JourneyContext {
353
359
  * @param {string} language Language to set (ISO 639-1 2-letter code).
354
360
  * @returns {JourneyContext} Chain.
355
361
  */
356
- setNavigationLanguage(language = 'en') {
362
+ setNavigationLanguage(language = "en") {
357
363
  this.#nav.language = language;
358
364
  return this;
359
365
  }
@@ -376,7 +382,9 @@ export default class JourneyContext {
376
382
  purge(waypoints = []) {
377
383
  const newData = Object.create(null);
378
384
  const newValidation = Object.create(null);
379
- const toKeep = Object.keys(this.#data).filter((w) => !waypoints.includes(w));
385
+ const toKeep = Object.keys(this.#data).filter(
386
+ (w) => !waypoints.includes(w),
387
+ );
380
388
 
381
389
  // ESLint disabled as `i` is an integer
382
390
  /* eslint-disable security/detect-object-injection */
@@ -405,8 +413,8 @@ export default class JourneyContext {
405
413
  }
406
414
 
407
415
  /**
408
- * Event listeners are transient. They are not stored in session, and generally
409
- * only apply for the current request.
416
+ * Event listeners are transient. They are not stored in session, and
417
+ * generally only apply for the current request.
410
418
  *
411
419
  * They also only act on a fixed snapshot of this context's state, which is
412
420
  * taken at the point of attaching the listeners (in the "data" middleware).
@@ -431,7 +439,7 @@ export default class JourneyContext {
431
439
  * @param {object} params Params
432
440
  * @param {string} params.event Event (waypoint-change | context-change)
433
441
  * @param {object} params.session Session
434
- * @param {ContextEventUserInfo|object} [params.userInfo] Pass-through info
442
+ * @param {ContextEventUserInfo | object} [params.userInfo] Pass-through info
435
443
  * @returns {JourneyContext} Chain
436
444
  */
437
445
  applyEventListeners({ event, session, userInfo }) {
@@ -439,7 +447,9 @@ export default class JourneyContext {
439
447
  return this;
440
448
  }
441
449
 
442
- const previousContext = JourneyContext.fromObject(this.#eventListenerPreState);
450
+ const previousContext = JourneyContext.fromObject(
451
+ this.#eventListenerPreState,
452
+ );
443
453
  const listeners = this.#eventListeners.filter((l) => l.event === event);
444
454
 
445
455
  // ESLint disabled as `listeners[i]` uses an integer key, and the other keys
@@ -453,20 +463,21 @@ export default class JourneyContext {
453
463
  let runHandler = false;
454
464
 
455
465
  if (!waypoint && !field) {
456
- logMessage = 'Calling generic event handler';
466
+ logMessage = "Calling generic event handler";
457
467
  runHandler = true;
458
468
  } else if (waypoint && !field) {
459
469
  logMessage = `Calling waypoint-specific event handler on "${waypoint}"`;
460
- runHandler = previousContext.data?.[waypoint] !== undefined && !isEqual(
461
- this.data?.[waypoint],
462
- previousContext.data?.[waypoint],
463
- );
470
+ runHandler =
471
+ previousContext.data?.[waypoint] !== undefined &&
472
+ !isEqual(this.data?.[waypoint], previousContext.data?.[waypoint]);
464
473
  } else if (waypoint && field) {
465
474
  logMessage = `Calling field-specific event handler on "${waypoint} : ${field}"`;
466
- runHandler = previousContext.data?.[waypoint]?.[field] !== undefined && !isEqual(
467
- this.data?.[waypoint]?.[field],
468
- previousContext.data?.[waypoint]?.[field],
469
- );
475
+ runHandler =
476
+ previousContext.data?.[waypoint]?.[field] !== undefined &&
477
+ !isEqual(
478
+ this.data?.[waypoint]?.[field],
479
+ previousContext.data?.[waypoint]?.[field],
480
+ );
470
481
  }
471
482
 
472
483
  if (runHandler) {
@@ -514,7 +525,7 @@ export default class JourneyContext {
514
525
  */
515
526
  static fromContext(context, req) {
516
527
  if (!(context instanceof JourneyContext)) {
517
- throw new TypeError('Source context must be a JourneyContext');
528
+ throw new TypeError("Source context must be a JourneyContext");
518
529
  }
519
530
 
520
531
  const newContextObj = context.toObject();
@@ -543,14 +554,16 @@ export default class JourneyContext {
543
554
  // being remodelled as an array, we need to convert the "legacy" structure
544
555
  // into an equivalent array.
545
556
  if (isPlainObject(session?.journeyContextList)) {
546
- log.trace('Session context list already initialised as an object (legacy structure). Will convert from object to array.');
557
+ log.trace(
558
+ "Session context list already initialised as an object (legacy structure). Will convert from object to array.",
559
+ );
547
560
  /* eslint-disable-next-line no-param-reassign */
548
561
  session.journeyContextList = Object.entries(session.journeyContextList);
549
562
  }
550
563
 
551
564
  // Initialise new context list in the session
552
- if (!has(session, 'journeyContextList')) {
553
- log.trace('Initialising session with a default journey context list');
565
+ if (!has(session, "journeyContextList")) {
566
+ log.trace("Initialising session with a default journey context list");
554
567
  /* eslint-disable-next-line no-param-reassign */
555
568
  session.journeyContextList = [];
556
569
 
@@ -562,6 +575,7 @@ export default class JourneyContext {
562
575
 
563
576
  /**
564
577
  * Validate the format of a context ID:
578
+ *
565
579
  * - Between 1 and 64 characters
566
580
  * - Contain only the characters a-z, 0-9, -
567
581
  *
@@ -575,18 +589,18 @@ export default class JourneyContext {
575
589
  return JourneyContext.DEFAULT_CONTEXT_ID;
576
590
  }
577
591
 
578
- if (typeof id !== 'string') {
579
- throw new TypeError('Context ID must be a string');
592
+ if (typeof id !== "string") {
593
+ throw new TypeError("Context ID must be a string");
580
594
  } else if (!id.match(/^[a-z0-9-]{1,64}$/)) {
581
- throw new SyntaxError('Context ID is not in the correct format');
595
+ throw new SyntaxError("Context ID is not in the correct format");
582
596
  }
583
597
 
584
598
  return id;
585
599
  }
586
600
 
587
601
  /**
588
- * Generate a new context ID, validate it, and throw if the ID has already been
589
- * generated during this request lifecycle. This may happen if an ID was
602
+ * Generate a new context ID, validate it, and throw if the ID has already
603
+ * been generated during this request lifecycle. This may happen if an ID was
590
604
  * generated, but never used to store a new context in the session. Therefore
591
605
  * it is important for user code to always call `putContext()` before
592
606
  * generating another ID.
@@ -599,8 +613,19 @@ export default class JourneyContext {
599
613
  // Can't generate custom ID when no request object is provided, because the
600
614
  // custom generator function itself exists on that object.
601
615
  if (!req) {
602
- log.warn('Generating a context ID without a given request object. Reverting to uuid().');
603
- return uuid();
616
+ throw new Error("Missing required request object.");
617
+ }
618
+
619
+ // Define a default context ID generator if required
620
+ if (!Object.hasOwn(req, JourneyContext.ID_GENERATOR_REQ_KEY)) {
621
+ log.warn(
622
+ "A context ID generator is not present in the request. Reverting to uuid().",
623
+ );
624
+ Object.defineProperty(req, JourneyContext.ID_GENERATOR_REQ_KEY, {
625
+ value: uuid,
626
+ enumerable: false,
627
+ writable: false,
628
+ });
604
629
  }
605
630
 
606
631
  // Collate a list of context IDs already in use, either from existing
@@ -611,14 +636,18 @@ export default class JourneyContext {
611
636
  .map((c) => c.identity.id)
612
637
  .filter((id) => id !== JourneyContext.DEFAULT_CONTEXT_ID);
613
638
  const inRequestIds = req[JourneyContext.ID_GENERATOR_REQ_LOG] ?? [];
614
- const reservedIds = Array.from(new Set([...inSessionIds, ...inRequestIds]).values());
639
+ const reservedIds = Array.from(
640
+ new Set([...inSessionIds, ...inRequestIds]).values(),
641
+ );
615
642
 
616
643
  // Generate and log the ID
617
644
  const id = JourneyContext.validateContextId(
618
645
  req[JourneyContext.ID_GENERATOR_REQ_KEY].call(null, { req, reservedIds }),
619
646
  );
620
647
  if (reservedIds.includes(id)) {
621
- throw new Error(`Regenerated a context ID, ${String(id)}. It has likely not yet been used to store a new context in session using JourneyContext.putContext().`);
648
+ throw new Error(
649
+ `Regenerated a context ID, ${String(id)}. It has likely not yet been used to store a new context in session using JourneyContext.putContext().`,
650
+ );
622
651
  }
623
652
 
624
653
  if (!req[JourneyContext.ID_GENERATOR_REQ_LOG]) {
@@ -641,7 +670,10 @@ export default class JourneyContext {
641
670
  * @returns {JourneyContext} The default Journey Context
642
671
  */
643
672
  static getDefaultContext(session) {
644
- return JourneyContext.getContextById(session, JourneyContext.DEFAULT_CONTEXT_ID);
673
+ return JourneyContext.getContextById(
674
+ session,
675
+ JourneyContext.DEFAULT_CONTEXT_ID,
676
+ );
645
677
  }
646
678
 
647
679
  /**
@@ -672,9 +704,7 @@ export default class JourneyContext {
672
704
  static getContextByName(session, name) {
673
705
  if (session) {
674
706
  const list = new Map(session?.journeyContextList);
675
- const context = [...list.values()].find(
676
- (c) => (c.identity.name === name),
677
- );
707
+ const context = [...list.values()].find((c) => c.identity.name === name);
678
708
  if (context) {
679
709
  return JourneyContext.fromObject(context);
680
710
  }
@@ -688,14 +718,14 @@ export default class JourneyContext {
688
718
  *
689
719
  * @param {object} session Request session
690
720
  * @param {string} tag Context tag
691
- * @returns {Array<JourneyContext>} The discovered JourneyContext instance
721
+ * @returns {JourneyContext[]} The discovered JourneyContext instance
692
722
  */
693
723
  static getContextsByTag(session, tag) {
694
724
  if (session) {
695
725
  const list = new Map(session?.journeyContextList);
696
- return [...list.values()].filter(
697
- (c) => (c.identity.tags?.includes(tag)),
698
- ).map((c) => (JourneyContext.fromObject(c)));
726
+ return [...list.values()]
727
+ .filter((c) => c.identity.tags?.includes(tag))
728
+ .map((c) => JourneyContext.fromObject(c));
699
729
  }
700
730
 
701
731
  return undefined;
@@ -708,10 +738,10 @@ export default class JourneyContext {
708
738
  * @returns {Array} Array of contexts
709
739
  */
710
740
  static getContexts(session) {
711
- if (has(session, 'journeyContextList')) {
712
- return session.journeyContextList.map(([, contextObj]) => (
713
- JourneyContext.fromObject(contextObj)
714
- ));
741
+ if (has(session, "journeyContextList")) {
742
+ return session.journeyContextList.map(([, contextObj]) =>
743
+ JourneyContext.fromObject(contextObj),
744
+ );
715
745
  }
716
746
 
717
747
  return [];
@@ -723,21 +753,22 @@ export default class JourneyContext {
723
753
  * @param {object} session Request session
724
754
  * @param {JourneyContext} context Context
725
755
  * @param {object} options Options
726
- * @param {ContextEventUserInfo|object} [options.userInfo] Pass-through event info
756
+ * @param {ContextEventUserInfo | object} [options.userInfo] Pass-through
757
+ * event info
727
758
  * @returns {void}
728
759
  * @throws {TypeError} When session is not a valid type, or context has no ID
729
760
  */
730
761
  static putContext(session, context, options = {}) {
731
762
  if (!isObject(session)) {
732
- throw new TypeError('Session must be an object');
763
+ throw new TypeError("Session must be an object");
733
764
  } else if (!(context instanceof JourneyContext)) {
734
- throw new TypeError('Context must be a valid JourneyContext');
765
+ throw new TypeError("Context must be a valid JourneyContext");
735
766
  } else if (context.identity.id === undefined) {
736
- throw new TypeError('Context must have an ID before storing in session');
767
+ throw new TypeError("Context must have an ID before storing in session");
737
768
  }
738
769
 
739
770
  // Initialise the session if necessary
740
- if (!has(session, 'journeyContextList')) {
771
+ if (!has(session, "journeyContextList")) {
741
772
  JourneyContext.initContextStore(session);
742
773
  }
743
774
 
@@ -745,13 +776,13 @@ export default class JourneyContext {
745
776
  const { userInfo = undefined } = options;
746
777
 
747
778
  context.applyEventListeners({
748
- event: 'waypoint-change',
779
+ event: "waypoint-change",
749
780
  session,
750
781
  userInfo,
751
782
  });
752
783
 
753
784
  context.applyEventListeners({
754
- event: 'context-change',
785
+ event: "context-change",
755
786
  session,
756
787
  userInfo,
757
788
  });
@@ -783,7 +814,9 @@ export default class JourneyContext {
783
814
  * @returns {void}
784
815
  */
785
816
  static removeContextById(session, id) {
786
- const index = (session?.journeyContextList ?? []).findIndex(([contextId]) => contextId === id);
817
+ const index = (session?.journeyContextList ?? []).findIndex(
818
+ ([contextId]) => contextId === id,
819
+ );
787
820
  if (index > -1) {
788
821
  session.journeyContextList.splice(index, 1);
789
822
  }
@@ -811,8 +844,8 @@ export default class JourneyContext {
811
844
  * @returns {void}
812
845
  */
813
846
  static removeContextsByTag(session, tag) {
814
- JourneyContext.getContextsByTag(session, tag).forEach(
815
- (c) => JourneyContext.removeContext(session, c),
847
+ JourneyContext.getContextsByTag(session, tag).forEach((c) =>
848
+ JourneyContext.removeContext(session, c),
816
849
  );
817
850
  }
818
851
 
@@ -823,15 +856,17 @@ export default class JourneyContext {
823
856
  * @returns {void}
824
857
  */
825
858
  static removeContexts(session) {
826
- JourneyContext.getContexts(session).forEach((c) => JourneyContext.removeContext(session, c));
859
+ JourneyContext.getContexts(session).forEach((c) =>
860
+ JourneyContext.removeContext(session, c),
861
+ );
827
862
  }
828
863
 
829
864
  /**
830
865
  * Extract the Journey Context referred to in the incoming request.
831
866
  *
832
- * This will look in `req.params`, `req.query` and
833
- * `req.body` for a `contextid` parameter, and use that
834
- * to load the correct Journey Context from the session.
867
+ * This will look in `req.params`, `req.query` and `req.body` for a
868
+ * `contextid` parameter, and use that to load the correct Journey Context
869
+ * from the session.
835
870
  *
836
871
  * @param {ExpressRequest} req ExpressJS incoming request
837
872
  * @returns {JourneyContext} The Journey Context
@@ -840,17 +875,19 @@ export default class JourneyContext {
840
875
  JourneyContext.initContextStore(req.session);
841
876
 
842
877
  let contextId;
843
- if (has(req?.params, 'contextid')) {
844
- log.trace('Context ID found in req.params.contextid');
878
+ if (has(req?.params, "contextid")) {
879
+ log.trace("Context ID found in req.params.contextid");
845
880
  contextId = String(req.params.contextid);
846
- } else if (has(req.query, 'contextid')) {
847
- log.trace('Context ID found in req.query.contextid');
881
+ } else if (has(req.query, "contextid")) {
882
+ log.trace("Context ID found in req.query.contextid");
848
883
  contextId = String(req.query.contextid);
849
- } else if (has(req?.body, 'contextid')) {
850
- log.trace('Context ID found in req.body.contextid');
884
+ } else if (has(req?.body, "contextid")) {
885
+ log.trace("Context ID found in req.body.contextid");
851
886
  contextId = String(req.body.contextid);
852
887
  } else {
853
- log.trace('Context ID not specified or not found; will attempt to use default');
888
+ log.trace(
889
+ "Context ID not specified or not found; will attempt to use default",
890
+ );
854
891
  contextId = JourneyContext.DEFAULT_CONTEXT_ID;
855
892
  }
856
893
 
@@ -858,13 +895,16 @@ export default class JourneyContext {
858
895
  contextId = JourneyContext.validateContextId(contextId);
859
896
  const context = JourneyContext.getContextById(req.session, contextId);
860
897
  if (!context) {
861
- throw (new Error(`Could not find a context with id, ${contextId}`));
898
+ throw new Error(`Could not find a context with id, ${contextId}`);
862
899
  }
863
900
  return context;
864
901
  } catch (err) {
865
902
  log.debug(err.message);
866
- log.trace('Falling back to default context');
867
- return JourneyContext.getContextById(req.session, JourneyContext.DEFAULT_CONTEXT_ID);
903
+ log.trace("Falling back to default context");
904
+ return JourneyContext.getContextById(
905
+ req.session,
906
+ JourneyContext.DEFAULT_CONTEXT_ID,
907
+ );
868
908
  }
869
909
  }
870
910
  }