@kravc/schema 2.7.6 → 2.8.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +19 -14
  2. package/dist/CredentialFactory.d.ts +345 -0
  3. package/dist/CredentialFactory.d.ts.map +1 -0
  4. package/dist/CredentialFactory.js +381 -0
  5. package/dist/CredentialFactory.js.map +1 -0
  6. package/dist/Schema.d.ts +448 -0
  7. package/dist/Schema.d.ts.map +1 -0
  8. package/dist/Schema.js +506 -0
  9. package/dist/Schema.js.map +1 -0
  10. package/dist/ValidationError.d.ts +70 -0
  11. package/dist/ValidationError.d.ts.map +1 -0
  12. package/dist/ValidationError.js +78 -0
  13. package/dist/ValidationError.js.map +1 -0
  14. package/dist/Validator.d.ts +483 -0
  15. package/dist/Validator.d.ts.map +1 -0
  16. package/dist/Validator.js +570 -0
  17. package/dist/Validator.js.map +1 -0
  18. package/dist/helpers/JsonSchema.d.ts +99 -0
  19. package/dist/helpers/JsonSchema.d.ts.map +1 -0
  20. package/dist/helpers/JsonSchema.js +3 -0
  21. package/dist/helpers/JsonSchema.js.map +1 -0
  22. package/dist/helpers/cleanupAttributes.d.ts +34 -0
  23. package/dist/helpers/cleanupAttributes.d.ts.map +1 -0
  24. package/dist/helpers/cleanupAttributes.js +113 -0
  25. package/dist/helpers/cleanupAttributes.js.map +1 -0
  26. package/dist/helpers/cleanupNulls.d.ts +27 -0
  27. package/dist/helpers/cleanupNulls.d.ts.map +1 -0
  28. package/dist/helpers/cleanupNulls.js +96 -0
  29. package/dist/helpers/cleanupNulls.js.map +1 -0
  30. package/dist/helpers/getReferenceIds.d.ts +169 -0
  31. package/dist/helpers/getReferenceIds.d.ts.map +1 -0
  32. package/dist/helpers/getReferenceIds.js +241 -0
  33. package/dist/helpers/getReferenceIds.js.map +1 -0
  34. package/dist/helpers/got.d.ts +60 -0
  35. package/dist/helpers/got.d.ts.map +1 -0
  36. package/dist/helpers/got.js +72 -0
  37. package/dist/helpers/got.js.map +1 -0
  38. package/dist/helpers/mapObjectProperties.d.ts +150 -0
  39. package/dist/helpers/mapObjectProperties.d.ts.map +1 -0
  40. package/dist/helpers/mapObjectProperties.js +229 -0
  41. package/dist/helpers/mapObjectProperties.js.map +1 -0
  42. package/dist/helpers/normalizeAttributes.d.ts +213 -0
  43. package/dist/helpers/normalizeAttributes.d.ts.map +1 -0
  44. package/dist/helpers/normalizeAttributes.js +243 -0
  45. package/dist/helpers/normalizeAttributes.js.map +1 -0
  46. package/dist/helpers/normalizeProperties.d.ts +168 -0
  47. package/dist/helpers/normalizeProperties.d.ts.map +1 -0
  48. package/dist/helpers/normalizeProperties.js +223 -0
  49. package/dist/helpers/normalizeProperties.js.map +1 -0
  50. package/dist/helpers/normalizeRequired.d.ts +159 -0
  51. package/dist/helpers/normalizeRequired.d.ts.map +1 -0
  52. package/dist/helpers/normalizeRequired.js +206 -0
  53. package/dist/helpers/normalizeRequired.js.map +1 -0
  54. package/dist/helpers/normalizeType.d.ts +81 -0
  55. package/dist/helpers/normalizeType.d.ts.map +1 -0
  56. package/dist/helpers/normalizeType.js +210 -0
  57. package/dist/helpers/normalizeType.js.map +1 -0
  58. package/dist/helpers/nullifyEmptyValues.d.ts +139 -0
  59. package/dist/helpers/nullifyEmptyValues.d.ts.map +1 -0
  60. package/dist/helpers/nullifyEmptyValues.js +191 -0
  61. package/dist/helpers/nullifyEmptyValues.js.map +1 -0
  62. package/dist/helpers/removeRequiredAndDefault.d.ts +106 -0
  63. package/dist/helpers/removeRequiredAndDefault.d.ts.map +1 -0
  64. package/dist/helpers/removeRequiredAndDefault.js +138 -0
  65. package/dist/helpers/removeRequiredAndDefault.js.map +1 -0
  66. package/dist/helpers/validateId.d.ts +39 -0
  67. package/dist/helpers/validateId.d.ts.map +1 -0
  68. package/dist/helpers/validateId.js +51 -0
  69. package/dist/helpers/validateId.js.map +1 -0
  70. package/dist/index.d.ts +7 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +17 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/ld/documentLoader.d.ts +8 -0
  75. package/dist/ld/documentLoader.d.ts.map +1 -0
  76. package/dist/ld/documentLoader.js +24 -0
  77. package/dist/ld/documentLoader.js.map +1 -0
  78. package/dist/ld/getLinkedDataAttributeType.d.ts +10 -0
  79. package/dist/ld/getLinkedDataAttributeType.d.ts.map +1 -0
  80. package/dist/ld/getLinkedDataAttributeType.js +32 -0
  81. package/dist/ld/getLinkedDataAttributeType.js.map +1 -0
  82. package/dist/ld/getLinkedDataContext.d.ts +19 -0
  83. package/dist/ld/getLinkedDataContext.d.ts.map +1 -0
  84. package/dist/ld/getLinkedDataContext.js +50 -0
  85. package/dist/ld/getLinkedDataContext.js.map +1 -0
  86. package/eslint.config.mjs +32 -52
  87. package/examples/credentials/createAccountCredential.ts +27 -0
  88. package/examples/credentials/createMineSweeperScoreCredential.ts +115 -0
  89. package/examples/index.ts +7 -0
  90. package/examples/schemas/FavoriteItemSchema.ts +27 -0
  91. package/examples/{Preferences.yaml → schemas/Preferences.yaml} +2 -0
  92. package/examples/schemas/PreferencesSchema.ts +29 -0
  93. package/examples/schemas/ProfileSchema.ts +91 -0
  94. package/examples/schemas/Status.yaml +3 -0
  95. package/examples/schemas/StatusSchema.ts +12 -0
  96. package/jest.config.mjs +5 -0
  97. package/package.json +27 -20
  98. package/src/CredentialFactory.ts +392 -0
  99. package/src/Schema.ts +583 -0
  100. package/src/ValidationError.ts +90 -0
  101. package/src/Validator.ts +603 -0
  102. package/src/__tests__/CredentialFactory.test.ts +588 -0
  103. package/src/__tests__/Schema.test.ts +371 -0
  104. package/src/__tests__/ValidationError.test.ts +235 -0
  105. package/src/__tests__/Validator.test.ts +787 -0
  106. package/src/helpers/JsonSchema.ts +119 -0
  107. package/src/helpers/__tests__/cleanupAttributes.test.ts +943 -0
  108. package/src/helpers/__tests__/cleanupNulls.test.ts +772 -0
  109. package/src/helpers/__tests__/getReferenceIds.test.ts +975 -0
  110. package/src/helpers/__tests__/got.test.ts +193 -0
  111. package/src/helpers/__tests__/mapObjectProperties.test.ts +1126 -0
  112. package/src/helpers/__tests__/normalizeAttributes.test.ts +1435 -0
  113. package/src/helpers/__tests__/normalizeProperties.test.ts +727 -0
  114. package/src/helpers/__tests__/normalizeRequired.test.ts +669 -0
  115. package/src/helpers/__tests__/normalizeType.test.ts +772 -0
  116. package/src/helpers/__tests__/nullifyEmptyValues.test.ts +735 -0
  117. package/src/helpers/__tests__/removeRequiredAndDefault.test.ts +734 -0
  118. package/src/helpers/__tests__/validateId.test.ts +118 -0
  119. package/src/helpers/cleanupAttributes.ts +151 -0
  120. package/src/helpers/cleanupNulls.ts +106 -0
  121. package/src/helpers/getReferenceIds.ts +273 -0
  122. package/src/helpers/got.ts +73 -0
  123. package/src/helpers/mapObjectProperties.ts +272 -0
  124. package/src/helpers/normalizeAttributes.ts +247 -0
  125. package/src/helpers/normalizeProperties.ts +249 -0
  126. package/src/helpers/normalizeRequired.ts +233 -0
  127. package/src/helpers/normalizeType.ts +235 -0
  128. package/src/helpers/nullifyEmptyValues.ts +207 -0
  129. package/src/helpers/removeRequiredAndDefault.ts +151 -0
  130. package/src/helpers/validateId.ts +53 -0
  131. package/src/index.ts +13 -0
  132. package/src/ld/__tests__/documentLoader.test.ts +57 -0
  133. package/src/ld/__tests__/getLinkedDataAttributeType.test.ts +212 -0
  134. package/src/ld/__tests__/getLinkedDataContext.test.ts +378 -0
  135. package/src/ld/documentLoader.ts +28 -0
  136. package/src/ld/getLinkedDataAttributeType.ts +46 -0
  137. package/src/ld/getLinkedDataContext.ts +80 -0
  138. package/tsconfig.json +27 -0
  139. package/types/credentials-context.d.ts +14 -0
  140. package/types/security-context.d.ts +6 -0
  141. package/examples/Status.yaml +0 -3
  142. package/examples/createAccountCredential.js +0 -27
  143. package/examples/createMineSweeperScoreCredential.js +0 -63
  144. package/examples/index.js +0 -9
  145. package/src/CredentialFactory.js +0 -67
  146. package/src/CredentialFactory.spec.js +0 -131
  147. package/src/Schema.js +0 -104
  148. package/src/Schema.spec.js +0 -172
  149. package/src/ValidationError.js +0 -31
  150. package/src/Validator.js +0 -128
  151. package/src/Validator.spec.js +0 -355
  152. package/src/helpers/cleanupAttributes.js +0 -71
  153. package/src/helpers/cleanupNulls.js +0 -42
  154. package/src/helpers/getReferenceIds.js +0 -71
  155. package/src/helpers/mapObject.js +0 -65
  156. package/src/helpers/normalizeAttributes.js +0 -28
  157. package/src/helpers/normalizeProperties.js +0 -61
  158. package/src/helpers/normalizeRequired.js +0 -37
  159. package/src/helpers/normalizeType.js +0 -41
  160. package/src/helpers/nullifyEmptyValues.js +0 -57
  161. package/src/helpers/removeRequiredAndDefault.js +0 -30
  162. package/src/helpers/validateId.js +0 -19
  163. package/src/index.d.ts +0 -25
  164. package/src/index.js +0 -8
  165. package/src/ld/documentLoader.js +0 -25
  166. package/src/ld/documentLoader.spec.js +0 -12
  167. package/src/ld/getLinkedDataContext.js +0 -63
  168. package/src/ld/getLinkedDataType.js +0 -38
  169. /package/examples/{FavoriteItem.yaml → schemas/FavoriteItem.yaml} +0 -0
  170. /package/examples/{Profile.yaml → schemas/Profile.yaml} +0 -0
@@ -0,0 +1,249 @@
1
+ import { get, set, isUndefined } from 'lodash';
2
+
3
+ import type {
4
+ EnumSchema,
5
+ PropertiesSchema,
6
+ ObjectPropertySchema,
7
+ ReferencePropertySchema,
8
+ ArrayPropertySchema
9
+ } from './JsonSchema';
10
+
11
+ /**
12
+ * Normalizes JSON schema properties by ensuring all properties have an explicit `type` attribute.
13
+ *
14
+ * **Intent:**
15
+ * This function transforms schemas that may have implicit or missing type information into
16
+ * fully normalized schemas with explicit types. It recursively processes nested structures
17
+ * (objects and arrays) to ensure type consistency throughout the schema hierarchy.
18
+ *
19
+ * **Use Cases:**
20
+ * - **Schema Validation Preparation**: Ensures schemas are ready for validation where type
21
+ * information is required by validators or processing tools
22
+ * - **External Schema Normalization**: Normalizes schemas imported from external sources
23
+ * (e.g., OpenAPI specs, YAML schemas) that may omit type information
24
+ * - **Type Inference**: Automatically infers types from structural hints (e.g., presence of
25
+ * `properties` implies `object`, presence of `items` implies `array`)
26
+ * - **Schema Consistency**: Guarantees consistent schema structure before further processing
27
+ * or transformation
28
+ * - **Default Type Assignment**: Provides sensible defaults (e.g., `string` for primitives)
29
+ * when type cannot be inferred
30
+ *
31
+ * **Behavior:**
32
+ * - **Enum Schemas**: Sets `type` to `'string'` if missing (preserves existing type if present)
33
+ * - **Reference Properties**: Skips `$ref` properties (they don't need type normalization)
34
+ * - **Type Inference Priority** (when type is missing):
35
+ * 1. If property has `properties` → sets `type: 'object'`
36
+ * 2. Else if property has `items` → sets `type: 'array'`
37
+ * 3. Else → sets `type: 'string'` (default)
38
+ * - **Object Properties**:
39
+ * - Infers `type: 'object'` from presence of `properties` (if type not already set)
40
+ * - Creates empty `properties: {}` if `type: 'object'` but no properties exist
41
+ * - Recursively normalizes nested object properties
42
+ * - Note: If both `properties` and `items` exist, `properties` takes precedence
43
+ * - **Array Properties**:
44
+ * - Infers `type: 'array'` from presence of `items` (if type not already set)
45
+ * - Sets default `items: { type: 'string' }` if array has no items
46
+ * - Normalizes item schemas: sets `type: 'object'` if items have `properties` (not undefined)
47
+ * - `properties: null` → treated as having properties (sets type to 'object')
48
+ * - `properties: undefined` → treated as not having properties (no type set)
49
+ * - Empty object `{}` → treated as not having properties (no type set)
50
+ * - Recursively normalizes nested properties within array items
51
+ * - **Primitive Properties**: Sets `type: 'string'` as default when no type, items, or properties exist
52
+ * - **Existing Types**: Never overrides existing type values (even if structure suggests different type)
53
+ * - **Edge Cases**:
54
+ * - Empty schemas (`{}`) are handled gracefully
55
+ * - Properties with conflicting structure (e.g., `type: 'object'` with `items`) are normalized
56
+ * according to the explicit type, ignoring conflicting structural hints
57
+ *
58
+ * **Examples:**
59
+ *
60
+ * @example Enum schema normalization
61
+ * ```typescript
62
+ * const schema: EnumSchema = { enum: ['red', 'green', 'blue'] };
63
+ * normalizeProperties(schema);
64
+ * // Result: { enum: ['red', 'green', 'blue'], type: 'string' }
65
+ * ```
66
+ *
67
+ * @example Object type inference
68
+ * ```typescript
69
+ * const schema: PropertiesSchema = {
70
+ * user: {
71
+ * properties: {
72
+ * name: {}
73
+ * }
74
+ * }
75
+ * };
76
+ * normalizeProperties(schema);
77
+ * // Result:
78
+ * // {
79
+ * // user: {
80
+ * // type: 'object',
81
+ * // properties: {
82
+ * // name: { type: 'string' }
83
+ * // }
84
+ * // }
85
+ * // }
86
+ * ```
87
+ *
88
+ * @example Array type inference
89
+ * ```typescript
90
+ * const schema: PropertiesSchema = {
91
+ * tags: {
92
+ * items: { type: 'string' }
93
+ * }
94
+ * };
95
+ * normalizeProperties(schema);
96
+ * // Result:
97
+ * // {
98
+ * // tags: {
99
+ * // type: 'array',
100
+ * // items: { type: 'string' }
101
+ * // }
102
+ * // }
103
+ * ```
104
+ *
105
+ * @example Complex nested structure
106
+ * ```typescript
107
+ * const schema: PropertiesSchema = {
108
+ * profile: {
109
+ * properties: {
110
+ * name: {},
111
+ * addresses: {
112
+ * items: {
113
+ * properties: {
114
+ * street: {},
115
+ * city: {}
116
+ * }
117
+ * }
118
+ * }
119
+ * }
120
+ * }
121
+ * };
122
+ * normalizeProperties(schema);
123
+ * // Result:
124
+ * // {
125
+ * // profile: {
126
+ * // type: 'object',
127
+ * // properties: {
128
+ * // name: { type: 'string' },
129
+ * // addresses: {
130
+ * // type: 'array',
131
+ * // items: {
132
+ * // type: 'object',
133
+ * // properties: {
134
+ * // street: { type: 'string' },
135
+ * // city: { type: 'string' }
136
+ * // }
137
+ * // }
138
+ * // }
139
+ * // }
140
+ * // }
141
+ * // }
142
+ * ```
143
+ *
144
+ * @example Reference properties are skipped
145
+ * ```typescript
146
+ * const schema: PropertiesSchema = {
147
+ * refField: { $ref: '#/definitions/User' },
148
+ * normalField: {}
149
+ * };
150
+ * normalizeProperties(schema);
151
+ * // Result:
152
+ * // {
153
+ * // refField: { $ref: '#/definitions/User' }, // Unchanged
154
+ * // normalField: { type: 'string' }
155
+ * // }
156
+ * ```
157
+ *
158
+ * @example Default type assignment
159
+ * ```typescript
160
+ * const schema: PropertiesSchema = {
161
+ * title: {},
162
+ * count: { type: 'number' }
163
+ * };
164
+ * normalizeProperties(schema);
165
+ * // Result:
166
+ * // {
167
+ * // title: { type: 'string' }, // Default assigned
168
+ * // count: { type: 'number' } // Existing preserved
169
+ * // }
170
+ * ```
171
+ *
172
+ * @param schema - The schema to normalize (either an EnumSchema or PropertiesSchema)
173
+ * @modifies The schema object is mutated in place with normalized types
174
+ */
175
+ const normalizeProperties = (schema: EnumSchema | PropertiesSchema) => {
176
+ const { enum: isEnum } = (schema as EnumSchema);
177
+
178
+ if (isEnum) {
179
+ const enumSchema = (schema as EnumSchema);
180
+ enumSchema.type = enumSchema.type || 'string';
181
+
182
+ return;
183
+ }
184
+
185
+ const properties = (schema as PropertiesSchema);
186
+
187
+ for (const name in properties) {
188
+ const property = properties[name];
189
+
190
+ const { $ref: isRef } = (property as ReferencePropertySchema);
191
+
192
+ if (isRef) {
193
+ continue;
194
+ }
195
+
196
+ const hasType = !!get(property, 'type');
197
+ const hasItems = !!get(property, 'items');
198
+ const hasProperties = !!get(property, 'properties');
199
+
200
+ if (!hasType) {
201
+ if (hasProperties) {
202
+ set(property, 'type', 'object');
203
+
204
+ } else if (hasItems) {
205
+ set(property, 'type', 'array');
206
+
207
+ } else {
208
+ set(property, 'type', 'string');
209
+
210
+ }
211
+ }
212
+
213
+ const type = get(property, 'type');
214
+
215
+ const isObject = type === 'object';
216
+
217
+ if (isObject) {
218
+ if (!hasProperties) {
219
+ (property as ObjectPropertySchema).properties = {};
220
+ }
221
+
222
+ // istanbul ignore next - unreachable defensive code: properties is always set to {} above if missing
223
+ normalizeProperties((property as ObjectPropertySchema).properties || {});
224
+ }
225
+
226
+ const isArray = type === 'array';
227
+
228
+ if (isArray) {
229
+ if (hasItems) {
230
+ // istanbul ignore next - unreachable defensive code: if items is undefined, hasItems would be false
231
+ const { items = {} } = (property as ArrayPropertySchema);
232
+
233
+ const isItemObject = !isUndefined((items as ObjectPropertySchema).properties);
234
+
235
+ if (isItemObject) {
236
+ set(items, 'type', 'object');
237
+
238
+ normalizeProperties((items as ObjectPropertySchema).properties || {});
239
+ }
240
+
241
+ } else {
242
+ (property as ArrayPropertySchema).items = { type: 'string' };
243
+
244
+ }
245
+ }
246
+ }
247
+ };
248
+
249
+ export default normalizeProperties;
@@ -0,0 +1,233 @@
1
+ import { isUndefined } from 'lodash';
2
+
3
+ import type {
4
+ JsonSchema,
5
+ EnumSchema,
6
+ ObjectSchema,
7
+ ArrayPropertySchema,
8
+ ObjectPropertySchema,
9
+ ReferencePropertySchema,
10
+ } from './JsonSchema';
11
+
12
+ /**
13
+ * Normalizes required field declarations in JSON schemas by converting property-level
14
+ * `required` flags to schema-level `required` arrays and `x-required` metadata flags.
15
+ *
16
+ * **Intent:**
17
+ * This function transforms JSON schemas from a property-centric required field model
18
+ * (where each property has its own `required: true/false` flag) to the standard JSON Schema
19
+ * format (where required fields are listed in a top-level `required` array). This normalization
20
+ * ensures compatibility with JSON Schema validators while preserving the original required
21
+ * field information through the `x-required` extension attribute.
22
+ *
23
+ * **Use Cases:**
24
+ * 1. **Schema Standardization**: Convert custom schema formats to standard JSON Schema format
25
+ * for validator compatibility
26
+ * 2. **Schema Transformation**: Prepare schemas for validation libraries that expect
27
+ * required fields in array format
28
+ * 3. **Metadata Preservation**: Maintain required field information in both standard format
29
+ * (`required` array) and extension format (`x-required` flag) for different use cases
30
+ * 4. **Schema Processing Pipeline**: Normalize schemas before validation, credential generation,
31
+ * or API documentation generation
32
+ *
33
+ * **Behavior:**
34
+ * - Mutates the input schema in-place
35
+ * - Moves `required: true` from property level to schema-level `required` array
36
+ * - Sets `x-required: true` on properties that were marked as required
37
+ * - Deletes the `required` property from individual property schemas
38
+ * - Recursively processes nested object properties
39
+ * - Recursively processes array items (including nested objects within arrays)
40
+ * - Skips reference properties (`$ref`) as they are resolved elsewhere
41
+ * - Skips EnumSchema (returns early)
42
+ * - Only sets `required` array if at least one field is required
43
+ *
44
+ * **Examples:**
45
+ *
46
+ * ```typescript
47
+ * // Example 1: Simple object schema
48
+ * const schema = {
49
+ * id: 'User',
50
+ * properties: {
51
+ * name: { type: 'string', required: true },
52
+ * email: { type: 'string', required: true },
53
+ * age: { type: 'number', required: false }
54
+ * }
55
+ * };
56
+ *
57
+ * normalizeRequired(schema);
58
+ * // Result:
59
+ * // schema.required = ['name', 'email']
60
+ * // schema.properties.name['x-required'] = true
61
+ * // schema.properties.email['x-required'] = true
62
+ * // schema.properties.name.required = undefined (deleted)
63
+ * // schema.properties.email.required = undefined (deleted)
64
+ * ```
65
+ *
66
+ * ```typescript
67
+ * // Example 2: Nested objects
68
+ * const schema = {
69
+ * id: 'Order',
70
+ * properties: {
71
+ * user: {
72
+ * type: 'object',
73
+ * required: true,
74
+ * properties: {
75
+ * name: { type: 'string', required: true },
76
+ * address: {
77
+ * type: 'object',
78
+ * properties: {
79
+ * street: { type: 'string', required: true }
80
+ * }
81
+ * }
82
+ * }
83
+ * }
84
+ * }
85
+ * };
86
+ *
87
+ * normalizeRequired(schema);
88
+ * // Result:
89
+ * // schema.required = ['user']
90
+ * // schema.properties.user['x-required'] = true
91
+ * // schema.properties.user.required = ['name']
92
+ * // schema.properties.user.properties.name['x-required'] = true
93
+ * // schema.properties.user.properties.address.required = ['street']
94
+ * // schema.properties.user.properties.address.properties.street['x-required'] = true
95
+ * ```
96
+ *
97
+ * ```typescript
98
+ * // Example 3: Arrays with object items
99
+ * const schema = {
100
+ * id: 'Order',
101
+ * properties: {
102
+ * items: {
103
+ * type: 'array',
104
+ * required: true,
105
+ * items: {
106
+ * type: 'object',
107
+ * properties: {
108
+ * productId: { type: 'string', required: true },
109
+ * quantity: { type: 'number', required: true }
110
+ * }
111
+ * }
112
+ * }
113
+ * }
114
+ * };
115
+ *
116
+ * normalizeRequired(schema);
117
+ * // Result:
118
+ * // schema.required = ['items']
119
+ * // schema.properties.items['x-required'] = true
120
+ * // schema.properties.items.items.required = ['productId', 'quantity']
121
+ * // schema.properties.items.items.properties.productId['x-required'] = true
122
+ * // schema.properties.items.items.properties.quantity['x-required'] = true
123
+ * ```
124
+ *
125
+ * ```typescript
126
+ * // Example 4: Mixed structure
127
+ * const schema = {
128
+ * id: 'Profile',
129
+ * properties: {
130
+ * name: { type: 'string', required: true },
131
+ * address: {
132
+ * type: 'object',
133
+ * required: true,
134
+ * properties: {
135
+ * street: { type: 'string', required: true },
136
+ * city: { type: 'string' }
137
+ * }
138
+ * },
139
+ * tags: {
140
+ * type: 'array',
141
+ * items: {
142
+ * type: 'object',
143
+ * properties: {
144
+ * label: { type: 'string', required: true }
145
+ * }
146
+ * }
147
+ * }
148
+ * }
149
+ * };
150
+ *
151
+ * normalizeRequired(schema);
152
+ * // Result:
153
+ * // schema.required = ['name', 'address']
154
+ * // All nested required fields are normalized recursively
155
+ * ```
156
+ *
157
+ * **Limitations:**
158
+ * - Only processes schemas with a `properties` field (ObjectSchema or ObjectPropertySchema)
159
+ * - EnumSchema is accepted but returns early without processing
160
+ * - Reference properties (`$ref`) are skipped and not processed
161
+ * - The function mutates the input schema object
162
+ * - Does not resolve `$ref` references (they must be resolved separately)
163
+ *
164
+ * @param jsonSchema - The JSON schema to normalize (ObjectSchema, ObjectPropertySchema, or ReferencePropertySchema)
165
+ * @returns void (mutates the input schema in-place)
166
+ */
167
+ const normalizeRequired = (jsonSchema: JsonSchema | ObjectPropertySchema | ReferencePropertySchema) => {
168
+ const { enum: enumItems } = (jsonSchema as EnumSchema);
169
+
170
+ const isEnum = !!enumItems;
171
+
172
+ if (isEnum) {
173
+ return;
174
+ }
175
+
176
+ const objectSchema = (jsonSchema as ObjectSchema);
177
+ const { properties } = objectSchema;
178
+
179
+ if (!properties) {
180
+ return;
181
+ }
182
+
183
+ const required = [];
184
+
185
+ for (const propertyName in properties) {
186
+ const property = properties[propertyName];
187
+
188
+ const { $ref: refSchemaId } = (property as ReferencePropertySchema);
189
+
190
+ const isReference = !isUndefined(refSchemaId);
191
+
192
+ // Handle required flag for all properties (including references)
193
+ if (property.required) {
194
+ property['x-required'] = true;
195
+ required.push(propertyName);
196
+ }
197
+
198
+ // Delete required property for all properties (whether true or false)
199
+ delete property.required;
200
+
201
+ // Skip recursive processing for reference properties
202
+ if (isReference) {
203
+ continue;
204
+ }
205
+
206
+ const { type } = (property as ObjectPropertySchema | ArrayPropertySchema);
207
+
208
+ const isObject = type === 'object';
209
+
210
+ if (isObject) {
211
+ normalizeRequired(property as ObjectPropertySchema);
212
+ continue;
213
+ }
214
+
215
+ const isArray = type === 'array';
216
+
217
+ if (isArray) {
218
+ const { items } = (property as ArrayPropertySchema);
219
+
220
+ if (items) {
221
+ normalizeRequired(items as ObjectPropertySchema | ReferencePropertySchema);
222
+ }
223
+
224
+ continue;
225
+ }
226
+ }
227
+
228
+ if (required.length > 0) {
229
+ objectSchema.required = required;
230
+ }
231
+ };
232
+
233
+ export default normalizeRequired;