ts-runtime-validation 1.0.1
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/.prettierrc +8 -0
- package/.scripts/generate-validation-enum.js +39 -0
- package/.vscode/settings.json +5 -0
- package/README.md +46 -0
- package/dist/SchemaGenerator.js +246 -0
- package/dist/SchemaGenerator.js.map +1 -0
- package/dist/generateSchema.js +51 -0
- package/dist/generateSchema.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
- package/src/SchemaGenerator.ts +242 -0
- package/src/index.ts +35 -0
- package/tsconfig.json +17 -0
package/.prettierrc
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const schema = require("../src/validation.schema.json");
|
|
3
|
+
const OUTPUT_FILE = "./src/SchemaDefinition.ts";
|
|
4
|
+
|
|
5
|
+
const reservedDefinitionNameFilterFn = (definition) => {
|
|
6
|
+
return definition !== "ISchema" && definition !== "Schemas";
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const definitions = Object.keys(schema.definitions).filter(reservedDefinitionNameFilterFn);
|
|
10
|
+
|
|
11
|
+
const lines = [];
|
|
12
|
+
lines.push(`// THIS IS AN AUTOGENERATED FILE PLEASE DO NOT MODIFY MANUALLY`);
|
|
13
|
+
|
|
14
|
+
lines.push(`import {${definitions.join(", ")}} from "./Types";`);
|
|
15
|
+
lines.push("");
|
|
16
|
+
|
|
17
|
+
lines.push(`export const schemas: Record<keyof ISchema, string> = {`);
|
|
18
|
+
definitions.forEach((definition) => {
|
|
19
|
+
lines.push(` ["#/definitions/${definition}"]: "${definition}",`);
|
|
20
|
+
});
|
|
21
|
+
lines.push(`}`);
|
|
22
|
+
|
|
23
|
+
lines.push("");
|
|
24
|
+
|
|
25
|
+
lines.push("export interface ISchema {");
|
|
26
|
+
|
|
27
|
+
definitions.forEach((definition) => {
|
|
28
|
+
lines.push(` ["#/definitions/${definition}"]: ${definition},`);
|
|
29
|
+
});
|
|
30
|
+
lines.push("}");
|
|
31
|
+
lines.push("");
|
|
32
|
+
|
|
33
|
+
const output = lines.join("\n");
|
|
34
|
+
|
|
35
|
+
fs.writeFile(OUTPUT_FILE, output, { flag: "w" }, function (err) {
|
|
36
|
+
if (err) {
|
|
37
|
+
return console.log(err);
|
|
38
|
+
}
|
|
39
|
+
});
|
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# ts-runtime-check
|
|
2
|
+
|
|
3
|
+
## Why?
|
|
4
|
+
|
|
5
|
+
Get bulletproof type validation based off typescript interfaces
|
|
6
|
+
|
|
7
|
+
## How?
|
|
8
|
+
|
|
9
|
+
This is a code generator that is designed to run as a yarn / npm script. By default scans your source directory for files ending in the provided glob pattern. By default: `*.jsonschema.{ts,tsx}`.
|
|
10
|
+
|
|
11
|
+
## Footnote
|
|
12
|
+
|
|
13
|
+
The helper file assumes you have [ajv-validator](https://github.com/ajv-validator/ajv) peer dependency installed.
|
|
14
|
+
|
|
15
|
+
## CLI usage
|
|
16
|
+
|
|
17
|
+
Ensure your project files containing the schemas you want to validate end with the prefix `.jsonschema.ts`
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Usage: ts-runtime-check [options]
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--glob Glob file path of typescript files to generate ts-interface -> json-schema validations - default: *.jsonschema.{ts,tsx}
|
|
24
|
+
--rootPath RootPath of source - default: ./src
|
|
25
|
+
--output Validation schema + typescript interface output directory (relative to root path) - default: ./.ts-runtime-check
|
|
26
|
+
--no-helpers Only generate JSON schema without typescript helper files
|
|
27
|
+
-h, --help display help for command
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage with helper function
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { isValidSchema } from "./.ts-runtime-check/isSchemaValid"; // this is autogenerated by the CLI as a helper file
|
|
34
|
+
|
|
35
|
+
if (isValidSchema(data, "#/definitions/ITypeA")) {
|
|
36
|
+
// variable: data in this block will have typings for ITypeA
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Contributing
|
|
41
|
+
|
|
42
|
+
Submit a PR
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
MIT
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.SchemaGenerator = void 0;
|
|
39
|
+
const fdir_1 = require("fdir");
|
|
40
|
+
const path_1 = require("path");
|
|
41
|
+
const TJS = __importStar(require("typescript-json-schema"));
|
|
42
|
+
const fs_1 = __importDefault(require("fs"));
|
|
43
|
+
const picomatch_1 = __importDefault(require("picomatch"));
|
|
44
|
+
const path_2 = __importDefault(require("path"));
|
|
45
|
+
const ts_morph_1 = require("ts-morph");
|
|
46
|
+
const defaultProjectSettings = {
|
|
47
|
+
manipulationSettings: {
|
|
48
|
+
indentationText: ts_morph_1.IndentationText.FourSpaces,
|
|
49
|
+
newLineKind: ts_morph_1.NewLineKind.LineFeed,
|
|
50
|
+
quoteKind: ts_morph_1.QuoteKind.Double,
|
|
51
|
+
usePrefixAndSuffixTextForRename: false,
|
|
52
|
+
useTrailingCommas: true,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const defaultCreateFileOptions = {
|
|
56
|
+
overwrite: true,
|
|
57
|
+
};
|
|
58
|
+
const validationSchemaFileName = "validation.schema.json";
|
|
59
|
+
const schemaDefinitionFileName = "SchemaDefinition.ts";
|
|
60
|
+
class SchemaGenerator {
|
|
61
|
+
constructor(options) {
|
|
62
|
+
this.options = options;
|
|
63
|
+
this.outputPath = path_2.default.join(this.options.rootPath, this.options.output);
|
|
64
|
+
this.jsonSchemaOutputFile = path_2.default.join(this.options.rootPath, this.options.output, validationSchemaFileName);
|
|
65
|
+
this.tsSchemaDefinitionOutputFile = path_2.default.join(this.options.rootPath, this.options.output, schemaDefinitionFileName);
|
|
66
|
+
this.isValidSchemaOutputFile = path_2.default.join(this.options.rootPath, this.options.output, "isSchemaValid.ts");
|
|
67
|
+
this.generateOutput = () => __awaiter(this, void 0, void 0, function* () {
|
|
68
|
+
const { helpers } = this.options;
|
|
69
|
+
const fileList = yield this.getMatchingFiles();
|
|
70
|
+
console.log(`Found ${fileList.length} schema file(s)`);
|
|
71
|
+
const map = yield this.getJsonSchemaMap(fileList);
|
|
72
|
+
console.log(`Generating ${map.size} validation schema(s)`);
|
|
73
|
+
this.writeSchemaMapToValidationSchema(map);
|
|
74
|
+
if (helpers === false) {
|
|
75
|
+
console.log("Skipping helper file generation");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
yield this.writeSchemaMapToValidationTypes(map, fileList);
|
|
79
|
+
this.writeValidatorFunction();
|
|
80
|
+
});
|
|
81
|
+
this.getMatchingFiles = () => __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
const { glob, rootPath } = this.options;
|
|
83
|
+
const api = new fdir_1.fdir().crawlWithOptions(rootPath, {
|
|
84
|
+
includeBasePath: true,
|
|
85
|
+
includeDirs: false,
|
|
86
|
+
filters: [
|
|
87
|
+
(path) => {
|
|
88
|
+
return picomatch_1.default.isMatch(path, glob, { contains: true });
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
return api.withPromise();
|
|
93
|
+
});
|
|
94
|
+
this.getJsonSchemaMap = (filesList) => __awaiter(this, void 0, void 0, function* () {
|
|
95
|
+
var _a;
|
|
96
|
+
const schemaMap = new Map();
|
|
97
|
+
const files = filesList.map((fileName) => {
|
|
98
|
+
return (0, path_1.resolve)(fileName);
|
|
99
|
+
});
|
|
100
|
+
const settings = {
|
|
101
|
+
required: true,
|
|
102
|
+
titles: true,
|
|
103
|
+
aliasRef: true,
|
|
104
|
+
ref: true,
|
|
105
|
+
noExtraProps: true,
|
|
106
|
+
propOrder: true,
|
|
107
|
+
};
|
|
108
|
+
const compilerOptions = {
|
|
109
|
+
strictNullChecks: true,
|
|
110
|
+
};
|
|
111
|
+
const program = TJS.getProgramFromFiles(files, compilerOptions);
|
|
112
|
+
const generator = TJS.buildGenerator(program, settings);
|
|
113
|
+
const userDefinedSymbols = (_a = generator === null || generator === void 0 ? void 0 : generator.getMainFileSymbols(program)) !== null && _a !== void 0 ? _a : [];
|
|
114
|
+
userDefinedSymbols.forEach((symbol) => {
|
|
115
|
+
if (schemaMap.has(symbol)) {
|
|
116
|
+
throw new Error(`Duplicate symbol "${symbol}" found.`);
|
|
117
|
+
}
|
|
118
|
+
const schema = generator === null || generator === void 0 ? void 0 : generator.getSchemaForSymbol(symbol);
|
|
119
|
+
if (schema) {
|
|
120
|
+
schemaMap.set(symbol, schema);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return schemaMap;
|
|
124
|
+
});
|
|
125
|
+
this.getSchemaVersion = (schemaMap) => {
|
|
126
|
+
var _a;
|
|
127
|
+
const firstEntry = schemaMap.values().next().value;
|
|
128
|
+
return (_a = firstEntry["$schema"]) !== null && _a !== void 0 ? _a : "";
|
|
129
|
+
};
|
|
130
|
+
this.ensureOutputPathExists = () => {
|
|
131
|
+
if (!fs_1.default.existsSync(this.outputPath)) {
|
|
132
|
+
fs_1.default.mkdirSync(this.outputPath, { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
this.writeSchemaMapToValidationSchema = (schemaMap) => {
|
|
136
|
+
const definitions = {};
|
|
137
|
+
schemaMap.forEach((schema, key) => {
|
|
138
|
+
definitions[key] = schema;
|
|
139
|
+
});
|
|
140
|
+
const outputBuffer = {
|
|
141
|
+
$schema: this.getSchemaVersion(schemaMap),
|
|
142
|
+
definitions,
|
|
143
|
+
};
|
|
144
|
+
this.ensureOutputPathExists();
|
|
145
|
+
fs_1.default.writeFileSync(this.jsonSchemaOutputFile, JSON.stringify(outputBuffer, null, 4));
|
|
146
|
+
};
|
|
147
|
+
this.writeSchemaMapToValidationTypes = (schemaMap, fileList) => __awaiter(this, void 0, void 0, function* () {
|
|
148
|
+
const project = new ts_morph_1.Project(defaultProjectSettings);
|
|
149
|
+
const symbols = Array.from(schemaMap.keys()).filter((symbol) => {
|
|
150
|
+
return symbol !== "ISchema" && symbol !== "Schemas";
|
|
151
|
+
});
|
|
152
|
+
const readerProject = new ts_morph_1.Project();
|
|
153
|
+
readerProject.addSourceFilesAtPaths(fileList);
|
|
154
|
+
const importMap = new Map();
|
|
155
|
+
fileList.forEach((file) => {
|
|
156
|
+
const dir = path_2.default.dirname(file);
|
|
157
|
+
const fileWithoutExtension = path_2.default.parse(file).name;
|
|
158
|
+
const relativeFilePath = path_2.default.relative(this.outputPath, dir);
|
|
159
|
+
const importPath = `${relativeFilePath}/${fileWithoutExtension}`;
|
|
160
|
+
const source = readerProject.getSourceFile(file);
|
|
161
|
+
source === null || source === void 0 ? void 0 : source.getInterfaces().forEach((interfaceDeclaration) => {
|
|
162
|
+
var _a;
|
|
163
|
+
const structure = interfaceDeclaration.getStructure();
|
|
164
|
+
const namedImports = (_a = importMap.get(importPath)) !== null && _a !== void 0 ? _a : [];
|
|
165
|
+
namedImports.push(structure.name);
|
|
166
|
+
importMap.set(importPath, namedImports);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
const sourceFile = project.createSourceFile(this.tsSchemaDefinitionOutputFile, {}, defaultCreateFileOptions);
|
|
170
|
+
importMap.forEach((namedImports, importPath) => {
|
|
171
|
+
sourceFile.addImportDeclaration({ namedImports, moduleSpecifier: importPath });
|
|
172
|
+
});
|
|
173
|
+
sourceFile.addVariableStatement({
|
|
174
|
+
declarationKind: ts_morph_1.VariableDeclarationKind.Const,
|
|
175
|
+
declarations: [
|
|
176
|
+
{
|
|
177
|
+
name: "schemas",
|
|
178
|
+
type: "Record<keyof ISchema, string>",
|
|
179
|
+
initializer: (writer) => {
|
|
180
|
+
writer.writeLine(`{`);
|
|
181
|
+
symbols.forEach((symbol) => {
|
|
182
|
+
writer.writeLine(`["#/definitions/${symbol}"] : "${symbol}",`);
|
|
183
|
+
}),
|
|
184
|
+
writer.writeLine(`}`);
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
});
|
|
189
|
+
sourceFile.addInterface({
|
|
190
|
+
kind: ts_morph_1.StructureKind.Interface,
|
|
191
|
+
name: "ISchema",
|
|
192
|
+
isExported: false,
|
|
193
|
+
properties: symbols.map((symbol) => {
|
|
194
|
+
return { name: `readonly ["#/definitions/${symbol}"]`, type: symbol };
|
|
195
|
+
}),
|
|
196
|
+
});
|
|
197
|
+
sourceFile.addExportDeclaration({
|
|
198
|
+
namedExports: ["schemas", "ISchema"],
|
|
199
|
+
});
|
|
200
|
+
yield project.save();
|
|
201
|
+
});
|
|
202
|
+
this.writeValidatorFunction = () => __awaiter(this, void 0, void 0, function* () {
|
|
203
|
+
const project = new ts_morph_1.Project(defaultProjectSettings);
|
|
204
|
+
const sourceFile = project.createSourceFile(this.isValidSchemaOutputFile, {}, defaultCreateFileOptions);
|
|
205
|
+
sourceFile.addImportDeclaration({ namespaceImport: "schema", moduleSpecifier: `./${validationSchemaFileName}` });
|
|
206
|
+
sourceFile.addImportDeclaration({ defaultImport: "Ajv", moduleSpecifier: "ajv" });
|
|
207
|
+
sourceFile.addImportDeclaration({
|
|
208
|
+
namedImports: ["ISchema", "schemas"],
|
|
209
|
+
moduleSpecifier: `./${path_2.default.parse(schemaDefinitionFileName).name}`,
|
|
210
|
+
});
|
|
211
|
+
sourceFile.addVariableStatement({
|
|
212
|
+
declarationKind: ts_morph_1.VariableDeclarationKind.Const,
|
|
213
|
+
declarations: [
|
|
214
|
+
{
|
|
215
|
+
name: "validator",
|
|
216
|
+
initializer: (writer) => {
|
|
217
|
+
writer.writeLine(`new Ajv({ allErrors: true });`);
|
|
218
|
+
writer.writeLine(`validator.compile(schema)`);
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
});
|
|
223
|
+
sourceFile.addVariableStatement({
|
|
224
|
+
declarationKind: ts_morph_1.VariableDeclarationKind.Const,
|
|
225
|
+
declarations: [
|
|
226
|
+
{
|
|
227
|
+
name: "isValidSchema",
|
|
228
|
+
initializer: (writer) => {
|
|
229
|
+
writer.writeLine(`<T extends keyof typeof schemas>(data: unknown, schemaKeyRef: T): data is ISchema[T] => {`);
|
|
230
|
+
writer.writeLine(`validator.validate(schemaKeyRef as string, data);`);
|
|
231
|
+
writer.writeLine(`return Boolean(validator.errors) === false;`);
|
|
232
|
+
writer.writeLine(`}`);
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
sourceFile.addExportDeclaration({
|
|
238
|
+
namedExports: ["validator", "isValidSchema"],
|
|
239
|
+
});
|
|
240
|
+
yield project.save();
|
|
241
|
+
});
|
|
242
|
+
this.generateOutput();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
exports.SchemaGenerator = SchemaGenerator;
|
|
246
|
+
//# sourceMappingURL=SchemaGenerator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SchemaGenerator.js","sourceRoot":"","sources":["../src/SchemaGenerator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
const path_1 = require("path");
|
|
28
|
+
const TJS = __importStar(require("typescript-json-schema"));
|
|
29
|
+
// optionally pass argument to schema generator
|
|
30
|
+
const settings = {
|
|
31
|
+
required: true,
|
|
32
|
+
};
|
|
33
|
+
// optionally pass ts compiler options
|
|
34
|
+
const compilerOptions = {
|
|
35
|
+
strictNullChecks: true,
|
|
36
|
+
};
|
|
37
|
+
// optionally pass a base path
|
|
38
|
+
const basePath = "./my-dir";
|
|
39
|
+
const program = TJS.getProgramFromFiles([(0, path_1.resolve)("my-file.ts")], compilerOptions, basePath);
|
|
40
|
+
// We can either get the schema for one file and one type...
|
|
41
|
+
const schema = TJS.generateSchema(program, "MyType", settings);
|
|
42
|
+
// ... or a generator that lets us incrementally get more schemas
|
|
43
|
+
const generator = TJS.buildGenerator(program, settings);
|
|
44
|
+
// generator can be also reused to speed up generating the schema if usecase allows:
|
|
45
|
+
const schemaWithReusedGenerator = TJS.generateSchema(program, "MyType", settings, [], generator);
|
|
46
|
+
// all symbols
|
|
47
|
+
const symbols = generator.getUserSymbols();
|
|
48
|
+
// Get symbols for different types from generator.
|
|
49
|
+
generator.getSchemaForSymbol("MyType");
|
|
50
|
+
generator.getSchemaForSymbol("AnotherType");
|
|
51
|
+
//# sourceMappingURL=generateSchema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateSchema.js","sourceRoot":"","sources":["../src/generateSchema.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AACA,+BAA+B;AAE/B,4DAA8C;AAE9C,+CAA+C;AAC/C,MAAM,QAAQ,GAAoB;IAC9B,QAAQ,EAAE,IAAI;CACjB,CAAC;AAEF,sCAAsC;AACtC,MAAM,eAAe,GAAwB;IACzC,gBAAgB,EAAE,IAAI;CACzB,CAAC;AAEF,8BAA8B;AAC9B,MAAM,QAAQ,GAAG,UAAU,CAAC;AAE5B,MAAM,OAAO,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC,IAAA,cAAO,EAAC,YAAY,CAAC,CAAC,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC;AAE5F,4DAA4D;AAC5D,MAAM,MAAM,GAAG,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAE/D,iEAAiE;AAEjE,MAAM,SAAS,GAAG,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAExD,oFAAoF;AACpF,MAAM,yBAAyB,GAAG,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;AAEjG,cAAc;AACd,MAAM,OAAO,GAAG,SAAS,CAAC,cAAc,EAAE,CAAC;AAE3C,kDAAkD;AAClD,SAAS,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;AACvC,SAAS,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const SchemaGenerator_1 = require("./SchemaGenerator");
|
|
5
|
+
const commander_1 = require("commander");
|
|
6
|
+
const defaultGlobPattern = "*.jsonschema.{ts,tsx}";
|
|
7
|
+
const defaultRootPath = "./src";
|
|
8
|
+
const defaultOutputFolder = "./.ts-runtime-check";
|
|
9
|
+
commander_1.program.option("--glob", `Glob file path of typescript files to generate ts-interface -> json-schema validations - default: ${defaultGlobPattern}`, defaultGlobPattern);
|
|
10
|
+
commander_1.program.option("--rootPath", `RootPath of source - default: ${defaultRootPath}`, defaultRootPath);
|
|
11
|
+
commander_1.program.option("--output", `Validation schema + typescript interface output directory (relative to root path) - default: ${defaultOutputFolder}`, defaultOutputFolder);
|
|
12
|
+
commander_1.program.option("--no-helpers", "Only generate JSON schema without typescript helper files", true);
|
|
13
|
+
commander_1.program.parse();
|
|
14
|
+
const options = commander_1.program.opts();
|
|
15
|
+
new SchemaGenerator_1.SchemaGenerator(options);
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,uDAAoD;AACpD,yCAAoC;AAEpC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAEnD,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AASlD,mBAAO,CAAC,MAAM,CACV,QAAQ,EACR,qGAAqG,kBAAkB,EAAE,EACzH,kBAAkB,CACrB,CAAC;AACF,mBAAO,CAAC,MAAM,CAAC,YAAY,EAAE,iCAAiC,eAAe,EAAE,EAAE,eAAe,CAAC,CAAC;AAClG,mBAAO,CAAC,MAAM,CACV,UAAU,EACV,gGAAgG,mBAAmB,EAAE,EACrH,mBAAmB,CACtB,CAAC;AACF,mBAAO,CAAC,MAAM,CAAC,cAAc,EAAE,2DAA2D,EAAE,IAAI,CAAC,CAAC;AAElG,mBAAO,CAAC,KAAK,EAAE,CAAC;AAEhB,MAAM,OAAO,GAAG,mBAAO,CAAC,IAAI,EAAmB,CAAC;AAEhD,IAAI,iCAAe,CAAC,OAAO,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ts-runtime-validation",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"author": "Matthew Duong <thegalah@gmail.com>",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"commander": "^9.1.0",
|
|
8
|
+
"fdir": "^5.2.0",
|
|
9
|
+
"picomatch": "^2.3.1",
|
|
10
|
+
"ts-morph": "^14.0.0",
|
|
11
|
+
"typescript-json-schema": "^0.53.0"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"ts-runtime-check": "dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prettier": "prettier --write \"{src,web,.scripts}/**/*.{js,jsx,ts,tsx}\" \"*.{js,json,md,yml,yaml}\""
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^17.0.24",
|
|
22
|
+
"@types/picomatch": "^2.3.0",
|
|
23
|
+
"prettier": "^2.6.2",
|
|
24
|
+
"ts-node": "^10.7.0",
|
|
25
|
+
"typescript": "^4.6.3"
|
|
26
|
+
},
|
|
27
|
+
"description": "A set of tools to generate json-schema validations from typescript interface(s) and methods to validate unknown data at runtime against them",
|
|
28
|
+
"main": "index.js",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/thegalah/ts-runtime-check.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/thegalah/ts-runtime-check/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/thegalah/ts-runtime-check#readme",
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"ajv": "^8.11.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { fdir } from "fdir";
|
|
2
|
+
import { ICommandOptions } from "./index";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import * as TJS from "typescript-json-schema";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import picomatch from "picomatch";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import {
|
|
9
|
+
Project,
|
|
10
|
+
IndentationText,
|
|
11
|
+
NewLineKind,
|
|
12
|
+
QuoteKind,
|
|
13
|
+
StructureKind,
|
|
14
|
+
VariableDeclarationKind,
|
|
15
|
+
CodeBlockWriter,
|
|
16
|
+
ProjectOptions,
|
|
17
|
+
SourceFileCreateOptions,
|
|
18
|
+
} from "ts-morph";
|
|
19
|
+
|
|
20
|
+
const defaultProjectSettings: ProjectOptions = {
|
|
21
|
+
manipulationSettings: {
|
|
22
|
+
indentationText: IndentationText.FourSpaces,
|
|
23
|
+
newLineKind: NewLineKind.LineFeed,
|
|
24
|
+
quoteKind: QuoteKind.Double,
|
|
25
|
+
usePrefixAndSuffixTextForRename: false,
|
|
26
|
+
useTrailingCommas: true,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const defaultCreateFileOptions: SourceFileCreateOptions = {
|
|
31
|
+
overwrite: true,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const validationSchemaFileName = "validation.schema.json";
|
|
35
|
+
const schemaDefinitionFileName = "SchemaDefinition.ts";
|
|
36
|
+
|
|
37
|
+
export class SchemaGenerator {
|
|
38
|
+
private outputPath = path.join(this.options.rootPath, this.options.output);
|
|
39
|
+
private jsonSchemaOutputFile = path.join(this.options.rootPath, this.options.output, validationSchemaFileName);
|
|
40
|
+
private tsSchemaDefinitionOutputFile = path.join(this.options.rootPath, this.options.output, schemaDefinitionFileName);
|
|
41
|
+
private isValidSchemaOutputFile = path.join(this.options.rootPath, this.options.output, "isSchemaValid.ts");
|
|
42
|
+
|
|
43
|
+
public constructor(private options: ICommandOptions) {
|
|
44
|
+
this.generateOutput();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private generateOutput = async () => {
|
|
48
|
+
const { helpers } = this.options;
|
|
49
|
+
const fileList = await this.getMatchingFiles();
|
|
50
|
+
console.log(`Found ${fileList.length} schema file(s)`);
|
|
51
|
+
const map = await this.getJsonSchemaMap(fileList);
|
|
52
|
+
console.log(`Generating ${map.size} validation schema(s)`);
|
|
53
|
+
this.writeSchemaMapToValidationSchema(map);
|
|
54
|
+
if (helpers === false) {
|
|
55
|
+
console.log("Skipping helper file generation");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await this.writeSchemaMapToValidationTypes(map, fileList);
|
|
59
|
+
this.writeValidatorFunction();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
private getMatchingFiles = async () => {
|
|
63
|
+
const { glob, rootPath } = this.options;
|
|
64
|
+
const api = new fdir().crawlWithOptions(rootPath, {
|
|
65
|
+
includeBasePath: true,
|
|
66
|
+
includeDirs: false,
|
|
67
|
+
filters: [
|
|
68
|
+
(path) => {
|
|
69
|
+
return picomatch.isMatch(path, glob, { contains: true });
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
return api.withPromise() as Promise<Array<string>>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
private getJsonSchemaMap = async (filesList: Array<string>) => {
|
|
77
|
+
const schemaMap = new Map<string, TJS.Definition>();
|
|
78
|
+
const files = filesList.map((fileName) => {
|
|
79
|
+
return resolve(fileName);
|
|
80
|
+
});
|
|
81
|
+
const settings: TJS.PartialArgs = {
|
|
82
|
+
required: true,
|
|
83
|
+
titles: true,
|
|
84
|
+
aliasRef: true,
|
|
85
|
+
ref: true,
|
|
86
|
+
noExtraProps: true,
|
|
87
|
+
propOrder: true,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const compilerOptions: TJS.CompilerOptions = {
|
|
91
|
+
strictNullChecks: true,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const program = TJS.getProgramFromFiles(files, compilerOptions);
|
|
95
|
+
|
|
96
|
+
const generator = TJS.buildGenerator(program, settings);
|
|
97
|
+
const userDefinedSymbols = generator?.getMainFileSymbols(program) ?? [];
|
|
98
|
+
userDefinedSymbols.forEach((symbol) => {
|
|
99
|
+
if (schemaMap.has(symbol)) {
|
|
100
|
+
throw new Error(`Duplicate symbol "${symbol}" found.`);
|
|
101
|
+
}
|
|
102
|
+
const schema = generator?.getSchemaForSymbol(symbol);
|
|
103
|
+
if (schema) {
|
|
104
|
+
schemaMap.set(symbol, schema);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return schemaMap;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
private getSchemaVersion = (schemaMap: Map<string, TJS.Definition>) => {
|
|
112
|
+
const firstEntry = schemaMap.values().next().value;
|
|
113
|
+
return firstEntry["$schema"] ?? "";
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
private ensureOutputPathExists = () => {
|
|
117
|
+
if (!fs.existsSync(this.outputPath)) {
|
|
118
|
+
fs.mkdirSync(this.outputPath, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
private writeSchemaMapToValidationSchema = (schemaMap: Map<string, TJS.Definition>) => {
|
|
123
|
+
const definitions: { [id: string]: TJS.Definition } = {};
|
|
124
|
+
schemaMap.forEach((schema, key) => {
|
|
125
|
+
definitions[key] = schema;
|
|
126
|
+
});
|
|
127
|
+
const outputBuffer: TJS.Definition = {
|
|
128
|
+
$schema: this.getSchemaVersion(schemaMap),
|
|
129
|
+
definitions,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
this.ensureOutputPathExists();
|
|
133
|
+
fs.writeFileSync(this.jsonSchemaOutputFile, JSON.stringify(outputBuffer, null, 4));
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
private writeSchemaMapToValidationTypes = async (schemaMap: Map<string, TJS.Definition>, fileList: Array<string>) => {
|
|
137
|
+
const project = new Project(defaultProjectSettings);
|
|
138
|
+
|
|
139
|
+
const symbols = Array.from(schemaMap.keys()).filter((symbol) => {
|
|
140
|
+
return symbol !== "ISchema" && symbol !== "Schemas";
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const readerProject = new Project();
|
|
144
|
+
readerProject.addSourceFilesAtPaths(fileList);
|
|
145
|
+
|
|
146
|
+
const importMap = new Map<string, Array<string>>();
|
|
147
|
+
fileList.forEach((file) => {
|
|
148
|
+
const dir = path.dirname(file);
|
|
149
|
+
const fileWithoutExtension = path.parse(file).name;
|
|
150
|
+
const relativeFilePath = path.relative(this.outputPath, dir);
|
|
151
|
+
const importPath = `${relativeFilePath}/${fileWithoutExtension}`;
|
|
152
|
+
const source = readerProject.getSourceFile(file);
|
|
153
|
+
source?.getInterfaces().forEach((interfaceDeclaration) => {
|
|
154
|
+
const structure = interfaceDeclaration.getStructure();
|
|
155
|
+
const namedImports = importMap.get(importPath) ?? [];
|
|
156
|
+
namedImports.push(structure.name);
|
|
157
|
+
importMap.set(importPath, namedImports);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const sourceFile = project.createSourceFile(this.tsSchemaDefinitionOutputFile, {}, defaultCreateFileOptions);
|
|
162
|
+
importMap.forEach((namedImports, importPath) => {
|
|
163
|
+
sourceFile.addImportDeclaration({ namedImports, moduleSpecifier: importPath });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
sourceFile.addVariableStatement({
|
|
167
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
168
|
+
declarations: [
|
|
169
|
+
{
|
|
170
|
+
name: "schemas",
|
|
171
|
+
type: "Record<keyof ISchema, string>",
|
|
172
|
+
initializer: (writer: CodeBlockWriter) => {
|
|
173
|
+
writer.writeLine(`{`);
|
|
174
|
+
symbols.forEach((symbol) => {
|
|
175
|
+
writer.writeLine(`["#/definitions/${symbol}"] : "${symbol}",`);
|
|
176
|
+
}),
|
|
177
|
+
writer.writeLine(`}`);
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
sourceFile.addInterface({
|
|
184
|
+
kind: StructureKind.Interface,
|
|
185
|
+
name: "ISchema",
|
|
186
|
+
isExported: false,
|
|
187
|
+
properties: symbols.map((symbol) => {
|
|
188
|
+
return { name: `readonly ["#/definitions/${symbol}"]`, type: symbol };
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
sourceFile.addExportDeclaration({
|
|
193
|
+
namedExports: ["schemas", "ISchema"],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await project.save();
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
private writeValidatorFunction = async () => {
|
|
200
|
+
const project = new Project(defaultProjectSettings);
|
|
201
|
+
const sourceFile = project.createSourceFile(this.isValidSchemaOutputFile, {}, defaultCreateFileOptions);
|
|
202
|
+
sourceFile.addImportDeclaration({ namespaceImport: "schema", moduleSpecifier: `./${validationSchemaFileName}` });
|
|
203
|
+
sourceFile.addImportDeclaration({ defaultImport: "Ajv", moduleSpecifier: "ajv" });
|
|
204
|
+
sourceFile.addImportDeclaration({
|
|
205
|
+
namedImports: ["ISchema", "schemas"],
|
|
206
|
+
moduleSpecifier: `./${path.parse(schemaDefinitionFileName).name}`,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
sourceFile.addVariableStatement({
|
|
210
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
211
|
+
declarations: [
|
|
212
|
+
{
|
|
213
|
+
name: "validator",
|
|
214
|
+
initializer: (writer: CodeBlockWriter) => {
|
|
215
|
+
writer.writeLine(`new Ajv({ allErrors: true });`);
|
|
216
|
+
writer.writeLine(`validator.compile(schema)`);
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
sourceFile.addVariableStatement({
|
|
223
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
224
|
+
declarations: [
|
|
225
|
+
{
|
|
226
|
+
name: "isValidSchema",
|
|
227
|
+
initializer: (writer: CodeBlockWriter) => {
|
|
228
|
+
writer.writeLine(`<T extends keyof typeof schemas>(data: unknown, schemaKeyRef: T): data is ISchema[T] => {`);
|
|
229
|
+
writer.writeLine(`validator.validate(schemaKeyRef as string, data);`);
|
|
230
|
+
writer.writeLine(`return Boolean(validator.errors) === false;`);
|
|
231
|
+
writer.writeLine(`}`);
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
sourceFile.addExportDeclaration({
|
|
238
|
+
namedExports: ["validator", "isValidSchema"],
|
|
239
|
+
});
|
|
240
|
+
await project.save();
|
|
241
|
+
};
|
|
242
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { SchemaGenerator } from "./SchemaGenerator";
|
|
4
|
+
import { program } from "commander";
|
|
5
|
+
|
|
6
|
+
const defaultGlobPattern = "*.jsonschema.{ts,tsx}";
|
|
7
|
+
|
|
8
|
+
const defaultRootPath = "./src";
|
|
9
|
+
const defaultOutputFolder = "./.ts-runtime-check";
|
|
10
|
+
|
|
11
|
+
export interface ICommandOptions {
|
|
12
|
+
readonly glob: string;
|
|
13
|
+
readonly rootPath: string;
|
|
14
|
+
readonly output: string;
|
|
15
|
+
readonly helpers: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
program.option(
|
|
19
|
+
"--glob",
|
|
20
|
+
`Glob file path of typescript files to generate ts-interface -> json-schema validations - default: ${defaultGlobPattern}`,
|
|
21
|
+
defaultGlobPattern
|
|
22
|
+
);
|
|
23
|
+
program.option("--rootPath", `RootPath of source - default: ${defaultRootPath}`, defaultRootPath);
|
|
24
|
+
program.option(
|
|
25
|
+
"--output",
|
|
26
|
+
`Validation schema + typescript interface output directory (relative to root path) - default: ${defaultOutputFolder}`,
|
|
27
|
+
defaultOutputFolder
|
|
28
|
+
);
|
|
29
|
+
program.option("--no-helpers", "Only generate JSON schema without typescript helper files", true);
|
|
30
|
+
|
|
31
|
+
program.parse();
|
|
32
|
+
|
|
33
|
+
const options = program.opts<ICommandOptions>();
|
|
34
|
+
|
|
35
|
+
new SchemaGenerator(options);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es6",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"rootDir": "src",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"sourceMap": true,
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"lib": ["es6", "dom"],
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noImplicitAny": true,
|
|
13
|
+
"noUnusedParameters": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts"],
|
|
16
|
+
"exclude": ["node_modules"]
|
|
17
|
+
}
|