zod-codegen 1.0.1 → 1.0.3

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.
@@ -28,20 +28,20 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
28
28
  if (safeCategorySchema.success) {
29
29
  const safeCategory = safeCategorySchema.data;
30
30
 
31
- if (safeCategory.anyOf && Array.isArray(safeCategory.anyOf) && safeCategory.anyOf.length > 0) {
32
- return this.handleLogicalOperator('anyOf', safeCategory.anyOf, required);
31
+ if (safeCategory['anyOf'] && Array.isArray(safeCategory['anyOf']) && safeCategory['anyOf'].length > 0) {
32
+ return this.handleLogicalOperator('anyOf', safeCategory['anyOf'], required);
33
33
  }
34
34
 
35
- if (safeCategory.oneOf && Array.isArray(safeCategory.oneOf) && safeCategory.oneOf.length > 0) {
36
- return this.handleLogicalOperator('oneOf', safeCategory.oneOf, required);
35
+ if (safeCategory['oneOf'] && Array.isArray(safeCategory['oneOf']) && safeCategory['oneOf'].length > 0) {
36
+ return this.handleLogicalOperator('oneOf', safeCategory['oneOf'], required);
37
37
  }
38
38
 
39
- if (safeCategory.allOf && Array.isArray(safeCategory.allOf) && safeCategory.allOf.length > 0) {
40
- return this.handleLogicalOperator('allOf', safeCategory.allOf, required);
39
+ if (safeCategory['allOf'] && Array.isArray(safeCategory['allOf']) && safeCategory['allOf'].length > 0) {
40
+ return this.handleLogicalOperator('allOf', safeCategory['allOf'], required);
41
41
  }
42
42
 
43
- if (safeCategory.not) {
44
- return this.handleLogicalOperator('not', [safeCategory.not], required);
43
+ if (safeCategory['not']) {
44
+ return this.handleLogicalOperator('not', [safeCategory['not']], required);
45
45
  }
46
46
 
47
47
  return this.buildProperty(safeCategory, required);
@@ -155,9 +155,9 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
155
155
  this.typeBuilder.createParameter('path', 'string'),
156
156
  this.typeBuilder.createParameter(
157
157
  'options',
158
- 'unknown',
158
+ '{params?: Record<string, string | number | boolean>; data?: unknown; contentType?: string}',
159
159
  ts.factory.createObjectLiteralExpression([], false),
160
- true,
160
+ false,
161
161
  ),
162
162
  ],
163
163
  ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [
@@ -165,12 +165,13 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
165
165
  ]),
166
166
  ts.factory.createBlock(
167
167
  [
168
+ // Build URL with query parameters
168
169
  ts.factory.createVariableStatement(
169
170
  undefined,
170
171
  ts.factory.createVariableDeclarationList(
171
172
  [
172
173
  ts.factory.createVariableDeclaration(
173
- ts.factory.createIdentifier('url'),
174
+ ts.factory.createIdentifier('baseUrl'),
174
175
  undefined,
175
176
  undefined,
176
177
  ts.factory.createTemplateExpression(ts.factory.createTemplateHead('', ''), [
@@ -191,6 +192,379 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
191
192
  ts.NodeFlags.Const,
192
193
  ),
193
194
  ),
195
+ ts.factory.createVariableStatement(
196
+ undefined,
197
+ ts.factory.createVariableDeclarationList(
198
+ [
199
+ ts.factory.createVariableDeclaration(
200
+ ts.factory.createIdentifier('url'),
201
+ undefined,
202
+ undefined,
203
+ ts.factory.createConditionalExpression(
204
+ ts.factory.createBinaryExpression(
205
+ ts.factory.createPropertyAccessExpression(
206
+ ts.factory.createIdentifier('options'),
207
+ ts.factory.createIdentifier('params'),
208
+ ),
209
+ ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
210
+ ts.factory.createBinaryExpression(
211
+ ts.factory.createPropertyAccessExpression(
212
+ ts.factory.createCallExpression(
213
+ ts.factory.createPropertyAccessExpression(
214
+ ts.factory.createIdentifier('Object'),
215
+ ts.factory.createIdentifier('keys'),
216
+ ),
217
+ undefined,
218
+ [
219
+ ts.factory.createPropertyAccessExpression(
220
+ ts.factory.createIdentifier('options'),
221
+ ts.factory.createIdentifier('params'),
222
+ ),
223
+ ],
224
+ ),
225
+ ts.factory.createIdentifier('length'),
226
+ ),
227
+ ts.factory.createToken(ts.SyntaxKind.GreaterThanToken),
228
+ ts.factory.createNumericLiteral('0'),
229
+ ),
230
+ ),
231
+ undefined,
232
+ (() => {
233
+ const urlObj = ts.factory.createNewExpression(ts.factory.createIdentifier('URL'), undefined, [
234
+ ts.factory.createIdentifier('baseUrl'),
235
+ ]);
236
+ const forEachCall = ts.factory.createCallExpression(
237
+ ts.factory.createPropertyAccessExpression(
238
+ ts.factory.createCallExpression(
239
+ ts.factory.createPropertyAccessExpression(
240
+ ts.factory.createIdentifier('Object'),
241
+ ts.factory.createIdentifier('entries'),
242
+ ),
243
+ undefined,
244
+ [
245
+ ts.factory.createPropertyAccessExpression(
246
+ ts.factory.createIdentifier('options'),
247
+ ts.factory.createIdentifier('params'),
248
+ ),
249
+ ],
250
+ ),
251
+ ts.factory.createIdentifier('forEach'),
252
+ ),
253
+ undefined,
254
+ [
255
+ ts.factory.createArrowFunction(
256
+ undefined,
257
+ undefined,
258
+ [
259
+ ts.factory.createParameterDeclaration(
260
+ undefined,
261
+ undefined,
262
+ ts.factory.createArrayBindingPattern([
263
+ ts.factory.createBindingElement(
264
+ undefined,
265
+ undefined,
266
+ ts.factory.createIdentifier('key'),
267
+ undefined,
268
+ ),
269
+ ts.factory.createBindingElement(
270
+ undefined,
271
+ undefined,
272
+ ts.factory.createIdentifier('value'),
273
+ undefined,
274
+ ),
275
+ ]),
276
+ undefined,
277
+ undefined,
278
+ ),
279
+ ],
280
+ undefined,
281
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
282
+ ts.factory.createBlock(
283
+ [
284
+ ts.factory.createExpressionStatement(
285
+ ts.factory.createCallExpression(
286
+ ts.factory.createPropertyAccessExpression(
287
+ ts.factory.createPropertyAccessExpression(
288
+ urlObj,
289
+ ts.factory.createIdentifier('searchParams'),
290
+ ),
291
+ ts.factory.createIdentifier('set'),
292
+ ),
293
+ undefined,
294
+ [
295
+ ts.factory.createIdentifier('key'),
296
+ ts.factory.createCallExpression(
297
+ ts.factory.createIdentifier('String'),
298
+ undefined,
299
+ [ts.factory.createIdentifier('value')],
300
+ ),
301
+ ],
302
+ ),
303
+ ),
304
+ ],
305
+ false,
306
+ ),
307
+ ),
308
+ ],
309
+ );
310
+ // Use IIFE to execute forEach and return URL string
311
+ return ts.factory.createCallExpression(
312
+ ts.factory.createParenthesizedExpression(
313
+ ts.factory.createArrowFunction(
314
+ undefined,
315
+ undefined,
316
+ [],
317
+ undefined,
318
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
319
+ ts.factory.createBlock(
320
+ [
321
+ ts.factory.createExpressionStatement(forEachCall),
322
+ ts.factory.createReturnStatement(
323
+ ts.factory.createCallExpression(
324
+ ts.factory.createPropertyAccessExpression(
325
+ urlObj,
326
+ ts.factory.createIdentifier('toString'),
327
+ ),
328
+ undefined,
329
+ [],
330
+ ),
331
+ ),
332
+ ],
333
+ false,
334
+ ),
335
+ ),
336
+ ),
337
+ undefined,
338
+ [],
339
+ );
340
+ })(),
341
+ undefined,
342
+ ts.factory.createCallExpression(
343
+ ts.factory.createPropertyAccessExpression(
344
+ ts.factory.createNewExpression(ts.factory.createIdentifier('URL'), undefined, [
345
+ ts.factory.createIdentifier('baseUrl'),
346
+ ]),
347
+ ts.factory.createIdentifier('toString'),
348
+ ),
349
+ undefined,
350
+ [],
351
+ ),
352
+ ),
353
+ ),
354
+ ],
355
+ ts.NodeFlags.Const,
356
+ ),
357
+ ),
358
+ // Build headers with dynamic Content-Type
359
+ ts.factory.createVariableStatement(
360
+ undefined,
361
+ ts.factory.createVariableDeclarationList(
362
+ [
363
+ ts.factory.createVariableDeclaration(
364
+ ts.factory.createIdentifier('headers'),
365
+ undefined,
366
+ undefined,
367
+ ts.factory.createObjectLiteralExpression(
368
+ [
369
+ ts.factory.createPropertyAssignment(
370
+ ts.factory.createStringLiteral('Content-Type', true),
371
+ ts.factory.createConditionalExpression(
372
+ ts.factory.createBinaryExpression(
373
+ ts.factory.createPropertyAccessExpression(
374
+ ts.factory.createIdentifier('options'),
375
+ ts.factory.createIdentifier('contentType'),
376
+ ),
377
+ ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),
378
+ ts.factory.createStringLiteral('application/x-www-form-urlencoded', true),
379
+ ),
380
+ undefined,
381
+ ts.factory.createStringLiteral('application/x-www-form-urlencoded', true),
382
+ undefined,
383
+ ts.factory.createStringLiteral('application/json', true),
384
+ ),
385
+ ),
386
+ ],
387
+ true,
388
+ ),
389
+ ),
390
+ ],
391
+ ts.NodeFlags.Const,
392
+ ),
393
+ ),
394
+ // Build body with form-urlencoded support
395
+ ts.factory.createVariableStatement(
396
+ undefined,
397
+ ts.factory.createVariableDeclarationList(
398
+ [
399
+ ts.factory.createVariableDeclaration(
400
+ ts.factory.createIdentifier('body'),
401
+ undefined,
402
+ undefined,
403
+ ts.factory.createConditionalExpression(
404
+ ts.factory.createBinaryExpression(
405
+ ts.factory.createPropertyAccessExpression(
406
+ ts.factory.createIdentifier('options'),
407
+ ts.factory.createIdentifier('data'),
408
+ ),
409
+ ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken),
410
+ ts.factory.createIdentifier('undefined'),
411
+ ),
412
+ undefined,
413
+ ts.factory.createConditionalExpression(
414
+ ts.factory.createBinaryExpression(
415
+ ts.factory.createPropertyAccessExpression(
416
+ ts.factory.createIdentifier('options'),
417
+ ts.factory.createIdentifier('contentType'),
418
+ ),
419
+ ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),
420
+ ts.factory.createStringLiteral('application/x-www-form-urlencoded', true),
421
+ ),
422
+ undefined,
423
+ // Form-urlencoded: convert object to URLSearchParams
424
+ ts.factory.createCallExpression(
425
+ ts.factory.createParenthesizedExpression(
426
+ ts.factory.createArrowFunction(
427
+ undefined,
428
+ undefined,
429
+ [],
430
+ undefined,
431
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
432
+ ts.factory.createBlock(
433
+ [
434
+ ts.factory.createVariableStatement(
435
+ undefined,
436
+ ts.factory.createVariableDeclarationList(
437
+ [
438
+ ts.factory.createVariableDeclaration(
439
+ ts.factory.createIdentifier('params'),
440
+ undefined,
441
+ undefined,
442
+ ts.factory.createNewExpression(
443
+ ts.factory.createIdentifier('URLSearchParams'),
444
+ undefined,
445
+ [],
446
+ ),
447
+ ),
448
+ ],
449
+ ts.NodeFlags.Const,
450
+ ),
451
+ ),
452
+ ts.factory.createExpressionStatement(
453
+ ts.factory.createCallExpression(
454
+ ts.factory.createPropertyAccessExpression(
455
+ ts.factory.createCallExpression(
456
+ ts.factory.createPropertyAccessExpression(
457
+ ts.factory.createIdentifier('Object'),
458
+ ts.factory.createIdentifier('entries'),
459
+ ),
460
+ undefined,
461
+ [
462
+ ts.factory.createPropertyAccessExpression(
463
+ ts.factory.createIdentifier('options'),
464
+ ts.factory.createIdentifier('data'),
465
+ ),
466
+ ],
467
+ ),
468
+ ts.factory.createIdentifier('forEach'),
469
+ ),
470
+ undefined,
471
+ [
472
+ ts.factory.createArrowFunction(
473
+ undefined,
474
+ undefined,
475
+ [
476
+ ts.factory.createParameterDeclaration(
477
+ undefined,
478
+ undefined,
479
+ ts.factory.createArrayBindingPattern([
480
+ ts.factory.createBindingElement(
481
+ undefined,
482
+ undefined,
483
+ ts.factory.createIdentifier('key'),
484
+ undefined,
485
+ ),
486
+ ts.factory.createBindingElement(
487
+ undefined,
488
+ undefined,
489
+ ts.factory.createIdentifier('value'),
490
+ undefined,
491
+ ),
492
+ ]),
493
+ undefined,
494
+ undefined,
495
+ ),
496
+ ],
497
+ undefined,
498
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
499
+ ts.factory.createBlock(
500
+ [
501
+ ts.factory.createExpressionStatement(
502
+ ts.factory.createCallExpression(
503
+ ts.factory.createPropertyAccessExpression(
504
+ ts.factory.createIdentifier('params'),
505
+ ts.factory.createIdentifier('set'),
506
+ ),
507
+ undefined,
508
+ [
509
+ ts.factory.createIdentifier('key'),
510
+ ts.factory.createCallExpression(
511
+ ts.factory.createIdentifier('String'),
512
+ undefined,
513
+ [ts.factory.createIdentifier('value')],
514
+ ),
515
+ ],
516
+ ),
517
+ ),
518
+ ],
519
+ false,
520
+ ),
521
+ ),
522
+ ],
523
+ ),
524
+ ),
525
+ ts.factory.createReturnStatement(
526
+ ts.factory.createCallExpression(
527
+ ts.factory.createPropertyAccessExpression(
528
+ ts.factory.createIdentifier('params'),
529
+ ts.factory.createIdentifier('toString'),
530
+ ),
531
+ undefined,
532
+ [],
533
+ ),
534
+ ),
535
+ ],
536
+ false,
537
+ ),
538
+ ),
539
+ ),
540
+ undefined,
541
+ [],
542
+ ),
543
+ undefined,
544
+ // JSON: stringify the data
545
+ ts.factory.createCallExpression(
546
+ ts.factory.createPropertyAccessExpression(
547
+ ts.factory.createIdentifier('JSON'),
548
+ ts.factory.createIdentifier('stringify'),
549
+ ),
550
+ undefined,
551
+ [
552
+ ts.factory.createPropertyAccessExpression(
553
+ ts.factory.createIdentifier('options'),
554
+ ts.factory.createIdentifier('data'),
555
+ ),
556
+ ],
557
+ ),
558
+ ),
559
+ undefined,
560
+ ts.factory.createNull(),
561
+ ),
562
+ ),
563
+ ],
564
+ ts.NodeFlags.Const,
565
+ ),
566
+ ),
567
+ // Make fetch request
194
568
  ts.factory.createVariableStatement(
195
569
  undefined,
196
570
  ts.factory.createVariableDeclarationList(
@@ -210,15 +584,11 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
210
584
  ),
211
585
  ts.factory.createPropertyAssignment(
212
586
  ts.factory.createIdentifier('headers'),
213
- ts.factory.createObjectLiteralExpression(
214
- [
215
- ts.factory.createPropertyAssignment(
216
- ts.factory.createStringLiteral('Content-Type', true),
217
- ts.factory.createStringLiteral('application/json', true),
218
- ),
219
- ],
220
- true,
221
- ),
587
+ ts.factory.createIdentifier('headers'),
588
+ ),
589
+ ts.factory.createPropertyAssignment(
590
+ ts.factory.createIdentifier('body'),
591
+ ts.factory.createIdentifier('body'),
222
592
  ),
223
593
  ],
224
594
  true,
@@ -230,6 +600,7 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
230
600
  ts.NodeFlags.Const,
231
601
  ),
232
602
  ),
603
+ // Check response status
233
604
  ts.factory.createIfStatement(
234
605
  ts.factory.createPrefixUnaryExpression(
235
606
  ts.SyntaxKind.ExclamationToken,
@@ -260,6 +631,7 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
260
631
  ),
261
632
  undefined,
262
633
  ),
634
+ // Return parsed JSON
263
635
  ts.factory.createReturnStatement(
264
636
  ts.factory.createAwaitExpression(
265
637
  ts.factory.createCallExpression(
@@ -306,8 +678,84 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
306
678
  schema: MethodSchemaType,
307
679
  schemas: Record<string, ts.VariableStatement>,
308
680
  ): ts.MethodDeclaration {
309
- const parameters = this.buildMethodParameters(schema, schemas);
681
+ const {parameters, pathParams, queryParams, hasRequestBody, contentType} = this.buildMethodParameters(
682
+ schema,
683
+ schemas,
684
+ );
310
685
  const responseType = this.getResponseType(schema, schemas);
686
+ const responseSchema = this.getResponseSchema(schema, schemas);
687
+
688
+ const statements: ts.Statement[] = [];
689
+
690
+ // Build path with parameter substitution
691
+ const pathExpression = this.buildPathExpression(path, pathParams);
692
+
693
+ // Build query parameters object
694
+ const queryParamsExpression: ts.Expression | undefined =
695
+ queryParams.length > 0
696
+ ? ts.factory.createObjectLiteralExpression(
697
+ queryParams.map((param) => {
698
+ const paramName = this.typeBuilder.sanitizeIdentifier(param.name);
699
+ return ts.factory.createPropertyAssignment(
700
+ ts.factory.createStringLiteral(param.name, true),
701
+ ts.factory.createIdentifier(paramName),
702
+ );
703
+ }),
704
+ false,
705
+ )
706
+ : undefined;
707
+
708
+ // Build request body
709
+ const requestBodyExpression: ts.Expression | undefined = hasRequestBody
710
+ ? ts.factory.createIdentifier('body')
711
+ : undefined;
712
+
713
+ // Build options object for makeRequest
714
+ const optionsProps: ts.ObjectLiteralElementLike[] = [];
715
+ if (queryParamsExpression) {
716
+ optionsProps.push(
717
+ ts.factory.createPropertyAssignment(ts.factory.createIdentifier('params'), queryParamsExpression),
718
+ );
719
+ }
720
+ if (requestBodyExpression) {
721
+ optionsProps.push(
722
+ ts.factory.createPropertyAssignment(ts.factory.createIdentifier('data'), requestBodyExpression),
723
+ );
724
+ }
725
+ // Add content type if it's form-urlencoded
726
+ if (hasRequestBody && contentType === 'application/x-www-form-urlencoded') {
727
+ optionsProps.push(
728
+ ts.factory.createPropertyAssignment(
729
+ ts.factory.createIdentifier('contentType'),
730
+ ts.factory.createStringLiteral('application/x-www-form-urlencoded', true),
731
+ ),
732
+ );
733
+ }
734
+
735
+ const optionsExpression = ts.factory.createObjectLiteralExpression(optionsProps, false);
736
+
737
+ // Call makeRequest
738
+ const makeRequestCall = ts.factory.createCallExpression(
739
+ ts.factory.createPropertyAccessExpression(
740
+ ts.factory.createThis(),
741
+ ts.factory.createPrivateIdentifier('#makeRequest'),
742
+ ),
743
+ undefined,
744
+ [ts.factory.createStringLiteral(method.toUpperCase(), true), pathExpression, optionsExpression],
745
+ );
746
+
747
+ // Add Zod validation if we have a response schema
748
+ if (responseSchema) {
749
+ const validateCall = ts.factory.createCallExpression(
750
+ ts.factory.createPropertyAccessExpression(responseSchema, ts.factory.createIdentifier('parse')),
751
+ undefined,
752
+ [ts.factory.createAwaitExpression(makeRequestCall)],
753
+ );
754
+
755
+ statements.push(ts.factory.createReturnStatement(validateCall));
756
+ } else {
757
+ statements.push(ts.factory.createReturnStatement(ts.factory.createAwaitExpression(makeRequestCall)));
758
+ }
311
759
 
312
760
  return ts.factory.createMethodDeclaration(
313
761
  [ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)],
@@ -317,68 +765,287 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
317
765
  undefined,
318
766
  parameters,
319
767
  responseType,
320
- ts.factory.createBlock(
321
- [
322
- ts.factory.createReturnStatement(
323
- ts.factory.createAwaitExpression(
324
- ts.factory.createCallExpression(
325
- ts.factory.createPropertyAccessExpression(
326
- ts.factory.createThis(),
327
- ts.factory.createPrivateIdentifier('#makeRequest'),
328
- ),
329
- undefined,
330
- [
331
- ts.factory.createStringLiteral(method.toUpperCase(), true),
332
- ts.factory.createStringLiteral(path, true),
333
- ],
334
- ),
335
- ),
336
- ),
337
- ],
338
- true,
339
- ),
768
+ ts.factory.createBlock(statements, true),
340
769
  );
341
770
  }
342
771
 
772
+ private buildPathExpression(path: string, pathParams: {name: string; type: string}[]): ts.Expression {
773
+ // Replace {param} with ${param} for template literal
774
+ const pathParamNames = new Set(pathParams.map((p) => p.name));
775
+ const pathParamRegex = /\{([^}]+)\}/g;
776
+ const matches: {index: number; length: number; name: string}[] = [];
777
+
778
+ // Find all path parameters
779
+ for (const match of path.matchAll(pathParamRegex)) {
780
+ const paramName = match[1];
781
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
782
+ if (paramName && pathParamNames.has(paramName) && match.index !== undefined) {
783
+ matches.push({
784
+ index: match.index,
785
+ length: match[0].length,
786
+ name: paramName,
787
+ });
788
+ }
789
+ }
790
+
791
+ if (matches.length === 0) {
792
+ // No path parameters, return as string literal
793
+ return ts.factory.createStringLiteral(path, true);
794
+ }
795
+
796
+ // Build template expression
797
+ const templateSpans: ts.TemplateSpan[] = [];
798
+ let lastIndex = 0;
799
+
800
+ for (const [index, m] of matches.entries()) {
801
+ const before = path.substring(lastIndex, m.index);
802
+ const sanitizedName = this.typeBuilder.sanitizeIdentifier(m.name);
803
+ const isLast = index === matches.length - 1;
804
+ const after = isLast ? path.substring(m.index + m.length) : '';
805
+
806
+ if (isLast) {
807
+ templateSpans.push(
808
+ ts.factory.createTemplateSpan(
809
+ ts.factory.createIdentifier(sanitizedName),
810
+ ts.factory.createTemplateTail(after, after),
811
+ ),
812
+ );
813
+ } else {
814
+ templateSpans.push(
815
+ ts.factory.createTemplateSpan(
816
+ ts.factory.createIdentifier(sanitizedName),
817
+ ts.factory.createTemplateMiddle(before, before),
818
+ ),
819
+ );
820
+ }
821
+
822
+ lastIndex = m.index + m.length;
823
+ }
824
+
825
+ const firstMatch = matches[0];
826
+ if (!firstMatch) {
827
+ return ts.factory.createStringLiteral(path, true);
828
+ }
829
+ const head = path.substring(0, firstMatch.index);
830
+ return ts.factory.createTemplateExpression(ts.factory.createTemplateHead(head, head), templateSpans);
831
+ }
832
+
343
833
  private buildMethodParameters(
344
834
  schema: MethodSchemaType,
345
835
  schemas: Record<string, ts.VariableStatement>,
346
- ): ts.ParameterDeclaration[] {
347
- void schemas; // Mark as intentionally unused
836
+ ): {
837
+ parameters: ts.ParameterDeclaration[];
838
+ pathParams: {name: string; type: string}[];
839
+ queryParams: {name: string; type: string; required: boolean}[];
840
+ hasRequestBody: boolean;
841
+ contentType: string;
842
+ } {
348
843
  const parameters: ts.ParameterDeclaration[] = [];
844
+ const pathParams: {name: string; type: string}[] = [];
845
+ const queryParams: {name: string; type: string; required: boolean}[] = [];
349
846
 
847
+ // Extract path and query parameters
350
848
  if (schema.parameters) {
351
- schema.parameters.forEach((param) => {
352
- if (param.in === 'path' && param.required) {
353
- parameters.push(
354
- this.typeBuilder.createParameter(
355
- this.typeBuilder.sanitizeIdentifier(param.name),
356
- 'string',
357
- undefined,
358
- false,
359
- ),
360
- );
849
+ for (const param of schema.parameters) {
850
+ const paramName = this.typeBuilder.sanitizeIdentifier(param.name);
851
+ const paramType = this.getParameterType(param.schema);
852
+
853
+ if (param.in === 'path') {
854
+ pathParams.push({name: param.name, type: paramType});
855
+ parameters.push(this.typeBuilder.createParameter(paramName, paramType, undefined, false));
856
+ } else if (param.in === 'query') {
857
+ // Improve type inference for query parameters
858
+ const queryParamType =
859
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
860
+ typeof param.schema === 'object' && param.schema !== null
861
+ ? (() => {
862
+ const paramSchema = param.schema as {type?: string; items?: unknown; enum?: unknown[]};
863
+ // eslint-disable-next-line @typescript-eslint/dot-notation
864
+ if (paramSchema['type'] === 'array' && paramSchema['items']) {
865
+ // eslint-disable-next-line @typescript-eslint/dot-notation
866
+ const itemSchema = paramSchema['items'] as {type?: string};
867
+ // eslint-disable-next-line @typescript-eslint/dot-notation
868
+ if (itemSchema['type'] === 'string') {
869
+ return 'string[]' as const;
870
+ }
871
+ // eslint-disable-next-line @typescript-eslint/dot-notation
872
+ if (itemSchema['type'] === 'number' || itemSchema['type'] === 'integer') {
873
+ return 'number[]' as const;
874
+ }
875
+ }
876
+ return paramType;
877
+ })()
878
+ : paramType;
879
+ queryParams.push({name: param.name, type: queryParamType, required: param.required ?? false});
880
+ parameters.push(this.typeBuilder.createParameter(paramName, queryParamType, undefined, !param.required));
361
881
  }
362
- });
882
+ }
883
+ }
884
+
885
+ // Add request body parameter if present
886
+ // Check for both application/json and application/x-www-form-urlencoded
887
+ const jsonContent = schema.requestBody?.content?.['application/json'];
888
+ const formContent = schema.requestBody?.content?.['application/x-www-form-urlencoded'];
889
+ const hasRequestBody = !!(jsonContent ?? formContent);
890
+
891
+ if (hasRequestBody) {
892
+ const requestBodyContent = jsonContent ?? formContent;
893
+ const requestBodySchema = requestBodyContent?.schema;
894
+ const bodyType =
895
+ typeof requestBodySchema === 'object' && requestBodySchema !== null
896
+ ? (() => {
897
+ const schemaObj = requestBodySchema as Record<string, unknown>;
898
+ const ref = schemaObj['$ref'];
899
+
900
+ if (ref && typeof ref === 'string' && ref.startsWith('#/components/schemas/')) {
901
+ const refName = ref.split('/').pop() ?? 'unknown';
902
+ return this.typeBuilder.sanitizeIdentifier(refName);
903
+ }
904
+ // Fallback to getSchemaTypeName for non-ref schemas
905
+ return this.getSchemaTypeName(requestBodySchema, schemas);
906
+ })()
907
+ : 'unknown';
908
+
909
+ parameters.push(this.typeBuilder.createParameter('body', bodyType, undefined, !schema.requestBody?.required));
910
+ }
911
+
912
+ // Determine content type for request body
913
+ const contentType =
914
+ hasRequestBody && schema.requestBody?.content?.['application/x-www-form-urlencoded']
915
+ ? 'application/x-www-form-urlencoded'
916
+ : 'application/json';
917
+
918
+ return {parameters, pathParams, queryParams, hasRequestBody, contentType};
919
+ }
920
+
921
+ private getParameterType(schema: unknown): string {
922
+ if (!schema || typeof schema !== 'object') {
923
+ return 'string';
924
+ }
925
+
926
+ const schemaObj = schema as {
927
+ type?: string;
928
+ format?: string;
929
+ $ref?: string;
930
+ enum?: unknown[];
931
+ items?: unknown;
932
+ };
933
+
934
+ if (schemaObj.$ref) {
935
+ const refName = schemaObj.$ref.split('/').pop() ?? 'unknown';
936
+ return this.typeBuilder.sanitizeIdentifier(refName);
937
+ }
938
+
939
+ if (schemaObj.type === 'array' && schemaObj.items) {
940
+ const itemType = this.getParameterType(schemaObj.items);
941
+ return `${itemType}[]`;
942
+ }
943
+
944
+ switch (schemaObj.type) {
945
+ case 'integer':
946
+ case 'number':
947
+ return 'number';
948
+ case 'boolean':
949
+ return 'boolean';
950
+ case 'array':
951
+ return 'unknown[]';
952
+ case 'object':
953
+ return 'Record<string, unknown>';
954
+ case 'string':
955
+ // If it has enum values, we could generate a union type, but for simplicity, keep as string
956
+ // The Zod schema will handle the validation
957
+ return 'string';
958
+ default:
959
+ return 'string';
960
+ }
961
+ }
962
+
963
+ private getSchemaTypeName(schema: unknown, _schemas: Record<string, ts.VariableStatement>): string {
964
+ if (typeof schema !== 'object' || schema === null) {
965
+ return 'unknown';
966
+ }
967
+
968
+ const schemaObj = schema as {$ref?: string; type?: string; items?: unknown};
969
+
970
+ // Check for $ref using both dot notation and bracket notation
971
+ // eslint-disable-next-line @typescript-eslint/dot-notation
972
+ const ref = schemaObj['$ref'];
973
+ if (ref && typeof ref === 'string') {
974
+ const refName = ref.split('/').pop() ?? 'unknown';
975
+ return this.typeBuilder.sanitizeIdentifier(refName);
976
+ }
977
+
978
+ if (schemaObj.type === 'array' && schemaObj.items) {
979
+ const itemType = this.getSchemaTypeName(schemaObj.items, _schemas);
980
+ return `${itemType}[]`;
981
+ }
982
+
983
+ switch (schemaObj.type) {
984
+ case 'integer':
985
+ case 'number':
986
+ return 'number';
987
+ case 'boolean':
988
+ return 'boolean';
989
+ case 'string':
990
+ return 'string';
991
+ case 'object':
992
+ return 'Record<string, unknown>';
993
+ default:
994
+ return 'unknown';
995
+ }
996
+ }
997
+
998
+ private getResponseSchema(
999
+ schema: MethodSchemaType,
1000
+ _schemas: Record<string, ts.VariableStatement>,
1001
+ ): ts.Identifier | undefined {
1002
+ // Try to find a 200 response first, then 201, then default
1003
+ const response200 = schema.responses?.['200'];
1004
+ const response201 = schema.responses?.['201'];
1005
+ const responseDefault = schema.responses?.['default'];
1006
+
1007
+ const response = response200 ?? response201 ?? responseDefault;
1008
+ if (!response?.content?.['application/json']?.schema) {
1009
+ return undefined;
363
1010
  }
364
1011
 
365
- parameters.push(this.typeBuilder.createParameter('_', 'unknown', undefined, true));
1012
+ const responseSchema = response.content['application/json'].schema;
1013
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1014
+ if (responseSchema !== null && typeof responseSchema === 'object' && '$ref' in responseSchema) {
1015
+ // eslint-disable-next-line @typescript-eslint/dot-notation
1016
+ const ref = (responseSchema as {$ref: string})['$ref'];
1017
+ if (typeof ref === 'string') {
1018
+ const refName = ref.split('/').pop() ?? 'unknown';
1019
+ return ts.factory.createIdentifier(this.typeBuilder.sanitizeIdentifier(refName));
1020
+ }
1021
+ }
366
1022
 
367
- return parameters;
1023
+ // For inline schemas, we'd need to generate a schema, but for now return undefined
1024
+ // This could be enhanced to generate inline schemas
1025
+ return undefined;
368
1026
  }
369
1027
 
370
1028
  private getResponseType(
371
1029
  schema: MethodSchemaType,
372
1030
  schemas: Record<string, ts.VariableStatement>,
373
1031
  ): ts.TypeNode | undefined {
374
- void schemas; // Mark as intentionally unused
1032
+ // Try to find a 200 response first, then 201, then default
375
1033
  const response200 = schema.responses?.['200'];
376
- if (!response200?.content?.['application/json']?.schema) {
377
- return undefined;
1034
+ const response201 = schema.responses?.['201'];
1035
+ const responseDefault = schema.responses?.['default'];
1036
+
1037
+ const response = response200 ?? response201 ?? responseDefault;
1038
+ if (!response?.content?.['application/json']?.schema) {
1039
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [
1040
+ ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword),
1041
+ ]);
378
1042
  }
379
1043
 
1044
+ const responseSchema = response.content['application/json'].schema;
1045
+ const typeName = this.getSchemaTypeName(responseSchema, schemas);
1046
+
380
1047
  return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [
381
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
1048
+ ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined),
382
1049
  ]);
383
1050
  }
384
1051
 
@@ -471,46 +1138,118 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
471
1138
  const prop = safeProperty.data;
472
1139
 
473
1140
  if (this.isReference(prop)) {
474
- return this.buildFromReference(prop);
1141
+ const refSchema = this.buildFromReference(prop);
1142
+ return required
1143
+ ? refSchema
1144
+ : ts.factory.createCallExpression(
1145
+ ts.factory.createPropertyAccessExpression(refSchema, ts.factory.createIdentifier('optional')),
1146
+ undefined,
1147
+ [],
1148
+ );
475
1149
  }
476
1150
 
477
- const methodsToApply: string[] = [];
1151
+ if (prop['anyOf'] && Array.isArray(prop['anyOf']) && prop['anyOf'].length > 0) {
1152
+ return this.handleLogicalOperator('anyOf', prop['anyOf'], required);
1153
+ }
478
1154
 
479
- if (prop.anyOf && Array.isArray(prop.anyOf) && prop.anyOf.length > 0) {
480
- return this.handleLogicalOperator('anyOf', prop.anyOf, required);
1155
+ if (prop['oneOf'] && Array.isArray(prop['oneOf']) && prop['oneOf'].length > 0) {
1156
+ return this.handleLogicalOperator('oneOf', prop['oneOf'], required);
481
1157
  }
482
1158
 
483
- if (prop.oneOf && Array.isArray(prop.oneOf) && prop.oneOf.length > 0) {
484
- return this.handleLogicalOperator('oneOf', prop.oneOf, required);
1159
+ if (prop['allOf'] && Array.isArray(prop['allOf']) && prop['allOf'].length > 0) {
1160
+ return this.handleLogicalOperator('allOf', prop['allOf'], required);
485
1161
  }
486
1162
 
487
- if (prop.allOf && Array.isArray(prop.allOf) && prop.allOf.length > 0) {
488
- return this.handleLogicalOperator('allOf', prop.allOf, required);
1163
+ if (prop['not']) {
1164
+ return this.handleLogicalOperator('not', [prop['not']], required);
489
1165
  }
490
1166
 
491
- if (prop.not) {
492
- return this.handleLogicalOperator('not', [prop.not], required);
1167
+ // Handle enum
1168
+ if (prop['enum'] && Array.isArray(prop['enum']) && prop['enum'].length > 0) {
1169
+ const enumValues = prop['enum'].map((val) => {
1170
+ if (typeof val === 'string') {
1171
+ return ts.factory.createStringLiteral(val, true);
1172
+ }
1173
+ if (typeof val === 'number') {
1174
+ // Handle negative numbers correctly
1175
+ if (val < 0) {
1176
+ return ts.factory.createPrefixUnaryExpression(
1177
+ ts.SyntaxKind.MinusToken,
1178
+ ts.factory.createNumericLiteral(String(Math.abs(val))),
1179
+ );
1180
+ }
1181
+ return ts.factory.createNumericLiteral(String(val));
1182
+ }
1183
+ if (typeof val === 'boolean') {
1184
+ return val ? ts.factory.createTrue() : ts.factory.createFalse();
1185
+ }
1186
+ return ts.factory.createStringLiteral(String(val), true);
1187
+ });
1188
+
1189
+ const enumExpression = ts.factory.createCallExpression(
1190
+ ts.factory.createPropertyAccessExpression(
1191
+ ts.factory.createIdentifier('z'),
1192
+ ts.factory.createIdentifier('enum'),
1193
+ ),
1194
+ undefined,
1195
+ [ts.factory.createArrayLiteralExpression(enumValues, false)],
1196
+ );
1197
+
1198
+ return required
1199
+ ? enumExpression
1200
+ : ts.factory.createCallExpression(
1201
+ ts.factory.createPropertyAccessExpression(enumExpression, ts.factory.createIdentifier('optional')),
1202
+ undefined,
1203
+ [],
1204
+ );
493
1205
  }
494
1206
 
495
- switch (prop.type) {
496
- case 'array':
497
- return this.buildZodAST([
1207
+ switch (prop['type']) {
1208
+ case 'array': {
1209
+ const itemsSchema = prop['items'] ? this.buildProperty(prop['items'], true) : this.buildZodAST(['unknown']);
1210
+ let arraySchema = this.buildZodAST([
498
1211
  {
499
1212
  type: 'array',
500
- args: prop.items ? [this.buildProperty(prop.items, true)] : [],
1213
+ args: [itemsSchema],
501
1214
  },
502
- ...(!required ? ['optional'] : []),
503
1215
  ]);
1216
+
1217
+ // Apply array constraints
1218
+ if (typeof prop['minItems'] === 'number') {
1219
+ arraySchema = ts.factory.createCallExpression(
1220
+ ts.factory.createPropertyAccessExpression(arraySchema, ts.factory.createIdentifier('min')),
1221
+ undefined,
1222
+ [ts.factory.createNumericLiteral(String(prop['minItems']))],
1223
+ );
1224
+ }
1225
+ if (typeof prop['maxItems'] === 'number') {
1226
+ arraySchema = ts.factory.createCallExpression(
1227
+ ts.factory.createPropertyAccessExpression(arraySchema, ts.factory.createIdentifier('max')),
1228
+ undefined,
1229
+ [ts.factory.createNumericLiteral(String(prop['maxItems']))],
1230
+ );
1231
+ }
1232
+
1233
+ return required
1234
+ ? arraySchema
1235
+ : ts.factory.createCallExpression(
1236
+ ts.factory.createPropertyAccessExpression(arraySchema, ts.factory.createIdentifier('optional')),
1237
+ undefined,
1238
+ [],
1239
+ );
1240
+ }
504
1241
  case 'object': {
505
- const {properties = {}, required: propRequired = []} = prop as {
1242
+ const propObj = prop satisfies {
506
1243
  properties?: Record<string, unknown>;
507
1244
  required?: string[];
508
1245
  };
1246
+ const properties = (propObj['properties'] ?? {}) as Record<string, unknown>;
1247
+ const propRequired = (propObj['required'] ?? []) as string[];
509
1248
 
510
1249
  const propertiesEntries = Object.entries(properties);
511
1250
 
512
1251
  if (propertiesEntries.length > 0) {
513
- return this.buildZodAST([
1252
+ const objectSchema = this.buildZodAST([
514
1253
  {
515
1254
  type: 'object',
516
1255
  args: [
@@ -525,8 +1264,94 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
525
1264
  ),
526
1265
  ],
527
1266
  },
528
- ...(!required ? ['optional'] : []),
529
1267
  ]);
1268
+
1269
+ // Apply object constraints
1270
+ let constrainedSchema = objectSchema;
1271
+ if (typeof prop['minProperties'] === 'number') {
1272
+ constrainedSchema = ts.factory.createCallExpression(
1273
+ ts.factory.createPropertyAccessExpression(constrainedSchema, ts.factory.createIdentifier('refine')),
1274
+ undefined,
1275
+ [
1276
+ ts.factory.createArrowFunction(
1277
+ undefined,
1278
+ undefined,
1279
+ [ts.factory.createParameterDeclaration(undefined, undefined, 'obj', undefined, undefined, undefined)],
1280
+ undefined,
1281
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
1282
+ ts.factory.createBinaryExpression(
1283
+ ts.factory.createPropertyAccessExpression(
1284
+ ts.factory.createCallExpression(
1285
+ ts.factory.createPropertyAccessExpression(
1286
+ ts.factory.createIdentifier('Object'),
1287
+ ts.factory.createIdentifier('keys'),
1288
+ ),
1289
+ undefined,
1290
+ [ts.factory.createIdentifier('obj')],
1291
+ ),
1292
+ ts.factory.createIdentifier('length'),
1293
+ ),
1294
+ ts.factory.createToken(ts.SyntaxKind.GreaterThanEqualsToken),
1295
+ ts.factory.createNumericLiteral(String(prop['minProperties'])),
1296
+ ),
1297
+ ),
1298
+ ts.factory.createObjectLiteralExpression([
1299
+ ts.factory.createPropertyAssignment(
1300
+ ts.factory.createIdentifier('message'),
1301
+ ts.factory.createStringLiteral(
1302
+ `Object must have at least ${String(prop['minProperties'])} properties`,
1303
+ ),
1304
+ ),
1305
+ ]),
1306
+ ],
1307
+ );
1308
+ }
1309
+ if (typeof prop['maxProperties'] === 'number') {
1310
+ constrainedSchema = ts.factory.createCallExpression(
1311
+ ts.factory.createPropertyAccessExpression(constrainedSchema, ts.factory.createIdentifier('refine')),
1312
+ undefined,
1313
+ [
1314
+ ts.factory.createArrowFunction(
1315
+ undefined,
1316
+ undefined,
1317
+ [ts.factory.createParameterDeclaration(undefined, undefined, 'obj', undefined, undefined, undefined)],
1318
+ undefined,
1319
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
1320
+ ts.factory.createBinaryExpression(
1321
+ ts.factory.createPropertyAccessExpression(
1322
+ ts.factory.createCallExpression(
1323
+ ts.factory.createPropertyAccessExpression(
1324
+ ts.factory.createIdentifier('Object'),
1325
+ ts.factory.createIdentifier('keys'),
1326
+ ),
1327
+ undefined,
1328
+ [ts.factory.createIdentifier('obj')],
1329
+ ),
1330
+ ts.factory.createIdentifier('length'),
1331
+ ),
1332
+ ts.factory.createToken(ts.SyntaxKind.LessThanEqualsToken),
1333
+ ts.factory.createNumericLiteral(String(prop['maxProperties'])),
1334
+ ),
1335
+ ),
1336
+ ts.factory.createObjectLiteralExpression([
1337
+ ts.factory.createPropertyAssignment(
1338
+ ts.factory.createIdentifier('message'),
1339
+ ts.factory.createStringLiteral(
1340
+ `Object must have at most ${String(prop['maxProperties'])} properties`,
1341
+ ),
1342
+ ),
1343
+ ]),
1344
+ ],
1345
+ );
1346
+ }
1347
+
1348
+ return required
1349
+ ? constrainedSchema
1350
+ : ts.factory.createCallExpression(
1351
+ ts.factory.createPropertyAccessExpression(constrainedSchema, ts.factory.createIdentifier('optional')),
1352
+ undefined,
1353
+ [],
1354
+ );
530
1355
  }
531
1356
 
532
1357
  return this.buildZodAST([
@@ -536,21 +1361,319 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
536
1361
  },
537
1362
  ]);
538
1363
  }
539
- case 'integer':
540
- methodsToApply.push('int');
541
- return this.buildZodAST(['number', ...methodsToApply, ...(!required ? ['optional'] : [])]);
542
- case 'number':
543
- return this.buildZodAST(['number', ...(!required ? ['optional'] : [])]);
544
- case 'string':
545
- return this.buildZodAST(['string', ...(!required ? ['optional'] : [])]);
546
- case 'boolean':
547
- return this.buildZodAST(['boolean', ...(!required ? ['optional'] : [])]);
1364
+ case 'integer': {
1365
+ let numberSchema = this.buildZodAST(['number', 'int']);
1366
+
1367
+ // Apply number constraints
1368
+ if (prop['minimum'] !== undefined && typeof prop['minimum'] === 'number') {
1369
+ const minValue =
1370
+ prop['exclusiveMinimum'] && typeof prop['exclusiveMinimum'] === 'boolean'
1371
+ ? prop['minimum'] + 1
1372
+ : prop['minimum'];
1373
+ numberSchema = ts.factory.createCallExpression(
1374
+ ts.factory.createPropertyAccessExpression(
1375
+ numberSchema,
1376
+ ts.factory.createIdentifier(
1377
+ prop['exclusiveMinimum'] && typeof prop['exclusiveMinimum'] === 'boolean' ? 'gt' : 'gte',
1378
+ ),
1379
+ ),
1380
+ undefined,
1381
+ [ts.factory.createNumericLiteral(String(minValue))],
1382
+ );
1383
+ }
1384
+ if (prop['maximum'] !== undefined && typeof prop['maximum'] === 'number') {
1385
+ const maxValue =
1386
+ prop['exclusiveMaximum'] && typeof prop['exclusiveMaximum'] === 'boolean'
1387
+ ? prop['maximum'] - 1
1388
+ : prop['maximum'];
1389
+ numberSchema = ts.factory.createCallExpression(
1390
+ ts.factory.createPropertyAccessExpression(
1391
+ numberSchema,
1392
+ ts.factory.createIdentifier(prop['exclusiveMaximum'] ? 'lt' : 'lte'),
1393
+ ),
1394
+ undefined,
1395
+ [ts.factory.createNumericLiteral(String(maxValue))],
1396
+ );
1397
+ }
1398
+ if (typeof prop['multipleOf'] === 'number') {
1399
+ const refineFunction = ts.factory.createArrowFunction(
1400
+ undefined,
1401
+ undefined,
1402
+ [ts.factory.createParameterDeclaration(undefined, undefined, 'val', undefined, undefined, undefined)],
1403
+ undefined,
1404
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
1405
+ ts.factory.createBinaryExpression(
1406
+ ts.factory.createBinaryExpression(
1407
+ ts.factory.createIdentifier('val'),
1408
+ ts.factory.createToken(ts.SyntaxKind.PercentToken),
1409
+ ts.factory.createNumericLiteral(String(prop['multipleOf'])),
1410
+ ),
1411
+ ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),
1412
+ ts.factory.createNumericLiteral('0'),
1413
+ ),
1414
+ );
1415
+ const refineOptions = ts.factory.createObjectLiteralExpression([
1416
+ ts.factory.createPropertyAssignment(
1417
+ ts.factory.createIdentifier('message'),
1418
+ ts.factory.createStringLiteral(`Number must be a multiple of ${String(prop['multipleOf'])}`),
1419
+ ),
1420
+ ]);
1421
+ numberSchema = ts.factory.createCallExpression(
1422
+ ts.factory.createPropertyAccessExpression(numberSchema, ts.factory.createIdentifier('refine')),
1423
+ undefined,
1424
+ [refineFunction, refineOptions],
1425
+ );
1426
+ }
1427
+
1428
+ return required
1429
+ ? numberSchema
1430
+ : ts.factory.createCallExpression(
1431
+ ts.factory.createPropertyAccessExpression(numberSchema, ts.factory.createIdentifier('optional')),
1432
+ undefined,
1433
+ [],
1434
+ );
1435
+ }
1436
+ case 'number': {
1437
+ let numberSchema = this.buildZodAST(['number']);
1438
+
1439
+ // Apply number constraints
1440
+ if (prop['minimum'] !== undefined && typeof prop['minimum'] === 'number') {
1441
+ const minValue =
1442
+ prop['exclusiveMinimum'] && typeof prop['exclusiveMinimum'] === 'boolean'
1443
+ ? prop['minimum'] + 1
1444
+ : prop['minimum'];
1445
+ numberSchema = ts.factory.createCallExpression(
1446
+ ts.factory.createPropertyAccessExpression(
1447
+ numberSchema,
1448
+ ts.factory.createIdentifier(
1449
+ prop['exclusiveMinimum'] && typeof prop['exclusiveMinimum'] === 'boolean' ? 'gt' : 'gte',
1450
+ ),
1451
+ ),
1452
+ undefined,
1453
+ [ts.factory.createNumericLiteral(String(minValue))],
1454
+ );
1455
+ }
1456
+ if (prop['maximum'] !== undefined && typeof prop['maximum'] === 'number') {
1457
+ const maxValue =
1458
+ prop['exclusiveMaximum'] && typeof prop['exclusiveMaximum'] === 'boolean'
1459
+ ? prop['maximum'] - 1
1460
+ : prop['maximum'];
1461
+ numberSchema = ts.factory.createCallExpression(
1462
+ ts.factory.createPropertyAccessExpression(
1463
+ numberSchema,
1464
+ ts.factory.createIdentifier(prop['exclusiveMaximum'] ? 'lt' : 'lte'),
1465
+ ),
1466
+ undefined,
1467
+ [ts.factory.createNumericLiteral(String(maxValue))],
1468
+ );
1469
+ }
1470
+ if (typeof prop['multipleOf'] === 'number') {
1471
+ const refineFunction = ts.factory.createArrowFunction(
1472
+ undefined,
1473
+ undefined,
1474
+ [ts.factory.createParameterDeclaration(undefined, undefined, 'val', undefined, undefined, undefined)],
1475
+ undefined,
1476
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
1477
+ ts.factory.createBinaryExpression(
1478
+ ts.factory.createBinaryExpression(
1479
+ ts.factory.createIdentifier('val'),
1480
+ ts.factory.createToken(ts.SyntaxKind.PercentToken),
1481
+ ts.factory.createNumericLiteral(String(prop['multipleOf'])),
1482
+ ),
1483
+ ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),
1484
+ ts.factory.createNumericLiteral('0'),
1485
+ ),
1486
+ );
1487
+ const refineOptions = ts.factory.createObjectLiteralExpression([
1488
+ ts.factory.createPropertyAssignment(
1489
+ ts.factory.createIdentifier('message'),
1490
+ ts.factory.createStringLiteral(`Number must be a multiple of ${String(prop['multipleOf'])}`),
1491
+ ),
1492
+ ]);
1493
+ numberSchema = ts.factory.createCallExpression(
1494
+ ts.factory.createPropertyAccessExpression(numberSchema, ts.factory.createIdentifier('refine')),
1495
+ undefined,
1496
+ [refineFunction, refineOptions],
1497
+ );
1498
+ }
1499
+
1500
+ return required
1501
+ ? numberSchema
1502
+ : ts.factory.createCallExpression(
1503
+ ts.factory.createPropertyAccessExpression(numberSchema, ts.factory.createIdentifier('optional')),
1504
+ undefined,
1505
+ [],
1506
+ );
1507
+ }
1508
+ case 'string': {
1509
+ let stringSchema = this.buildZodAST(['string']);
1510
+
1511
+ // Apply string format
1512
+ if (prop['format']) {
1513
+ switch (prop['format']) {
1514
+ case 'email':
1515
+ stringSchema = ts.factory.createCallExpression(
1516
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('email')),
1517
+ undefined,
1518
+ [],
1519
+ );
1520
+ break;
1521
+ case 'uri':
1522
+ case 'url':
1523
+ stringSchema = ts.factory.createCallExpression(
1524
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('url')),
1525
+ undefined,
1526
+ [],
1527
+ );
1528
+ break;
1529
+ case 'uuid':
1530
+ stringSchema = ts.factory.createCallExpression(
1531
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('uuid')),
1532
+ undefined,
1533
+ [],
1534
+ );
1535
+ break;
1536
+ case 'date-time':
1537
+ stringSchema = ts.factory.createCallExpression(
1538
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('datetime')),
1539
+ undefined,
1540
+ [],
1541
+ );
1542
+ break;
1543
+ case 'date':
1544
+ stringSchema = ts.factory.createCallExpression(
1545
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('date')),
1546
+ undefined,
1547
+ [],
1548
+ );
1549
+ break;
1550
+ case 'time':
1551
+ stringSchema = ts.factory.createCallExpression(
1552
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('time')),
1553
+ undefined,
1554
+ [],
1555
+ );
1556
+ break;
1557
+ // Add more formats as needed
1558
+ }
1559
+ }
1560
+
1561
+ // Apply string constraints
1562
+ if (typeof prop['minLength'] === 'number') {
1563
+ stringSchema = ts.factory.createCallExpression(
1564
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('min')),
1565
+ undefined,
1566
+ [ts.factory.createNumericLiteral(String(prop['minLength']))],
1567
+ );
1568
+ }
1569
+ if (typeof prop['maxLength'] === 'number') {
1570
+ stringSchema = ts.factory.createCallExpression(
1571
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('max')),
1572
+ undefined,
1573
+ [ts.factory.createNumericLiteral(String(prop['maxLength']))],
1574
+ );
1575
+ }
1576
+ if (prop['pattern'] && typeof prop['pattern'] === 'string') {
1577
+ stringSchema = ts.factory.createCallExpression(
1578
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('regex')),
1579
+ undefined,
1580
+ [
1581
+ ts.factory.createNewExpression(ts.factory.createIdentifier('RegExp'), undefined, [
1582
+ ts.factory.createStringLiteral(prop['pattern'], true),
1583
+ ]),
1584
+ ],
1585
+ );
1586
+ }
1587
+
1588
+ // Apply default value if not required
1589
+ if (!required && prop['default'] !== undefined) {
1590
+ const defaultValue = this.buildDefaultValue(prop['default']);
1591
+ stringSchema = ts.factory.createCallExpression(
1592
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('default')),
1593
+ undefined,
1594
+ [defaultValue],
1595
+ );
1596
+ }
1597
+
1598
+ return required
1599
+ ? stringSchema
1600
+ : ts.factory.createCallExpression(
1601
+ ts.factory.createPropertyAccessExpression(stringSchema, ts.factory.createIdentifier('optional')),
1602
+ undefined,
1603
+ [],
1604
+ );
1605
+ }
1606
+ case 'boolean': {
1607
+ let booleanSchema = this.buildZodAST(['boolean']);
1608
+
1609
+ // Apply default value if not required
1610
+ if (!required && prop['default'] !== undefined) {
1611
+ const defaultValue =
1612
+ typeof prop['default'] === 'boolean'
1613
+ ? prop['default']
1614
+ ? ts.factory.createTrue()
1615
+ : ts.factory.createFalse()
1616
+ : ts.factory.createFalse();
1617
+ booleanSchema = ts.factory.createCallExpression(
1618
+ ts.factory.createPropertyAccessExpression(booleanSchema, ts.factory.createIdentifier('default')),
1619
+ undefined,
1620
+ [defaultValue],
1621
+ );
1622
+ }
1623
+
1624
+ return required
1625
+ ? booleanSchema
1626
+ : ts.factory.createCallExpression(
1627
+ ts.factory.createPropertyAccessExpression(booleanSchema, ts.factory.createIdentifier('optional')),
1628
+ undefined,
1629
+ [],
1630
+ );
1631
+ }
548
1632
  case 'unknown':
549
1633
  default:
550
1634
  return this.buildZodAST(['unknown', ...(!required ? ['optional'] : [])]);
551
1635
  }
552
1636
  }
553
1637
 
1638
+ private buildDefaultValue(value: unknown): ts.Expression {
1639
+ if (typeof value === 'string') {
1640
+ return ts.factory.createStringLiteral(value, true);
1641
+ }
1642
+ if (typeof value === 'number') {
1643
+ return ts.factory.createNumericLiteral(String(value));
1644
+ }
1645
+ if (typeof value === 'boolean') {
1646
+ return value ? ts.factory.createTrue() : ts.factory.createFalse();
1647
+ }
1648
+ if (value === null) {
1649
+ return ts.factory.createNull();
1650
+ }
1651
+ if (Array.isArray(value)) {
1652
+ return ts.factory.createArrayLiteralExpression(
1653
+ value.map((item) => this.buildDefaultValue(item)),
1654
+ false,
1655
+ );
1656
+ }
1657
+ if (typeof value === 'object') {
1658
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1659
+ if (value === null) {
1660
+ return ts.factory.createNull();
1661
+ }
1662
+ return ts.factory.createObjectLiteralExpression(
1663
+ Object.entries(value).map(([key, val]) =>
1664
+ ts.factory.createPropertyAssignment(ts.factory.createIdentifier(key), this.buildDefaultValue(val)),
1665
+ ),
1666
+ true,
1667
+ );
1668
+ }
1669
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
1670
+ return ts.factory.createStringLiteral(String(value), true);
1671
+ }
1672
+ // For objects and arrays, we need to handle them differently
1673
+ // This should not happen in practice, but we handle it for type safety
1674
+ return ts.factory.createStringLiteral(JSON.stringify(value), true);
1675
+ }
1676
+
554
1677
  private handleLogicalOperator(
555
1678
  operator: 'anyOf' | 'oneOf' | 'allOf' | 'not',
556
1679
  schemas: unknown[],
@@ -641,6 +1764,7 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
641
1764
 
642
1765
  private buildSchemaFromLogicalOperator(schema: unknown): ts.CallExpression | ts.Identifier {
643
1766
  if (this.isReference(schema)) {
1767
+ // In logical operators, references are always required (they're part of a union/intersection)
644
1768
  return this.buildFromReference(schema);
645
1769
  }
646
1770
 
@@ -721,7 +1845,7 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
721
1845
 
722
1846
  private isReference(reference: unknown): reference is ReferenceType {
723
1847
  if (typeof reference === 'object' && reference !== null && '$ref' in reference) {
724
- const ref = reference as {$ref?: unknown};
1848
+ const ref = reference satisfies {$ref?: unknown};
725
1849
  return typeof ref.$ref === 'string' && ref.$ref.length > 0;
726
1850
  }
727
1851
  return false;