@tsofist/schema-forge 2.0.0 → 2.2.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.
- package/lib/generator/schema-generator.d.ts +1 -1
- package/lib/generator/schema-generator.js +8 -1
- package/lib/generator.js +26 -28
- package/lib/generator.spec.js +57 -2
- package/lib/index.d.ts +2 -1
- package/lib/index.js +5 -1
- package/lib/validator.d.ts +24 -12
- package/lib/validator.js +93 -9
- package/package.json +8 -8
|
@@ -26,10 +26,13 @@ async function generateSchemaByDraftTypes(options) {
|
|
|
26
26
|
expose: options.expose ?? types_1.SG_CONFIG_DEFAULTS.expose,
|
|
27
27
|
path: `${options.sourcesDirectoryPattern}/*${types_1.TMP_FILES_SUFFIX}.ts`,
|
|
28
28
|
tsconfig: options.tsconfig,
|
|
29
|
-
discriminatorType: options.
|
|
29
|
+
discriminatorType: options.openapiCompatible
|
|
30
|
+
? 'open-api'
|
|
31
|
+
: ts_json_schema_generator_1.DEFAULT_CONFIG.discriminatorType,
|
|
30
32
|
...types_1.SG_CONFIG_MANDATORY,
|
|
31
33
|
};
|
|
32
34
|
const generatorProgram = (0, ts_json_schema_generator_1.createProgram)(generatorConfig);
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
33
36
|
const typeChecker = generatorProgram.getTypeChecker();
|
|
34
37
|
const parser = (0, ts_json_schema_generator_1.createParser)(generatorProgram, options.sourcesTypesGeneratorConfig, (parser) => {
|
|
35
38
|
parser.addNodeParser(new TupleTypeParser(parser, allowUseFallbackDescription));
|
|
@@ -41,6 +44,10 @@ async function generateSchemaByDraftTypes(options) {
|
|
|
41
44
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
42
45
|
$id: options.schemaId,
|
|
43
46
|
hash: '',
|
|
47
|
+
title: undefined,
|
|
48
|
+
description: undefined,
|
|
49
|
+
version: undefined,
|
|
50
|
+
$comment: undefined,
|
|
44
51
|
definitions: {},
|
|
45
52
|
};
|
|
46
53
|
for (const definitionName of options.definitions) {
|
package/lib/generator.js
CHANGED
|
@@ -10,10 +10,10 @@ const promises_2 = require("node:fs/promises");
|
|
|
10
10
|
const as_array_1 = require("@tsofist/stem/lib/as-array");
|
|
11
11
|
const error_1 = require("@tsofist/stem/lib/error");
|
|
12
12
|
const noop_1 = require("@tsofist/stem/lib/noop");
|
|
13
|
-
const keys_1 = require("@tsofist/stem/lib/object/keys");
|
|
14
13
|
const random_1 = require("@tsofist/stem/lib/string/random");
|
|
15
14
|
const schema_generator_1 = require("./generator/schema-generator");
|
|
16
15
|
const types_generator_1 = require("./generator/types-generator");
|
|
16
|
+
const index_1 = require("./index");
|
|
17
17
|
const KEEP_ARTEFACTS = false;
|
|
18
18
|
async function forgeSchema(options) {
|
|
19
19
|
const { schemaId, sourcesDirectoryPattern, outputSchemaFile } = options;
|
|
@@ -37,33 +37,30 @@ async function forgeSchema(options) {
|
|
|
37
37
|
tsconfig,
|
|
38
38
|
sourcesPattern,
|
|
39
39
|
});
|
|
40
|
-
const refs = definitions.map((item) =>
|
|
40
|
+
const refs = definitions.map((item) => (0, index_1.buildSchemaDefinitionRef)(item, options.schemaId));
|
|
41
41
|
let schema;
|
|
42
42
|
try {
|
|
43
43
|
{
|
|
44
|
-
schema =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
}
|
|
44
|
+
schema = {
|
|
45
|
+
...(await (0, schema_generator_1.generateSchemaByDraftTypes)({
|
|
46
|
+
schemaId,
|
|
47
|
+
tsconfig,
|
|
48
|
+
definitions,
|
|
49
|
+
sourcesDirectoryPattern,
|
|
50
|
+
outputSchemaFile,
|
|
51
|
+
sourcesTypesGeneratorConfig,
|
|
52
|
+
expose: options.expose,
|
|
53
|
+
openapiCompatible: options.openapiCompatible,
|
|
54
|
+
sortObjectProperties: options.sortObjectProperties,
|
|
55
|
+
allowUseFallbackDescription: options.allowUseFallbackDescription,
|
|
56
|
+
})),
|
|
57
|
+
...(options.schemaMetadata || {}),
|
|
58
|
+
};
|
|
61
59
|
{
|
|
62
|
-
const algorithm = options.schemaMetadata?.hash == null
|
|
60
|
+
const algorithm = options.schemaMetadata?.hash == null ||
|
|
61
|
+
options.schemaMetadata?.hash === true
|
|
63
62
|
? 'md5'
|
|
64
|
-
: options.schemaMetadata
|
|
65
|
-
? 'md5'
|
|
66
|
-
: options.schemaMetadata.hash;
|
|
63
|
+
: options.schemaMetadata.hash;
|
|
67
64
|
if (algorithm) {
|
|
68
65
|
schema.hash = (0, node_crypto_1.createHash)(algorithm, {})
|
|
69
66
|
.update(JSON.stringify(schema))
|
|
@@ -79,11 +76,11 @@ async function forgeSchema(options) {
|
|
|
79
76
|
if (options.outputSchemaMetadataFile) {
|
|
80
77
|
const map = {
|
|
81
78
|
$id: options.schemaId || '',
|
|
82
|
-
|
|
79
|
+
schemaHash: schema.hash,
|
|
83
80
|
title: options.schemaMetadata?.title,
|
|
84
81
|
description: options.schemaMetadata?.description,
|
|
82
|
+
version: options.schemaMetadata?.version,
|
|
85
83
|
$comment: options.schemaMetadata?.$comment,
|
|
86
|
-
schemaHash: schema.hash,
|
|
87
84
|
refs: {},
|
|
88
85
|
names: {},
|
|
89
86
|
serviceRefs: {},
|
|
@@ -91,14 +88,15 @@ async function forgeSchema(options) {
|
|
|
91
88
|
};
|
|
92
89
|
const defs = new Set(Object.keys((schema.definitions || {})));
|
|
93
90
|
for (const name of definitions) {
|
|
94
|
-
const ref =
|
|
91
|
+
const ref = (0, index_1.buildSchemaDefinitionRef)(name, schemaId);
|
|
95
92
|
map.names[name] = ref;
|
|
96
93
|
map.refs[ref] = name;
|
|
97
94
|
defs.delete(name);
|
|
98
95
|
}
|
|
99
96
|
for (const name of defs) {
|
|
100
|
-
|
|
101
|
-
map.
|
|
97
|
+
const ref = (0, index_1.buildSchemaDefinitionRef)(name, options.schemaId);
|
|
98
|
+
map.serviceNames[name] = ref;
|
|
99
|
+
map.serviceRefs[ref] = name;
|
|
102
100
|
}
|
|
103
101
|
const content = JSON.stringify(map, null, 2);
|
|
104
102
|
await (0, promises_1.writeFile)(options.outputSchemaMetadataFile, content, {
|
package/lib/generator.spec.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const promises_1 = require("node:fs/promises");
|
|
4
4
|
const error_1 = require("@tsofist/stem/lib/error");
|
|
5
5
|
const noop_1 = require("@tsofist/stem/lib/noop");
|
|
6
|
+
const pick_1 = require("@tsofist/stem/lib/object/pick");
|
|
6
7
|
const generator_1 = require("./generator");
|
|
7
8
|
const types_1 = require("./types");
|
|
8
9
|
const validator_1 = require("./validator");
|
|
@@ -12,8 +13,14 @@ const KEEP_ARTEFACTS = false;
|
|
|
12
13
|
describe('generator for a7', () => {
|
|
13
14
|
const outputSchemaFile = './a7.generated.schema.tmp.json';
|
|
14
15
|
const outputSchemaMetadataFile = './a7.generated.definitions.tmp.json';
|
|
16
|
+
const schemaMetadata = {
|
|
17
|
+
title: 'Generator TEST',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
$comment: 'WARN: This is a test schema.',
|
|
20
|
+
};
|
|
15
21
|
let forgeSchemaResult;
|
|
16
22
|
let validator;
|
|
23
|
+
let loadedSchema;
|
|
17
24
|
beforeAll(async () => {
|
|
18
25
|
forgeSchemaResult = await (0, generator_1.forgeSchema)({
|
|
19
26
|
schemaId: 'test',
|
|
@@ -25,10 +32,11 @@ describe('generator for a7', () => {
|
|
|
25
32
|
outputSchemaMetadataFile,
|
|
26
33
|
expose: 'all',
|
|
27
34
|
explicitPublic: true,
|
|
35
|
+
schemaMetadata,
|
|
28
36
|
});
|
|
29
37
|
validator = (0, validator_1.createSchemaForgeValidator)({}, true);
|
|
30
|
-
|
|
31
|
-
validator.addSchema(
|
|
38
|
+
loadedSchema = await (0, generator_1.loadJSONSchema)([outputSchemaFile]);
|
|
39
|
+
validator.addSchema(loadedSchema);
|
|
32
40
|
});
|
|
33
41
|
afterAll(async () => {
|
|
34
42
|
if (!KEEP_ARTEFACTS) {
|
|
@@ -36,6 +44,11 @@ describe('generator for a7', () => {
|
|
|
36
44
|
await (0, promises_1.unlink)(outputSchemaMetadataFile).catch(noop_1.noop);
|
|
37
45
|
}
|
|
38
46
|
});
|
|
47
|
+
it('generated schema should have correct metadata', () => {
|
|
48
|
+
expect(forgeSchemaResult).toBeTruthy();
|
|
49
|
+
const schema = forgeSchemaResult.schema;
|
|
50
|
+
expect((0, pick_1.pickProps)(schema, Object.keys(schemaMetadata))).toStrictEqual(schemaMetadata);
|
|
51
|
+
});
|
|
39
52
|
it('generated schema should be valid', () => {
|
|
40
53
|
expect(forgeSchemaResult).toBeTruthy();
|
|
41
54
|
const schema = validator.getSchema('test#/definitions/SomeAPI_doSomeWithUser_Args');
|
|
@@ -50,6 +63,48 @@ describe('generator for a7', () => {
|
|
|
50
63
|
expect(schema.maxItems).toStrictEqual(3);
|
|
51
64
|
});
|
|
52
65
|
});
|
|
66
|
+
describe('validator for a7', () => {
|
|
67
|
+
const outputSchemaFile = './a7.generated.schema.tmp.json';
|
|
68
|
+
const outputSchemaMetadataFile = './a7.generated.definitions.tmp.json';
|
|
69
|
+
let validator;
|
|
70
|
+
let loadedSchema;
|
|
71
|
+
beforeAll(async () => {
|
|
72
|
+
await (0, generator_1.forgeSchema)({
|
|
73
|
+
schemaId: 'test',
|
|
74
|
+
allowUseFallbackDescription: true,
|
|
75
|
+
tsconfigFrom: './tsconfig.build-test.json',
|
|
76
|
+
sourcesDirectoryPattern: 'test-sources/a7',
|
|
77
|
+
sourcesFilesPattern: ['service-api.ts', 'types.ts'],
|
|
78
|
+
outputSchemaFile,
|
|
79
|
+
outputSchemaMetadataFile,
|
|
80
|
+
expose: 'all',
|
|
81
|
+
explicitPublic: true,
|
|
82
|
+
});
|
|
83
|
+
validator = (0, validator_1.createSchemaForgeValidator)({}, true);
|
|
84
|
+
loadedSchema = await (0, generator_1.loadJSONSchema)([outputSchemaFile]);
|
|
85
|
+
validator.addSchema(loadedSchema);
|
|
86
|
+
});
|
|
87
|
+
afterAll(async () => {
|
|
88
|
+
if (!KEEP_ARTEFACTS) {
|
|
89
|
+
await (0, promises_1.unlink)(outputSchemaFile).catch(noop_1.noop);
|
|
90
|
+
await (0, promises_1.unlink)(outputSchemaMetadataFile).catch(noop_1.noop);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
it('should be able to warm-up cache', async () => {
|
|
94
|
+
const initial = validator.compilationArtifactCount;
|
|
95
|
+
expect(initial).toStrictEqual(2);
|
|
96
|
+
validator.warmupCacheSync();
|
|
97
|
+
const warmed = validator.compilationArtifactCount;
|
|
98
|
+
expect(warmed).toStrictEqual(10);
|
|
99
|
+
validator.clear();
|
|
100
|
+
const cleared = validator.compilationArtifactCount;
|
|
101
|
+
expect(cleared).toStrictEqual(1);
|
|
102
|
+
validator.addSchema(loadedSchema);
|
|
103
|
+
expect(validator.compilationArtifactCount).toStrictEqual(initial);
|
|
104
|
+
await validator.warmupCache();
|
|
105
|
+
expect(validator.compilationArtifactCount).toStrictEqual(warmed);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
53
108
|
describe('generator for a6', () => {
|
|
54
109
|
const outputSchemaFile = './a6.generated.schema.tmp.json';
|
|
55
110
|
const outputSchemaMetadataFile = './a6.generated.definitions.tmp.json';
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '@total-typescript/ts-reset';
|
|
2
|
-
import { SchemaDefinitionInfo, SchemaForgeSignatureSuffix } from './types';
|
|
2
|
+
import { SchemaDefinitionInfo, SchemaForgeDefinitionRef, SchemaForgeSignatureSuffix } from './types';
|
|
3
3
|
export declare function buildAPIInterfaceSchemaSignature(interfaceName: string): string;
|
|
4
4
|
export declare function buildAPIInterfaceSchemaSignature(interfaceName: string, memberName: string): string;
|
|
5
5
|
export declare function buildAPIInterfaceSchemaSignature(interfaceName: string, methodName: string, suffix: SchemaForgeSignatureSuffix): string;
|
|
6
|
+
export declare function buildSchemaDefinitionRef(definitionName: string, schemaId: string | undefined): SchemaForgeDefinitionRef;
|
|
6
7
|
export declare function parseSchemaDefinitionInfo(name: string, schemaId: string): SchemaDefinitionInfo;
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildAPIInterfaceSchemaSignature = buildAPIInterfaceSchemaSignature;
|
|
4
|
+
exports.buildSchemaDefinitionRef = buildSchemaDefinitionRef;
|
|
4
5
|
exports.parseSchemaDefinitionInfo = parseSchemaDefinitionInfo;
|
|
5
6
|
require("@total-typescript/ts-reset");
|
|
6
7
|
const substr_1 = require("@tsofist/stem/lib/string/substr");
|
|
@@ -20,6 +21,9 @@ function buildAPIInterfaceSchemaSignature(interfaceName, memberName, suffix) {
|
|
|
20
21
|
}
|
|
21
22
|
return result;
|
|
22
23
|
}
|
|
24
|
+
function buildSchemaDefinitionRef(definitionName, schemaId) {
|
|
25
|
+
return `${schemaId || ''}#/definitions/${definitionName}`;
|
|
26
|
+
}
|
|
23
27
|
function parseSchemaDefinitionInfo(name, schemaId) {
|
|
24
28
|
const kind = name.endsWith(N_I)
|
|
25
29
|
? types_1.SchemaDefinitionKind.API
|
|
@@ -28,7 +32,7 @@ function parseSchemaDefinitionInfo(name, schemaId) {
|
|
|
28
32
|
: name.endsWith(N_R)
|
|
29
33
|
? types_1.SchemaDefinitionKind.APIMethodResult
|
|
30
34
|
: types_1.SchemaDefinitionKind.Type;
|
|
31
|
-
const ref =
|
|
35
|
+
const ref = buildSchemaDefinitionRef(name, schemaId);
|
|
32
36
|
switch (kind) {
|
|
33
37
|
case types_1.SchemaDefinitionKind.API:
|
|
34
38
|
return {
|
package/lib/validator.d.ts
CHANGED
|
@@ -1,35 +1,47 @@
|
|
|
1
|
-
import { Nullable } from '@tsofist/stem';
|
|
2
|
-
import {
|
|
1
|
+
import { ArrayMay, Nullable } from '@tsofist/stem';
|
|
2
|
+
import { ErrorsTextOptions, Options, SchemaObject } from 'ajv';
|
|
3
3
|
import { SchemaDefinitionInfo, SchemaForgeDefinitionRef, SchemaForgeValidationContextBase, SchemaForgeValidationFunction, SchemaForgeValidationReport, SchemaForgeValidationResult } from './types';
|
|
4
4
|
export type SchemaForgeValidator = ReturnType<typeof createSchemaForgeValidator>;
|
|
5
|
+
export type SchemaForgeValidatorOptions = Parameters<typeof createSchemaForgeValidator>;
|
|
6
|
+
/**
|
|
7
|
+
* Create SchemaForge Registry: a json-schema validator with additional features
|
|
8
|
+
*/
|
|
5
9
|
export declare function createSchemaForgeValidator(engineOptions?: Options, useAdditionalFormats?: boolean): {
|
|
10
|
+
readonly compilationArtifactCount: number;
|
|
6
11
|
readonly rev: number;
|
|
12
|
+
clear: () => void;
|
|
7
13
|
clone: (options?: Omit<Options, "schemas">, onSchema?: (value: SchemaObject) => SchemaObject) => {
|
|
14
|
+
readonly compilationArtifactCount: number;
|
|
8
15
|
readonly rev: number;
|
|
16
|
+
clear: () => void;
|
|
9
17
|
clone: /*elided*/ any;
|
|
10
|
-
removeSchema: (
|
|
18
|
+
removeSchema: (ref: ArrayMay<SchemaForgeDefinitionRef>) => void;
|
|
11
19
|
hasValidator: (ref: SchemaForgeDefinitionRef) => boolean;
|
|
12
|
-
getValidator: <TData = unknown>(ref: SchemaForgeDefinitionRef |
|
|
13
|
-
getSchema: (ref: SchemaForgeDefinitionRef) => AnySchema | undefined;
|
|
20
|
+
getValidator: <TData = unknown>(ref: SchemaForgeDefinitionRef | SchemaObject) => SchemaForgeValidationFunction<TData> | undefined;
|
|
21
|
+
getSchema: (ref: SchemaForgeDefinitionRef) => import("ajv").AnySchema | undefined;
|
|
14
22
|
getRootSchema: (schemaId: string) => (SchemaObject & {
|
|
15
|
-
definitions?:
|
|
23
|
+
definitions?: SchemaObject;
|
|
16
24
|
}) | undefined;
|
|
17
|
-
addSchema: (schema:
|
|
25
|
+
addSchema: (schema: SchemaObject[]) => void;
|
|
18
26
|
validateBySchema: (ref: SchemaForgeDefinitionRef, data: unknown, instancePath?: string) => SchemaForgeValidationResult;
|
|
19
27
|
checkBySchema: <T = unknown, Ctx extends SchemaForgeValidationContextBase = SchemaForgeValidationContextBase>(ref: SchemaForgeDefinitionRef, data: unknown, context?: Ctx) => data is T;
|
|
20
28
|
validationErrorsText: (errors: Nullable<SchemaForgeValidationReport>, options?: ErrorsTextOptions) => string;
|
|
21
29
|
listDefinitions: (predicate?: (info: SchemaDefinitionInfo) => boolean) => SchemaDefinitionInfo[];
|
|
30
|
+
warmupCache: (schemasPerIteration?: number, delayMs?: number) => Promise<void>;
|
|
31
|
+
warmupCacheSync: () => void;
|
|
22
32
|
};
|
|
23
|
-
removeSchema: (
|
|
33
|
+
removeSchema: (ref: ArrayMay<SchemaForgeDefinitionRef>) => void;
|
|
24
34
|
hasValidator: (ref: SchemaForgeDefinitionRef) => boolean;
|
|
25
|
-
getValidator: <TData = unknown>(ref: SchemaForgeDefinitionRef |
|
|
26
|
-
getSchema: (ref: SchemaForgeDefinitionRef) => AnySchema | undefined;
|
|
35
|
+
getValidator: <TData = unknown>(ref: SchemaForgeDefinitionRef | SchemaObject) => SchemaForgeValidationFunction<TData> | undefined;
|
|
36
|
+
getSchema: (ref: SchemaForgeDefinitionRef) => import("ajv").AnySchema | undefined;
|
|
27
37
|
getRootSchema: (schemaId: string) => (SchemaObject & {
|
|
28
|
-
definitions?:
|
|
38
|
+
definitions?: SchemaObject;
|
|
29
39
|
}) | undefined;
|
|
30
|
-
addSchema: (schema:
|
|
40
|
+
addSchema: (schema: SchemaObject[]) => void;
|
|
31
41
|
validateBySchema: (ref: SchemaForgeDefinitionRef, data: unknown, instancePath?: string) => SchemaForgeValidationResult;
|
|
32
42
|
checkBySchema: <T = unknown, Ctx extends SchemaForgeValidationContextBase = SchemaForgeValidationContextBase>(ref: SchemaForgeDefinitionRef, data: unknown, context?: Ctx) => data is T;
|
|
33
43
|
validationErrorsText: (errors: Nullable<SchemaForgeValidationReport>, options?: ErrorsTextOptions) => string;
|
|
34
44
|
listDefinitions: (predicate?: (info: SchemaDefinitionInfo) => boolean) => SchemaDefinitionInfo[];
|
|
45
|
+
warmupCache: (schemasPerIteration?: number, delayMs?: number) => Promise<void>;
|
|
46
|
+
warmupCacheSync: () => void;
|
|
35
47
|
};
|
package/lib/validator.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createSchemaForgeValidator = createSchemaForgeValidator;
|
|
4
|
+
const as_array_1 = require("@tsofist/stem/lib/as-array");
|
|
5
|
+
const chunk_1 = require("@tsofist/stem/lib/chunk");
|
|
4
6
|
const error_1 = require("@tsofist/stem/lib/error");
|
|
5
7
|
const entries_1 = require("@tsofist/stem/lib/object/entries");
|
|
6
8
|
const values_1 = require("@tsofist/stem/lib/object/values");
|
|
9
|
+
const delay_1 = require("@tsofist/stem/lib/timers/delay");
|
|
7
10
|
const ajv_1 = require("ajv");
|
|
8
11
|
const ajv_formats_1 = require("ajv-formats");
|
|
9
12
|
const types_1 = require("./types");
|
|
10
13
|
const index_1 = require("./index");
|
|
11
14
|
const DEF_OPTIONS = {
|
|
15
|
+
meta: true,
|
|
16
|
+
defaultMeta: 'http://json-schema.org/draft-07/schema',
|
|
12
17
|
allErrors: true,
|
|
13
18
|
strict: true,
|
|
14
19
|
strictSchema: true,
|
|
@@ -18,14 +23,33 @@ const DEF_OPTIONS = {
|
|
|
18
23
|
coerceTypes: false,
|
|
19
24
|
removeAdditional: false,
|
|
20
25
|
unicodeRegExp: true,
|
|
26
|
+
useDefaults: false,
|
|
27
|
+
addUsedSchema: false,
|
|
28
|
+
inlineRefs: true,
|
|
29
|
+
ownProperties: true,
|
|
30
|
+
discriminator: false,
|
|
31
|
+
code: {
|
|
32
|
+
es5: false,
|
|
33
|
+
esm: false,
|
|
34
|
+
optimize: 2,
|
|
35
|
+
},
|
|
21
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Create SchemaForge Registry: a json-schema validator with additional features
|
|
39
|
+
*/
|
|
22
40
|
function createSchemaForgeValidator(engineOptions, useAdditionalFormats = false) {
|
|
23
41
|
engineOptions = {
|
|
24
42
|
...DEF_OPTIONS,
|
|
25
43
|
...engineOptions,
|
|
26
44
|
};
|
|
45
|
+
const initialSchemas = engineOptions.schemas;
|
|
46
|
+
if (initialSchemas)
|
|
47
|
+
delete engineOptions.schemas;
|
|
27
48
|
let rev = 0;
|
|
28
49
|
let engine = new ajv_1.default(engineOptions);
|
|
50
|
+
engine.removeSchema();
|
|
51
|
+
if (initialSchemas)
|
|
52
|
+
engine.addSchema(initialSchemas);
|
|
29
53
|
addJSDocKeywords(engine);
|
|
30
54
|
if (useAdditionalFormats)
|
|
31
55
|
engine = (0, ajv_formats_1.default)(engine);
|
|
@@ -37,7 +61,10 @@ function createSchemaForgeValidator(engineOptions, useAdditionalFormats = false)
|
|
|
37
61
|
for (const env of (0, values_1.nonNullableValues)(engine.schemas)) {
|
|
38
62
|
if (env.meta)
|
|
39
63
|
continue;
|
|
40
|
-
|
|
64
|
+
const schema = onSchema
|
|
65
|
+
? onSchema(env.schema)
|
|
66
|
+
: env.schema;
|
|
67
|
+
schemas.push(schema);
|
|
41
68
|
}
|
|
42
69
|
const opts = {
|
|
43
70
|
...engineOptions,
|
|
@@ -52,13 +79,16 @@ function createSchemaForgeValidator(engineOptions, useAdditionalFormats = false)
|
|
|
52
79
|
function getValidator(ref) {
|
|
53
80
|
if (typeof ref === 'string')
|
|
54
81
|
return engine.getSchema(ref);
|
|
55
|
-
|
|
82
|
+
const result = engine.compile(ref);
|
|
83
|
+
checkIsSyncValidator(result);
|
|
84
|
+
return result;
|
|
56
85
|
}
|
|
57
86
|
/**
|
|
58
87
|
* Check if schema exists
|
|
88
|
+
* WARN: this method may compile schema if it's not compiled yet
|
|
59
89
|
*/
|
|
60
90
|
function hasValidator(ref) {
|
|
61
|
-
return engine.getSchema(ref) != null;
|
|
91
|
+
return ref in engine.schemas || ref in engine.refs || engine.getSchema(ref) != null;
|
|
62
92
|
}
|
|
63
93
|
/**
|
|
64
94
|
* Add root schema(s) to registry
|
|
@@ -68,10 +98,18 @@ function createSchemaForgeValidator(engineOptions, useAdditionalFormats = false)
|
|
|
68
98
|
rev++;
|
|
69
99
|
}
|
|
70
100
|
/**
|
|
71
|
-
* Remove root schema(s) from registry
|
|
101
|
+
* Remove root schema(s) from registry
|
|
102
|
+
*/
|
|
103
|
+
function removeSchema(ref) {
|
|
104
|
+
for (const item of (0, as_array_1.asArray)(ref))
|
|
105
|
+
engine.removeSchema(item);
|
|
106
|
+
rev++;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Remove all schemas from registry
|
|
72
110
|
*/
|
|
73
|
-
function
|
|
74
|
-
engine.removeSchema(
|
|
111
|
+
function clear() {
|
|
112
|
+
engine.removeSchema();
|
|
75
113
|
rev++;
|
|
76
114
|
}
|
|
77
115
|
/**
|
|
@@ -119,7 +157,8 @@ function createSchemaForgeValidator(engineOptions, useAdditionalFormats = false)
|
|
|
119
157
|
}
|
|
120
158
|
return result.valid;
|
|
121
159
|
}
|
|
122
|
-
function
|
|
160
|
+
function mapDefinitions(callback) {
|
|
161
|
+
const result = [];
|
|
123
162
|
for (const [schemaId, env] of (0, entries_1.entries)(engine.schemas)) {
|
|
124
163
|
if (env &&
|
|
125
164
|
typeof env.schema === 'object' &&
|
|
@@ -127,17 +166,18 @@ function createSchemaForgeValidator(engineOptions, useAdditionalFormats = false)
|
|
|
127
166
|
env.schema.definitions &&
|
|
128
167
|
typeof env.schema.definitions === 'object') {
|
|
129
168
|
for (const name of Object.keys(env.schema.definitions)) {
|
|
130
|
-
callback(name, schemaId);
|
|
169
|
+
result.push(callback(name, schemaId));
|
|
131
170
|
}
|
|
132
171
|
}
|
|
133
172
|
}
|
|
173
|
+
return result;
|
|
134
174
|
}
|
|
135
175
|
/**
|
|
136
176
|
* List schema definitions
|
|
137
177
|
*/
|
|
138
178
|
function listDefinitions(predicate) {
|
|
139
179
|
const result = [];
|
|
140
|
-
|
|
180
|
+
mapDefinitions((name, schemaId) => {
|
|
141
181
|
const info = (0, index_1.parseSchemaDefinitionInfo)(name, schemaId);
|
|
142
182
|
if (predicate === undefined || predicate(info))
|
|
143
183
|
result.push(info);
|
|
@@ -156,10 +196,40 @@ function createSchemaForgeValidator(engineOptions, useAdditionalFormats = false)
|
|
|
156
196
|
function getRootSchema(schemaId) {
|
|
157
197
|
return engine.schemas[schemaId]?.schema;
|
|
158
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Synchronously warm up validator cache
|
|
201
|
+
* This action is useful to pre-compile all schemas and their definitions
|
|
202
|
+
*/
|
|
203
|
+
function warmupCacheSync() {
|
|
204
|
+
mapDefinitions((name, schemaId) => {
|
|
205
|
+
const ref = (0, index_1.buildSchemaDefinitionRef)(name, schemaId);
|
|
206
|
+
checkIsSyncValidator(engine.getSchema(ref));
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Asynchronously warm up validator cache
|
|
211
|
+
* This action is useful to pre-compile all schemas and their definitions
|
|
212
|
+
*/
|
|
213
|
+
async function warmupCache(schemasPerIteration = 5, delayMs = 1) {
|
|
214
|
+
await (0, chunk_1.chunk)(mapDefinitions((name, schemaId) => (0, index_1.buildSchemaDefinitionRef)(name, schemaId)), schemasPerIteration, async (items) => {
|
|
215
|
+
for (const ref of items)
|
|
216
|
+
engine.getSchema(ref);
|
|
217
|
+
return (0, delay_1.delay)(delayMs);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
159
220
|
return {
|
|
221
|
+
get compilationArtifactCount() {
|
|
222
|
+
const names = new Set([
|
|
223
|
+
//
|
|
224
|
+
...Object.keys(engine.refs),
|
|
225
|
+
...Object.keys(engine.schemas),
|
|
226
|
+
]);
|
|
227
|
+
return names.size;
|
|
228
|
+
},
|
|
160
229
|
get rev() {
|
|
161
230
|
return rev;
|
|
162
231
|
},
|
|
232
|
+
clear,
|
|
163
233
|
clone,
|
|
164
234
|
removeSchema,
|
|
165
235
|
hasValidator,
|
|
@@ -171,6 +241,8 @@ function createSchemaForgeValidator(engineOptions, useAdditionalFormats = false)
|
|
|
171
241
|
checkBySchema,
|
|
172
242
|
validationErrorsText,
|
|
173
243
|
listDefinitions,
|
|
244
|
+
warmupCache,
|
|
245
|
+
warmupCacheSync,
|
|
174
246
|
};
|
|
175
247
|
}
|
|
176
248
|
function addJSDocKeywords(engine) {
|
|
@@ -181,6 +253,13 @@ function addJSDocKeywords(engine) {
|
|
|
181
253
|
const IXNamePattern = '^ix_[a-z][a-zA-Z0-9_]+$';
|
|
182
254
|
const EntityNamePattern = '^([a-zA-Z_][a-z0-9_]*\\.)?[a-z_][a-z0-9_]*$';
|
|
183
255
|
const FakerModulePattern = '^[a-zA-Z.]+$';
|
|
256
|
+
engine.addKeyword({
|
|
257
|
+
keyword: 'version',
|
|
258
|
+
metaSchema: {
|
|
259
|
+
type: 'string',
|
|
260
|
+
},
|
|
261
|
+
dependencies: ['$id', '$schema'],
|
|
262
|
+
});
|
|
184
263
|
engine.addKeyword({
|
|
185
264
|
keyword: 'hash',
|
|
186
265
|
metaSchema: {
|
|
@@ -265,3 +344,8 @@ function addJSDocKeywords(engine) {
|
|
|
265
344
|
},
|
|
266
345
|
});
|
|
267
346
|
}
|
|
347
|
+
function checkIsSyncValidator(fn) {
|
|
348
|
+
if (typeof fn === 'function' && '$async' in fn) {
|
|
349
|
+
(0, error_1.raise)('[SchemaForge] Asynchronous validation schemas are not supported');
|
|
350
|
+
}
|
|
351
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsofist/schema-forge",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Generate JSON schema from TypeScript types",
|
|
5
5
|
"author": "Andrew Berdnikov <tsofistgudmen@gmail.com>",
|
|
6
6
|
"license": "LGPL-3.0",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"test:watch": "jest --watch"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@faker-js/faker": "^9.
|
|
24
|
-
"@tsofist/stem": "^2.
|
|
23
|
+
"@faker-js/faker": "^9.6.0",
|
|
24
|
+
"@tsofist/stem": "^2.2.0",
|
|
25
25
|
"ajv": "^8.17.1",
|
|
26
26
|
"ajv-formats": "^3.0.1",
|
|
27
27
|
"json-schema-faker": "^0.5.8",
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@tsofist/web-buddy": "^1.21.0",
|
|
33
33
|
"@types/jest": "^29.5.14",
|
|
34
|
-
"@types/node": "^20.17.
|
|
35
|
-
"@types/supertest": "^6.0.
|
|
34
|
+
"@types/node": "^20.17.28",
|
|
35
|
+
"@types/supertest": "^6.0.3",
|
|
36
36
|
"jest": "^29.7.0",
|
|
37
37
|
"rimraf": "^6.0.1",
|
|
38
|
-
"supertest": "^7.
|
|
39
|
-
"ts-jest": "^29.
|
|
40
|
-
"typescript": "~5.
|
|
38
|
+
"supertest": "^7.1.0",
|
|
39
|
+
"ts-jest": "^29.3.0",
|
|
40
|
+
"typescript": "~5.8.2"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"registry": "https://registry.npmjs.org/",
|