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 ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "printWidth": 140,
3
+ "tabWidth": 4,
4
+ "useTabs": false,
5
+ "singleQuote": false,
6
+ "jsxBracketSameLine": false,
7
+ "trailingComma": "es5"
8
+ }
@@ -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
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "files.exclude": {
3
+ "node_modules": true
4
+ }
5
+ }
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
+ }