@kravc/schema 2.8.0-alpha.7 → 2.8.0-alpha.8
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.
- package/dist/CredentialFactory.d.ts +5 -318
- package/dist/CredentialFactory.d.ts.map +1 -1
- package/dist/CredentialFactory.js +5 -317
- package/dist/CredentialFactory.js.map +1 -1
- package/dist/Schema.d.ts +15 -420
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +13 -384
- package/dist/Schema.js.map +1 -1
- package/dist/ValidationError.d.ts +3 -49
- package/dist/ValidationError.d.ts.map +1 -1
- package/dist/ValidationError.js +3 -48
- package/dist/ValidationError.js.map +1 -1
- package/dist/Validator.d.ts +6 -470
- package/dist/Validator.d.ts.map +1 -1
- package/dist/Validator.js +50 -478
- package/dist/Validator.js.map +1 -1
- package/dist/helpers/cleanupAttributes.d.ts +3 -32
- package/dist/helpers/cleanupAttributes.d.ts.map +1 -1
- package/dist/helpers/cleanupAttributes.js +1 -30
- package/dist/helpers/cleanupAttributes.js.map +1 -1
- package/dist/helpers/cleanupNulls.d.ts +1 -25
- package/dist/helpers/cleanupNulls.d.ts.map +1 -1
- package/dist/helpers/cleanupNulls.js +2 -61
- package/dist/helpers/cleanupNulls.js.map +1 -1
- package/dist/helpers/createSchemasMap.d.ts +2 -93
- package/dist/helpers/createSchemasMap.d.ts.map +1 -1
- package/dist/helpers/createSchemasMap.js +4 -162
- package/dist/helpers/createSchemasMap.js.map +1 -1
- package/dist/helpers/getReferenceIds.d.ts +1 -165
- package/dist/helpers/getReferenceIds.d.ts.map +1 -1
- package/dist/helpers/getReferenceIds.js +2 -166
- package/dist/helpers/getReferenceIds.js.map +1 -1
- package/dist/helpers/got.d.ts +1 -57
- package/dist/helpers/got.d.ts.map +1 -1
- package/dist/helpers/got.js +1 -57
- package/dist/helpers/got.js.map +1 -1
- package/dist/helpers/mapObjectProperties.d.ts +2 -144
- package/dist/helpers/mapObjectProperties.d.ts.map +1 -1
- package/dist/helpers/mapObjectProperties.js +1 -142
- package/dist/helpers/mapObjectProperties.js.map +1 -1
- package/dist/helpers/normalizeAttributes.d.ts +3 -211
- package/dist/helpers/normalizeAttributes.d.ts.map +1 -1
- package/dist/helpers/normalizeAttributes.js +1 -209
- package/dist/helpers/normalizeAttributes.js.map +1 -1
- package/dist/helpers/normalizeProperties.d.ts +1 -165
- package/dist/helpers/normalizeProperties.d.ts.map +1 -1
- package/dist/helpers/normalizeProperties.js +1 -164
- package/dist/helpers/normalizeProperties.js.map +1 -1
- package/dist/helpers/normalizeRequired.d.ts +1 -153
- package/dist/helpers/normalizeRequired.d.ts.map +1 -1
- package/dist/helpers/normalizeRequired.js +0 -151
- package/dist/helpers/normalizeRequired.js.map +1 -1
- package/dist/helpers/normalizeType.d.ts +1 -77
- package/dist/helpers/normalizeType.d.ts.map +1 -1
- package/dist/helpers/normalizeType.js +11 -139
- package/dist/helpers/normalizeType.js.map +1 -1
- package/dist/helpers/nullifyEmptyValues.d.ts +1 -135
- package/dist/helpers/nullifyEmptyValues.d.ts.map +1 -1
- package/dist/helpers/nullifyEmptyValues.js +13 -143
- package/dist/helpers/nullifyEmptyValues.js.map +1 -1
- package/dist/helpers/removeRequiredAndDefault.d.ts +2 -102
- package/dist/helpers/removeRequiredAndDefault.d.ts.map +1 -1
- package/dist/helpers/removeRequiredAndDefault.js +1 -100
- package/dist/helpers/removeRequiredAndDefault.js.map +1 -1
- package/dist/helpers/validateId.d.ts +1 -36
- package/dist/helpers/validateId.d.ts.map +1 -1
- package/dist/helpers/validateId.js +1 -36
- package/dist/helpers/validateId.js.map +1 -1
- package/dist/ld/documentLoader.d.ts +1 -1
- package/dist/ld/documentLoader.d.ts.map +1 -1
- package/dist/ld/documentLoader.js +1 -1
- package/dist/ld/documentLoader.js.map +1 -1
- package/dist/ld/getLinkedDataAttributeType.d.ts +1 -1
- package/dist/ld/getLinkedDataAttributeType.d.ts.map +1 -1
- package/dist/ld/getLinkedDataAttributeType.js +1 -1
- package/dist/ld/getLinkedDataAttributeType.js.map +1 -1
- package/dist/ld/getLinkedDataContext.d.ts +1 -1
- package/dist/ld/getLinkedDataContext.d.ts.map +1 -1
- package/dist/ld/getLinkedDataContext.js +1 -1
- package/dist/ld/getLinkedDataContext.js.map +1 -1
- package/package.json +2 -2
- package/src/CredentialFactory.ts +5 -318
- package/src/Schema.ts +17 -427
- package/src/ValidationError.ts +5 -52
- package/src/Validator.ts +19 -483
- package/src/__tests__/CredentialFactory.test.ts +1 -1
- package/src/__tests__/Schema.test.ts +3 -8
- package/src/__tests__/ValidationError.test.ts +4 -2
- package/src/__tests__/Validator.test.ts +21 -4
- package/src/helpers/__tests__/cleanupAttributes.test.ts +3 -1
- package/src/helpers/__tests__/createSchemasMap.test.ts +1 -1
- package/src/helpers/__tests__/mapObjectProperties.test.ts +2 -1
- package/src/helpers/__tests__/normalizeAttributes.test.ts +4 -2
- package/src/helpers/__tests__/normalizeProperties.test.ts +3 -1
- package/src/helpers/__tests__/normalizeRequired.test.ts +4 -9
- package/src/helpers/__tests__/nullifyEmptyValues.test.ts +43 -12
- package/src/helpers/__tests__/removeRequiredAndDefault.test.ts +3 -10
- package/src/helpers/cleanupAttributes.ts +6 -44
- package/src/helpers/cleanupNulls.ts +2 -63
- package/src/helpers/createSchemasMap.ts +4 -163
- package/src/helpers/getReferenceIds.ts +2 -173
- package/src/helpers/got.ts +1 -57
- package/src/helpers/mapObjectProperties.ts +4 -156
- package/src/helpers/normalizeAttributes.ts +7 -211
- package/src/helpers/normalizeProperties.ts +1 -172
- package/src/helpers/normalizeRequired.ts +1 -161
- package/src/helpers/normalizeType.ts +11 -139
- package/src/helpers/nullifyEmptyValues.ts +10 -145
- package/src/helpers/removeRequiredAndDefault.ts +1 -106
- package/src/helpers/validateId.ts +1 -36
- package/src/ld/documentLoader.ts +1 -1
- package/src/ld/getLinkedDataAttributeType.ts +1 -1
- package/src/ld/getLinkedDataContext.ts +1 -1
- package/src/{helpers/JsonSchema.ts → types.d.ts} +12 -16
- package/dist/helpers/JsonSchema.d.ts +0 -99
- package/dist/helpers/JsonSchema.d.ts.map +0 -1
- package/dist/helpers/JsonSchema.js +0 -3
- package/dist/helpers/JsonSchema.js.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
|
2
|
+
/// <reference path="../../types.d.ts" />
|
|
3
|
+
|
|
1
4
|
import normalizeProperties from '../normalizeProperties';
|
|
2
|
-
import type { EnumSchema, PropertiesSchema } from '../JsonSchema';
|
|
3
5
|
|
|
4
6
|
describe('normalizeProperties(schema)', () => {
|
|
5
7
|
describe('enum schema', () => {
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
|
2
|
+
/// <reference path="../../types.d.ts" />
|
|
3
|
+
|
|
1
4
|
import { get } from 'lodash';
|
|
2
5
|
import normalizeRequired from '../normalizeRequired';
|
|
3
|
-
import type {
|
|
4
|
-
EnumSchema,
|
|
5
|
-
ObjectSchema,
|
|
6
|
-
ObjectPropertySchema,
|
|
7
|
-
ArrayPropertySchema,
|
|
8
|
-
ReferencePropertySchema,
|
|
9
|
-
PropertySchema,
|
|
10
|
-
} from '../JsonSchema';
|
|
11
6
|
|
|
12
7
|
describe('normalizeRequired(schema)', () => {
|
|
13
8
|
describe('basic functionality', () => {
|
|
@@ -572,7 +567,7 @@ describe('normalizeRequired(schema)', () => {
|
|
|
572
567
|
expect(schema.required).toEqual(['refField', 'normalField']);
|
|
573
568
|
expect(schema.properties.refField.required).toBeUndefined(); // Removed after normalization
|
|
574
569
|
expect(schema.properties.refField['x-required']).toBe(true); // x-required flag set
|
|
575
|
-
|
|
570
|
+
|
|
576
571
|
// Normal field should be processed
|
|
577
572
|
expect(schema.properties.normalField['x-required']).toBe(true);
|
|
578
573
|
expect(schema.properties.normalField.required).toBeUndefined();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import nullifyEmptyValues from '../nullifyEmptyValues';
|
|
2
|
-
import {
|
|
3
|
-
import type { SchemaErrorDetail } from 'z-schema';
|
|
2
|
+
import ZSchema, { type SchemaErrorDetail } from 'z-schema';
|
|
4
3
|
|
|
5
4
|
describe('nullifyEmptyValues(object, validationErrors)', () => {
|
|
6
5
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
@@ -19,9 +18,9 @@ describe('nullifyEmptyValues(object, validationErrors)', () => {
|
|
|
19
18
|
inner: [],
|
|
20
19
|
} as SchemaErrorDetail;
|
|
21
20
|
|
|
22
|
-
// Attach symbols to the error object
|
|
23
|
-
(error as SchemaErrorDetail)[schemaSymbol] = schema;
|
|
24
|
-
(error as SchemaErrorDetail)[jsonSymbol] = json;
|
|
21
|
+
// Attach symbols to the error object (z-schema v9: symbols are on ZSchema class)
|
|
22
|
+
(error as SchemaErrorDetail)[ZSchema.schemaSymbol] = schema;
|
|
23
|
+
(error as SchemaErrorDetail)[ZSchema.jsonSymbol] = json;
|
|
25
24
|
|
|
26
25
|
return error;
|
|
27
26
|
};
|
|
@@ -355,6 +354,38 @@ describe('nullifyEmptyValues(object, validationErrors)', () => {
|
|
|
355
354
|
expect(remainingErrors).toHaveLength(0);
|
|
356
355
|
});
|
|
357
356
|
|
|
357
|
+
it('should handle path as array (z-schema reportPathAsArray format)', () => {
|
|
358
|
+
const object = { field: '', items: ['', 'value'] };
|
|
359
|
+
const error1 = {
|
|
360
|
+
code: 'PATTERN',
|
|
361
|
+
path: ['field'],
|
|
362
|
+
message: 'Error at field',
|
|
363
|
+
params: [],
|
|
364
|
+
inner: [],
|
|
365
|
+
} as SchemaErrorDetail;
|
|
366
|
+
(error1 as SchemaErrorDetail)[ZSchema.schemaSymbol] = {};
|
|
367
|
+
(error1 as SchemaErrorDetail)[ZSchema.jsonSymbol] = object;
|
|
368
|
+
|
|
369
|
+
const error2 = {
|
|
370
|
+
code: 'ENUM_MISMATCH',
|
|
371
|
+
path: ['items', 0],
|
|
372
|
+
message: 'Error at items/0',
|
|
373
|
+
params: [],
|
|
374
|
+
inner: [],
|
|
375
|
+
} as SchemaErrorDetail;
|
|
376
|
+
(error2 as SchemaErrorDetail)[ZSchema.schemaSymbol] = {};
|
|
377
|
+
(error2 as SchemaErrorDetail)[ZSchema.jsonSymbol] = object;
|
|
378
|
+
|
|
379
|
+
const validationErrors = [error1, error2];
|
|
380
|
+
|
|
381
|
+
const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
|
|
382
|
+
|
|
383
|
+
expect(result.field).toBeNull();
|
|
384
|
+
expect(result.items[0]).toBeNull();
|
|
385
|
+
expect(result.items[1]).toBe('value');
|
|
386
|
+
expect(remainingErrors).toHaveLength(0);
|
|
387
|
+
});
|
|
388
|
+
|
|
358
389
|
it('should preserve other object properties', () => {
|
|
359
390
|
const object = {
|
|
360
391
|
emptyField: '',
|
|
@@ -527,7 +558,7 @@ describe('nullifyEmptyValues(object, validationErrors)', () => {
|
|
|
527
558
|
} as SchemaErrorDetail;
|
|
528
559
|
|
|
529
560
|
// Don't attach schemaSymbol
|
|
530
|
-
(error as SchemaErrorDetail)[jsonSymbol] = object;
|
|
561
|
+
(error as SchemaErrorDetail)[ZSchema.jsonSymbol] = object;
|
|
531
562
|
const validationErrors = [error];
|
|
532
563
|
|
|
533
564
|
const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
|
|
@@ -548,15 +579,15 @@ describe('nullifyEmptyValues(object, validationErrors)', () => {
|
|
|
548
579
|
inner: [],
|
|
549
580
|
} as SchemaErrorDetail;
|
|
550
581
|
|
|
551
|
-
// Don't attach jsonSymbol
|
|
552
|
-
(error as SchemaErrorDetail)[schemaSymbol] = {};
|
|
582
|
+
// Don't attach jsonSymbol - falls back to object for value lookup
|
|
583
|
+
(error as SchemaErrorDetail)[ZSchema.schemaSymbol] = {};
|
|
553
584
|
const validationErrors = [error];
|
|
554
585
|
|
|
555
|
-
const [, remainingErrors] = nullifyEmptyValues(object, validationErrors);
|
|
586
|
+
const [result, remainingErrors] = nullifyEmptyValues(object, validationErrors);
|
|
556
587
|
|
|
557
|
-
//
|
|
558
|
-
expect(
|
|
559
|
-
expect(remainingErrors
|
|
588
|
+
// When json is missing, falls back to object; empty string is nullified
|
|
589
|
+
expect(result.field).toBeNull();
|
|
590
|
+
expect(remainingErrors).toHaveLength(0);
|
|
560
591
|
});
|
|
561
592
|
|
|
562
593
|
it('should handle all format error codes together', () => {
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
|
2
|
+
/// <reference path="../../types.d.ts" />
|
|
3
|
+
|
|
1
4
|
import { get } from 'lodash';
|
|
2
5
|
import removeRequiredAndDefault from '../removeRequiredAndDefault';
|
|
3
|
-
import type {
|
|
4
|
-
ObjectPropertySchema,
|
|
5
|
-
ArrayPropertySchema,
|
|
6
|
-
StringPropertySchema,
|
|
7
|
-
NumberPropertySchema,
|
|
8
|
-
IntegerPropertySchema,
|
|
9
|
-
BooleanPropertySchema,
|
|
10
|
-
EnumSchema,
|
|
11
|
-
ReferencePropertySchema,
|
|
12
|
-
} from '../JsonSchema';
|
|
13
6
|
|
|
14
7
|
describe('removeRequiredAndDefault(jsonSchema)', () => {
|
|
15
8
|
describe('basic property removal', () => {
|
|
@@ -1,51 +1,13 @@
|
|
|
1
1
|
import { isUndefined } from 'lodash';
|
|
2
|
+
import { type JsonSchema } from 'z-schema';
|
|
2
3
|
|
|
3
4
|
import got from './got';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
EnumSchema,
|
|
7
|
-
TargetObject,
|
|
8
|
-
ObjectSchema,
|
|
9
|
-
JsonSchemasMap,
|
|
10
|
-
ArrayPropertySchema,
|
|
11
|
-
ObjectPropertySchema,
|
|
12
|
-
ReferencePropertySchema
|
|
13
|
-
} from './JsonSchema';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Removes properties from an object that are not defined in the JSON schema.
|
|
17
|
-
*
|
|
18
|
-
* **Intent:**
|
|
19
|
-
* This function ensures that objects conform to their schema definition by removing
|
|
20
|
-
* any properties that are not explicitly defined in the schema. It performs a deep
|
|
21
|
-
* cleanup, recursively processing nested objects, arrays, and schema references.
|
|
22
|
-
*
|
|
23
|
-
* **Use Cases:**
|
|
24
|
-
* - **Third-party API integrations**: When integrating with external services (e.g., Telegram)
|
|
25
|
-
* that may send additional fields you don't want to process, this function allows you
|
|
26
|
-
* to define a minimal schema and automatically strip unwanted properties.
|
|
27
|
-
* - **Data sanitization**: Clean up objects received from external sources or user input
|
|
28
|
-
* before validation or processing, ensuring only expected fields are present.
|
|
29
|
-
* - **Schema enforcement**: Enforce strict schema compliance by removing any properties
|
|
30
|
-
* that don't match the defined schema structure.
|
|
31
|
-
* - **Pre-validation cleanup**: Remove extraneous properties before schema validation to
|
|
32
|
-
* prevent validation errors from unexpected fields.
|
|
33
|
-
*
|
|
34
|
-
* **Behavior:**
|
|
35
|
-
* - Mutates the input object in-place (does not return a new object)
|
|
36
|
-
* - Recursively processes nested objects, arrays, and schema references ($ref)
|
|
37
|
-
* - Skips enum schemas (returns early without modification)
|
|
38
|
-
* - Only processes object values (skips null, undefined, and primitive values)
|
|
39
|
-
* - Handles array items by cleaning each object item according to the array's item schema
|
|
40
|
-
*
|
|
41
|
-
* @param object - The target object to clean up (mutated in-place)
|
|
42
|
-
* @param jsonSchema - The JSON schema defining allowed properties
|
|
43
|
-
* @param schemasMap - Optional map of schema IDs to schema definitions for resolving $ref references
|
|
44
|
-
*/
|
|
5
|
+
|
|
6
|
+
/** Removes properties from an object that are not defined in the JSON schema. */
|
|
45
7
|
const cleanupAttributes = (
|
|
46
8
|
object: TargetObject,
|
|
47
9
|
jsonSchema: JsonSchema,
|
|
48
|
-
schemasMap:
|
|
10
|
+
schemasMap: Record<string, JsonSchema> = {}
|
|
49
11
|
) => {
|
|
50
12
|
const { enum: enumItems } = (jsonSchema as EnumSchema);
|
|
51
13
|
|
|
@@ -105,7 +67,7 @@ const cleanupAttributes = (
|
|
|
105
67
|
const nestedJsonSchema = {
|
|
106
68
|
id: `${objectSchema.id}.${fieldName}.properties`,
|
|
107
69
|
properties
|
|
108
|
-
};
|
|
70
|
+
} as JsonSchema;
|
|
109
71
|
|
|
110
72
|
cleanupAttributes(fieldValue as TargetObject, nestedJsonSchema, schemasMap);
|
|
111
73
|
}
|
|
@@ -132,7 +94,7 @@ const cleanupAttributes = (
|
|
|
132
94
|
: {
|
|
133
95
|
id: `${objectSchema.id}.${fieldName}.items.properties`,
|
|
134
96
|
properties: itemObjectProperties
|
|
135
|
-
};
|
|
97
|
+
} as JsonSchema;
|
|
136
98
|
|
|
137
99
|
for (const item of fieldValue) {
|
|
138
100
|
const isObjectItem = item &&
|
|
@@ -1,45 +1,8 @@
|
|
|
1
1
|
import { isObject, cloneDeep } from 'lodash';
|
|
2
2
|
|
|
3
|
-
import { type TargetObject } from './JsonSchema';
|
|
4
|
-
|
|
5
3
|
const { isArray } = Array;
|
|
6
4
|
|
|
7
|
-
/**
|
|
8
|
-
* Recursively removes null properties from an object.
|
|
9
|
-
*
|
|
10
|
-
* **Intent:**
|
|
11
|
-
* This function provides a deep cleanup of objects by removing all properties
|
|
12
|
-
* that have `null` values. It's designed to sanitize objects before validation,
|
|
13
|
-
* storage, or transmission by eliminating explicitly null fields.
|
|
14
|
-
*
|
|
15
|
-
* **Use Cases:**
|
|
16
|
-
* - **Pre-validation cleanup**: Remove null values before schema validation to
|
|
17
|
-
* prevent validation errors from optional fields that were explicitly set to null.
|
|
18
|
-
* This is particularly useful when `shouldCleanupNulls` is enabled in the Validator,
|
|
19
|
-
* allowing you to clean objects before `cleanupAttributes` removes undefined properties.
|
|
20
|
-
* - **Data sanitization**: Clean objects received from external sources (APIs, user input,
|
|
21
|
-
* databases) by removing null properties that may have been set during data transformation
|
|
22
|
-
* or migration processes.
|
|
23
|
-
* - **API response normalization**: Prepare objects for API responses by removing null fields,
|
|
24
|
-
* reducing payload size and ensuring consistent data structures across different endpoints.
|
|
25
|
-
* - **Database operations**: Clean objects before database storage or updates, removing
|
|
26
|
-
* null fields that might cause issues with database constraints or indexing.
|
|
27
|
-
* - **JSON serialization optimization**: Reduce JSON payload size by removing null properties
|
|
28
|
-
* before serialization, which is especially beneficial for large objects or high-frequency
|
|
29
|
-
* API calls.
|
|
30
|
-
* - **Optional field handling**: Remove explicitly null optional fields that weren't provided
|
|
31
|
-
* by the user, distinguishing between "field not provided" (undefined) and "field set to null".
|
|
32
|
-
*
|
|
33
|
-
* **Behavior:**
|
|
34
|
-
* - Returns a deep clone of the input object (does not mutate the original)
|
|
35
|
-
* - Recursively processes nested objects and arrays at all depth levels
|
|
36
|
-
* - Only removes properties with `null` values (preserves `undefined`, `0`, `false`, `''`, etc.)
|
|
37
|
-
* - Skips non-object values (returns early for primitives)
|
|
38
|
-
* - Handles arrays by recursively processing each item
|
|
39
|
-
* - Preserves object structure and non-null values exactly as they are
|
|
40
|
-
*
|
|
41
|
-
* @param target - The target object to clean (processed recursively, not mutated)
|
|
42
|
-
*/
|
|
5
|
+
/** Recursively removes null properties from an object. */
|
|
43
6
|
const cleanupNulls = (target: TargetObject) => {
|
|
44
7
|
const shouldSkip = !isObject(target);
|
|
45
8
|
|
|
@@ -72,31 +35,7 @@ const cleanupNulls = (target: TargetObject) => {
|
|
|
72
35
|
}
|
|
73
36
|
};
|
|
74
37
|
|
|
75
|
-
/**
|
|
76
|
-
* Returns a deep copy of the object with all null properties removed.
|
|
77
|
-
*
|
|
78
|
-
* This is the main exported function that creates a clone of the input object
|
|
79
|
-
* and removes all null properties recursively before returning it.
|
|
80
|
-
*
|
|
81
|
-
* @param object - The object to clean (will be cloned, original is not modified)
|
|
82
|
-
* @returns A new object with all null properties removed recursively
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```typescript
|
|
86
|
-
* const dirty = {
|
|
87
|
-
* name: 'John',
|
|
88
|
-
* age: null,
|
|
89
|
-
* address: {
|
|
90
|
-
* street: 'Main St',
|
|
91
|
-
* zip: null
|
|
92
|
-
* }
|
|
93
|
-
* };
|
|
94
|
-
*
|
|
95
|
-
* const clean = cleanupNulls(dirty);
|
|
96
|
-
* // Result: { name: 'John', address: { street: 'Main St' } }
|
|
97
|
-
* // Original 'dirty' object is unchanged
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
38
|
+
/** Returns a deep copy of the object with all null properties removed. */
|
|
100
39
|
export default function (object: Record<string, unknown>) {
|
|
101
40
|
const clone = cloneDeep(object);
|
|
102
41
|
|
|
@@ -2,39 +2,9 @@ import path from 'path';
|
|
|
2
2
|
import Schema from '../Schema';
|
|
3
3
|
import { load } from 'js-yaml';
|
|
4
4
|
import { keyBy } from 'lodash';
|
|
5
|
-
import type { EnumSchema, PropertiesSchema } from './JsonSchema';
|
|
6
5
|
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
7
6
|
|
|
8
|
-
/**
|
|
9
|
-
* Reads schema source from YAML file and returns a Schema instance.
|
|
10
|
-
*
|
|
11
|
-
* **Intent:** Load and parse a single YAML schema file, extracting the schema ID
|
|
12
|
-
* from the filename and creating a Schema instance for use in validation or
|
|
13
|
-
* schema composition.
|
|
14
|
-
*
|
|
15
|
-
* **Use Cases:**
|
|
16
|
-
* - Load individual schema files during application startup
|
|
17
|
-
* - Parse YAML schema definitions into Schema instances
|
|
18
|
-
* - Extract schema identifiers from file paths automatically
|
|
19
|
-
* - Support both enum and properties-based schemas from YAML files
|
|
20
|
-
*
|
|
21
|
-
* @param yamlPath - Absolute or relative path to the YAML schema file
|
|
22
|
-
* @returns A Schema instance with ID extracted from the filename (without .yaml extension)
|
|
23
|
-
*
|
|
24
|
-
* **Example:**
|
|
25
|
-
* ```typescript
|
|
26
|
-
* const schema = loadSync('/path/to/schemas/User.yaml');
|
|
27
|
-
* // schema.id === 'User'
|
|
28
|
-
* // schema.source contains the parsed YAML content
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* **Example - Enum Schema:**
|
|
32
|
-
* ```typescript
|
|
33
|
-
* const statusSchema = loadSync('/path/to/schemas/Status.yaml');
|
|
34
|
-
* // If Status.yaml contains: { enum: ['PENDING', 'ACTIVE'] }
|
|
35
|
-
* // statusSchema.isEnum === true
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
7
|
+
/** Reads schema source from YAML file and returns a Schema instance. */
|
|
38
8
|
export const loadSync = (yamlPath: string) => {
|
|
39
9
|
const schemaId = yamlPath
|
|
40
10
|
.split('/')
|
|
@@ -47,40 +17,7 @@ export const loadSync = (yamlPath: string) => {
|
|
|
47
17
|
return new Schema(source, schemaId);
|
|
48
18
|
};
|
|
49
19
|
|
|
50
|
-
/**
|
|
51
|
-
* Recursively lists all files in a directory and its subdirectories.
|
|
52
|
-
*
|
|
53
|
-
* **Intent:** Traverse a directory tree and collect all file paths, enabling
|
|
54
|
-
* discovery of schema files nested in subdirectories without manual path specification.
|
|
55
|
-
*
|
|
56
|
-
* **Use Cases:**
|
|
57
|
-
* - Find all schema files in a directory structure
|
|
58
|
-
* - Support organized schema layouts with nested folders
|
|
59
|
-
* - Enable schema discovery without hardcoding file paths
|
|
60
|
-
* - Prepare file list for filtering and processing
|
|
61
|
-
*
|
|
62
|
-
* @param servicePath - Path to the directory to traverse
|
|
63
|
-
* @returns Array of absolute file paths found in the directory tree
|
|
64
|
-
*
|
|
65
|
-
* **Example:**
|
|
66
|
-
* ```typescript
|
|
67
|
-
* const files = listFilesSync('/path/to/schemas');
|
|
68
|
-
* // Returns: [
|
|
69
|
-
* // '/path/to/schemas/User.yaml',
|
|
70
|
-
* // '/path/to/schemas/nested/Profile.yaml',
|
|
71
|
-
* // '/path/to/schemas/nested/deep/Status.yaml'
|
|
72
|
-
* // ]
|
|
73
|
-
* ```
|
|
74
|
-
*
|
|
75
|
-
* **Example - Flat Directory:**
|
|
76
|
-
* ```typescript
|
|
77
|
-
* const files = listFilesSync('/path/to/schemas');
|
|
78
|
-
* // Returns: [
|
|
79
|
-
* // '/path/to/schemas/User.yaml',
|
|
80
|
-
* // '/path/to/schemas/Profile.yaml'
|
|
81
|
-
* // ]
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
20
|
+
/** Recursively lists all files in a directory and its subdirectories. */
|
|
84
21
|
const listFilesSync = (servicePath: string): string[] =>
|
|
85
22
|
readdirSync(servicePath)
|
|
86
23
|
.reduce(
|
|
@@ -92,109 +29,13 @@ const listFilesSync = (servicePath: string): string[] =>
|
|
|
92
29
|
)
|
|
93
30
|
, []);
|
|
94
31
|
|
|
95
|
-
/**
|
|
96
|
-
* Reads all YAML schema files from a directory and creates Schema instances.
|
|
97
|
-
*
|
|
98
|
-
* **Intent:** Bulk load schema definitions from YAML files in a directory,
|
|
99
|
-
* automatically discovering and parsing all schema files for use in schema
|
|
100
|
-
* registries or validators.
|
|
101
|
-
*
|
|
102
|
-
* **Use Cases:**
|
|
103
|
-
* - Load all schemas from a schemas directory at application startup
|
|
104
|
-
* - Initialize schema registries from file-based definitions
|
|
105
|
-
* - Support schema-as-code workflows where schemas are stored as YAML files
|
|
106
|
-
* - Enable automatic schema discovery without manual registration
|
|
107
|
-
*
|
|
108
|
-
* @param servicePath - Path to the directory containing YAML schema files
|
|
109
|
-
* @returns Array of Schema instances, one for each YAML file found
|
|
110
|
-
*
|
|
111
|
-
* **Example:**
|
|
112
|
-
* ```typescript
|
|
113
|
-
* const schemas = readSchemasSync('/path/to/examples/schemas');
|
|
114
|
-
* // Returns: [
|
|
115
|
-
* // Schema { id: 'FavoriteItem', ... },
|
|
116
|
-
* // Schema { id: 'Profile', ... },
|
|
117
|
-
* // Schema { id: 'Status', ... },
|
|
118
|
-
* // Schema { id: 'Preferences', ... }
|
|
119
|
-
* // ]
|
|
120
|
-
* ```
|
|
121
|
-
*
|
|
122
|
-
* **Example - With Nested Directories:**
|
|
123
|
-
* ```typescript
|
|
124
|
-
* const schemas = readSchemasSync('/path/to/schemas');
|
|
125
|
-
* // Automatically finds schemas in subdirectories:
|
|
126
|
-
* // - /path/to/schemas/User.yaml
|
|
127
|
-
* // - /path/to/schemas/nested/Profile.yaml
|
|
128
|
-
* ```
|
|
129
|
-
*/
|
|
32
|
+
/** Reads all YAML schema files from a directory and creates Schema instances. */
|
|
130
33
|
const readSchemasSync = (servicePath: string) =>
|
|
131
34
|
listFilesSync(servicePath)
|
|
132
35
|
.filter((fileName: string) => fileName.endsWith('.yaml'))
|
|
133
36
|
.map((schemaPath: string) => loadSync(schemaPath));
|
|
134
37
|
|
|
135
|
-
/**
|
|
136
|
-
* Creates a map of schemas by ID, loading from YAML files and merging with programmatic schemas.
|
|
137
|
-
*
|
|
138
|
-
* **Intent:** Build a centralized schema registry that combines file-based YAML schemas
|
|
139
|
-
* with programmatically created Schema instances, providing a unified lookup mechanism
|
|
140
|
-
* by schema ID. This enables hybrid schema management where some schemas are defined
|
|
141
|
-
* in YAML files while others are created dynamically in code.
|
|
142
|
-
*
|
|
143
|
-
* **Use Cases:**
|
|
144
|
-
* - Initialize schema registries for validators from both files and code
|
|
145
|
-
* - Support schema composition where base schemas come from files and extended
|
|
146
|
-
* schemas are created programmatically
|
|
147
|
-
* - Enable schema overriding where programmatic schemas can replace file-based ones
|
|
148
|
-
* - Build schema maps for credential factories or API validation systems
|
|
149
|
-
* - Support development workflows where schemas evolve from YAML to code
|
|
150
|
-
*
|
|
151
|
-
* @param servicePath - Path to directory containing YAML schema files (searched recursively)
|
|
152
|
-
* @param modules - Array of Schema instances or other values (non-Schema values are filtered out)
|
|
153
|
-
* @returns Record mapping schema IDs to Schema instances, with modules schemas overriding YAML schemas
|
|
154
|
-
*
|
|
155
|
-
* **Example - Basic Usage:**
|
|
156
|
-
* ```typescript
|
|
157
|
-
* const schemasMap = createSchemasMap('/path/to/examples/schemas', []);
|
|
158
|
-
* // Returns: {
|
|
159
|
-
* // FavoriteItem: Schema { id: 'FavoriteItem', ... },
|
|
160
|
-
* // Profile: Schema { id: 'Profile', ... },
|
|
161
|
-
* // Status: Schema { id: 'Status', ... },
|
|
162
|
-
* // Preferences: Schema { id: 'Preferences', ... }
|
|
163
|
-
* // }
|
|
164
|
-
* ```
|
|
165
|
-
*
|
|
166
|
-
* **Example - Merging Programmatic Schemas:**
|
|
167
|
-
* ```typescript
|
|
168
|
-
* const customSchema = new Schema(
|
|
169
|
-
* { customField: { type: 'string' } },
|
|
170
|
-
* 'CustomSchema'
|
|
171
|
-
* );
|
|
172
|
-
* const schemasMap = createSchemasMap('/path/to/schemas', [customSchema]);
|
|
173
|
-
* // schemasMap contains both YAML schemas and CustomSchema
|
|
174
|
-
* ```
|
|
175
|
-
*
|
|
176
|
-
* **Example - Overriding YAML Schemas:**
|
|
177
|
-
* ```typescript
|
|
178
|
-
* const updatedProfile = new Schema(
|
|
179
|
-
* { name: { type: 'string' }, newField: { type: 'number' } },
|
|
180
|
-
* 'Profile'
|
|
181
|
-
* );
|
|
182
|
-
* const schemasMap = createSchemasMap('/path/to/schemas', [updatedProfile]);
|
|
183
|
-
* // schemasMap.Profile is the updatedProfile instance, not the YAML version
|
|
184
|
-
* ```
|
|
185
|
-
*
|
|
186
|
-
* **Example - Filtering Non-Schema Values:**
|
|
187
|
-
* ```typescript
|
|
188
|
-
* const schema = new Schema({ field: { type: 'string' } }, 'Test');
|
|
189
|
-
* const schemasMap = createSchemasMap('/path/to/schemas', [
|
|
190
|
-
* schema,
|
|
191
|
-
* 'not a schema',
|
|
192
|
-
* { id: 'fake' },
|
|
193
|
-
* null
|
|
194
|
-
* ]);
|
|
195
|
-
* // Only the Schema instance is included, other values are ignored
|
|
196
|
-
* ```
|
|
197
|
-
*/
|
|
38
|
+
/** Creates a map of schemas by ID, loading from YAML files and merging with programmatic schemas. */
|
|
198
39
|
const createSchemasMap = (servicePath: string, modules: unknown[]): Record<string, Schema> => {
|
|
199
40
|
const yamlSchemas = readSchemasSync(servicePath);
|
|
200
41
|
const schemasMap = keyBy(yamlSchemas, 'id');
|
|
@@ -1,180 +1,9 @@
|
|
|
1
1
|
import { isUndefined, uniq } from 'lodash';
|
|
2
2
|
|
|
3
|
-
import Schema from '../Schema';
|
|
4
3
|
import got from './got';
|
|
5
|
-
import
|
|
6
|
-
EnumSchema,
|
|
7
|
-
ObjectSchema,
|
|
8
|
-
ArrayPropertySchema,
|
|
9
|
-
ObjectPropertySchema,
|
|
10
|
-
ReferencePropertySchema,
|
|
11
|
-
} from './JsonSchema';
|
|
4
|
+
import Schema from '../Schema';
|
|
12
5
|
|
|
13
|
-
/**
|
|
14
|
-
* Recursively extracts all referenced schema IDs from a schema structure.
|
|
15
|
-
*
|
|
16
|
-
* **Intent:** Traverse a schema's entire structure (including nested objects and arrays)
|
|
17
|
-
* to collect all schema IDs that are referenced via `$ref` properties. This enables
|
|
18
|
-
* dependency resolution, schema bundling, and validation of schema completeness.
|
|
19
|
-
*
|
|
20
|
-
* **Use Cases:**
|
|
21
|
-
* - **Dependency Resolution:** Identify all schemas that a given schema depends on,
|
|
22
|
-
* ensuring they are loaded before validation
|
|
23
|
-
* - **Schema Bundling:** Collect all related schemas into a single bundle for
|
|
24
|
-
* distribution or storage
|
|
25
|
-
* - **Validation Preparation:** Pre-load all referenced schemas to ensure complete
|
|
26
|
-
* validation context
|
|
27
|
-
* - **Dependency Graph Building:** Understand the relationships and dependencies
|
|
28
|
-
* between schemas in a schema registry
|
|
29
|
-
* - **Schema Analysis:** Analyze schema complexity by identifying all dependencies
|
|
30
|
-
* - **Circular Reference Detection:** (Note: current implementation does not handle
|
|
31
|
-
* circular references and will recurse infinitely)
|
|
32
|
-
*
|
|
33
|
-
* **Behavior:**
|
|
34
|
-
* - Returns an empty array for enum schemas (they don't reference other schemas)
|
|
35
|
-
* - Recursively traverses nested object properties
|
|
36
|
-
* - Handles array items that reference schemas or contain object properties
|
|
37
|
-
* - Follows nested references to collect transitive dependencies
|
|
38
|
-
* - Returns unique schema IDs (deduplicates if same schema is referenced multiple times)
|
|
39
|
-
* - Throws an error if a referenced schema is not found in the schemasMap
|
|
40
|
-
*
|
|
41
|
-
* **Example - Simple Reference:**
|
|
42
|
-
* ```typescript
|
|
43
|
-
* const userSchema = new Schema({
|
|
44
|
-
* profile: { $ref: 'Profile' }
|
|
45
|
-
* }, 'User');
|
|
46
|
-
*
|
|
47
|
-
* const profileSchema = new Schema({
|
|
48
|
-
* name: { type: 'string' }
|
|
49
|
-
* }, 'Profile');
|
|
50
|
-
*
|
|
51
|
-
* const schemasMap = { 'Profile': profileSchema };
|
|
52
|
-
* const referenceIds = getReferenceIds(userSchema, schemasMap);
|
|
53
|
-
* // Returns: ['Profile']
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* **Example - Multiple References:**
|
|
57
|
-
* ```typescript
|
|
58
|
-
* const orderSchema = new Schema({
|
|
59
|
-
* customer: { $ref: 'Customer' },
|
|
60
|
-
* product: { $ref: 'Product' },
|
|
61
|
-
* shipping: { $ref: 'Address' }
|
|
62
|
-
* }, 'Order');
|
|
63
|
-
*
|
|
64
|
-
* const schemasMap = {
|
|
65
|
-
* 'Customer': customerSchema,
|
|
66
|
-
* 'Product': productSchema,
|
|
67
|
-
* 'Address': addressSchema
|
|
68
|
-
* };
|
|
69
|
-
* const referenceIds = getReferenceIds(orderSchema, schemasMap);
|
|
70
|
-
* // Returns: ['Customer', 'Product', 'Address']
|
|
71
|
-
* ```
|
|
72
|
-
*
|
|
73
|
-
* **Example - Nested References:**
|
|
74
|
-
* ```typescript
|
|
75
|
-
* const userSchema = new Schema({
|
|
76
|
-
* profile: { $ref: 'Profile' }
|
|
77
|
-
* }, 'User');
|
|
78
|
-
*
|
|
79
|
-
* const profileSchema = new Schema({
|
|
80
|
-
* address: { $ref: 'Address' }
|
|
81
|
-
* }, 'Profile');
|
|
82
|
-
*
|
|
83
|
-
* const addressSchema = new Schema({
|
|
84
|
-
* street: { type: 'string' }
|
|
85
|
-
* }, 'Address');
|
|
86
|
-
*
|
|
87
|
-
* const schemasMap = {
|
|
88
|
-
* 'Profile': profileSchema,
|
|
89
|
-
* 'Address': addressSchema
|
|
90
|
-
* };
|
|
91
|
-
* const referenceIds = getReferenceIds(userSchema, schemasMap);
|
|
92
|
-
* // Returns: ['Profile', 'Address'] (includes transitive dependencies)
|
|
93
|
-
* ```
|
|
94
|
-
*
|
|
95
|
-
* **Example - Array with Reference Items:**
|
|
96
|
-
* ```typescript
|
|
97
|
-
* const orderSchema = new Schema({
|
|
98
|
-
* items: {
|
|
99
|
-
* type: 'array',
|
|
100
|
-
* items: { $ref: 'OrderItem' }
|
|
101
|
-
* }
|
|
102
|
-
* }, 'Order');
|
|
103
|
-
*
|
|
104
|
-
* const schemasMap = { 'OrderItem': orderItemSchema };
|
|
105
|
-
* const referenceIds = getReferenceIds(orderSchema, schemasMap);
|
|
106
|
-
* // Returns: ['OrderItem']
|
|
107
|
-
* ```
|
|
108
|
-
*
|
|
109
|
-
* **Example - Nested Object Properties:**
|
|
110
|
-
* ```typescript
|
|
111
|
-
* const userSchema = new Schema({
|
|
112
|
-
* contact: {
|
|
113
|
-
* type: 'object',
|
|
114
|
-
* properties: {
|
|
115
|
-
* address: { $ref: 'Address' }
|
|
116
|
-
* }
|
|
117
|
-
* }
|
|
118
|
-
* }, 'User');
|
|
119
|
-
*
|
|
120
|
-
* const schemasMap = { 'Address': addressSchema };
|
|
121
|
-
* const referenceIds = getReferenceIds(userSchema, schemasMap);
|
|
122
|
-
* // Returns: ['Address']
|
|
123
|
-
* ```
|
|
124
|
-
*
|
|
125
|
-
* **Example - Complex Mixed Structure:**
|
|
126
|
-
* ```typescript
|
|
127
|
-
* const orderSchema = new Schema({
|
|
128
|
-
* customer: { $ref: 'Customer' },
|
|
129
|
-
* items: {
|
|
130
|
-
* type: 'array',
|
|
131
|
-
* items: {
|
|
132
|
-
* type: 'object',
|
|
133
|
-
* properties: {
|
|
134
|
-
* product: { $ref: 'Product' }
|
|
135
|
-
* }
|
|
136
|
-
* }
|
|
137
|
-
* },
|
|
138
|
-
* shipping: {
|
|
139
|
-
* type: 'object',
|
|
140
|
-
* properties: {
|
|
141
|
-
* address: { $ref: 'Address' }
|
|
142
|
-
* }
|
|
143
|
-
* }
|
|
144
|
-
* }, 'Order');
|
|
145
|
-
*
|
|
146
|
-
* const schemasMap = {
|
|
147
|
-
* 'Customer': customerSchema,
|
|
148
|
-
* 'Product': productSchema,
|
|
149
|
-
* 'Address': addressSchema
|
|
150
|
-
* };
|
|
151
|
-
* const referenceIds = getReferenceIds(orderSchema, schemasMap);
|
|
152
|
-
* // Returns: ['Customer', 'Product', 'Address']
|
|
153
|
-
* ```
|
|
154
|
-
*
|
|
155
|
-
* **Example - Duplicate References:**
|
|
156
|
-
* ```typescript
|
|
157
|
-
* const schema = new Schema({
|
|
158
|
-
* field1: { $ref: 'SharedSchema' },
|
|
159
|
-
* field2: { $ref: 'SharedSchema' }
|
|
160
|
-
* }, 'Test');
|
|
161
|
-
*
|
|
162
|
-
* const schemasMap = { 'SharedSchema': sharedSchema };
|
|
163
|
-
* const referenceIds = getReferenceIds(schema, schemasMap);
|
|
164
|
-
* // Returns: ['SharedSchema'] (deduplicated)
|
|
165
|
-
* ```
|
|
166
|
-
*
|
|
167
|
-
* @param schema - The schema to extract references from
|
|
168
|
-
* @param schemasMap - A map of schema IDs to Schema instances, used to resolve
|
|
169
|
-
* referenced schemas and traverse nested references
|
|
170
|
-
* @returns An array of unique schema IDs that are referenced (directly or indirectly)
|
|
171
|
-
* by the given schema
|
|
172
|
-
* @throws Error if a referenced schema is not found in the schemasMap
|
|
173
|
-
*
|
|
174
|
-
* **Limitations:**
|
|
175
|
-
* - Does not handle circular references (will cause infinite recursion)
|
|
176
|
-
* - Requires all referenced schemas to be present in schemasMap
|
|
177
|
-
*/
|
|
6
|
+
/** Recursively extracts all referenced schema IDs from a schema structure. */
|
|
178
7
|
const getReferenceIds = (schema: Schema, schemasMap: Record<string, Schema>): string[] => {
|
|
179
8
|
/** Returns schema from the map by ID */
|
|
180
9
|
const getSchema = (id: string) => got(schemasMap, id, 'Schema "$PATH" not found');
|