@pdtf/schemas 3.6.0-2 → 3.6.0-20

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 (35) hide show
  1. package/.claude/settings.local.json +17 -2
  2. package/docs/sef25-extensions-ui-spec.md +146 -0
  3. package/index.js +97 -58
  4. package/package.json +3 -2
  5. package/src/schemas/v3/combined.json +6997 -1310
  6. package/src/schemas/v3/compactSkeleton.txt +352 -10
  7. package/src/schemas/v3/overlays/baspi4.json +161 -17
  8. package/src/schemas/v3/overlays/baspi5.json +164 -17
  9. package/src/schemas/v3/overlays/extensions/jk.json +24 -6
  10. package/src/schemas/v3/overlays/extensions/pc.json +28 -0
  11. package/src/schemas/v3/overlays/extensions/ph.json +191 -0
  12. package/src/schemas/v3/overlays/extensions/sc.json +77 -0
  13. package/src/schemas/v3/overlays/fme1.json +16 -14
  14. package/src/schemas/v3/overlays/lpe1.json +60 -30
  15. package/src/schemas/v3/overlays/nts.json +68 -1
  16. package/src/schemas/v3/overlays/nts2.json +68 -1
  17. package/src/schemas/v3/overlays/ntsl.json +68 -1
  18. package/src/schemas/v3/overlays/ntsl2.json +68 -1
  19. package/src/schemas/v3/overlays/piq.json +38 -1
  20. package/src/schemas/v3/overlays/rds.json +118 -6
  21. package/src/schemas/v3/overlays/ta6.json +40 -14
  22. package/src/schemas/v3/overlays/ta6ed6.json +6436 -0
  23. package/src/schemas/v3/overlays/ta7.json +70 -6
  24. package/src/schemas/v3/overlays/ta7ed5.json +1539 -0
  25. package/src/schemas/v3/pdtf-transaction.json +4077 -611
  26. package/src/schemas/v3/skeleton.json +491 -34
  27. package/src/tests/v3/buyerCircumstances.test.js +543 -0
  28. package/src/tests/v3/extensionOverlays.test.js +433 -0
  29. package/src/tests/v3/offers.test.js +37 -40
  30. package/src/tests/v3/patternProperties.test.js +149 -30
  31. package/src/tests/v3/ta6ed6.test.js +2356 -0
  32. package/src/tests/v3/ta7ed5.test.js +567 -0
  33. package/src/utils/extractExtensionOverlays.js +103 -31
  34. package/src/utils/extractOverlay.js +87 -23
  35. package/src/utils/pathSkeleton.js +1 -1
@@ -15,7 +15,6 @@
15
15
  "Bash(git commit:*)",
16
16
  "Bash(git push:*)",
17
17
  "Bash(git fetch:*)",
18
- "Bash(git checkout:*)",
19
18
  "Bash(gh pr view:*)",
20
19
  "WebFetch(domain:github.com)",
21
20
  "Bash(tree:*)",
@@ -35,7 +34,23 @@
35
34
  "Bash(base64:*)",
36
35
  "Bash(gh issue view:*)",
37
36
  "Read(//Users/ed/.claude/**)",
38
- "Bash(npm init:*)"
37
+ "Bash(npm init:*)",
38
+ "Bash(do echo \"=== printWidth $w ===\")",
39
+ "Bash(pdftoppm:*)",
40
+ "Bash(pdftotext:*)",
41
+ "Bash(/tmp/inspect2.js:*)",
42
+ "Bash(/tmp/inspect3.js:*)",
43
+ "Bash(/tmp/inspect4.js:*)",
44
+ "Bash(/tmp/inspect5.js:*)",
45
+ "Bash(/tmp/inspect6.js:*)",
46
+ "Bash(/tmp/final_inspect.js:*)",
47
+ "Bash(file:*)",
48
+ "Bash(gh pr create:*)",
49
+ "Bash(claude --version)",
50
+ "Bash(gh pr list --head ta6ed6 --json number,title,url)",
51
+ "Bash(gh pr:*)",
52
+ "Bash(sort -t'.' -k1,1n -k2,2n -k3,3n -k4,4n)",
53
+ "Bash(wc -l /Users/ed/Code/schemas/src/schemas/v3/combined.json /Users/ed/Code/schemas/src/schemas/v3/overlays/*.json)"
39
54
  ],
40
55
  "deny": []
41
56
  }
@@ -0,0 +1,146 @@
1
+ # SEF25 Extension Overlays — Front-End UI Spec
2
+
3
+ ## Overview
4
+
5
+ Three new extension overlays add follow-up questions to existing form sections. Each overlay is loaded by passing its key to `getTransactionSchema(schemaId, overlays)`.
6
+
7
+ | Overlay Key | Name | Section | Fields Added |
8
+ |-------------|------|---------|-------------|
9
+ | `sc` | Supply Costs | Water & Drainage | 2 cost fields (private water, private sewerage) |
10
+ | `pc` | Parking Permit Cost | Parking | 1 frequency field |
11
+ | `ph` | Property Hazards | Specialist Issues | 4 Yes/No + details fields |
12
+
13
+ All fields use `sef25Ref` for reference codes.
14
+
15
+ ---
16
+
17
+ ## 1. Supply Costs (`sc`)
18
+
19
+ ### 1a. Private Water Cost
20
+
21
+ **Trigger:** Show when `propertyPack.waterAndDrainage.water.mainsWater.yesNo` = `"No"`
22
+
23
+ **Path:** `propertyPack.waterAndDrainage.water.mainsWater.associatedCost`
24
+
25
+ **UI:** Render alongside the existing "How is water supplied?" details field.
26
+
27
+ | Field | Path (relative) | Type | Validation | UI Element |
28
+ |-------|-----------------|------|------------|------------|
29
+ | Cost (£) | `associatedCost.amount` | `number` | Required | Numeric input |
30
+ | Payment frequency | `associatedCost.frequency` | `string` | Required, enum | Dropdown |
31
+
32
+ **Frequency enum values:** `"Per month"`, `"Per year"`
33
+
34
+ **Ref:** `U1.1` (amount: `U1.1.1`, frequency: `U1.1.2`)
35
+
36
+ ### 1b. Private Sewerage Cost
37
+
38
+ **Trigger:** Show when `propertyPack.waterAndDrainage.drainage.mainsFoulDrainage.yesNo` = `"No"` or `"Not known"`
39
+
40
+ **Path:** `propertyPack.waterAndDrainage.drainage.mainsFoulDrainage.associatedCost`
41
+
42
+ **UI:** Render alongside the existing off-mains drainage system questions.
43
+
44
+ | Field | Path (relative) | Type | Validation | UI Element |
45
+ |-------|-----------------|------|------------|------------|
46
+ | Cost (£) | `associatedCost.amount` | `number` | Required | Numeric input |
47
+ | Payment frequency | `associatedCost.frequency` | `string` | Required, enum | Dropdown |
48
+
49
+ **Frequency enum values:** `"Per month"`, `"Per year"`
50
+
51
+ **Ref:** `U1.2` (amount: `U1.2.1`, frequency: `U1.2.2`)
52
+
53
+ ---
54
+
55
+ ## 2. Parking Permit Cost (`pc`)
56
+
57
+ **Trigger:** Show when `propertyPack.parking.controlledParking.yesNo` = `"Yes"`
58
+
59
+ **Path:** `propertyPack.parking.controlledParking.costOfPermitFrequency`
60
+
61
+ **UI:** Render alongside the existing cost of permit field (previously titled "Annual cost of permit (£)", now retitled to "Cost of permit (£)"). The frequency dropdown clarifies whether the cost amount is monthly or annual.
62
+
63
+ | Field | Path (relative) | Type | Validation | UI Element |
64
+ |-------|-----------------|------|------------|------------|
65
+ | Payment frequency | `costOfPermitFrequency` | `string` | Enum | Dropdown |
66
+
67
+ **Frequency enum values:** `"Per month"`, `"Per year"`
68
+
69
+ **Note:** The existing `annualCostOfPermit` numeric field remains unchanged. The new frequency field sits beside it to indicate whether the entered amount is per month or per year.
70
+
71
+ **Ref:** `P1.1`
72
+
73
+ ---
74
+
75
+ ## 3. Property Hazards (`ph`)
76
+
77
+ **Trigger:** Always shown (no conditional trigger — these are standalone questions within Specialist Issues).
78
+
79
+ **Path prefix:** `propertyPack.specialistIssues`
80
+
81
+ All 4 fields follow the same pattern:
82
+
83
+ - **Initial question:** Yes/No radio/toggle
84
+ - **If "Yes":** Show a required free-text "Give details" field
85
+
86
+ ### Fields
87
+
88
+ | Property | Title | sef25Ref |
89
+ |----------|-------|----------|
90
+ | `wellsDitchesShaft` | Are there any wells, ditches, or shafts at the property? | H1.1 |
91
+ | `damagedOrExposedElectrics` | Are there any damaged or exposed electrics at the property? | H1.2 |
92
+ | `damageToFlooringOrStaircases` | Is there any damage to flooring and/or staircases? | H1.3 |
93
+ | `knownAreasInPoorCondition` | Are there any known areas in poor condition (internal or external)? | H1.4 |
94
+
95
+ ### Data shape per field
96
+
97
+ ```json
98
+ // When "No":
99
+ { "yesNo": "No" }
100
+
101
+ // When "Yes":
102
+ { "yesNo": "Yes", "details": "Free text description..." }
103
+ ```
104
+
105
+ ### Validation
106
+
107
+ - `yesNo` is required (enum: `["Yes", "No"]`)
108
+ - When `yesNo` = `"Yes"`, `details` is required (string, minLength: 1)
109
+ - When `yesNo` = `"No"`, `details` should not be shown or submitted
110
+
111
+ ### UI pattern
112
+
113
+ These follow the identical pattern to existing specialist issues (dry rot, asbestos, subsidence, health & safety). Render them the same way — group them under "Hazards and Known Issues" within the Specialist Issues section.
114
+
115
+ ---
116
+
117
+ ## Loading the Extensions
118
+
119
+ ```js
120
+ import { getTransactionSchema, getValidator } from "@pdtf/schemas";
121
+
122
+ const schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
123
+
124
+ // Load with one or more SEF25 extensions
125
+ const schema = getTransactionSchema(schemaId, ["sc", "pc", "ph"]);
126
+
127
+ // Or combine with other overlays
128
+ const schema = getTransactionSchema(schemaId, ["baspiV5", "sc", "pc", "ph"]);
129
+
130
+ // Validate data against the merged schema
131
+ const validator = getValidator(schemaId, ["sc", "pc", "ph"]);
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Summary of Trigger Conditions
137
+
138
+ | Field | Visible when... |
139
+ |-------|----------------|
140
+ | Water cost | `mainsWater.yesNo` = `"No"` |
141
+ | Sewerage cost | `mainsFoulDrainage.yesNo` = `"No"` or `"Not known"` |
142
+ | Parking permit frequency | `controlledParking.yesNo` = `"Yes"` |
143
+ | Wells/ditches/shaft | Always (within Specialist Issues) |
144
+ | Damaged electrics | Always (within Specialist Issues) |
145
+ | Flooring/staircase damage | Always (within Specialist Issues) |
146
+ | Areas in poor condition | Always (within Specialist Issues) |
package/index.js CHANGED
@@ -18,7 +18,7 @@ const cacheStats = {
18
18
  hits: 0,
19
19
  misses: 0,
20
20
  totalQueries: 0,
21
- cacheStartTime: Date.now()
21
+ cacheStartTime: Date.now(),
22
22
  };
23
23
 
24
24
  const verifiedClaimsSchema = require("./src/schemas/verifiedClaims/pdtf-verified-claims.json");
@@ -52,6 +52,11 @@ const extensionOverlays = {
52
52
 
53
53
  // Transaction Extensions
54
54
  oc: require("./src/schemas/v3/overlays/extensions/oc.json"),
55
+
56
+ // SEF25 Extensions
57
+ sc: require("./src/schemas/v3/overlays/extensions/sc.json"),
58
+ pc: require("./src/schemas/v3/overlays/extensions/pc.json"),
59
+ ph: require("./src/schemas/v3/overlays/extensions/ph.json"),
55
60
  };
56
61
 
57
62
  const overlaysMap = {
@@ -75,7 +80,9 @@ const overlaysMap = {
75
80
  baspiV4: require("./src/schemas/v3/overlays/baspi4.json"),
76
81
  baspiV5: require("./src/schemas/v3/overlays/baspi5.json"),
77
82
  ta6ed4: require("./src/schemas/v3/overlays/ta6.json"),
83
+ ta6ed6: require("./src/schemas/v3/overlays/ta6ed6.json"),
78
84
  ta7ed3: require("./src/schemas/v3/overlays/ta7.json"),
85
+ ta7ed5: require("./src/schemas/v3/overlays/ta7ed5.json"),
79
86
  ta10ed3: require("./src/schemas/v3/overlays/ta10.json"),
80
87
  lpe1ed4: require("./src/schemas/v3/overlays/lpe1.json"),
81
88
  fme1ed2: require("./src/schemas/v3/overlays/fme1.json"),
@@ -117,21 +124,27 @@ const arrayMerge = (target, source, options) => {
117
124
  // Detect if this is a required array by checking the parent key
118
125
  // Required arrays are property names (camelCase, no spaces)
119
126
  // Enum arrays are values (can contain spaces, capitals, etc.)
120
- const isRequiredArray = target.every(
121
- (item) =>
122
- typeof item === "string" &&
123
- item.length > 0 &&
124
- // Required field names are typically camelCase without spaces
125
- !item.includes(" ") &&
126
- // First character is usually lowercase for field names
127
- (item[0] === item[0].toLowerCase() || item.startsWith("is") || item.startsWith("has"))
128
- ) && source.every(
129
- (item) =>
130
- typeof item === "string" &&
131
- item.length > 0 &&
132
- !item.includes(" ") &&
133
- (item[0] === item[0].toLowerCase() || item.startsWith("is") || item.startsWith("has"))
134
- );
127
+ const isRequiredArray =
128
+ target.every(
129
+ (item) =>
130
+ typeof item === "string" &&
131
+ item.length > 0 &&
132
+ // Required field names are typically camelCase without spaces
133
+ !item.includes(" ") &&
134
+ // First character is usually lowercase for field names
135
+ (item[0] === item[0].toLowerCase() ||
136
+ item.startsWith("is") ||
137
+ item.startsWith("has")),
138
+ ) &&
139
+ source.every(
140
+ (item) =>
141
+ typeof item === "string" &&
142
+ item.length > 0 &&
143
+ !item.includes(" ") &&
144
+ (item[0] === item[0].toLowerCase() ||
145
+ item.startsWith("is") ||
146
+ item.startsWith("has")),
147
+ );
135
148
 
136
149
  if (isRequiredArray) {
137
150
  // Combine required arrays
@@ -176,7 +189,7 @@ const arrayMerge = (target, source, options) => {
176
189
 
177
190
  const getTransactionSchema = (
178
191
  schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json",
179
- overlays
192
+ overlays,
180
193
  ) => {
181
194
  const sourceSchema = transactionSchemas[schemaId];
182
195
  if (!overlays || overlays.length < 1) return sourceSchema;
@@ -184,7 +197,7 @@ const getTransactionSchema = (
184
197
  let mergedSchema = JSON.parse(JSON.stringify(sourceSchema)); // Deep copy
185
198
  overlays.forEach((overlay) => {
186
199
  const overlaySchema = JSON.parse(
187
- JSON.stringify(overlaysMap[schemaId][overlay] || {})
200
+ JSON.stringify(overlaysMap[schemaId][overlay] || {}),
188
201
  ); // Deep copy
189
202
  mergedSchema = merge(mergedSchema, overlaySchema, { arrayMerge });
190
203
  });
@@ -211,7 +224,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
211
224
  const sourceSchema = getTransactionSchema(schemaId, overlays);
212
225
  const pathArray = path.split("/").slice(1);
213
226
  let subSchema = sourceSchema;
214
-
227
+
215
228
  if (pathArray.length >= 1) {
216
229
  subSchema = pathArray.reduce((schema, pathElement) => {
217
230
  if (!schema) return undefined;
@@ -233,7 +246,10 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
233
246
  }
234
247
  } catch (e) {
235
248
  // Invalid regex pattern in schema - skip it
236
- console.warn(`Invalid regex pattern in patternProperties: ${pattern}`, e);
249
+ console.warn(
250
+ `Invalid regex pattern in patternProperties: ${pattern}`,
251
+ e,
252
+ );
237
253
  }
238
254
  }
239
255
  }
@@ -246,6 +262,18 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
246
262
  matchingProperty = aOneOf.items;
247
263
  } else if (aOneOf.properties?.[pathElement]) {
248
264
  matchingProperty = aOneOf.properties?.[pathElement];
265
+ } else if (aOneOf.patternProperties) {
266
+ for (const pattern in aOneOf.patternProperties) {
267
+ try {
268
+ const regex = new RegExp(pattern);
269
+ if (regex.test(pathElement)) {
270
+ matchingProperty = aOneOf.patternProperties[pattern];
271
+ break;
272
+ }
273
+ } catch (e) {
274
+ // Invalid regex pattern - skip
275
+ }
276
+ }
249
277
  }
250
278
  });
251
279
  if (matchingProperty) return matchingProperty;
@@ -258,7 +286,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
258
286
  // Add schema to AJV and get validator
259
287
  // Only add valid schemas to AJV
260
288
  let validator;
261
- if (subSchema && typeof subSchema === 'object') {
289
+ if (subSchema && typeof subSchema === "object") {
262
290
  // Check if schema already exists in AJV to avoid duplicates
263
291
  validator = ajv.getSchema(cacheKey);
264
292
  if (!validator) {
@@ -275,7 +303,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
275
303
  subSchema,
276
304
  validator,
277
305
  cacheKey,
278
- createdAt: Date.now()
306
+ createdAt: Date.now(),
279
307
  };
280
308
  schemaCache.set(cacheKey, cached);
281
309
  } else {
@@ -341,13 +369,13 @@ const getTitleAtPath = (schema, path, rootPath = path) => {
341
369
  if (oneOfs) {
342
370
  // only single dependency discriminator, oneOf keyword is supported
343
371
  const matchingOneOf = oneOfs.find(
344
- (oneOf) => oneOf["properties"][propertyName]
372
+ (oneOf) => oneOf["properties"][propertyName],
345
373
  );
346
374
  if (matchingOneOf)
347
375
  return getTitleAtPath(
348
376
  matchingOneOf["properties"][propertyName],
349
377
  subPath,
350
- rootPath
378
+ rootPath,
351
379
  );
352
380
  }
353
381
  };
@@ -380,7 +408,7 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
380
408
  }
381
409
  } else {
382
410
  validationErrorsArr.push(
383
- `Path ${path} is not a valid PDTF schema path`
411
+ `Path ${path} is not a valid PDTF schema path`,
384
412
  );
385
413
  }
386
414
  }
@@ -392,8 +420,11 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
392
420
  // Cache management functions
393
421
  const getCacheStats = () => {
394
422
  const runtime = Date.now() - cacheStats.cacheStartTime;
395
- const hitRate = cacheStats.totalQueries > 0 ? (cacheStats.hits / cacheStats.totalQueries * 100) : 0;
396
-
423
+ const hitRate =
424
+ cacheStats.totalQueries > 0
425
+ ? (cacheStats.hits / cacheStats.totalQueries) * 100
426
+ : 0;
427
+
397
428
  return {
398
429
  totalEntries: schemaCache.size,
399
430
  hits: cacheStats.hits,
@@ -405,8 +436,8 @@ const getCacheStats = () => {
405
436
  memoryUsage: {
406
437
  entriesCount: schemaCache.size,
407
438
  // Rough estimate of memory usage per entry
408
- estimatedSizeKB: Math.round((schemaCache.size * 2) / 1024 * 100) / 100 // Rough estimate
409
- }
439
+ estimatedSizeKB: Math.round(((schemaCache.size * 2) / 1024) * 100) / 100, // Rough estimate
440
+ },
410
441
  };
411
442
  };
412
443
 
@@ -416,15 +447,17 @@ const getDetailedCacheStats = () => {
416
447
  key,
417
448
  createdAt: value.createdAt,
418
449
  age: Date.now() - value.createdAt,
419
- hasValidator: typeof value.validator === 'function',
420
- hasSubSchema: value.subSchema !== undefined
450
+ hasValidator: typeof value.validator === "function",
451
+ hasSubSchema: value.subSchema !== undefined,
421
452
  }));
422
453
 
423
454
  return {
424
455
  ...baseStats,
425
456
  entries: entries.sort((a, b) => b.createdAt - a.createdAt), // Most recent first
426
- oldestEntry: entries.length > 0 ? Math.max(...entries.map(e => e.age)) : 0,
427
- newestEntry: entries.length > 0 ? Math.min(...entries.map(e => e.age)) : 0
457
+ oldestEntry:
458
+ entries.length > 0 ? Math.max(...entries.map((e) => e.age)) : 0,
459
+ newestEntry:
460
+ entries.length > 0 ? Math.min(...entries.map((e) => e.age)) : 0,
428
461
  };
429
462
  };
430
463
 
@@ -437,17 +470,17 @@ const clearSchemaCache = (pattern) => {
437
470
  keysToDelete.push(key);
438
471
  }
439
472
  });
440
- keysToDelete.forEach(key => schemaCache.delete(key));
441
-
473
+ keysToDelete.forEach((key) => schemaCache.delete(key));
474
+
442
475
  // Also remove matching schemas from AJV
443
- keysToDelete.forEach(key => {
476
+ keysToDelete.forEach((key) => {
444
477
  try {
445
478
  ajv.removeSchema(key);
446
479
  } catch (e) {
447
480
  // Schema might not exist in AJV, ignore
448
481
  }
449
482
  });
450
-
483
+
451
484
  return keysToDelete.length;
452
485
  } else {
453
486
  // Clear all cache
@@ -458,20 +491,24 @@ const clearSchemaCache = (pattern) => {
458
491
  cacheStats.misses = 0;
459
492
  cacheStats.totalQueries = 0;
460
493
  cacheStats.cacheStartTime = Date.now();
461
-
494
+
462
495
  // Also clear AJV's internal cache to prevent duplicates
463
496
  ajv.removeSchema();
464
-
497
+
465
498
  return entriesCleared;
466
499
  }
467
500
  };
468
501
 
469
- const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json", overlaysList = []) => {
502
+ const warmupCache = (
503
+ paths,
504
+ schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json",
505
+ overlaysList = [],
506
+ ) => {
470
507
  const warmupStats = {
471
508
  totalAttempted: 0,
472
509
  successful: 0,
473
510
  failed: 0,
474
- errors: []
511
+ errors: [],
475
512
  };
476
513
 
477
514
  // Default common paths if none provided
@@ -483,7 +520,7 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
483
520
  "/propertyPack/valuations",
484
521
  "/propertyPack/waterAndDrainage",
485
522
  "/propertyPack/ownership",
486
- "/propertyPack/notices"
523
+ "/propertyPack/notices",
487
524
  ];
488
525
 
489
526
  // Default common overlays if none provided
@@ -492,14 +529,15 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
492
529
  ["baspiV5"],
493
530
  ["ta6ed4"],
494
531
  ["nts2023"],
495
- ["baspiV5", "ta6ed4"]
532
+ ["baspiV5", "ta6ed4"],
496
533
  ];
497
534
 
498
535
  const pathsToWarm = paths || defaultPaths;
499
- const overlaysToWarm = overlaysList.length > 0 ? overlaysList : defaultOverlays;
536
+ const overlaysToWarm =
537
+ overlaysList.length > 0 ? overlaysList : defaultOverlays;
500
538
 
501
- pathsToWarm.forEach(path => {
502
- overlaysToWarm.forEach(overlays => {
539
+ pathsToWarm.forEach((path) => {
540
+ overlaysToWarm.forEach((overlays) => {
503
541
  warmupStats.totalAttempted++;
504
542
  try {
505
543
  // This will populate the cache
@@ -511,7 +549,7 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
511
549
  warmupStats.errors.push({
512
550
  path,
513
551
  overlays,
514
- error: error.message
552
+ error: error.message,
515
553
  });
516
554
  }
517
555
  });
@@ -523,14 +561,14 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
523
561
  const pruneCacheByAge = (maxAgeMs) => {
524
562
  const now = Date.now();
525
563
  const keysToDelete = [];
526
-
564
+
527
565
  schemaCache.forEach((value, key) => {
528
566
  if (now - value.createdAt > maxAgeMs) {
529
567
  keysToDelete.push(key);
530
568
  }
531
569
  });
532
-
533
- keysToDelete.forEach(key => {
570
+
571
+ keysToDelete.forEach((key) => {
534
572
  schemaCache.delete(key);
535
573
  try {
536
574
  ajv.removeSchema(key);
@@ -538,21 +576,22 @@ const pruneCacheByAge = (maxAgeMs) => {
538
576
  // Schema might not exist in AJV, ignore
539
577
  }
540
578
  });
541
-
579
+
542
580
  return keysToDelete.length;
543
581
  };
544
582
 
545
583
  const setCacheMaxSize = (maxSize) => {
546
584
  if (schemaCache.size <= maxSize) return 0;
547
-
585
+
548
586
  // Get entries sorted by creation time (oldest first)
549
- const entries = Array.from(schemaCache.entries())
550
- .sort((a, b) => a[1].createdAt - b[1].createdAt);
551
-
587
+ const entries = Array.from(schemaCache.entries()).sort(
588
+ (a, b) => a[1].createdAt - b[1].createdAt,
589
+ );
590
+
552
591
  const toRemove = schemaCache.size - maxSize;
553
592
  const keysToDelete = entries.slice(0, toRemove).map(([key]) => key);
554
-
555
- keysToDelete.forEach(key => {
593
+
594
+ keysToDelete.forEach((key) => {
556
595
  schemaCache.delete(key);
557
596
  try {
558
597
  ajv.removeSchema(key);
@@ -560,7 +599,7 @@ const setCacheMaxSize = (maxSize) => {
560
599
  // Schema might not exist in AJV, ignore
561
600
  }
562
601
  });
563
-
602
+
564
603
  return keysToDelete.length;
565
604
  };
566
605
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pdtf/schemas",
3
- "version": "3.6.0-2",
3
+ "version": "3.6.0-20",
4
4
  "description": "Property Data Trust Framework Schemas and Utilities",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -8,7 +8,8 @@
8
8
  },
9
9
  "scripts": {
10
10
  "test": "jest",
11
- "test:watch": "jest --watch"
11
+ "test:watch": "jest --watch",
12
+ "extract-overlays": "cd src/utils && node extractOverlay.js"
12
13
  },
13
14
  "repository": {
14
15
  "type": "git",