@defra/forms-model 3.0.430 → 3.0.431

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 (45) hide show
  1. package/README.md +163 -1
  2. package/dist/module/common/pagination/index.js +2 -2
  3. package/dist/module/common/pagination/index.js.map +1 -1
  4. package/dist/module/common/search/index.js +4 -4
  5. package/dist/module/common/search/index.js.map +1 -1
  6. package/dist/module/common/sorting/index.js +2 -2
  7. package/dist/module/common/sorting/index.js.map +1 -1
  8. package/dist/module/form/form-definition/index.js +156 -156
  9. package/dist/module/form/form-definition/index.js.map +1 -1
  10. package/dist/module/form/form-editor/index.js +42 -42
  11. package/dist/module/form/form-editor/index.js.map +1 -1
  12. package/dist/module/form/form-manager/index.js +3 -3
  13. package/dist/module/form/form-manager/index.js.map +1 -1
  14. package/dist/module/form/form-metadata/index.js +34 -34
  15. package/dist/module/form/form-metadata/index.js.map +1 -1
  16. package/dist/module/form/form-submission/index.js +13 -13
  17. package/dist/module/form/form-submission/index.js.map +1 -1
  18. package/dist/module/types/joi-to-json.d.js +2 -0
  19. package/dist/module/types/joi-to-json.d.js.map +1 -0
  20. package/dist/types/common/pagination/index.d.ts.map +1 -1
  21. package/dist/types/common/search/index.d.ts.map +1 -1
  22. package/dist/types/common/sorting/index.d.ts.map +1 -1
  23. package/dist/types/form/form-definition/index.d.ts.map +1 -1
  24. package/dist/types/form/form-editor/index.d.ts +12 -12
  25. package/dist/types/form/form-editor/index.d.ts.map +1 -1
  26. package/dist/types/form/form-manager/index.d.ts.map +1 -1
  27. package/dist/types/form/form-metadata/index.d.ts.map +1 -1
  28. package/dist/types/form/form-submission/index.d.ts.map +1 -1
  29. package/package.json +6 -4
  30. package/scripts/generate-schemas.js +238 -0
  31. package/scripts/schema-modules/constants.js +39 -0
  32. package/scripts/schema-modules/schema-processors.js +109 -0
  33. package/scripts/schema-modules/schema-simplifiers.js +351 -0
  34. package/scripts/schema-modules/title-processors.js +327 -0
  35. package/scripts/schema-modules/types.js +21 -0
  36. package/scripts/schema-modules/utils.js +41 -0
  37. package/src/common/pagination/index.ts +8 -1
  38. package/src/common/search/index.ts +17 -3
  39. package/src/common/sorting/index.ts +8 -2
  40. package/src/form/form-definition/index.ts +567 -238
  41. package/src/form/form-editor/index.ts +202 -29
  42. package/src/form/form-manager/index.ts +11 -2
  43. package/src/form/form-metadata/index.ts +118 -40
  44. package/src/form/form-submission/index.ts +33 -10
  45. package/src/types/joi-to-json.d.ts +15 -0
@@ -0,0 +1,351 @@
1
+ import {
2
+ CONDITION_TYPES,
3
+ DESCRIPTIONS,
4
+ PATH_SEGMENTS,
5
+ VALUE_TYPES
6
+ } from './constants.js'
7
+ import { fixConditionTitles } from './schema-processors.js'
8
+
9
+ /**
10
+ * Handles reference-specific titles
11
+ * @param {SchemaObject} result - Schema to process
12
+ */
13
+ export function handleReferenceSpecificTitles(result) {
14
+ if (result.$ref?.includes('conditionGroupSchema')) {
15
+ result.title = CONDITION_TYPES.NESTED_GROUP
16
+ result.description = DESCRIPTIONS.NESTED_CONDITION_GROUP
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Simplifies anyOf validations that just enumerate types
22
+ * @param {SchemaObject} result - Schema to process
23
+ */
24
+ export function simplifyTypeEnumerations(result) {
25
+ if (!result.anyOf || !Array.isArray(result.anyOf)) {
26
+ return
27
+ }
28
+
29
+ /** @type {Array<string>} */
30
+ const types = result.anyOf
31
+ .map(
32
+ /**
33
+ * @param {SchemaObject} item
34
+ * @returns {string|undefined}
35
+ */
36
+ (item) => {
37
+ return item.type
38
+ }
39
+ )
40
+ .filter(/** @returns {val is string} */ (val) => Boolean(val))
41
+
42
+ const MIN_TYPES_FOR_ENUMERATION = 3
43
+
44
+ const isTypeEnumeration =
45
+ types.length >= MIN_TYPES_FOR_ENUMERATION &&
46
+ types.some((t) =>
47
+ ['string', 'number', 'boolean', 'array', 'object'].includes(t)
48
+ )
49
+
50
+ if (isTypeEnumeration) {
51
+ delete result.anyOf
52
+ if (
53
+ !result.description ||
54
+ result.description.indexOf('value type') === -1
55
+ ) {
56
+ result.description = `${String(result.description || '')}${result.description ? '\n\n' : ''}Value can be of various types (${types.join(', ')}).`
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Improves titles for condition items
63
+ * @param {SchemaObject} result - Schema to process
64
+ * @param {string} parentPath - Path to current schema
65
+ */
66
+ export function improveConditionItemTitles(result, parentPath) {
67
+ if (!parentPath.includes(`/${PATH_SEGMENTS.CONDITIONS}`) || !result.anyOf) {
68
+ return
69
+ }
70
+
71
+ result.anyOf.forEach(
72
+ /** @param {SchemaObject} item */ (item) => {
73
+ if (item.title?.includes('Item (object)') && item.properties) {
74
+ if (item.properties.field) {
75
+ item.title = CONDITION_TYPES.DEFINITION
76
+ } else if (item.properties.conditionName) {
77
+ item.title = CONDITION_TYPES.REFERENCE
78
+ } else if (item.properties.conditions) {
79
+ item.title = CONDITION_TYPES.NESTED_GROUP
80
+ } else {
81
+ item.title = `Unknown Condition Item Type`
82
+ }
83
+ }
84
+ }
85
+ )
86
+ }
87
+
88
+ /**
89
+ * Improves titles for value objects in conditions
90
+ * @param {SchemaObject} result - Schema to process
91
+ * @param {string} parentPath - Path to current schema
92
+ */
93
+ export function improveValueObjectTitles(result, parentPath) {
94
+ if (!parentPath.includes('/value') || !result.anyOf) {
95
+ return
96
+ }
97
+
98
+ result.anyOf.forEach(
99
+ /** @param {SchemaObject} item */ (item) => {
100
+ if (!item.properties) {
101
+ return
102
+ }
103
+
104
+ if (
105
+ item.properties.period &&
106
+ item.properties.unit &&
107
+ item.properties.direction
108
+ ) {
109
+ item.title = VALUE_TYPES.RELATIVE_DATE
110
+ } else if (
111
+ item.properties.value &&
112
+ item.properties.type &&
113
+ item.properties.display
114
+ ) {
115
+ item.title = VALUE_TYPES.STATIC
116
+ } else {
117
+ item.title = 'Unknown Value Type'
118
+ }
119
+ }
120
+ )
121
+ }
122
+
123
+ /**
124
+ * Improves descriptions for condition operators
125
+ * @param {SchemaObject} result - Schema to process
126
+ * @param {string} parentPath - Path to current schema
127
+ */
128
+ export function improveOperatorDescriptions(result, parentPath) {
129
+ if (
130
+ parentPath.endsWith('/operator') &&
131
+ parentPath.includes(`/${PATH_SEGMENTS.CONDITIONS}`)
132
+ ) {
133
+ result.description =
134
+ 'Comparison operator: equals, notEquals, contains, notContains, greaterThan, lessThan, isEmpty, isNotEmpty'
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Adds examples to recursive condition groups
140
+ * @param {SchemaObject} result - Schema to process
141
+ * @param {string} parentPath - Path to current schema
142
+ */
143
+ export function addExamplesToConditionGroups(result, parentPath) {
144
+ if (
145
+ result.title !== 'Condition Group Schema' &&
146
+ !parentPath.includes('conditionGroupSchema')
147
+ ) {
148
+ return
149
+ }
150
+
151
+ if (result.description) {
152
+ result.description = `${result.description} Note: This structure can be nested multiple levels deep for complex condition logic.`
153
+ }
154
+
155
+ result.examples = [
156
+ {
157
+ conditions: [
158
+ {
159
+ field: {
160
+ name: 'farmType',
161
+ type: 'string',
162
+ display: 'Type of farm'
163
+ },
164
+ operator: 'equals',
165
+ value: {
166
+ type: 'string',
167
+ value: 'livestock',
168
+ display: 'Livestock farm'
169
+ },
170
+ coordinator: 'AND'
171
+ },
172
+ {
173
+ conditionName: 'hasEnvironmentalPermit',
174
+ conditionDisplayName: 'Has an environmental permit',
175
+ coordinator: 'OR'
176
+ }
177
+ ]
178
+ }
179
+ ]
180
+ }
181
+
182
+ /**
183
+ * Handles repeat property special case
184
+ * @param {SchemaObject} result - Schema to process
185
+ * @param {string} parentPath - Path to current schema
186
+ * @returns {SchemaObject|null} Processed schema or null if no special handling needed
187
+ */
188
+ export function handleRepeatProperty(result, parentPath) {
189
+ if (
190
+ !parentPath.endsWith('/repeat') ||
191
+ !result.oneOf ||
192
+ result.oneOf.length <= 1
193
+ ) {
194
+ return null
195
+ }
196
+
197
+ const mainOption = result.oneOf[0]
198
+
199
+ mainOption.description = `${mainOption.description || ''}\n\nNOTE: This configuration is only used when the 'controller' property is set to 'RepeatPageController'. Otherwise, this property should not be specified.`
200
+
201
+ delete result.oneOf
202
+ delete result.anyOf
203
+
204
+ Object.assign(result, mainOption)
205
+ return result
206
+ }
207
+
208
+ /**
209
+ * Handles name/title fields
210
+ * @param {SchemaObject} result - Schema to process
211
+ * @param {string} parentPath - Path to current schema
212
+ */
213
+ export function handleNameTitleFields(result, parentPath) {
214
+ if (
215
+ (parentPath.endsWith('/name') || parentPath.endsWith('/title')) &&
216
+ result.oneOf &&
217
+ result.oneOf.length > 1 &&
218
+ result.anyOf
219
+ ) {
220
+ delete result.anyOf
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Improves titles for list items
226
+ * @param {SchemaObject} result - Schema to process
227
+ * @param {string} parentPath - Path to current schema
228
+ */
229
+ export function improveListItemTitles(result, parentPath) {
230
+ if (
231
+ !parentPath.endsWith(`/${PATH_SEGMENTS.ITEMS}`) ||
232
+ !result.oneOf ||
233
+ result.oneOf.length <= 1
234
+ ) {
235
+ return
236
+ }
237
+
238
+ result.oneOf.forEach(
239
+ /** @param {SchemaObject} option */ (option) => {
240
+ if (option.description?.includes('string values')) {
241
+ option.title = 'String List Items'
242
+ } else if (option.description?.includes('numeric values')) {
243
+ option.title = 'Number List Items'
244
+ } else {
245
+ option.title = 'Generic List Items'
246
+ }
247
+ }
248
+ )
249
+
250
+ if (result.anyOf) {
251
+ delete result.anyOf
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Simplifies documentation for condition items in arrays
257
+ * @param {SchemaObject} result - Schema to process
258
+ * @param {string} parentPath - Path to current schema
259
+ */
260
+ export function simplifyConditionArrays(result, parentPath) {
261
+ if (
262
+ !parentPath.includes(
263
+ `${PATH_SEGMENTS.CONDITIONS}/${PATH_SEGMENTS.ITEMS}`
264
+ ) &&
265
+ !parentPath.endsWith(`/${PATH_SEGMENTS.CONDITIONS}`)
266
+ ) {
267
+ return
268
+ }
269
+
270
+ if (result.items?.anyOf) {
271
+ result.description = `${result.description || ''}\n\nElements can be direct conditions, references to named conditions, or nested condition groups. This structure allows building complex logical expressions with AND/OR operators.`
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Recursively processes properties of a schema
277
+ * @param {SchemaObject} result - Schema to process
278
+ * @param {string} parentPath - Path to current schema
279
+ */
280
+ export function processNestedSchemas(result, parentPath) {
281
+ if (result.properties) {
282
+ Object.entries(result.properties).forEach(([propName, propSchema]) => {
283
+ result.properties[propName] = simplifyForDocs(
284
+ propSchema,
285
+ `${parentPath}/${PATH_SEGMENTS.PROPERTIES}/${propName}`
286
+ )
287
+ })
288
+ }
289
+
290
+ if (result.items) {
291
+ if (Array.isArray(result.items)) {
292
+ result.items = result.items.map((item, idx) =>
293
+ simplifyForDocs(item, `${parentPath}/${PATH_SEGMENTS.ITEMS}/${idx}`)
294
+ )
295
+ } else {
296
+ result.items = simplifyForDocs(
297
+ result.items,
298
+ `${parentPath}/${PATH_SEGMENTS.ITEMS}`
299
+ )
300
+ }
301
+ }
302
+
303
+ ;['oneOf', 'anyOf', 'allOf'].forEach((keyword) => {
304
+ if (result[keyword] && Array.isArray(result[keyword])) {
305
+ result[keyword] = result[keyword].map((subSchema, idx) =>
306
+ simplifyForDocs(subSchema, `${parentPath}/${keyword}/${idx}`)
307
+ )
308
+ }
309
+ })
310
+ }
311
+
312
+ /**
313
+ * Simplifies a schema for documentation purposes by removing unnecessary internal validation structures
314
+ * @param {SchemaObject} schema - The schema to simplify
315
+ * @param {string} parentPath - Path to the current schema location
316
+ * @returns {SchemaObject} Simplified schema
317
+ */
318
+ export function simplifyForDocs(schema, parentPath = '') {
319
+ if (typeof schema !== 'object') {
320
+ return schema
321
+ }
322
+
323
+ /** @type {SchemaObject} */
324
+ const result = JSON.parse(JSON.stringify(schema))
325
+
326
+ handleReferenceSpecificTitles(result)
327
+ simplifyTypeEnumerations(result)
328
+ improveConditionItemTitles(result, parentPath)
329
+ improveValueObjectTitles(result, parentPath)
330
+ improveOperatorDescriptions(result, parentPath)
331
+ addExamplesToConditionGroups(result, parentPath)
332
+
333
+ const specialResult = handleRepeatProperty(result, parentPath)
334
+ if (specialResult) {
335
+ return specialResult
336
+ }
337
+
338
+ handleNameTitleFields(result, parentPath)
339
+ improveListItemTitles(result, parentPath)
340
+ simplifyConditionArrays(result, parentPath)
341
+
342
+ fixConditionTitles(result)
343
+
344
+ processNestedSchemas(result, parentPath)
345
+
346
+ return result
347
+ }
348
+
349
+ /**
350
+ * @import { SchemaObject } from './types.js'
351
+ */
@@ -0,0 +1,327 @@
1
+ import { formatPropertyName } from './utils.js'
2
+
3
+ /**
4
+ * Sets a title on schema object if missing
5
+ * @param {SchemaObject} schema - Schema object to update
6
+ * @param {string} parentName - Parent object name for context
7
+ */
8
+ export function setSchemaTitle(schema, parentName) {
9
+ if (schema.title) {
10
+ return
11
+ } else if (schema.description && typeof schema.description === 'string') {
12
+ schema.title = schema.description.split('.')[0].trim()
13
+ } else if (parentName) {
14
+ schema.title = formatPropertyName(parentName)
15
+ } else if (schema.type) {
16
+ schema.title = formatPropertyName(String(schema.type))
17
+ } else {
18
+ schema.title = 'Unknown Schema'
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Sets titles for repeat-related schemas
24
+ * @param {SchemaObject} subSchema - Schema to update
25
+ * @param {number} index - Index in array
26
+ */
27
+ export function setRepeatTitles(subSchema, index) {
28
+ if (index === 0) {
29
+ subSchema.title = 'Repeat Configuration'
30
+ subSchema.description =
31
+ 'Configuration for repeatable pages, used with RepeatPageController. ' +
32
+ 'Defines how repeatable sections behave including min/max repetitions and display options.'
33
+ } else {
34
+ subSchema.title = 'Alternative Validation'
35
+ subSchema.description =
36
+ 'Alternative validation schema used internally. Not typically configured directly.'
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Sets titles for component name schemas
42
+ * @param {SchemaObject} subSchema - Schema to update
43
+ * @param {number} index - Index in array
44
+ */
45
+ export function setNameTitles(subSchema, index) {
46
+ if (index === 0) {
47
+ subSchema.title = 'Display Component Name'
48
+ subSchema.description =
49
+ 'Name format for display-only components like HTML, Markdown, etc.'
50
+ } else {
51
+ subSchema.title = 'Input Component Name'
52
+ subSchema.description =
53
+ 'Name format for input components that collect user data.'
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Sets titles for title-related schemas
59
+ * @param {SchemaObject} subSchema - Schema to update
60
+ * @param {number} index - Index in array
61
+ */
62
+ export function setTitleTitles(subSchema, index) {
63
+ if (index === 0) {
64
+ subSchema.title = 'Display Component Title'
65
+ subSchema.description = 'Title format for display-only components.'
66
+ } else {
67
+ subSchema.title = 'Input Component Title'
68
+ subSchema.description = 'Title displayed above input components.'
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Sets titles for pages schemas
74
+ * @param {SchemaObject} subSchema - Schema to update
75
+ * @param {number} index - Index in array
76
+ */
77
+ export function setPagesTitles(subSchema, index) {
78
+ if (index === 0) {
79
+ subSchema.title = 'V2 Pages'
80
+ subSchema.description =
81
+ 'Pages definition for forms using engine version V2.'
82
+ } else {
83
+ subSchema.title = 'V1 Pages'
84
+ subSchema.description =
85
+ 'Pages definition for forms using engine version V1.'
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Sets specific titles based on parent name and keyword
91
+ * @param {SchemaObject} subSchema - The schema to process
92
+ * @param {string} parentName - Parent name
93
+ * @param {string} keyword - oneOf, anyOf, or allOf
94
+ * @param {number} index - Index in the array
95
+ * @returns {boolean} Whether a specific title was set
96
+ */
97
+ export function setSpecificTitle(subSchema, parentName, keyword, index) {
98
+ if (parentName === 'repeat' && keyword === 'oneOf') {
99
+ setRepeatTitles(subSchema, index)
100
+ return true
101
+ }
102
+
103
+ if (parentName === 'name' && keyword === 'oneOf') {
104
+ setNameTitles(subSchema, index)
105
+ return true
106
+ }
107
+
108
+ if (parentName === 'title' && keyword === 'oneOf') {
109
+ setTitleTitles(subSchema, index)
110
+ return true
111
+ }
112
+
113
+ if (parentName === 'pages' && keyword === 'oneOf') {
114
+ setPagesTitles(subSchema, index)
115
+ return true
116
+ }
117
+
118
+ return false
119
+ }
120
+
121
+ /**
122
+ * Sets title for alternative validation schemas
123
+ * @param {SchemaObject} subSchema - The schema to process
124
+ * @param {string} parentName - Parent name
125
+ * @param {string} keyword - oneOf, anyOf, or allOf
126
+ */
127
+ export function setAlternativeValidationTitle(subSchema, parentName, keyword) {
128
+ if (
129
+ keyword === 'anyOf' &&
130
+ parentName.includes('Alternative Validation') &&
131
+ subSchema.type
132
+ ) {
133
+ subSchema.title = `${subSchema.type} Type`
134
+ subSchema.description = `**INTERNAL VALIDATION ONLY** - This is an internal schema structure used for validation purposes.
135
+ When using the ${parentName} property, you should only configure the Repeat Configuration structure
136
+ when controller is set to "RepeatPageController".`
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Handles specialized title setting for oneOf/anyOf cases
142
+ * @param {SchemaObject} subSchema - The schema to process
143
+ * @param {string} parentName - Parent name
144
+ * @param {string} keyword - oneOf, anyOf, or allOf
145
+ * @param {number} index - Index in the array
146
+ */
147
+ export function handleSpecialTitles(subSchema, parentName, keyword, index) {
148
+ if (subSchema.title) {
149
+ return
150
+ }
151
+
152
+ if (!setSpecificTitle(subSchema, parentName, keyword, index)) {
153
+ if (subSchema.type) {
154
+ subSchema.title = `${formatPropertyName(parentName)} (${subSchema.type})`
155
+ } else {
156
+ subSchema.title = `${formatPropertyName(parentName)} Variant ${index + 1}`
157
+ }
158
+ }
159
+
160
+ setAlternativeValidationTitle(subSchema, parentName, keyword)
161
+ }
162
+
163
+ /**
164
+ * Process schema properties
165
+ * @param {SchemaObject} schema - Schema to process
166
+ */
167
+ export function processProperties(schema) {
168
+ if (!schema.properties || typeof schema.properties !== 'object') {
169
+ return
170
+ }
171
+
172
+ const entries = Object.entries(schema.properties)
173
+
174
+ entries.forEach(([propName, propSchema]) => {
175
+ if (!propSchema.title) {
176
+ propSchema.title = formatPropertyName(propName)
177
+ }
178
+
179
+ if (!propSchema.description && propSchema.title) {
180
+ propSchema.description = `The ${propSchema.title.toLowerCase()} value.`
181
+ }
182
+
183
+ addTitles(propSchema, propName)
184
+ })
185
+ }
186
+
187
+ /**
188
+ * Process array items in schema
189
+ * @param {SchemaObject} schema - Schema to process
190
+ * @param {string} parentName - Parent object name
191
+ */
192
+ export function processArrayItems(schema, parentName) {
193
+ if (!schema.items) {
194
+ return
195
+ }
196
+
197
+ if (Array.isArray(schema.items)) {
198
+ schema.items.forEach((item, index) => {
199
+ if (!item.title) {
200
+ item.title = `${formatPropertyName(parentName)} Item ${index + 1}`
201
+ }
202
+ addTitles(item, item.title)
203
+ })
204
+ } else {
205
+ if (!schema.items.title) {
206
+ schema.items.title = `${formatPropertyName(parentName)} Item`
207
+ }
208
+ addTitles(schema.items, schema.items.title)
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Process combination keywords (oneOf, anyOf, allOf)
214
+ * @param {SchemaObject} schema - Schema to process
215
+ * @param {string} parentName - Parent object name
216
+ */
217
+ export function processCombinationKeywords(schema, parentName) {
218
+ ;['oneOf', 'anyOf', 'allOf'].forEach((keyword) => {
219
+ if (!schema[keyword] || !Array.isArray(schema[keyword])) {
220
+ return
221
+ }
222
+
223
+ schema[`${keyword}Titles`] = []
224
+
225
+ schema[keyword].forEach((subSchema, index) => {
226
+ handleSpecialTitles(subSchema, parentName, keyword, index)
227
+
228
+ schema[`${keyword}Titles`][index] = subSchema.title
229
+ addTitles(subSchema, subSchema.title || parentName)
230
+ })
231
+ })
232
+ }
233
+
234
+ /**
235
+ * Process schema references and definitions
236
+ * @param {SchemaObject} schema - Schema to process
237
+ */
238
+ export function processReferences(schema) {
239
+ if (schema.schemas) {
240
+ Object.entries(schema.schemas).forEach(([schemaName, schemaObj]) => {
241
+ if (!schemaObj.title) {
242
+ schemaObj.title = formatPropertyName(schemaName)
243
+ }
244
+ addTitles(schemaObj, schemaName)
245
+ })
246
+ }
247
+
248
+ if (schema.components?.schemas) {
249
+ Object.entries(schema.components.schemas).forEach(
250
+ ([schemaName, schemaObj]) => {
251
+ if (!schemaObj.title) {
252
+ schemaObj.title = formatPropertyName(schemaName)
253
+ }
254
+ addTitles(schemaObj, schemaName)
255
+ }
256
+ )
257
+ }
258
+
259
+ if (schema.$defs) {
260
+ Object.entries(schema.$defs).forEach(([defName, defSchema]) => {
261
+ if (!defSchema.title) {
262
+ defSchema.title = formatPropertyName(defName)
263
+ }
264
+ addTitles(defSchema, defName)
265
+ })
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Process additional and pattern properties
271
+ * @param {SchemaObject} schema - Schema to process
272
+ * @param {string} parentName - Parent object name
273
+ */
274
+ export function processAdditionalProperties(schema, parentName) {
275
+ if (
276
+ schema.additionalProperties &&
277
+ typeof schema.additionalProperties === 'object'
278
+ ) {
279
+ if (!schema.additionalProperties.title) {
280
+ schema.additionalProperties.title = `Additional Properties (${parentName})`
281
+ }
282
+ addTitles(schema.additionalProperties, `Additional ${parentName}`)
283
+ }
284
+
285
+ if (schema.patternProperties) {
286
+ Object.entries(schema.patternProperties).forEach(
287
+ ([pattern, patternSchema]) => {
288
+ if (!patternSchema.title) {
289
+ patternSchema.title = `Pattern Property (${pattern})`
290
+ }
291
+ addTitles(patternSchema, `Pattern ${parentName}`)
292
+ }
293
+ )
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Recursively enhances schemas with descriptive titles and descriptions
299
+ * for better documentation output
300
+ * @param {SchemaObject} schema - The schema object to enhance
301
+ * @param {string} parentName - Name of the parent object for context
302
+ * @returns {SchemaObject} The enhanced schema
303
+ */
304
+ export function addTitles(schema, parentName = '') {
305
+ if (!schema || typeof schema !== 'object') {
306
+ return schema
307
+ }
308
+
309
+ setSchemaTitle(schema, parentName)
310
+
311
+ // process oneOf, anyOf, allOf with improved titles
312
+ processCombinationKeywords(schema, parentName)
313
+
314
+ processProperties(schema)
315
+
316
+ processArrayItems(schema, parentName)
317
+
318
+ processReferences(schema)
319
+
320
+ processAdditionalProperties(schema, parentName)
321
+
322
+ return schema
323
+ }
324
+
325
+ /**
326
+ * @import { SchemaObject } from './types.js'
327
+ */
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @typedef {{[key: string]: any}} StringIndexedObject
3
+ * @typedef {object & StringIndexedObject} SchemaObject
4
+ * @property {string} [title] - Schema title
5
+ * @property {string} [description] - Schema description
6
+ * @property {string} [type] - Schema type
7
+ * @property {{[key: string]: SchemaObject}} [properties] - Schema properties
8
+ * @property {SchemaObject|SchemaObject[]} [items] - Array items schema
9
+ * @property {SchemaObject[]} [oneOf] - OneOf array of schemas
10
+ * @property {SchemaObject[]} [anyOf] - AnyOf array of schemas
11
+ * @property {SchemaObject[]} [allOf] - AllOf array of schemas
12
+ * @property {object} [additionalProperties] - Additional properties schema
13
+ * @property {object} [patternProperties] - Pattern properties schemas
14
+ * @property {object} [components] - OpenAPI components
15
+ * @property {object} [schemas] - OpenAPI schemas
16
+ * @property {object} [$defs] - JSON Schema definitions
17
+ * @property {string} [$ref] - JSON Schema reference
18
+ * @property {*} [x] - Any additional properties with string keys
19
+ */
20
+
21
+ export {}