@pdtf/schemas 3.6.0-9 → 3.6.0-dev.10

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 (85) hide show
  1. package/README.md +57 -33
  2. package/docs/branch-release-workflow.md +138 -0
  3. package/docs/sef25-extensions-ui-spec.md +355 -0
  4. package/index.js +131 -75
  5. package/package.json +14 -2
  6. package/src/schemas/v3/combined.json +7952 -1721
  7. package/src/schemas/v3/compactSkeleton.txt +379 -5
  8. package/src/schemas/v3/overlays/baspi4.json +205 -93
  9. package/src/schemas/v3/overlays/baspi5.json +210 -95
  10. package/src/schemas/v3/overlays/extensions/ac.json +334 -0
  11. package/src/schemas/v3/overlays/extensions/as.json +4 -2
  12. package/src/schemas/v3/overlays/extensions/dk.json +33 -0
  13. package/src/schemas/v3/overlays/extensions/dr.json +9 -9
  14. package/src/schemas/v3/overlays/extensions/er.json +4 -2
  15. package/src/schemas/v3/overlays/extensions/fd.json +6 -3
  16. package/src/schemas/v3/overlays/extensions/hi.json +6 -3
  17. package/src/schemas/v3/overlays/extensions/hs.json +4 -2
  18. package/src/schemas/v3/overlays/extensions/ic.json +94 -0
  19. package/src/schemas/v3/overlays/extensions/jk.json +28 -8
  20. package/src/schemas/v3/overlays/extensions/la.json +4 -2
  21. package/src/schemas/v3/overlays/extensions/lc.json +76 -0
  22. package/src/schemas/v3/overlays/extensions/ma.json +6 -3
  23. package/src/schemas/v3/overlays/extensions/mc.json +4 -2
  24. package/src/schemas/v3/overlays/extensions/mi.json +70 -0
  25. package/src/schemas/v3/overlays/extensions/nd.json +44 -0
  26. package/src/schemas/v3/overlays/extensions/oa.json +4 -2
  27. package/src/schemas/v3/overlays/extensions/oc.json +4 -2
  28. package/src/schemas/v3/overlays/extensions/pc.json +38 -0
  29. package/src/schemas/v3/overlays/extensions/ph.json +195 -0
  30. package/src/schemas/v3/overlays/extensions/rw.json +69 -0
  31. package/src/schemas/v3/overlays/extensions/sb.json +4 -2
  32. package/src/schemas/v3/overlays/extensions/sc.json +133 -0
  33. package/src/schemas/v3/overlays/extensions/sd.json +64 -0
  34. package/src/schemas/v3/overlays/extensions/sf.json +4 -2
  35. package/src/schemas/v3/overlays/extensions/sl.json +4 -2
  36. package/src/schemas/v3/overlays/extensions/ta.json +204 -0
  37. package/src/schemas/v3/overlays/extensions/tf.json +6 -3
  38. package/src/schemas/v3/overlays/extensions/tr.json +74 -0
  39. package/src/schemas/v3/overlays/extensions/wg.json +351 -0
  40. package/src/schemas/v3/overlays/fme1.json +16 -14
  41. package/src/schemas/v3/overlays/llc1.json +1 -2
  42. package/src/schemas/v3/overlays/lpe1.json +61 -31
  43. package/src/schemas/v3/overlays/nts.json +71 -2
  44. package/src/schemas/v3/overlays/nts2.json +71 -5
  45. package/src/schemas/v3/overlays/ntsl.json +71 -2
  46. package/src/schemas/v3/overlays/ntsl2.json +71 -5
  47. package/src/schemas/v3/overlays/piq.json +60 -27
  48. package/src/schemas/v3/overlays/rds.json +139 -57
  49. package/src/schemas/v3/overlays/sef25.json +1653 -0
  50. package/src/schemas/v3/overlays/ta10.json +0 -9
  51. package/src/schemas/v3/overlays/ta6.json +83 -85
  52. package/src/schemas/v3/overlays/ta6ed6.json +6641 -0
  53. package/src/schemas/v3/overlays/ta7.json +70 -6
  54. package/src/schemas/v3/overlays/ta7ed5.json +1597 -0
  55. package/src/schemas/v3/pdtf-transaction.json +4525 -1013
  56. package/src/schemas/v3/skeleton.json +512 -40
  57. package/src/utils/extractExtensionOverlays.js +302 -31
  58. package/src/utils/extractOverlay.js +114 -27
  59. package/src/utils/pathSkeleton.js +1 -1
  60. package/.claude/settings.local.json +0 -42
  61. package/.github/workflows/pages.yml +0 -50
  62. package/CNAME +0 -1
  63. package/_config.yml +0 -1
  64. package/src/examples/v1/exampleAddParticipantVouch.json +0 -32
  65. package/src/examples/v1/exampleDocumentedVouch.json +0 -40
  66. package/src/examples/v1/exampleElectronicRecord.json +0 -30
  67. package/src/examples/v1/exampleTransaction.json +0 -878
  68. package/src/examples/v1/exampleVouch.json +0 -25
  69. package/src/examples/v2/exampleTransaction.json +0 -901
  70. package/src/examples/v3/exampleAddParticipantVouch.json +0 -38
  71. package/src/examples/v3/exampleCustomOverlay.json +0 -55
  72. package/src/examples/v3/exampleCustomOverlay2.json +0 -93
  73. package/src/examples/v3/exampleDocumentedVouch.json +0 -47
  74. package/src/examples/v3/exampleElectronicRecord.json +0 -36
  75. package/src/examples/v3/exampleTransaction.json +0 -1038
  76. package/src/examples/v3/exampleVouch.json +0 -32
  77. package/src/mappings/SendPropertyDetailsRequestSchema.json +0 -1588
  78. package/src/mappings/adf.js +0 -198
  79. package/src/tests/v3/caching.test.js +0 -591
  80. package/src/tests/v3/enquiries.test.js +0 -1096
  81. package/src/tests/v3/extensionOverlays.test.js +0 -525
  82. package/src/tests/v3/offers.test.js +0 -823
  83. package/src/tests/v3/patternProperties.test.js +0 -688
  84. package/src/tests/v3/transactionSchema.test.js +0 -615
  85. package/src/tests/v3/verifiedClaimsValidator.test.js +0 -195
@@ -5,6 +5,41 @@ const path = require("path");
5
5
 
6
6
  const combinedSchema = require("../schemas/v3/combined.json");
7
7
 
8
+ // Derive ref-related field names from a refType
9
+ function getRefConfig(refType) {
10
+ if (refType === "nts2Ref") {
11
+ // Legacy mapping: nts2Ref -> ntsRef in output
12
+ return {
13
+ ref: "nts2Ref",
14
+ outputRef: "ntsRef",
15
+ required: "nts2Required",
16
+ title: "nts2Title",
17
+ description: "nts2Description",
18
+ enumKey: "nts2Enum",
19
+ };
20
+ }
21
+ if (refType === "sef25Ref") {
22
+ // SEF25 extensions also appear as ntsRef in output
23
+ return {
24
+ ref: "sef25Ref",
25
+ outputRef: "ntsRef",
26
+ required: "sef25Required",
27
+ title: "sef25Title",
28
+ description: "sef25Description",
29
+ enumKey: "sef25Enum",
30
+ };
31
+ }
32
+ // For other ref types, derive keys by replacing "Ref" suffix
33
+ return {
34
+ ref: refType,
35
+ outputRef: refType,
36
+ required: refType.replace("Ref", "Required"),
37
+ title: refType.replace("Ref", "Title"),
38
+ description: refType.replace("Ref", "Description"),
39
+ enumKey: refType.replace("Ref", "Enum"),
40
+ };
41
+ }
42
+
8
43
  // Define the extension overlay mappings
9
44
  const extensionMappings = {
10
45
  // Outside areas
@@ -150,18 +185,192 @@ const extensionMappings = {
150
185
  "/properties/propertyPack/properties/completionAndMoving/properties/otherPropertyInChain",
151
186
  ],
152
187
  },
188
+
189
+ // Private supply costs (water & sewerage)
190
+ sc: {
191
+ name: "supplyCosts",
192
+ description: "Associated costs for private water and sewerage supply",
193
+ refType: "sef25Ref",
194
+ paths: [
195
+ "/properties/propertyPack/properties/waterAndDrainage/properties/water/properties/mainsWater/oneOf/0/properties/associatedCost",
196
+ "/properties/propertyPack/properties/waterAndDrainage/properties/drainage/properties/mainsFoulDrainage/oneOf/2/properties/associatedCost",
197
+ ],
198
+ },
199
+
200
+ // Parking permit costs
201
+ pc: {
202
+ name: "parkingPermitCost",
203
+ description: "Parking permit cost and frequency",
204
+ refType: "sef25Ref",
205
+ paths: [
206
+ "/properties/propertyPack/properties/parking/properties/controlledParking/oneOf/1/properties/costOfPermit",
207
+ "/properties/propertyPack/properties/parking/properties/controlledParking/oneOf/1/properties/costOfPermitFrequency",
208
+ ],
209
+ },
210
+
211
+ // Property hazards
212
+ ph: {
213
+ name: "propertyHazards",
214
+ description: "Property hazards and known issues",
215
+ refType: "sef25Ref",
216
+ paths: [
217
+ "/properties/propertyPack/properties/specialistIssues",
218
+ ],
219
+ },
220
+
221
+ // Dropped kerb access to parking
222
+ dk: {
223
+ name: "droppedKerbAccess",
224
+ description: "Dropped kerb access to parking",
225
+ refType: "sef25Ref",
226
+ paths: [
227
+ "/properties/propertyPack/properties/parking/properties/droppedKerbAccess",
228
+ ],
229
+ },
230
+
231
+ // Private right of way
232
+ rw: {
233
+ name: "privateRightOfWay",
234
+ description: "Private right of way",
235
+ refType: "sef25Ref",
236
+ paths: [
237
+ "/properties/propertyPack/properties/rightsAndInformalArrangements/properties/rightsOrArrangements/properties/privateRightOfWay",
238
+ ],
239
+ },
240
+
241
+ // Storm, fire or flood damage
242
+ sd: {
243
+ name: "stormFireFloodDamage",
244
+ description: "Storm, fire or flood damage",
245
+ refType: "sef25Ref",
246
+ paths: [
247
+ "/properties/propertyPack/properties/environmentalIssues/properties/stormFireFloodDamage",
248
+ ],
249
+ },
250
+
251
+ // Solar panel lease costs
252
+ lc: {
253
+ name: "solarLeaseCosts",
254
+ description: "Solar panel lease costs",
255
+ refType: "sef25Ref",
256
+ paths: [
257
+ "/properties/propertyPack/properties/electricity/properties/solarPanels/oneOf/1/properties/panelsOwnedOutright/oneOf/1/properties/associatedCost",
258
+ ],
259
+ },
260
+
261
+ // Warranties and guarantees upfront
262
+ wg: {
263
+ name: "warrantiesGuarantees",
264
+ description: "Warranties and guarantees upfront Y/N for SEF25",
265
+ refType: "sef25Ref",
266
+ paths: [
267
+ "/properties/propertyPack/properties/guaranteesWarrantiesAndIndemnityInsurances",
268
+ ],
269
+ },
270
+
271
+ // Insurance claims upfront
272
+ ic: {
273
+ name: "insuranceClaims",
274
+ description: "Insurance claims upfront for SEF25",
275
+ refType: "sef25Ref",
276
+ paths: [
277
+ "/properties/propertyPack/properties/insurance",
278
+ ],
279
+ },
280
+
281
+ // Neighbour development upfront
282
+ nd: {
283
+ name: "neighbourDevelopment",
284
+ description: "Neighbour development upfront for SEF25",
285
+ refType: "sef25Ref",
286
+ paths: [
287
+ "/properties/propertyPack/properties/notices/properties/neighbourDevelopment",
288
+ ],
289
+ },
290
+
291
+ // Other material issue upfront
292
+ mi: {
293
+ name: "otherMaterialIssue",
294
+ description: "Other material issue upfront for SEF25",
295
+ refType: "sef25Ref",
296
+ paths: [
297
+ "/properties/propertyPack/properties/additionalInformation/properties/otherMaterialIssue",
298
+ ],
299
+ },
300
+
301
+ // Title restrictions for freehold
302
+ tr: {
303
+ name: "titleRestrictions",
304
+ description: "Title restrictions for freehold properties",
305
+ refType: "sef25Ref",
306
+ paths: [
307
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/0/properties/titleRestrictions",
308
+ ],
309
+ },
310
+
311
+ // Alterations and changes
312
+ ac: {
313
+ name: "alterationsAndChanges",
314
+ description: "Selected alterations and changes fields for SEF25",
315
+ refType: "sef25Ref",
316
+ preserveDiscriminators: true,
317
+ paths: [
318
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/extension/properties/yesNo",
319
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/extension/oneOf/1/properties/details",
320
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/extension/oneOf/1/properties/planningPermission/properties/yesNo",
321
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/extension/oneOf/1/properties/buildingRegApproval/properties/yesNo",
322
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/properties/yesNo",
323
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/oneOf/1/properties/details",
324
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/oneOf/1/properties/planningPermission/properties/yesNo",
325
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/oneOf/1/properties/buildingRegApproval/properties/yesNo",
326
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/properties/yesNo",
327
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/oneOf/1/properties/details",
328
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/oneOf/1/properties/planningPermission/properties/yesNo",
329
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/oneOf/1/properties/buildingRegApproval/properties/yesNo",
330
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/properties/yesNo",
331
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/oneOf/1/properties/details",
332
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/oneOf/1/properties/planningPermission/properties/yesNo",
333
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/oneOf/1/properties/buildingRegApproval/properties/yesNo",
334
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/properties/yesNo",
335
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/oneOf/1/properties/details",
336
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/oneOf/1/properties/planningPermission/properties/yesNo",
337
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/oneOf/1/properties/buildingRegApproval/properties/yesNo",
338
+ ],
339
+ },
340
+
341
+ // TA6 edition 6 compatibility for shared MI/NTS validation
342
+ ta: {
343
+ name: "ta6Ed6Compatibility",
344
+ description:
345
+ "Compatibility enum ranges for validating shared PDTF state containing TA6 edition 6 answers",
346
+ refType: "ta6ed6CompatRef",
347
+ paths: [
348
+ "/properties/propertyPack/properties/parking",
349
+ "/properties/propertyPack/properties/heating",
350
+ "/properties/propertyPack/properties/rightsAndInformalArrangements",
351
+ "/properties/propertyPack/properties/legalBoundaries",
352
+ ],
353
+ },
153
354
  };
154
355
 
155
- // Function to extract properties at specific paths with nts2Ref
156
- function extractPathsWithNts2Ref(schema, paths) {
356
+ // Function to extract properties at specific paths with given ref type
357
+ function extractPathsWithRef(
358
+ schema,
359
+ paths,
360
+ refConfig,
361
+ preserveDiscriminators = false
362
+ ) {
157
363
  const result = {};
158
364
 
159
365
  paths.forEach((path) => {
160
366
  try {
161
367
  const value = jp.get(schema, path);
162
- if (value && hasNts2Ref(value)) {
368
+ if (value && hasRef(value, refConfig)) {
163
369
  // Set the value at the same path in result
164
- jp.set(result, path, extractNts2Properties(value));
370
+ jp.set(result, path, extractRefProperties(value, refConfig));
371
+ if (preserveDiscriminators) {
372
+ addAncestorDiscriminators(result, schema, path);
373
+ }
165
374
  }
166
375
  } catch (err) {
167
376
  console.warn(`Path not found: ${path}`);
@@ -171,11 +380,31 @@ function extractPathsWithNts2Ref(schema, paths) {
171
380
  return result;
172
381
  }
173
382
 
174
- // Check if an object or its descendants have nts2Ref
175
- function hasNts2Ref(obj) {
383
+ function addAncestorDiscriminators(result, schema, path) {
384
+ const parts = path.split("/").filter(Boolean);
385
+
386
+ for (let index = 1; index <= parts.length; index += 1) {
387
+ const ancestorPath = `/${parts.slice(0, index).join("/")}`;
388
+
389
+ try {
390
+ const schemaNode = jp.get(schema, ancestorPath);
391
+ if (!schemaNode?.discriminator) continue;
392
+
393
+ const resultNode = jp.get(result, ancestorPath);
394
+ if (resultNode && typeof resultNode === "object") {
395
+ resultNode.discriminator = schemaNode.discriminator;
396
+ }
397
+ } catch (err) {
398
+ // Ancestor was not materialised by this selected path.
399
+ }
400
+ }
401
+ }
402
+
403
+ // Check if an object or its descendants have the specified ref
404
+ function hasRef(obj, refConfig) {
176
405
  let found = false;
177
406
  traverse(obj).forEach(function (element) {
178
- if (element && element.nts2Ref) {
407
+ if (element && element[refConfig.ref]) {
179
408
  found = true;
180
409
  this.stop();
181
410
  }
@@ -183,16 +412,16 @@ function hasNts2Ref(obj) {
183
412
  return found;
184
413
  }
185
414
 
186
- // Extract only NTS2-specific properties (with nts2Ref)
187
- function extractNts2Properties(obj, parentKey) {
415
+ // Extract only ref-specific properties
416
+ function extractRefProperties(obj, refConfig, parentKey) {
188
417
  const result = {};
189
418
 
190
- // Copy nts2-specific metadata at current level
191
- if (obj.nts2Ref) result.ntsRef = obj.nts2Ref;
192
- if (obj.nts2Required) result.required = obj.nts2Required;
193
- if (obj.nts2Title) result.title = obj.nts2Title;
194
- if (obj.nts2Description) result.description = obj.nts2Description;
195
- if (obj.nts2Enum) result.enum = obj.nts2Enum;
419
+ // Copy ref-specific metadata at current level
420
+ if (obj[refConfig.ref]) result[refConfig.outputRef] = obj[refConfig.ref];
421
+ if (obj[refConfig.required]) result.required = obj[refConfig.required];
422
+ if (obj[refConfig.title]) result.title = obj[refConfig.title];
423
+ if (obj[refConfig.description]) result.description = obj[refConfig.description];
424
+ if (obj[refConfig.enumKey]) result.enum = obj[refConfig.enumKey];
196
425
 
197
426
  // Handle discriminator
198
427
  if (obj.discriminator) {
@@ -204,11 +433,11 @@ function extractNts2Properties(obj, parentKey) {
204
433
  result.properties = {};
205
434
  Object.keys(obj.properties).forEach((key) => {
206
435
  const prop = obj.properties[key];
207
- if (hasNts2Ref(prop)) {
208
- result.properties[key] = extractNts2Properties(prop, key);
436
+ if (hasRef(prop, refConfig)) {
437
+ result.properties[key] = extractRefProperties(prop, refConfig, key);
209
438
  }
210
439
  });
211
- // Only keep properties if we found some with nts2Ref
440
+ // Only keep properties if we found some with ref
212
441
  if (Object.keys(result.properties).length === 0) {
213
442
  delete result.properties;
214
443
  }
@@ -216,8 +445,8 @@ function extractNts2Properties(obj, parentKey) {
216
445
 
217
446
  // Handle arrays
218
447
  if (obj.items) {
219
- if (hasNts2Ref(obj.items)) {
220
- result.items = extractNts2Properties(obj.items);
448
+ if (hasRef(obj.items, refConfig)) {
449
+ result.items = extractRefProperties(obj.items, refConfig);
221
450
  }
222
451
  }
223
452
 
@@ -225,8 +454,8 @@ function extractNts2Properties(obj, parentKey) {
225
454
  if (obj.oneOf) {
226
455
  // Always preserve the full array structure and order
227
456
  const processedOneOf = obj.oneOf.map((schema, index) => {
228
- if (hasNts2Ref(schema)) {
229
- const extracted = extractNts2Properties(schema);
457
+ if (hasRef(schema, refConfig)) {
458
+ const extracted = extractRefProperties(schema, refConfig);
230
459
  // For oneOf schemas, we need to preserve discriminator enum values
231
460
  if (obj.discriminator && schema.properties) {
232
461
  const propName = obj.discriminator.propertyName;
@@ -234,7 +463,7 @@ function extractNts2Properties(obj, parentKey) {
234
463
  if (!extracted.properties) extracted.properties = {};
235
464
  extracted.properties[propName] = {
236
465
  enum:
237
- schema.properties[propName].nts2Enum ||
466
+ schema.properties[propName][refConfig.enumKey] ||
238
467
  schema.properties[propName].enum,
239
468
  };
240
469
  }
@@ -258,7 +487,7 @@ function extractNts2Properties(obj, parentKey) {
258
487
  }
259
488
  return extracted;
260
489
  } else {
261
- // If this branch doesn't have nts2Ref, preserve the base discriminator enum
490
+ // If this branch doesn't have ref, preserve the base discriminator enum
262
491
  if (obj.discriminator && schema.properties) {
263
492
  const propName = obj.discriminator.propertyName;
264
493
  if (schema.properties[propName]) {
@@ -281,7 +510,7 @@ function extractNts2Properties(obj, parentKey) {
281
510
  // If parentKey is set, and this object has extension metadata, return a stub for the parent
282
511
  if (
283
512
  parentKey &&
284
- (result.ntsRef || result.required || result.discriminator)
513
+ (result[refConfig.outputRef] || result.required || result.discriminator)
285
514
  ) {
286
515
  // This is an extension property inside a oneOf branch
287
516
  // Return a stub for the parent property with metadata and oneOf structure
@@ -311,16 +540,16 @@ function extractNts2Properties(obj, parentKey) {
311
540
  }
312
541
 
313
542
  // Add required array at parent levels if needed
314
- function addRequiredArrays(overlay, schema) {
543
+ function addRequiredArrays(overlay, schema, refConfig) {
315
544
  traverse(overlay).forEach(function (node) {
316
545
  if (node && node.properties) {
317
546
  const schemaPath = "/" + this.path.join("/");
318
547
  try {
319
548
  const schemaNode = jp.get(schema, schemaPath);
320
- if (schemaNode && schemaNode.nts2Required) {
549
+ if (schemaNode && schemaNode[refConfig.required]) {
321
550
  // Filter to only include properties that exist in this overlay
322
551
  const overlayProps = Object.keys(node.properties);
323
- const requiredProps = schemaNode.nts2Required.filter((prop) =>
552
+ const requiredProps = schemaNode[refConfig.required].filter((prop) =>
324
553
  overlayProps.includes(prop)
325
554
  );
326
555
  if (requiredProps.length > 0) {
@@ -335,15 +564,57 @@ function addRequiredArrays(overlay, schema) {
335
564
  return overlay;
336
565
  }
337
566
 
567
+ // Annotate intermediate nodes with refs from the source schema
568
+ // This ensures every level of the overlay tree carries an ntsRef
569
+ function addIntermediateRefs(overlay, schema, refConfig) {
570
+ traverse(overlay).forEach(function (node) {
571
+ if (node && typeof node === "object" && !Array.isArray(node) && node.properties) {
572
+ // Skip if this node already has a ref
573
+ if (node[refConfig.outputRef]) return;
574
+
575
+ const schemaPath = "/" + this.path.join("/");
576
+ try {
577
+ const schemaNode = jp.get(schema, schemaPath);
578
+ if (!schemaNode) return;
579
+ // When the output key differs from the source key (e.g. sef25Ref -> ntsRef),
580
+ // prefer the schema's existing outputRef on intermediate parents so we
581
+ // don't clobber legitimate NTS refs when the extension is later merged.
582
+ if (
583
+ refConfig.ref !== refConfig.outputRef &&
584
+ schemaNode[refConfig.outputRef]
585
+ ) {
586
+ node[refConfig.outputRef] = schemaNode[refConfig.outputRef];
587
+ } else if (schemaNode[refConfig.ref]) {
588
+ node[refConfig.outputRef] = schemaNode[refConfig.ref];
589
+ }
590
+ } catch (err) {
591
+ // Path doesn't exist in schema, skip
592
+ }
593
+ }
594
+ });
595
+ return overlay;
596
+ }
597
+
338
598
  // Generate extension overlays
339
599
  Object.entries(extensionMappings).forEach(([code, config]) => {
340
- console.log(`\nGenerating extension overlay: ${code} (${config.name})`);
600
+ const refType = config.refType || "nts2Ref";
601
+ const refConfig = getRefConfig(refType);
602
+
603
+ console.log(`\nGenerating extension overlay: ${code} (${config.name}) [${refType}]`);
341
604
 
342
605
  // Extract the specific paths
343
- let overlay = extractPathsWithNts2Ref(combinedSchema, config.paths);
606
+ let overlay = extractPathsWithRef(
607
+ combinedSchema,
608
+ config.paths,
609
+ refConfig,
610
+ config.preserveDiscriminators
611
+ );
344
612
 
345
613
  // Add required arrays where needed
346
- overlay = addRequiredArrays(overlay, combinedSchema);
614
+ overlay = addRequiredArrays(overlay, combinedSchema, refConfig);
615
+
616
+ // Add refs to intermediate nodes in the path hierarchy
617
+ overlay = addIntermediateRefs(overlay, combinedSchema, refConfig);
347
618
 
348
619
  // Add schema metadata
349
620
  overlay.$schema = "http://json-schema.org/draft-07/schema#";