ts-runtime-validation 1.6.16 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CONTRIBUTING.md +430 -0
  2. package/README.md +505 -62
  3. package/dist/ICommandOptions.js +3 -0
  4. package/dist/ICommandOptions.js.map +1 -0
  5. package/dist/SchemaGenerator.deterministic-extended.test.js +420 -0
  6. package/dist/SchemaGenerator.deterministic-extended.test.js.map +1 -0
  7. package/dist/SchemaGenerator.deterministic.test.js +251 -0
  8. package/dist/SchemaGenerator.deterministic.test.js.map +1 -0
  9. package/dist/SchemaGenerator.integration.test.js +323 -0
  10. package/dist/SchemaGenerator.integration.test.js.map +1 -0
  11. package/dist/SchemaGenerator.js +120 -0
  12. package/dist/SchemaGenerator.js.map +1 -0
  13. package/dist/SchemaGenerator.test.js +226 -0
  14. package/dist/SchemaGenerator.test.js.map +1 -0
  15. package/dist/cli.test.js +155 -0
  16. package/dist/cli.test.js.map +1 -0
  17. package/dist/errors/index.js +95 -0
  18. package/dist/errors/index.js.map +1 -0
  19. package/dist/errors/index.test.js +232 -0
  20. package/dist/errors/index.test.js.map +1 -0
  21. package/dist/getPosixPath.js +13 -0
  22. package/dist/getPosixPath.js.map +1 -0
  23. package/dist/index.js +27 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/lib.js.map +1 -0
  26. package/dist/services/CodeGenerator.js +321 -0
  27. package/dist/services/CodeGenerator.js.map +1 -0
  28. package/dist/services/FileDiscovery.js +123 -0
  29. package/dist/services/FileDiscovery.js.map +1 -0
  30. package/dist/services/FileDiscovery.test.js +184 -0
  31. package/dist/services/FileDiscovery.test.js.map +1 -0
  32. package/dist/services/SchemaProcessor.js +198 -0
  33. package/dist/services/SchemaProcessor.js.map +1 -0
  34. package/dist/services/SchemaProcessor.test.js +455 -0
  35. package/dist/services/SchemaProcessor.test.js.map +1 -0
  36. package/dist/services/SchemaWriter.js +76 -0
  37. package/dist/services/SchemaWriter.js.map +1 -0
  38. package/dist/services/SchemaWriter.test.js +255 -0
  39. package/dist/services/SchemaWriter.test.js.map +1 -0
  40. package/dist/test/basic-scenario/types.jsonschema.js +3 -0
  41. package/dist/test/basic-scenario/types.jsonschema.js.map +1 -0
  42. package/dist/test/duplicate-symbols-different-implementation/IBaseType.js +3 -0
  43. package/dist/test/duplicate-symbols-different-implementation/IBaseType.js.map +1 -0
  44. package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js +3 -0
  45. package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
  46. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js +3 -0
  47. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js.map +1 -0
  48. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js +3 -0
  49. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js.map +1 -0
  50. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js +3 -0
  51. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js.map +1 -0
  52. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js +3 -0
  53. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
  54. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js +3 -0
  55. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js.map +1 -0
  56. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js +3 -0
  57. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js.map +1 -0
  58. package/dist/utils/ProgressReporter.js +67 -0
  59. package/dist/utils/ProgressReporter.js.map +1 -0
  60. package/dist/utils/ProgressReporter.test.js +267 -0
  61. package/dist/utils/ProgressReporter.test.js.map +1 -0
  62. package/dist/writeLine.js +12 -0
  63. package/dist/writeLine.js.map +1 -0
  64. package/package.json +2 -2
  65. package/src/ICommandOptions.ts +7 -0
  66. package/src/SchemaGenerator.deterministic-extended.test.ts +429 -0
  67. package/src/SchemaGenerator.deterministic.test.ts +276 -0
  68. package/src/SchemaGenerator.integration.test.ts +411 -0
  69. package/src/SchemaGenerator.test.ts +118 -0
  70. package/src/SchemaGenerator.ts +112 -298
  71. package/src/cli.test.ts +130 -0
  72. package/src/errors/index.test.ts +319 -0
  73. package/src/errors/index.ts +92 -0
  74. package/src/index.ts +8 -1
  75. package/src/services/CodeGenerator.ts +370 -0
  76. package/src/services/FileDiscovery.test.ts +216 -0
  77. package/src/services/FileDiscovery.ts +140 -0
  78. package/src/services/SchemaProcessor.test.ts +536 -0
  79. package/src/services/SchemaProcessor.ts +194 -0
  80. package/src/services/SchemaWriter.test.ts +304 -0
  81. package/src/services/SchemaWriter.ts +75 -0
  82. package/src/utils/ProgressReporter.test.ts +357 -0
  83. package/src/utils/ProgressReporter.ts +76 -0
@@ -0,0 +1,319 @@
1
+ import {
2
+ BaseError,
3
+ FileDiscoveryError,
4
+ SchemaGenerationError,
5
+ DuplicateSymbolError,
6
+ CodeGenerationError,
7
+ ValidationError,
8
+ ConfigurationError,
9
+ CacheError,
10
+ isKnownError,
11
+ formatError
12
+ } from "./index";
13
+
14
+ describe("Error Classes", () => {
15
+ describe("BaseError", () => {
16
+ it("should create error with message and code", () => {
17
+ const error = new BaseError("Test message", "TEST_CODE");
18
+
19
+ expect(error.message).toBe("Test message");
20
+ expect(error.code).toBe("TEST_CODE");
21
+ expect(error.name).toBe("BaseError");
22
+ expect(error instanceof Error).toBe(true);
23
+ });
24
+
25
+ it("should capture stack trace", () => {
26
+ const error = new BaseError("Test", "CODE");
27
+ expect(error.stack).toBeDefined();
28
+ expect(error.stack).toContain("BaseError");
29
+ });
30
+ });
31
+
32
+ describe("FileDiscoveryError", () => {
33
+ it("should create error with path information", () => {
34
+ const error = new FileDiscoveryError("File not found", "/path/to/file");
35
+
36
+ expect(error.message).toBe("File not found");
37
+ expect(error.code).toBe("FILE_DISCOVERY_ERROR");
38
+ expect(error.path).toBe("/path/to/file");
39
+ expect(error.name).toBe("FileDiscoveryError");
40
+ });
41
+
42
+ it("should work without path", () => {
43
+ const error = new FileDiscoveryError("General error");
44
+
45
+ expect(error.message).toBe("General error");
46
+ expect(error.path).toBeUndefined();
47
+ });
48
+ });
49
+
50
+ describe("SchemaGenerationError", () => {
51
+ it("should create error with file information", () => {
52
+ const error = new SchemaGenerationError("Schema generation failed", "user.ts");
53
+
54
+ expect(error.message).toBe("Schema generation failed");
55
+ expect(error.code).toBe("SCHEMA_GENERATION_ERROR");
56
+ expect(error.file).toBe("user.ts");
57
+ });
58
+
59
+ it("should work without file", () => {
60
+ const error = new SchemaGenerationError("General schema error");
61
+
62
+ expect(error.message).toBe("General schema error");
63
+ expect(error.file).toBeUndefined();
64
+ });
65
+ });
66
+
67
+ describe("DuplicateSymbolError", () => {
68
+ it("should create error with symbol details", () => {
69
+ const existingDef = { type: "object", properties: { id: { type: "string" } } };
70
+ const newDef = { type: "object", properties: { id: { type: "number" } } };
71
+
72
+ const error = new DuplicateSymbolError(
73
+ "Duplicate symbol found",
74
+ "IUser",
75
+ "user2.ts",
76
+ existingDef,
77
+ newDef
78
+ );
79
+
80
+ expect(error.message).toBe("Duplicate symbol found");
81
+ expect(error.code).toBe("DUPLICATE_SYMBOL_ERROR");
82
+ expect(error.symbol).toBe("IUser");
83
+ expect(error.file).toBe("user2.ts");
84
+ expect(error.existingDefinition).toBe(existingDef);
85
+ expect(error.newDefinition).toBe(newDef);
86
+ });
87
+
88
+ it("should provide detailed message", () => {
89
+ const existingDef = { type: "string" };
90
+ const newDef = { type: "number" };
91
+
92
+ const error = new DuplicateSymbolError(
93
+ "Conflict",
94
+ "TestType",
95
+ "test.ts",
96
+ existingDef,
97
+ newDef
98
+ );
99
+
100
+ const detailed = error.getDetailedMessage();
101
+
102
+ expect(detailed).toContain("Conflict");
103
+ expect(detailed).toContain("Symbol: TestType");
104
+ expect(detailed).toContain("File: test.ts");
105
+ expect(detailed).toContain("Existing Definition:");
106
+ expect(detailed).toContain("New Definition:");
107
+ expect(detailed).toContain(JSON.stringify(existingDef, null, 2));
108
+ expect(detailed).toContain(JSON.stringify(newDef, null, 2));
109
+ });
110
+ });
111
+
112
+ describe("CodeGenerationError", () => {
113
+ it("should create error with output file information", () => {
114
+ const error = new CodeGenerationError("Generation failed", "output.ts");
115
+
116
+ expect(error.message).toBe("Generation failed");
117
+ expect(error.code).toBe("CODE_GENERATION_ERROR");
118
+ expect(error.outputFile).toBe("output.ts");
119
+ });
120
+
121
+ it("should work without output file", () => {
122
+ const error = new CodeGenerationError("General generation error");
123
+
124
+ expect(error.message).toBe("General generation error");
125
+ expect(error.outputFile).toBeUndefined();
126
+ });
127
+ });
128
+
129
+ describe("ValidationError", () => {
130
+ it("should create error with validation errors", () => {
131
+ const validationErrors = [
132
+ { path: "user.name", message: "Required field missing" },
133
+ { path: "user.age", message: "Must be a number" }
134
+ ];
135
+
136
+ const error = new ValidationError("Validation failed", validationErrors);
137
+
138
+ expect(error.message).toBe("Validation failed");
139
+ expect(error.code).toBe("VALIDATION_ERROR");
140
+ expect(error.errors).toBe(validationErrors);
141
+ });
142
+
143
+ it("should provide detailed message", () => {
144
+ const validationErrors = [
145
+ { path: "user.name", message: "Required" },
146
+ { path: "user.email", message: "Invalid format" }
147
+ ];
148
+
149
+ const error = new ValidationError("Failed", validationErrors);
150
+ const detailed = error.getDetailedMessage();
151
+
152
+ expect(detailed).toContain("Failed");
153
+ expect(detailed).toContain("user.name: Required");
154
+ expect(detailed).toContain("user.email: Invalid format");
155
+ });
156
+
157
+ it("should handle empty errors array", () => {
158
+ const error = new ValidationError("No specific errors", []);
159
+ const detailed = error.getDetailedMessage();
160
+
161
+ expect(detailed).toBe("No specific errors\n");
162
+ });
163
+ });
164
+
165
+ describe("ConfigurationError", () => {
166
+ it("should create error with field information", () => {
167
+ const error = new ConfigurationError("Invalid configuration", "rootPath");
168
+
169
+ expect(error.message).toBe("Invalid configuration");
170
+ expect(error.code).toBe("CONFIGURATION_ERROR");
171
+ expect(error.field).toBe("rootPath");
172
+ });
173
+
174
+ it("should work without field", () => {
175
+ const error = new ConfigurationError("General config error");
176
+
177
+ expect(error.message).toBe("General config error");
178
+ expect(error.field).toBeUndefined();
179
+ });
180
+ });
181
+
182
+ describe("CacheError", () => {
183
+ it("should create error with operation information", () => {
184
+ const error = new CacheError("Cache operation failed", "read");
185
+
186
+ expect(error.message).toBe("Cache operation failed");
187
+ expect(error.code).toBe("CACHE_ERROR");
188
+ expect(error.operation).toBe("read");
189
+ });
190
+
191
+ it("should work without operation", () => {
192
+ const error = new CacheError("General cache error");
193
+
194
+ expect(error.message).toBe("General cache error");
195
+ expect(error.operation).toBeUndefined();
196
+ });
197
+ });
198
+ });
199
+
200
+ describe("Error Utilities", () => {
201
+ describe("isKnownError", () => {
202
+ it("should return true for known errors", () => {
203
+ expect(isKnownError(new FileDiscoveryError("test"))).toBe(true);
204
+ expect(isKnownError(new SchemaGenerationError("test"))).toBe(true);
205
+ expect(isKnownError(new DuplicateSymbolError("test", "sym", "file", {}, {}))).toBe(true);
206
+ expect(isKnownError(new CodeGenerationError("test"))).toBe(true);
207
+ expect(isKnownError(new ValidationError("test", []))).toBe(true);
208
+ expect(isKnownError(new ConfigurationError("test"))).toBe(true);
209
+ expect(isKnownError(new CacheError("test"))).toBe(true);
210
+ });
211
+
212
+ it("should return false for unknown errors", () => {
213
+ expect(isKnownError(new Error("regular error"))).toBe(false);
214
+ expect(isKnownError(new TypeError("type error"))).toBe(false);
215
+ expect(isKnownError("string error")).toBe(false);
216
+ expect(isKnownError(null)).toBe(false);
217
+ expect(isKnownError(undefined)).toBe(false);
218
+ expect(isKnownError({ message: "object error" })).toBe(false);
219
+ });
220
+ });
221
+
222
+ describe("formatError", () => {
223
+ it("should format known errors with code", () => {
224
+ const error = new FileDiscoveryError("File not found");
225
+ const formatted = formatError(error);
226
+
227
+ expect(formatted).toBe("[FILE_DISCOVERY_ERROR] File not found");
228
+ });
229
+
230
+ it("should use detailed message in verbose mode", () => {
231
+ const validationErrors = [
232
+ { path: "user.name", message: "Required" }
233
+ ];
234
+ const error = new ValidationError("Validation failed", validationErrors);
235
+
236
+ const formatted = formatError(error, true);
237
+
238
+ expect(formatted).toContain("Validation failed");
239
+ expect(formatted).toContain("user.name: Required");
240
+ });
241
+
242
+ it("should format regular errors", () => {
243
+ const error = new Error("Regular error");
244
+
245
+ expect(formatError(error, false)).toBe("Regular error");
246
+ expect(formatError(error, true)).toContain("Regular error");
247
+ });
248
+
249
+ it("should include stack trace in verbose mode for regular errors", () => {
250
+ const error = new Error("Test error");
251
+ const formatted = formatError(error, true);
252
+
253
+ expect(formatted).toContain("Error: Test error");
254
+ });
255
+
256
+ it("should handle errors without stack", () => {
257
+ const error = new Error("No stack");
258
+ error.stack = undefined;
259
+
260
+ const formatted = formatError(error, true);
261
+ expect(formatted).toBe("No stack");
262
+ });
263
+
264
+ it("should handle non-error objects", () => {
265
+ expect(formatError("string error")).toBe("string error");
266
+ expect(formatError(123)).toBe("123");
267
+ expect(formatError(null)).toBe("null");
268
+ expect(formatError(undefined)).toBe("undefined");
269
+ expect(formatError({ message: "object" })).toBe("[object Object]");
270
+ });
271
+
272
+ it("should handle DuplicateSymbolError detailed message", () => {
273
+ const error = new DuplicateSymbolError(
274
+ "Duplicate found",
275
+ "ITest",
276
+ "test.ts",
277
+ { type: "string" },
278
+ { type: "number" }
279
+ );
280
+
281
+ const formatted = formatError(error, true);
282
+
283
+ expect(formatted).toContain("Symbol: ITest");
284
+ expect(formatted).toContain("File: test.ts");
285
+ });
286
+
287
+ it("should not use detailed message for errors without it", () => {
288
+ const error = new FileDiscoveryError("Test error");
289
+ const formatted = formatError(error, true);
290
+
291
+ expect(formatted).toBe("[FILE_DISCOVERY_ERROR] Test error");
292
+ });
293
+ });
294
+
295
+ describe("error inheritance", () => {
296
+ it("should maintain proper instanceof relationships", () => {
297
+ const fileError = new FileDiscoveryError("test");
298
+
299
+ expect(fileError instanceof FileDiscoveryError).toBe(true);
300
+ expect(fileError instanceof BaseError).toBe(true);
301
+ expect(fileError instanceof Error).toBe(true);
302
+ });
303
+
304
+ it("should have correct constructor names", () => {
305
+ const errors = [
306
+ new FileDiscoveryError("test"),
307
+ new SchemaGenerationError("test"),
308
+ new CodeGenerationError("test"),
309
+ new ValidationError("test", []),
310
+ new ConfigurationError("test"),
311
+ new CacheError("test")
312
+ ];
313
+
314
+ errors.forEach(error => {
315
+ expect(error.constructor.name).toBe(error.name);
316
+ });
317
+ });
318
+ });
319
+ });
@@ -0,0 +1,92 @@
1
+ export class BaseError extends Error {
2
+ constructor(message: string, public readonly code: string) {
3
+ super(message);
4
+ this.name = this.constructor.name;
5
+ Error.captureStackTrace(this, this.constructor);
6
+ }
7
+ }
8
+
9
+ export class FileDiscoveryError extends BaseError {
10
+ constructor(message: string, public readonly path?: string) {
11
+ super(message, 'FILE_DISCOVERY_ERROR');
12
+ }
13
+ }
14
+
15
+ export class SchemaGenerationError extends BaseError {
16
+ constructor(message: string, public readonly file?: string) {
17
+ super(message, 'SCHEMA_GENERATION_ERROR');
18
+ }
19
+ }
20
+
21
+ export class DuplicateSymbolError extends BaseError {
22
+ constructor(
23
+ message: string,
24
+ public readonly symbol: string,
25
+ public readonly file: string,
26
+ public readonly existingDefinition: any,
27
+ public readonly newDefinition: any
28
+ ) {
29
+ super(message, 'DUPLICATE_SYMBOL_ERROR');
30
+ }
31
+
32
+ getDetailedMessage(): string {
33
+ return `${this.message}
34
+ Symbol: ${this.symbol}
35
+ File: ${this.file}
36
+ Existing Definition: ${JSON.stringify(this.existingDefinition, null, 2)}
37
+ New Definition: ${JSON.stringify(this.newDefinition, null, 2)}`;
38
+ }
39
+ }
40
+
41
+ export class CodeGenerationError extends BaseError {
42
+ constructor(message: string, public readonly outputFile?: string) {
43
+ super(message, 'CODE_GENERATION_ERROR');
44
+ }
45
+ }
46
+
47
+ export class ValidationError extends BaseError {
48
+ constructor(
49
+ message: string,
50
+ public readonly errors: Array<{ path: string; message: string }>
51
+ ) {
52
+ super(message, 'VALIDATION_ERROR');
53
+ }
54
+
55
+ getDetailedMessage(): string {
56
+ const errorList = this.errors
57
+ .map(err => ` - ${err.path}: ${err.message}`)
58
+ .join('\n');
59
+ return `${this.message}\n${errorList}`;
60
+ }
61
+ }
62
+
63
+ export class ConfigurationError extends BaseError {
64
+ constructor(message: string, public readonly field?: string) {
65
+ super(message, 'CONFIGURATION_ERROR');
66
+ }
67
+ }
68
+
69
+ export class CacheError extends BaseError {
70
+ constructor(message: string, public readonly operation?: string) {
71
+ super(message, 'CACHE_ERROR');
72
+ }
73
+ }
74
+
75
+ export function isKnownError(error: unknown): error is BaseError {
76
+ return error instanceof BaseError;
77
+ }
78
+
79
+ export function formatError(error: unknown, verbose: boolean = false): string {
80
+ if (isKnownError(error)) {
81
+ if (verbose && 'getDetailedMessage' in error) {
82
+ return (error as any).getDetailedMessage();
83
+ }
84
+ return `[${error.code}] ${error.message}`;
85
+ }
86
+
87
+ if (error instanceof Error) {
88
+ return verbose ? error.stack || error.message : error.message;
89
+ }
90
+
91
+ return String(error);
92
+ }
package/src/index.ts CHANGED
@@ -11,7 +11,7 @@ const defaultOutputFolder = "./.ts-runtime-validation";
11
11
  const defaultTsconfig = "";
12
12
 
13
13
  program.option(
14
- "--glob",
14
+ "--glob <glob>",
15
15
  `Glob file path of typescript files to generate ts-interface -> json-schema validations - default: ${defaultGlobPattern}`,
16
16
  defaultGlobPattern
17
17
  );
@@ -20,6 +20,13 @@ program.option("--output <outputFolder>", `Code generation output directory (rel
20
20
  program.option("--tsconfigPath <tsconfigPath>", `Path to customt tsconfig (relative to root path)`, defaultTsconfig);
21
21
  program.option("--generate-helpers", "Only generate JSON schema without typescript helper files", true);
22
22
  program.option("--additionalProperties", "Allow additional properties to pass validation", false);
23
+ program.option("--verbose", "Enable verbose logging", false);
24
+ program.option("--progress", "Show progress information", false);
25
+ program.option("--minify", "Minify generated output", false);
26
+ program.option("--cache", "Enable file caching for incremental builds", false);
27
+ program.option("--no-parallel", "Disable parallel processing", false);
28
+ program.option("--tree-shaking", "Generate tree-shaking friendly exports", false);
29
+ program.option("--lazy-load", "Generate lazy-loaded validators", false);
23
30
 
24
31
  program.parse();
25
32