heicat 0.1.2

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 (37) hide show
  1. package/.eslintrc.js +29 -0
  2. package/README.md +346 -0
  3. package/examples/express-app/README.md +72 -0
  4. package/examples/express-app/contracts/auth.contract.json +38 -0
  5. package/examples/express-app/contracts/users.contract.json +49 -0
  6. package/examples/express-app/debug.js +13 -0
  7. package/examples/express-app/package-lock.json +913 -0
  8. package/examples/express-app/package.json +21 -0
  9. package/examples/express-app/server.js +116 -0
  10. package/jest.config.js +5 -0
  11. package/package.json +43 -0
  12. package/packages/cli/jest.config.js +7 -0
  13. package/packages/cli/package-lock.json +5041 -0
  14. package/packages/cli/package.json +37 -0
  15. package/packages/cli/src/cli.ts +49 -0
  16. package/packages/cli/src/commands/init.ts +103 -0
  17. package/packages/cli/src/commands/status.ts +75 -0
  18. package/packages/cli/src/commands/test.ts +188 -0
  19. package/packages/cli/src/commands/validate.ts +73 -0
  20. package/packages/cli/src/commands/watch.ts +655 -0
  21. package/packages/cli/src/index.ts +3 -0
  22. package/packages/cli/tsconfig.json +18 -0
  23. package/packages/core/jest.config.js +7 -0
  24. package/packages/core/package-lock.json +4581 -0
  25. package/packages/core/package.json +45 -0
  26. package/packages/core/src/__tests__/contract-loader.test.ts +112 -0
  27. package/packages/core/src/__tests__/validation-engine.test.ts +213 -0
  28. package/packages/core/src/contract-loader.ts +55 -0
  29. package/packages/core/src/engine.ts +95 -0
  30. package/packages/core/src/index.ts +9 -0
  31. package/packages/core/src/middleware.ts +97 -0
  32. package/packages/core/src/types/contract.ts +28 -0
  33. package/packages/core/src/types/options.ts +7 -0
  34. package/packages/core/src/types/violation.ts +19 -0
  35. package/packages/core/src/validation-engine.ts +157 -0
  36. package/packages/core/src/violation-store.ts +46 -0
  37. package/packages/core/tsconfig.json +18 -0
@@ -0,0 +1,157 @@
1
+ import { z } from 'zod';
2
+ import { Contract, RequestContract, ResponseContract } from './types/contract';
3
+ import { Violation, ValidationResult } from './types/violation';
4
+
5
+ export class ValidationEngine {
6
+ private createSchema(obj: Record<string, any>): z.ZodTypeAny {
7
+ if (!obj || typeof obj !== 'object') {
8
+ return z.any();
9
+ }
10
+
11
+ const shape: Record<string, z.ZodTypeAny> = {};
12
+
13
+ for (const [key, value] of Object.entries(obj)) {
14
+ if (typeof value === 'object' && value !== null) {
15
+ if (value.type) {
16
+ // Handle type-based schema
17
+ shape[key] = this.createTypeSchema(value);
18
+ } else {
19
+ // Handle nested object
20
+ shape[key] = this.createSchema(value);
21
+ }
22
+ } else if (typeof value === 'string') {
23
+ // Simple string type
24
+ shape[key] = z.string();
25
+ } else {
26
+ shape[key] = z.any();
27
+ }
28
+ }
29
+
30
+ return z.object(shape);
31
+ }
32
+
33
+ private createTypeSchema(config: any): z.ZodTypeAny {
34
+ let schema: z.ZodTypeAny;
35
+
36
+ switch (config.type) {
37
+ case 'string':
38
+ schema = z.string();
39
+ if (config.minLength) schema = (schema as z.ZodString).min(config.minLength);
40
+ if (config.maxLength) schema = (schema as z.ZodString).max(config.maxLength);
41
+ break;
42
+ case 'number':
43
+ schema = z.number();
44
+ if (config.minimum) schema = (schema as z.ZodNumber).min(config.minimum);
45
+ if (config.maximum) schema = (schema as z.ZodNumber).max(config.maximum);
46
+ break;
47
+ case 'boolean':
48
+ schema = z.boolean();
49
+ break;
50
+ case 'array':
51
+ schema = z.array(this.createTypeSchema(config.items || {}));
52
+ break;
53
+ default:
54
+ schema = z.any();
55
+ }
56
+
57
+ if (config.required === false) {
58
+ schema = schema.optional();
59
+ }
60
+
61
+ return schema;
62
+ }
63
+
64
+ validateRequest(
65
+ contract: Contract,
66
+ req: any
67
+ ): ValidationResult {
68
+ const violations: Violation[] = [];
69
+
70
+ if (contract.request?.body) {
71
+ const bodySchema = this.createSchema(contract.request.body);
72
+ const result = bodySchema.safeParse(req.body);
73
+ if (!result.success) {
74
+ violations.push({
75
+ id: `req-${Date.now()}-${Math.random()}`,
76
+ timestamp: new Date(),
77
+ type: 'request',
78
+ contractPath: contract.path,
79
+ endpoint: { method: contract.method, path: contract.path },
80
+ expected: contract.request.body,
81
+ actual: req.body,
82
+ message: `Request body validation failed: ${result.error.message}`,
83
+ severity: 'error'
84
+ });
85
+ }
86
+ }
87
+
88
+ // TODO: Add query, params, headers validation
89
+
90
+ return {
91
+ isValid: violations.length === 0,
92
+ violations
93
+ };
94
+ }
95
+
96
+ validateResponse(
97
+ contract: Contract,
98
+ statusCode: number,
99
+ body: any
100
+ ): ValidationResult {
101
+ const violations: Violation[] = [];
102
+
103
+ if (contract.response && contract.response[statusCode]) {
104
+ const responseSchema = this.createSchema(contract.response[statusCode]);
105
+ const result = responseSchema.safeParse(body);
106
+ if (!result.success) {
107
+ violations.push({
108
+ id: `res-${Date.now()}-${Math.random()}`,
109
+ timestamp: new Date(),
110
+ type: 'response',
111
+ contractPath: contract.path,
112
+ endpoint: { method: contract.method, path: contract.path },
113
+ expected: contract.response[statusCode],
114
+ actual: body,
115
+ message: `Response validation failed for status ${statusCode}: ${result.error.message}`,
116
+ severity: 'warning' // In dev mode, responses are warnings
117
+ });
118
+ }
119
+ }
120
+
121
+ return {
122
+ isValid: violations.length === 0,
123
+ violations
124
+ };
125
+ }
126
+
127
+ validateError(
128
+ contract: Contract,
129
+ statusCode: number,
130
+ body: any
131
+ ): ValidationResult {
132
+ const violations: Violation[] = [];
133
+
134
+ if (contract.errors && contract.errors[statusCode]) {
135
+ const errorSchema = this.createSchema(contract.errors[statusCode]);
136
+ const result = errorSchema.safeParse(body);
137
+ if (!result.success) {
138
+ violations.push({
139
+ id: `err-${Date.now()}-${Math.random()}`,
140
+ timestamp: new Date(),
141
+ type: 'error',
142
+ contractPath: contract.path,
143
+ endpoint: { method: contract.method, path: contract.path },
144
+ expected: contract.errors[statusCode],
145
+ actual: body,
146
+ message: `Error validation failed for status ${statusCode}: ${result.error.message}`,
147
+ severity: 'warning'
148
+ });
149
+ }
150
+ }
151
+
152
+ return {
153
+ isValid: violations.length === 0,
154
+ violations
155
+ };
156
+ }
157
+ }
@@ -0,0 +1,46 @@
1
+ import { Violation } from './types/violation';
2
+
3
+ export class ViolationStore {
4
+ private violations: Violation[] = [];
5
+ private maxViolations = 1000;
6
+
7
+ add(violation: Violation): void {
8
+ this.violations.unshift(violation);
9
+ if (this.violations.length > this.maxViolations) {
10
+ this.violations = this.violations.slice(0, this.maxViolations);
11
+ }
12
+ }
13
+
14
+ getAll(): Violation[] {
15
+ return [...this.violations];
16
+ }
17
+
18
+ getByEndpoint(method: string, path: string): Violation[] {
19
+ return this.violations.filter(v =>
20
+ v.endpoint.method === method && v.endpoint.path === path
21
+ );
22
+ }
23
+
24
+ getByType(type: Violation['type']): Violation[] {
25
+ return this.violations.filter(v => v.type === type);
26
+ }
27
+
28
+ clear(): void {
29
+ this.violations = [];
30
+ }
31
+
32
+ getStats() {
33
+ const total = this.violations.length;
34
+ const byType = this.violations.reduce((acc, v) => {
35
+ acc[v.type] = (acc[v.type] || 0) + 1;
36
+ return acc;
37
+ }, {} as Record<string, number>);
38
+
39
+ const bySeverity = this.violations.reduce((acc, v) => {
40
+ acc[v.severity] = (acc[v.severity] || 0) + 1;
41
+ return acc;
42
+ }, {} as Record<string, number>);
43
+
44
+ return { total, byType, bySeverity };
45
+ }
46
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
18
+ }