canxjs 1.0.1 → 1.1.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.
@@ -0,0 +1,10 @@
1
+ import type { MiddlewareHandler, CanxRequest } from '../types';
2
+ export interface RateLimitConfig {
3
+ windowMs: number;
4
+ max: number;
5
+ message?: string | object;
6
+ statusCode?: number;
7
+ keyGenerator?: (req: CanxRequest) => string;
8
+ }
9
+ export declare function rateLimit(config: RateLimitConfig): MiddlewareHandler;
10
+ //# sourceMappingURL=RateLimitMiddleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RateLimitMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/RateLimitMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAgB,MAAM,UAAU,CAAC;AAE7E,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,MAAM,CAAC;CAC7C;AAsBD,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB,CA+BpE"}
@@ -0,0 +1,14 @@
1
+ import type { MiddlewareHandler } from '../types';
2
+ export interface SecurityConfig {
3
+ xssProtection?: boolean;
4
+ contentTypeOptions?: boolean;
5
+ frameOptions?: 'DENY' | 'SAMEORIGIN';
6
+ hsts?: boolean | {
7
+ maxAge: number;
8
+ includeSubDomains: boolean;
9
+ };
10
+ contentSecurityPolicy?: string;
11
+ referrerPolicy?: string;
12
+ }
13
+ export declare function security(config?: SecurityConfig): MiddlewareHandler;
14
+ //# sourceMappingURL=SecurityMiddleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/SecurityMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IACrC,IAAI,CAAC,EAAE,OAAO,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,OAAO,CAAA;KAAE,CAAC;IAChE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,QAAQ,CAAC,MAAM,GAAE,cAAmB,GAAG,iBAAiB,CA2CvE"}
@@ -0,0 +1,4 @@
1
+ import type { MiddlewareHandler } from '../types';
2
+ import { Schema } from '../schema/Schema';
3
+ export declare function validateSchema(schema: Schema<any>, target?: 'body' | 'query' | 'params'): MiddlewareHandler;
4
+ //# sourceMappingURL=ValidationMiddleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ValidationMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/ValidationMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAe,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG1C,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,GAAE,MAAM,GAAG,OAAO,GAAG,QAAiB,GAAG,iBAAiB,CA8BnH"}
@@ -0,0 +1,64 @@
1
+ import { ValidationError } from '../utils/ErrorHandler';
2
+ export type Infer<T extends Schema<any>> = T['_output'];
3
+ export interface ParseResult<T> {
4
+ success: boolean;
5
+ data?: T;
6
+ error?: ValidationError;
7
+ }
8
+ export declare abstract class Schema<Output = any, Input = unknown> {
9
+ readonly _output: Output;
10
+ readonly _input: Input;
11
+ protected description?: string;
12
+ protected isOptional: boolean;
13
+ abstract parse(value: unknown): Output;
14
+ abstract getJsonSchema(): Record<string, any>;
15
+ safeParse(value: unknown): ParseResult<Output>;
16
+ optional(): Schema<Output | undefined, Input | undefined>;
17
+ describe(description: string): this;
18
+ }
19
+ declare class StringSchema extends Schema<string> {
20
+ private checks;
21
+ constructor();
22
+ min(length: number, message?: string): this;
23
+ max(length: number, message?: string): this;
24
+ email(message?: string): this;
25
+ parse(value: unknown): string;
26
+ getJsonSchema(): Record<string, any>;
27
+ }
28
+ declare class NumberSchema extends Schema<number> {
29
+ private checks;
30
+ min(min: number, message?: string): this;
31
+ max(max: number, message?: string): this;
32
+ parse(value: unknown): number;
33
+ private validateChecks;
34
+ getJsonSchema(): Record<string, any>;
35
+ }
36
+ declare class BooleanSchema extends Schema<boolean> {
37
+ parse(value: unknown): boolean;
38
+ getJsonSchema(): Record<string, any>;
39
+ }
40
+ declare class ObjectSchema<T extends Record<string, Schema<any>>> extends Schema<{
41
+ [K in keyof T]: Infer<T[K]>;
42
+ }> {
43
+ private shape;
44
+ constructor(shape: T);
45
+ parse(value: unknown): {
46
+ [K in keyof T]: Infer<T[K]>;
47
+ };
48
+ getJsonSchema(): Record<string, any>;
49
+ }
50
+ declare class ArraySchema<T extends Schema<any>> extends Schema<Infer<T>[]> {
51
+ private element;
52
+ constructor(element: T);
53
+ parse(value: unknown): Infer<T>[];
54
+ getJsonSchema(): Record<string, any>;
55
+ }
56
+ export declare const z: {
57
+ string: () => StringSchema;
58
+ number: () => NumberSchema;
59
+ boolean: () => BooleanSchema;
60
+ object: <T extends Record<string, Schema<any>>>(shape: T) => ObjectSchema<T>;
61
+ array: <T extends Schema<any>>(element: T) => ArraySchema<T>;
62
+ };
63
+ export {};
64
+ //# sourceMappingURL=Schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Schema.d.ts","sourceRoot":"","sources":["../../src/schema/Schema.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorHandler.d.ts","sourceRoot":"","sources":["../../src/utils/ErrorHandler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ErrorHandler.d.ts","sourceRoot":"","sources":["../../src/utils/ErrorHandler.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canxjs",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Ultra-fast async-first MVC backend framework for Bun runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -93,5 +93,8 @@
93
93
  "bugs": {
94
94
  "url": "https://github.com/chandafa/canx.JS/issues"
95
95
  },
96
- "homepage": "https://github.com/chandafa/canx.JS#readme"
96
+ "homepage": "https://github.com/chandafa/canx.JS#readme",
97
+ "publishConfig": {
98
+ "access": "public"
99
+ }
97
100
  }
@@ -76,8 +76,15 @@ export class Canx implements CanxApplication {
76
76
  /**
77
77
  * Start the server
78
78
  */
79
- async listen(port?: number, callback?: () => void): Promise<void> {
80
- if (port) this.config.port = port;
79
+ async listen(port?: number | (() => void), callback?: () => void): Promise<void> {
80
+ if (typeof port === 'function') {
81
+ callback = port;
82
+ port = undefined;
83
+ }
84
+
85
+ if (port && typeof port === 'number') {
86
+ this.config.port = port;
87
+ }
81
88
 
82
89
  // Initialize plugins
83
90
  for (const plugin of this.plugins) {
@@ -77,25 +77,33 @@ export function ApiParam(param: ApiParameter): MethodDecorator {
77
77
  };
78
78
  }
79
79
 
80
- export function ApiBody(schema: ApiSchema, required = true): MethodDecorator {
80
+ import { Schema } from '../schema/Schema';
81
+
82
+ export function ApiBody(schema: ApiSchema | Schema<any>, required = true): MethodDecorator {
81
83
  return (target: any, propertyKey: string | symbol) => {
82
84
  if (!apiMetadata.has(target)) apiMetadata.set(target, new Map());
83
85
  const meta = apiMetadata.get(target)!;
84
86
  const existing = meta.get(String(propertyKey)) || {} as ApiEndpoint;
85
- existing.requestBody = { required, content: { 'application/json': { schema } } };
87
+
88
+ const finalSchema = schema instanceof Schema ? schema.getJsonSchema() : schema;
89
+
90
+ existing.requestBody = { required, content: { 'application/json': { schema: finalSchema as ApiSchema } } };
86
91
  meta.set(String(propertyKey), existing);
87
92
  };
88
93
  }
89
94
 
90
- export function ApiResponse(status: number, description: string, schema?: ApiSchema): MethodDecorator {
95
+ export function ApiResponse(status: number, description: string, schema?: ApiSchema | Schema<any>): MethodDecorator {
91
96
  return (target: any, propertyKey: string | symbol) => {
92
97
  if (!apiMetadata.has(target)) apiMetadata.set(target, new Map());
93
98
  const meta = apiMetadata.get(target)!;
94
99
  const existing = meta.get(String(propertyKey)) || {} as ApiEndpoint;
95
100
  existing.responses = existing.responses || {};
101
+
102
+ const finalSchema = schema instanceof Schema ? schema.getJsonSchema() : schema;
103
+
96
104
  existing.responses[String(status)] = {
97
105
  description,
98
- content: schema ? { 'application/json': { schema } } : undefined,
106
+ content: finalSchema ? { 'application/json': { schema: finalSchema as ApiSchema } } : undefined,
99
107
  };
100
108
  meta.set(String(propertyKey), existing);
101
109
  };
package/src/index.ts CHANGED
@@ -9,7 +9,19 @@
9
9
  // ============================================
10
10
  export { Server, createCanxRequest, createCanxResponse } from './core/Server';
11
11
  export { Router, createRouter } from './core/Router';
12
+ // ============================================
13
+ // Middleware Exports
14
+ // ============================================
12
15
  export { MiddlewarePipeline, cors, logger, bodyParser, rateLimit, compress, createMiddlewarePipeline } from './core/Middleware';
16
+ export { security } from './middlewares/SecurityMiddleware';
17
+ export { rateLimit as rateLimitMiddleware } from './middlewares/RateLimitMiddleware';
18
+ export { validateSchema } from './middlewares/ValidationMiddleware';
19
+
20
+ // ============================================
21
+ // Schema / Validation Exports
22
+ // ============================================
23
+ export { z, Schema as ZSchema } from './schema/Schema';
24
+ export type { Infer } from './schema/Schema';
13
25
 
14
26
  // ============================================
15
27
  // MVC Exports
@@ -0,0 +1,62 @@
1
+ import type { MiddlewareHandler, CanxRequest, CanxResponse } from '../types';
2
+
3
+ export interface RateLimitConfig {
4
+ windowMs: number;
5
+ max: number;
6
+ message?: string | object;
7
+ statusCode?: number;
8
+ keyGenerator?: (req: CanxRequest) => string;
9
+ }
10
+
11
+ // Simple in-memory store. In production for multiple instances, Redis is recommended.
12
+ // This is exposed so it can be swapped if needed.
13
+ class MemoryStore {
14
+ hits = new Map<string, { count: number; resetTime: number }>();
15
+
16
+ increment(key: string, windowMs: number): { count: number; resetTime: number } {
17
+ const now = Date.now();
18
+ let record = this.hits.get(key);
19
+
20
+ if (!record || record.resetTime <= now) {
21
+ record = { count: 1, resetTime: now + windowMs };
22
+ } else {
23
+ record.count++;
24
+ }
25
+
26
+ this.hits.set(key, record);
27
+ return record;
28
+ }
29
+ }
30
+
31
+ export function rateLimit(config: RateLimitConfig): MiddlewareHandler {
32
+ const store = new MemoryStore();
33
+ const {
34
+ windowMs = 60000,
35
+ max = 100,
36
+ message = 'Too many requests, please try again later.',
37
+ statusCode = 429,
38
+ keyGenerator = (req: CanxRequest) => req.header('x-forwarded-for') || req.header('cf-connecting-ip') || 'unknown-ip'
39
+ } = config;
40
+
41
+ return async (req, res, next) => {
42
+ const key = keyGenerator(req);
43
+ const { count, resetTime } = store.increment(key, windowMs);
44
+
45
+ const remaining = Math.max(0, max - count);
46
+ const resetDate = new Date(resetTime);
47
+
48
+ // Standard Rate Limit Headers
49
+ res.header('X-RateLimit-Limit', String(max));
50
+ res.header('X-RateLimit-Remaining', String(remaining));
51
+ res.header('X-RateLimit-Reset', String(Math.ceil(resetTime / 1000)));
52
+
53
+ if (count > max) {
54
+ if (typeof message === 'object') {
55
+ return res.status(statusCode).json(message);
56
+ }
57
+ return res.status(statusCode).text(String(message));
58
+ }
59
+
60
+ return next();
61
+ };
62
+ }
@@ -0,0 +1,55 @@
1
+ import type { MiddlewareHandler } from '../types';
2
+
3
+ export interface SecurityConfig {
4
+ xssProtection?: boolean;
5
+ contentTypeOptions?: boolean;
6
+ frameOptions?: 'DENY' | 'SAMEORIGIN';
7
+ hsts?: boolean | { maxAge: number; includeSubDomains: boolean };
8
+ contentSecurityPolicy?: string;
9
+ referrerPolicy?: string;
10
+ }
11
+
12
+ export function security(config: SecurityConfig = {}): MiddlewareHandler {
13
+ return async (req, res, next) => {
14
+ // X-XSS-Protection
15
+ if (config.xssProtection !== false) {
16
+ res.header('X-XSS-Protection', '1; mode=block');
17
+ }
18
+
19
+ // X-Content-Type-Options
20
+ if (config.contentTypeOptions !== false) {
21
+ res.header('X-Content-Type-Options', 'nosniff');
22
+ }
23
+
24
+ // X-Frame-Options
25
+ if (config.frameOptions) {
26
+ res.header('X-Frame-Options', config.frameOptions);
27
+ } else if (config.frameOptions !== undefined) {
28
+ // Allow if explicit string (e.g. ALLOW-FROM) - though deprecated
29
+ res.header('X-Frame-Options', config.frameOptions);
30
+ } else {
31
+ // Default deny if not specified? Or standard default?
32
+ // Modern default is usually SAMEORIGIN or DENY. Let's do SAMEORIGIN.
33
+ res.header('X-Frame-Options', 'SAMEORIGIN');
34
+ }
35
+
36
+ // Strict-Transport-Security
37
+ if (config.hsts) {
38
+ const maxAge = typeof config.hsts === 'object' ? config.hsts.maxAge : 31536000;
39
+ const includeSubDomains = typeof config.hsts === 'object' && config.hsts.includeSubDomains ? '; includeSubDomains' : '';
40
+ res.header('Strict-Transport-Security', `max-age=${maxAge}${includeSubDomains}`);
41
+ }
42
+
43
+ // Content-Security-Policy
44
+ if (config.contentSecurityPolicy) {
45
+ res.header('Content-Security-Policy', config.contentSecurityPolicy);
46
+ }
47
+
48
+ // Referrer-Policy
49
+ if (config.referrerPolicy) {
50
+ res.header('Referrer-Policy', config.referrerPolicy);
51
+ }
52
+
53
+ return next();
54
+ };
55
+ }
@@ -0,0 +1,35 @@
1
+ import type { MiddlewareHandler, CanxRequest } from '../types';
2
+ import { Schema } from '../schema/Schema';
3
+ import { ValidationError } from '../utils/ErrorHandler';
4
+
5
+ export function validateSchema(schema: Schema<any>, target: 'body' | 'query' | 'params' = 'body'): MiddlewareHandler {
6
+ return async (req: CanxRequest, res, next) => {
7
+ let data: unknown;
8
+
9
+ if (target === 'body') {
10
+ data = await req.body();
11
+ } else if (target === 'query') {
12
+ data = req.query;
13
+ } else {
14
+ data = req.params;
15
+ }
16
+
17
+ try {
18
+ const validated = schema.parse(data);
19
+ // Attach validated data to request for type safety in controllers
20
+ (req as any).validatedData = validated;
21
+ return next();
22
+ } catch (error) {
23
+ if (error instanceof ValidationError) {
24
+ return res.status(422).json({
25
+ error: {
26
+ code: 'VALIDATION_ERROR',
27
+ message: 'Validation failed',
28
+ details: Object.fromEntries(error.errors),
29
+ }
30
+ });
31
+ }
32
+ throw error;
33
+ }
34
+ };
35
+ }
@@ -0,0 +1,299 @@
1
+ import { ValidationError } from '../utils/ErrorHandler';
2
+
3
+ // ============================================
4
+ // Core Types
5
+ // ============================================
6
+
7
+ export type Infer<T extends Schema<any>> = T['_output'];
8
+
9
+ export interface ParseResult<T> {
10
+ success: boolean;
11
+ data?: T;
12
+ error?: ValidationError;
13
+ }
14
+
15
+ export abstract class Schema<Output = any, Input = unknown> {
16
+ readonly _output!: Output;
17
+ readonly _input!: Input;
18
+ protected description?: string;
19
+ protected isOptional: boolean = false;
20
+
21
+ abstract parse(value: unknown): Output;
22
+ abstract getJsonSchema(): Record<string, any>;
23
+
24
+ safeParse(value: unknown): ParseResult<Output> {
25
+ try {
26
+ const data = this.parse(value);
27
+ return { success: true, data };
28
+ } catch (error) {
29
+ if (error instanceof ValidationError) {
30
+ return { success: false, error };
31
+ }
32
+ throw error;
33
+ }
34
+ }
35
+
36
+ optional(): Schema<Output | undefined, Input | undefined> {
37
+ const newSchema = Object.create(this);
38
+ newSchema.isOptional = true;
39
+ return newSchema as any;
40
+ }
41
+
42
+ describe(description: string): this {
43
+ this.description = description;
44
+ return this;
45
+ }
46
+ }
47
+
48
+ // ============================================
49
+ // String Schema
50
+ // ============================================
51
+
52
+ class StringSchema extends Schema<string> {
53
+ private checks: Array<(v: string) => string | null> = [];
54
+
55
+ constructor() {
56
+ super();
57
+ }
58
+
59
+ min(length: number, message?: string): this {
60
+ this.checks.push((v) => v.length >= length ? null : (message || `String must contain at least ${length} character(s)`));
61
+ return this;
62
+ }
63
+
64
+ max(length: number, message?: string): this {
65
+ this.checks.push((v) => v.length <= length ? null : (message || `String must contain at most ${length} character(s)`));
66
+ return this;
67
+ }
68
+
69
+ email(message?: string): this {
70
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
71
+ this.checks.push((v) => emailRegex.test(v) ? null : (message || 'Invalid email address'));
72
+ return this;
73
+ }
74
+
75
+ parse(value: unknown): string {
76
+ if (this.isOptional && (value === undefined || value === null)) {
77
+ return value as any;
78
+ }
79
+
80
+ if (typeof value !== 'string') {
81
+ throw new ValidationError({ _errors: ['Expected string, received ' + typeof value] });
82
+ }
83
+
84
+ for (const check of this.checks) {
85
+ const error = check(value);
86
+ if (error) {
87
+ throw new ValidationError({ _errors: [error] });
88
+ }
89
+ }
90
+
91
+ return value;
92
+ }
93
+
94
+ getJsonSchema(): Record<string, any> {
95
+ return {
96
+ type: 'string',
97
+ description: this.description,
98
+ };
99
+ }
100
+ }
101
+
102
+ // ============================================
103
+ // Number Schema
104
+ // ============================================
105
+
106
+ class NumberSchema extends Schema<number> {
107
+ private checks: Array<(v: number) => string | null> = [];
108
+
109
+ min(min: number, message?: string): this {
110
+ this.checks.push((v) => v >= min ? null : (message || `Number must be greater than or equal to ${min}`));
111
+ return this;
112
+ }
113
+
114
+ max(max: number, message?: string): this {
115
+ this.checks.push((v) => v <= max ? null : (message || `Number must be less than or equal to ${max}`));
116
+ return this;
117
+ }
118
+
119
+ parse(value: unknown): number {
120
+ if (this.isOptional && (value === undefined || value === null)) {
121
+ return value as any;
122
+ }
123
+
124
+ if (typeof value !== 'number' || isNaN(value)) {
125
+ // Try converting string number
126
+ if (typeof value === 'string' && !isNaN(parseFloat(value))) {
127
+ const num = parseFloat(value);
128
+ return this.validateChecks(num);
129
+ }
130
+ throw new ValidationError({ _errors: ['Expected number, received ' + typeof value] });
131
+ }
132
+
133
+ return this.validateChecks(value);
134
+ }
135
+
136
+ private validateChecks(value: number): number {
137
+ for (const check of this.checks) {
138
+ const error = check(value);
139
+ if (error) {
140
+ throw new ValidationError({ _errors: [error] });
141
+ }
142
+ }
143
+ return value;
144
+ }
145
+
146
+ getJsonSchema(): Record<string, any> {
147
+ return {
148
+ type: 'number',
149
+ description: this.description,
150
+ };
151
+ }
152
+ }
153
+
154
+ // ============================================
155
+ // Boolean Schema
156
+ // ============================================
157
+
158
+ class BooleanSchema extends Schema<boolean> {
159
+ parse(value: unknown): boolean {
160
+ if (this.isOptional && (value === undefined || value === null)) {
161
+ return value as any;
162
+ }
163
+
164
+ if (typeof value === 'boolean') return value;
165
+ if (value === 'true') return true;
166
+ if (value === 'false') return false;
167
+
168
+ throw new ValidationError({ _errors: ['Expected boolean, received ' + typeof value] });
169
+ }
170
+
171
+ getJsonSchema(): Record<string, any> {
172
+ return {
173
+ type: 'boolean',
174
+ description: this.description,
175
+ };
176
+ }
177
+ }
178
+
179
+ // ============================================
180
+ // Object Schema
181
+ // ============================================
182
+
183
+ class ObjectSchema<T extends Record<string, Schema<any>>> extends Schema<{ [K in keyof T]: Infer<T[K]> }> {
184
+ constructor(private shape: T) {
185
+ super();
186
+ }
187
+
188
+ parse(value: unknown): { [K in keyof T]: Infer<T[K]> } {
189
+ if (this.isOptional && (value === undefined || value === null)) {
190
+ return value as any;
191
+ }
192
+
193
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
194
+ throw new ValidationError({ _errors: ['Expected object, received ' + typeof value] });
195
+ }
196
+
197
+ const result: any = {};
198
+ const errors = new Map<string, string[]>();
199
+
200
+ for (const [key, schema] of Object.entries(this.shape)) {
201
+ try {
202
+ result[key] = schema.parse((value as any)[key]);
203
+ } catch (error) {
204
+ if (error instanceof ValidationError) {
205
+ const fieldErrors = error.errors.get('_errors') || [];
206
+ // If the child error has map errors (nested object), merge them
207
+ if (error.errors.size > 0 && !error.errors.has('_errors')) {
208
+ error.errors.forEach((msgs, path) => {
209
+ errors.set(`${key}.${path}`, msgs);
210
+ });
211
+ } else {
212
+ errors.set(key, fieldErrors);
213
+ }
214
+ } else {
215
+ errors.set(key, ['Invalid value']);
216
+ }
217
+ }
218
+ }
219
+
220
+ if (errors.size > 0) {
221
+ throw new ValidationError(errors);
222
+ }
223
+
224
+ return result;
225
+ }
226
+
227
+ getJsonSchema(): Record<string, any> {
228
+ const properties: Record<string, any> = {};
229
+ const required: string[] = [];
230
+
231
+ for (const [key, schema] of Object.entries(this.shape)) {
232
+ properties[key] = schema.getJsonSchema();
233
+ if (!(schema as any).isOptional) {
234
+ // Actually we can check isOptional property
235
+ if (!(schema as any).isOptional) {
236
+ required.push(key);
237
+ }
238
+ }
239
+ }
240
+
241
+ return {
242
+ type: 'object',
243
+ properties,
244
+ required: required.length > 0 ? required : undefined,
245
+ description: this.description,
246
+ };
247
+ }
248
+ }
249
+
250
+ // ============================================
251
+ // Array Schema
252
+ // ============================================
253
+
254
+ class ArraySchema<T extends Schema<any>> extends Schema<Infer<T>[]> {
255
+ constructor(private element: T) {
256
+ super();
257
+ }
258
+
259
+ parse(value: unknown): Infer<T>[] {
260
+ if (this.isOptional && (value === undefined || value === null)) {
261
+ return value as any;
262
+ }
263
+
264
+ if (!Array.isArray(value)) {
265
+ throw new ValidationError({ _errors: ['Expected array, received ' + typeof value] });
266
+ }
267
+
268
+ return value.map((item, index) => {
269
+ try {
270
+ return this.element.parse(item);
271
+ } catch (error) {
272
+ if (error instanceof ValidationError) {
273
+ throw new ValidationError({ [index]: error.errors.get('_errors') || ['Invalid Item'] });
274
+ }
275
+ throw error;
276
+ }
277
+ });
278
+ }
279
+
280
+ getJsonSchema(): Record<string, any> {
281
+ return {
282
+ type: 'array',
283
+ items: this.element.getJsonSchema(),
284
+ description: this.description,
285
+ };
286
+ }
287
+ }
288
+
289
+ // ============================================
290
+ // Builder
291
+ // ============================================
292
+
293
+ export const z = {
294
+ string: () => new StringSchema(),
295
+ number: () => new NumberSchema(),
296
+ boolean: () => new BooleanSchema(),
297
+ object: <T extends Record<string, Schema<any>>>(shape: T) => new ObjectSchema(shape),
298
+ array: <T extends Schema<any>>(element: T) => new ArraySchema(element),
299
+ };