@pdtf/schemas 3.4.1-1 → 3.4.1-3

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.
@@ -8,7 +8,6 @@ const {
8
8
  getValidator,
9
9
  getSubschemaValidator,
10
10
  getTitleAtPath,
11
- overlaysMap,
12
11
  } = require("../../../index.js");
13
12
 
14
13
  const exampleTransaction = require("../../examples/v3/exampleTransaction.json");
@@ -33,14 +32,12 @@ test("sample is valid BASPI", () => {
33
32
  expect(testSchema.properties.propertyPack.baspi4Ref).toEqual("0");
34
33
  const validator = getValidator(schemaId, ["baspiV4"]);
35
34
  const isValid = validator(exampleTransaction);
36
- if (!isValid) console.log(validator.errors);
37
35
  expect(isValid).toBe(true);
38
36
  });
39
37
 
40
38
  test("sample is valid NTS", () => {
41
39
  const validator = getValidator(schemaId, ["nts2023"]);
42
40
  const isValid = validator(exampleTransaction);
43
- if (!isValid) console.log(validator.errors);
44
41
  expect(isValid).toBe(true);
45
42
  });
46
43
 
@@ -102,7 +99,6 @@ test("sample is valid NTSL if we change it accordingly", () => {
102
99
  delete clonedExampleTransaction.propertyPack.priceInformation;
103
100
  delete clonedExampleTransaction.propertyPack.ownership;
104
101
  const isValid = validator(clonedExampleTransaction);
105
- if (!isValid) console.log(validator.errors);
106
102
  expect(isValid).toBe(true);
107
103
  });
108
104
 
@@ -452,227 +448,3 @@ test("validates a valid contract", () => {
452
448
  const isValid = validator(data);
453
449
  expect(isValid).toBe(true);
454
450
  });
455
-
456
- test("overlay as key and as object produce identical results", () => {
457
- // Get the NTS overlay object directly from the overlays map
458
- const ntsOverlayObject = { ...overlaysMap[schemaId]["nts2023"] };
459
- // Remove the $id to prevent schema registration conflicts
460
- ntsOverlayObject.$id =
461
- "https://trust.propdata.org.uk/schemas/v3/overlays/nts2024.json";
462
-
463
- // Get schema using the key
464
- const schemaWithKey = getTransactionSchema(schemaId, ["nts2023"]);
465
-
466
- // Get schema using the object
467
- const schemaWithObject = getTransactionSchema(schemaId, [ntsOverlayObject]);
468
-
469
- // Create copies without $id for comparison
470
- const schemaWithKeyNoId = { ...schemaWithKey };
471
- const schemaWithObjectNoId = { ...schemaWithObject };
472
- delete schemaWithKeyNoId.$id;
473
- delete schemaWithObjectNoId.$id;
474
-
475
- // Deep compare the results (excluding $id)
476
- expect(schemaWithObjectNoId).toEqual(schemaWithKeyNoId);
477
-
478
- // Test that validation produces the same errors
479
- const validatorWithKey = getValidator(schemaId, ["nts2023"]);
480
- const validatorWithObject = getValidator(schemaId, [ntsOverlayObject]);
481
-
482
- validatorWithKey(exampleTransaction);
483
- validatorWithObject(exampleTransaction);
484
-
485
- // Compare validation errors
486
- expect(validatorWithObject.errors).toEqual(validatorWithKey.errors);
487
- });
488
-
489
- test("correctly merges custom Japanese Knotweed overlay with base schema", () => {
490
- // Load the custom overlay
491
- const customOverlay = require("../../examples/v3/exampleCustomOverlay.json");
492
-
493
- // Get base schema and schema with overlay
494
- const baseSchema = getTransactionSchema(schemaId);
495
- const schemaWithOverlay = getTransactionSchema(schemaId, [customOverlay]);
496
-
497
- // Verify that other properties in propertyPack are preserved
498
- expect(
499
- Object.keys(schemaWithOverlay.properties.propertyPack.properties)
500
- ).toEqual(Object.keys(baseSchema.properties.propertyPack.properties));
501
-
502
- // Verify that other properties in specialistIssues are preserved
503
- const baseSpecialistIssues =
504
- baseSchema.properties.propertyPack.properties.specialistIssues;
505
- const overlaidSpecialistIssues =
506
- schemaWithOverlay.properties.propertyPack.properties.specialistIssues;
507
- expect(Object.keys(overlaidSpecialistIssues.properties)).toEqual(
508
- Object.keys(baseSpecialistIssues.properties)
509
- );
510
-
511
- // Verify that Japanese Knotweed section is updated
512
- const japaneseKnotweed = overlaidSpecialistIssues.properties.japaneseKnotweed;
513
- expect(japaneseKnotweed.title).toBe(
514
- "Is the property affected by Japanese knotweed?"
515
- );
516
- expect(japaneseKnotweed.required).toEqual(["yesNo"]);
517
-
518
- // Verify the oneOf conditions are updated
519
- expect(japaneseKnotweed.oneOf).toHaveLength(2);
520
- expect(japaneseKnotweed.oneOf[0].properties.yesNo.enum).toEqual([
521
- "No",
522
- "Not known",
523
- ]);
524
- expect(japaneseKnotweed.oneOf[1].properties.yesNo.enum).toEqual(["Yes"]);
525
- expect(japaneseKnotweed.oneOf[1].required).toEqual([
526
- "managementPlanInPlace",
527
- "attachments",
528
- ]);
529
- });
530
-
531
- test("correctly merges multiple overlays using both key and object references", () => {
532
- // Load the custom overlay
533
- const customOverlay = require("../../examples/v3/exampleCustomOverlay.json");
534
-
535
- // Get schema with both overlays
536
- const schemaWithOverlays = getTransactionSchema(schemaId, [
537
- "nts2023",
538
- customOverlay,
539
- ]);
540
-
541
- // Get individual overlay schemas for comparison
542
- const ntsSchema = getTransactionSchema(schemaId, ["nts2023"]);
543
- const customSchema = getTransactionSchema(schemaId, [customOverlay]);
544
-
545
- // Verify propertyPack structure is preserved
546
- const propertyPack = schemaWithOverlays.properties.propertyPack;
547
- expect(Object.keys(propertyPack.properties)).toEqual(
548
- expect.arrayContaining(
549
- Object.keys(ntsSchema.properties.propertyPack.properties)
550
- )
551
- );
552
-
553
- // Verify Japanese Knotweed changes from custom overlay are present
554
- const japaneseKnotweed =
555
- propertyPack.properties.specialistIssues.properties.japaneseKnotweed;
556
- expect(japaneseKnotweed.title).toBe(
557
- "Is the property affected by Japanese knotweed?"
558
- );
559
- expect(japaneseKnotweed.oneOf[1].required).toEqual([
560
- "managementPlanInPlace",
561
- "attachments",
562
- ]);
563
-
564
- // Verify customRef properties are correctly merged
565
- expect(propertyPack.customRef).toBe("JKW");
566
- expect(propertyPack.properties.specialistIssues.customRef).toBe("JKW.7");
567
- expect(japaneseKnotweed.customRef).toBe("JKW.7.8");
568
- expect(
569
- japaneseKnotweed.oneOf[1].properties.managementPlanInPlace.customRef
570
- ).toBe("JKW.7.8.2");
571
- expect(japaneseKnotweed.oneOf[1].properties.attachments.customRef).toBe(
572
- "JKW.7.8.3"
573
- );
574
- expect(japaneseKnotweed.properties.yesNo.customRef).toBe("JKW.7.8.1");
575
-
576
- // Verify NTS specific changes are present (e.g., simplified enums)
577
- const mainsElectricity =
578
- propertyPack.properties.electricity.properties.mainsElectricity;
579
- expect(mainsElectricity.properties.yesNo.enum).toEqual(["Yes", "No"]);
580
-
581
- // Verify required fields from both overlays are merged
582
- expect(propertyPack.required).toEqual(
583
- expect.arrayContaining([
584
- ...ntsSchema.properties.propertyPack.required,
585
- ...customSchema.properties.propertyPack.required,
586
- ])
587
- );
588
- });
589
-
590
- test("correctly merges NTS and both custom overlays with proper required fields", () => {
591
- // Load both custom overlays
592
- const japaneseKnoweedOverlay = require("../../examples/v3/exampleCustomOverlay.json");
593
- const managingAgentOverlay = require("../../examples/v3/exampleCustomOverlay2.json");
594
-
595
- // Get schema with all overlays
596
- const schemaWithOverlays = getTransactionSchema(schemaId, [
597
- "nts2023",
598
- japaneseKnoweedOverlay,
599
- managingAgentOverlay,
600
- ]);
601
-
602
- // Navigate to the leasehold section in the oneOf array
603
- const leaseholdSchema =
604
- schemaWithOverlays.properties.propertyPack.properties.ownership.properties.ownershipsToBeTransferred.items.oneOf.find(
605
- (schema) => schema.properties.ownershipType.enum[0] === "Leasehold"
606
- );
607
-
608
- // Verify the leasehold information structure
609
- const leaseholdInfo = leaseholdSchema.properties.leaseholdInformation;
610
-
611
- // Check required fields - should include both NTS and custom overlay requirements
612
- expect(leaseholdInfo.required).toContain("contactDetails");
613
- expect(leaseholdInfo.required).toContain("leaseTerm"); // from NTS
614
- expect(leaseholdInfo.required).toContain("groundRent"); // from NTS
615
-
616
- // Verify managing agent contact structure and refs
617
- const managingAgent =
618
- leaseholdInfo.properties.contactDetails.properties.contacts.properties
619
- .managingAgent;
620
- expect(managingAgent.macRef).toBe("MAC.4.1c");
621
-
622
- // Verify contact details structure
623
- const contact = managingAgent.properties.contact;
624
- expect(contact.properties.nameOrOrganisation.macRef).toBe("MAC.4.1c.1");
625
- expect(contact.properties.address.macRef).toBe("MAC.4.1c.0");
626
- expect(contact.properties.telephone.macRef).toBe("MAC.4.1c.7");
627
- expect(contact.properties.emailAddress.macRef).toBe("MAC.4.1c.8");
628
-
629
- // Verify Japanese Knotweed section is still present and correct
630
- const japaneseKnotweed =
631
- schemaWithOverlays.properties.propertyPack.properties.specialistIssues
632
- .properties.japaneseKnotweed;
633
- expect(japaneseKnotweed.customRef).toBe("JKW.7.8");
634
-
635
- // Verify NTS changes are still present
636
- const mainsElectricity =
637
- schemaWithOverlays.properties.propertyPack.properties.electricity.properties
638
- .mainsElectricity;
639
- expect(mainsElectricity.properties.yesNo.enum).toEqual(["Yes", "No"]);
640
- });
641
-
642
- test("ta7 overlay as key and as object produce identical results at leasehold info level", () => {
643
- // Get the TA7 overlay object directly from the overlays map
644
- const ta7OverlayObject = { ...overlaysMap[schemaId]["ta7ed3"] };
645
- // Remove the $id to prevent schema registration conflicts
646
- ta7OverlayObject.$id =
647
- "https://trust.propdata.org.uk/schemas/v3/overlays/ta7ed3-copy.json";
648
-
649
- // Get schemas using both methods
650
- const schemaWithKey = getTransactionSchema(schemaId, ["ta7ed3"]);
651
- const schemaWithObject = getTransactionSchema(schemaId, [ta7OverlayObject]);
652
-
653
- // Navigate to the leasehold information section in both schemas
654
- const getLeaseholdInfo = (schema) => {
655
- const ownershipsToBeTransferred =
656
- schema.properties.propertyPack.properties.ownership.properties
657
- .ownershipsToBeTransferred;
658
- const leaseholdSchema = ownershipsToBeTransferred.items.oneOf.find(
659
- (schema) => schema.properties.ownershipType.enum[0] === "Leasehold"
660
- );
661
- return leaseholdSchema.properties.leaseholdInformation;
662
- };
663
-
664
- const leaseholdInfoWithKey = getLeaseholdInfo(schemaWithKey);
665
- const leaseholdInfoWithObject = getLeaseholdInfo(schemaWithObject);
666
-
667
- // Deep compare the leasehold information sections
668
- expect(leaseholdInfoWithObject).toEqual(leaseholdInfoWithKey);
669
-
670
- // Verify specific TA7 requirements are present in both
671
- expect(leaseholdInfoWithKey.required).toContain("contactDetails");
672
- expect(leaseholdInfoWithObject.required).toContain("contactDetails");
673
-
674
- // Verify TA7 refs are present and identical
675
- const contactDetails = leaseholdInfoWithKey.properties.contactDetails;
676
- expect(contactDetails.ta7Ref).toBe("4.1");
677
- expect(leaseholdInfoWithObject.properties.contactDetails.ta7Ref).toBe("4.1");
678
- });
@@ -11,120 +11,152 @@ const extensionMappings = {
11
11
  oa: {
12
12
  name: "outsideAreas",
13
13
  description: "Outside areas extension for NTS",
14
- paths: ["/properties/propertyPack/properties/residentialPropertyFeatures/properties/outsideAreas"]
14
+ paths: [
15
+ "/properties/propertyPack/properties/residentialPropertyFeatures/properties/outsideAreas",
16
+ ],
15
17
  },
16
-
18
+
17
19
  // Estate rentcharges for freehold
18
20
  er: {
19
21
  name: "estateRentcharges",
20
22
  description: "Estate rentcharges for freehold properties",
21
- paths: ["/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/0/properties/estateRentcharges"]
23
+ paths: [
24
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/0/properties/estateRentcharges",
25
+ ],
22
26
  },
23
-
27
+
24
28
  // Managing agent contact details for leasehold
25
29
  ma: {
26
30
  name: "managingAgent",
27
31
  description: "Managing agent contact details for leasehold",
28
- paths: ["/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/contactDetails"]
32
+ paths: [
33
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/contactDetails",
34
+ ],
29
35
  },
30
-
36
+
31
37
  // Transfer fees for leasehold
32
38
  tf: {
33
39
  name: "transferFees",
34
40
  description: "Transfer fees for leasehold",
35
- paths: ["/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/serviceCharge/oneOf/1/properties/transferFees"]
41
+ paths: [
42
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/serviceCharge/oneOf/1/properties/transferFees",
43
+ ],
36
44
  },
37
-
45
+
38
46
  // Main construction type if standard
39
47
  mc: {
40
48
  name: "mainConstruction",
41
49
  description: "Main construction type if standard form",
42
- paths: ["/properties/propertyPack/properties/typeOfConstruction/properties/isStandardForm/oneOf/0/properties/constructionType"]
50
+ paths: [
51
+ "/properties/propertyPack/properties/typeOfConstruction/properties/isStandardForm/oneOf/0/properties/constructionType",
52
+ ],
43
53
  },
44
-
54
+
45
55
  // Loft access and details
46
56
  la: {
47
57
  name: "loftAccess",
48
58
  description: "Loft access and details",
49
- paths: ["/properties/propertyPack/properties/typeOfConstruction/properties/loft"]
59
+ paths: [
60
+ "/properties/propertyPack/properties/typeOfConstruction/properties/loft",
61
+ ],
50
62
  },
51
-
63
+
52
64
  // Spray foam insulation
53
65
  sf: {
54
66
  name: "sprayFoam",
55
67
  description: "Spray foam insulation",
56
- paths: ["/properties/propertyPack/properties/typeOfConstruction/properties/sprayFoamInsulation"]
68
+ paths: [
69
+ "/properties/propertyPack/properties/typeOfConstruction/properties/sprayFoamInsulation",
70
+ ],
57
71
  },
58
-
72
+
59
73
  // Specialist issues - dry rot
60
74
  dr: {
61
75
  name: "dryRot",
62
76
  description: "Dry rot treatment specialist issue",
63
- paths: ["/properties/propertyPack/properties/specialistIssues/properties/dryRotEtcTreatment"]
77
+ paths: [
78
+ "/properties/propertyPack/properties/specialistIssues/properties/dryRotEtcTreatment",
79
+ ],
64
80
  },
65
-
81
+
66
82
  // Specialist issues - asbestos
67
83
  as: {
68
84
  name: "asbestos",
69
85
  description: "Asbestos specialist issue",
70
- paths: ["/properties/propertyPack/properties/specialistIssues/properties/containsAsbestos"]
86
+ paths: [
87
+ "/properties/propertyPack/properties/specialistIssues/properties/containsAsbestos",
88
+ ],
71
89
  },
72
-
90
+
73
91
  // Specialist issues - Japanese knotweed
74
92
  jk: {
75
93
  name: "japaneseKnotweed",
76
94
  description: "Japanese knotweed specialist issue",
77
- paths: ["/properties/propertyPack/properties/specialistIssues/properties/japaneseKnotweed"]
95
+ paths: [
96
+ "/properties/propertyPack/properties/specialistIssues/properties/japaneseKnotweed",
97
+ ],
78
98
  },
79
-
99
+
80
100
  // Specialist issues - subsidence
81
101
  sb: {
82
102
  name: "subsidence",
83
103
  description: "Subsidence or structural fault specialist issue",
84
- paths: ["/properties/propertyPack/properties/specialistIssues/properties/subsidenceOrStructuralFault"]
104
+ paths: [
105
+ "/properties/propertyPack/properties/specialistIssues/properties/subsidenceOrStructuralFault",
106
+ ],
85
107
  },
86
-
108
+
87
109
  // Specialist issues - health and safety
88
110
  hs: {
89
111
  name: "healthSafety",
90
112
  description: "Health and safety specialist issue",
91
- paths: ["/properties/propertyPack/properties/specialistIssues/properties/ongoingHealthOrSafetyIssue"]
113
+ paths: [
114
+ "/properties/propertyPack/properties/specialistIssues/properties/ongoingHealthOrSafetyIssue",
115
+ ],
92
116
  },
93
-
117
+
94
118
  // Solar panels leased
95
119
  sl: {
96
120
  name: "solarPanelsLeased",
97
121
  description: "Solar panels ownership details",
98
- paths: ["/properties/propertyPack/properties/electricity/properties/solarPanels/oneOf/1/properties/panelsOwnedOutright"]
122
+ paths: [
123
+ "/properties/propertyPack/properties/electricity/properties/solarPanels/oneOf/1/properties/panelsOwnedOutright",
124
+ ],
99
125
  },
100
-
126
+
101
127
  // Heating installation date
102
128
  hi: {
103
129
  name: "heatingInstalled",
104
130
  description: "Central heating installation date",
105
- paths: ["/properties/propertyPack/properties/heating/properties/heatingSystem/oneOf/1/properties/centralHeatingDetails/properties/centralHeatingInstalled"]
131
+ paths: [
132
+ "/properties/propertyPack/properties/heating/properties/heatingSystem/oneOf/1/properties/centralHeatingDetails/properties/centralHeatingInstalled",
133
+ ],
106
134
  },
107
-
135
+
108
136
  // Flood defences
109
137
  fd: {
110
138
  name: "floodDefences",
111
139
  description: "Flood defence information",
112
- paths: ["/properties/propertyPack/properties/environmentalIssues/properties/flooding/properties/floodDefences"]
140
+ paths: [
141
+ "/properties/propertyPack/properties/environmentalIssues/properties/flooding/properties/floodDefences",
142
+ ],
113
143
  },
114
-
144
+
115
145
  // Other property in chain
116
146
  oc: {
117
147
  name: "otherPropertyChain",
118
148
  description: "Other property in chain dependency",
119
- paths: ["/properties/propertyPack/properties/completionAndMoving/properties/otherPropertyInChain"]
120
- }
149
+ paths: [
150
+ "/properties/propertyPack/properties/completionAndMoving/properties/otherPropertyInChain",
151
+ ],
152
+ },
121
153
  };
122
154
 
123
155
  // Function to extract properties at specific paths with nts2Ref
124
156
  function extractPathsWithNts2Ref(schema, paths) {
125
157
  const result = {};
126
-
127
- paths.forEach(path => {
158
+
159
+ paths.forEach((path) => {
128
160
  try {
129
161
  const value = jp.get(schema, path);
130
162
  if (value && hasNts2Ref(value)) {
@@ -135,14 +167,14 @@ function extractPathsWithNts2Ref(schema, paths) {
135
167
  console.warn(`Path not found: ${path}`);
136
168
  }
137
169
  });
138
-
170
+
139
171
  return result;
140
172
  }
141
173
 
142
174
  // Check if an object or its descendants have nts2Ref
143
175
  function hasNts2Ref(obj) {
144
176
  let found = false;
145
- traverse(obj).forEach(function(element) {
177
+ traverse(obj).forEach(function (element) {
146
178
  if (element && element.nts2Ref) {
147
179
  found = true;
148
180
  this.stop();
@@ -152,28 +184,28 @@ function hasNts2Ref(obj) {
152
184
  }
153
185
 
154
186
  // Extract only NTS2-specific properties (with nts2Ref)
155
- function extractNts2Properties(obj) {
187
+ function extractNts2Properties(obj, parentKey) {
156
188
  const result = {};
157
-
189
+
158
190
  // Copy nts2-specific metadata at current level
159
191
  if (obj.nts2Ref) result.ntsRef = obj.nts2Ref;
160
192
  if (obj.nts2Required) result.required = obj.nts2Required;
161
193
  if (obj.nts2Title) result.title = obj.nts2Title;
162
194
  if (obj.nts2Description) result.description = obj.nts2Description;
163
195
  if (obj.nts2Enum) result.enum = obj.nts2Enum;
164
-
196
+
165
197
  // Handle discriminator
166
198
  if (obj.discriminator) {
167
199
  result.discriminator = obj.discriminator;
168
200
  }
169
-
201
+
170
202
  // Recursively process properties
171
203
  if (obj.properties) {
172
204
  result.properties = {};
173
- Object.keys(obj.properties).forEach(key => {
205
+ Object.keys(obj.properties).forEach((key) => {
174
206
  const prop = obj.properties[key];
175
207
  if (hasNts2Ref(prop)) {
176
- result.properties[key] = extractNts2Properties(prop);
208
+ result.properties[key] = extractNts2Properties(prop, key);
177
209
  }
178
210
  });
179
211
  // Only keep properties if we found some with nts2Ref
@@ -181,18 +213,18 @@ function extractNts2Properties(obj) {
181
213
  delete result.properties;
182
214
  }
183
215
  }
184
-
216
+
185
217
  // Handle arrays
186
218
  if (obj.items) {
187
219
  if (hasNts2Ref(obj.items)) {
188
220
  result.items = extractNts2Properties(obj.items);
189
221
  }
190
222
  }
191
-
223
+
192
224
  // Handle oneOf
193
225
  if (obj.oneOf) {
194
- const processedOneOf = [];
195
- obj.oneOf.forEach((schema, index) => {
226
+ // Always preserve the full array structure and order
227
+ const processedOneOf = obj.oneOf.map((schema, index) => {
196
228
  if (hasNts2Ref(schema)) {
197
229
  const extracted = extractNts2Properties(schema);
198
230
  // For oneOf schemas, we need to preserve discriminator enum values
@@ -201,31 +233,86 @@ function extractNts2Properties(obj) {
201
233
  if (schema.properties[propName]) {
202
234
  if (!extracted.properties) extracted.properties = {};
203
235
  extracted.properties[propName] = {
204
- enum: schema.properties[propName].nts2Enum || schema.properties[propName].enum
236
+ enum:
237
+ schema.properties[propName].nts2Enum ||
238
+ schema.properties[propName].enum,
205
239
  };
206
240
  }
207
241
  }
208
- processedOneOf.push(extracted);
242
+ // Also preserve base enum values for all other properties in the extension
243
+ if (schema.properties) {
244
+ if (!extracted.properties) extracted.properties = {};
245
+ Object.keys(schema.properties).forEach((propName) => {
246
+ const prop = schema.properties[propName];
247
+ if (prop.enum) {
248
+ if (!extracted.properties[propName]) {
249
+ extracted.properties[propName] = {
250
+ enum: prop.enum,
251
+ };
252
+ } else if (!extracted.properties[propName].enum) {
253
+ // If the property exists but doesn't have enum, add it
254
+ extracted.properties[propName].enum = prop.enum;
255
+ }
256
+ }
257
+ });
258
+ }
259
+ return extracted;
260
+ } else {
261
+ // If this branch doesn't have nts2Ref, preserve the base discriminator enum
262
+ if (obj.discriminator && schema.properties) {
263
+ const propName = obj.discriminator.propertyName;
264
+ if (schema.properties[propName]) {
265
+ return {
266
+ properties: {
267
+ [propName]: {
268
+ enum: schema.properties[propName].enum,
269
+ },
270
+ },
271
+ };
272
+ }
273
+ }
274
+ // If no discriminator, return empty object to preserve structure
275
+ return {};
209
276
  }
210
277
  });
211
- if (processedOneOf.length > 0) {
212
- result.oneOf = processedOneOf;
278
+ result.oneOf = processedOneOf;
279
+
280
+ // --- NEW: If this is an extension property inside a oneOf, promote it to the parent overlay ---
281
+ // If parentKey is set, and this object has extension metadata, return a stub for the parent
282
+ if (
283
+ parentKey &&
284
+ (result.ntsRef || result.required || result.discriminator)
285
+ ) {
286
+ // This is an extension property inside a oneOf branch
287
+ // Return a stub for the parent property with metadata and oneOf structure
288
+ const parentStub = {};
289
+ Object.assign(parentStub, result);
290
+ return parentStub;
213
291
  }
214
292
  }
215
-
293
+
216
294
  // Copy other JSON Schema keywords that might be present
217
- ['type', 'format', 'minimum', 'maximum', 'minLength', 'maxLength', 'minItems', 'maxItems'].forEach(keyword => {
295
+ [
296
+ "type",
297
+ "format",
298
+ "minimum",
299
+ "maximum",
300
+ "minLength",
301
+ "maxLength",
302
+ "minItems",
303
+ "maxItems",
304
+ ].forEach((keyword) => {
218
305
  if (obj[keyword] !== undefined) {
219
306
  result[keyword] = obj[keyword];
220
307
  }
221
308
  });
222
-
309
+
223
310
  return result;
224
311
  }
225
312
 
226
313
  // Add required array at parent levels if needed
227
314
  function addRequiredArrays(overlay, schema) {
228
- traverse(overlay).forEach(function(node) {
315
+ traverse(overlay).forEach(function (node) {
229
316
  if (node && node.properties) {
230
317
  const schemaPath = "/" + this.path.join("/");
231
318
  try {
@@ -233,7 +320,9 @@ function addRequiredArrays(overlay, schema) {
233
320
  if (schemaNode && schemaNode.nts2Required) {
234
321
  // Filter to only include properties that exist in this overlay
235
322
  const overlayProps = Object.keys(node.properties);
236
- const requiredProps = schemaNode.nts2Required.filter(prop => overlayProps.includes(prop));
323
+ const requiredProps = schemaNode.nts2Required.filter((prop) =>
324
+ overlayProps.includes(prop)
325
+ );
237
326
  if (requiredProps.length > 0) {
238
327
  node.required = requiredProps;
239
328
  }
@@ -249,23 +338,25 @@ function addRequiredArrays(overlay, schema) {
249
338
  // Generate extension overlays
250
339
  Object.entries(extensionMappings).forEach(([code, config]) => {
251
340
  console.log(`\nGenerating extension overlay: ${code} (${config.name})`);
252
-
341
+
253
342
  // Extract the specific paths
254
343
  let overlay = extractPathsWithNts2Ref(combinedSchema, config.paths);
255
-
344
+
256
345
  // Add required arrays where needed
257
346
  overlay = addRequiredArrays(overlay, combinedSchema);
258
-
347
+
259
348
  // Add schema metadata
260
349
  overlay.$schema = "http://json-schema.org/draft-07/schema#";
261
350
  overlay.$id = `https://trust.propdata.org.uk/schemas/v3/overlays/extensions/${code}.json`;
262
351
  overlay.$comment = config.description;
263
-
352
+
264
353
  // Write the overlay file
265
- const fileName = path.join(__dirname, `../schemas/v3/overlays/extensions/${code}.json`);
354
+ const fileName = path.join(
355
+ __dirname,
356
+ `../schemas/v3/overlays/extensions/${code}.json`
357
+ );
266
358
  fs.writeFileSync(fileName, JSON.stringify(overlay, null, 2));
267
359
  console.log(`Extension overlay ${code} written to ${fileName}`);
268
360
  });
269
361
 
270
-
271
- console.log("\nExtension overlay extraction complete!");
362
+ console.log("\nExtension overlay extraction complete!");