fork of hey-api/openapi-ts because I need some additional things

Add visited set to prevent infinite recursion in $ref following

Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com>

+63 -1
+8
packages/openapi-ts-tests/main/test/3.1.x.test.ts
··· 649 649 }, 650 650 { 651 651 config: createConfig({ 652 + input: 'transforms-read-write-nested.yaml', 653 + output: 'transforms-read-write-nested', 654 + plugins: ['@hey-api/typescript'], 655 + }), 656 + description: 'handles write-only types in nested schemas', 657 + }, 658 + { 659 + config: createConfig({ 652 660 input: 'ref-type.json', 653 661 output: 'ref-type', 654 662 }),
+3
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write-nested/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type * from './types.gen';
+35
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transforms-read-write-nested/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type ClientOptions = { 4 + baseUrl: `${string}://${string}` | (string & {}); 5 + }; 6 + 7 + export type CreateItemRequest = { 8 + payload: PayloadWritable; 9 + }; 10 + 11 + export type Payload = { 12 + kind: 'jpeg'; 13 + }; 14 + 15 + export type PayloadWritable = { 16 + kind: 'jpeg'; 17 + /** 18 + * Data required on write 19 + */ 20 + encoded: string; 21 + }; 22 + 23 + export type ItemCreateData = { 24 + body: CreateItemRequest; 25 + path?: never; 26 + query?: never; 27 + url: '/items'; 28 + }; 29 + 30 + export type ItemCreateResponses = { 31 + /** 32 + * Created 33 + */ 34 + 201: unknown; 35 + };
+17 -1
packages/openapi-ts/src/openApi/shared/transforms/readWrite.ts
··· 462 462 inSchema: boolean; 463 463 node: unknown; 464 464 path: ReadonlyArray<string | number>; 465 + visited?: Set<string>; 465 466 }; 466 467 467 468 /** ··· 489 490 inSchema, 490 491 node, 491 492 path, 493 + visited = new Set(), 492 494 }: WalkArgs): void => { 493 495 if (node instanceof Array) { 494 496 node.forEach((item, index) => ··· 498 500 inSchema, 499 501 node: item, 500 502 path: [...path, index], 503 + visited, 501 504 }), 502 505 ); 503 506 } else if (node && typeof node === 'object') { ··· 533 536 inSchema: false, 534 537 node: (node as Record<string, unknown>)[key], 535 538 path: [...path, key], 539 + visited, 536 540 }); 537 541 } 538 542 return; ··· 553 557 inSchema: false, 554 558 node: value, 555 559 path: [...path, key], 560 + visited, 556 561 }); 557 562 continue; 558 563 } ··· 563 568 inSchema: false, 564 569 node: value, 565 570 path: [...path, key], 571 + visited, 566 572 }); 567 573 continue; 568 574 } ··· 575 581 inSchema: true, 576 582 node: param.schema, 577 583 path: [...path, key, index, 'schema'], 584 + visited, 578 585 }); 579 586 } 580 587 // Also handle content (OpenAPI 3.x) ··· 585 592 inSchema: false, 586 593 node: param.content, 587 594 path: [...path, key, index, 'content'], 595 + visited, 588 596 }); 589 597 } 590 598 }); ··· 606 614 inSchema: false, 607 615 node: (value as Record<string, unknown>)[headerKey], 608 616 path: [...path, key, headerKey], 617 + visited, 609 618 }); 610 619 } 611 620 continue; ··· 620 629 inSchema: true, 621 630 node: value, 622 631 path: [...path, key], 632 + visited, 623 633 }); 624 634 } else if (key === '$ref' && typeof value === 'string') { 625 635 // Prefer exact match first ··· 633 643 } else if ( 634 644 inSchema && 635 645 nextContext && 636 - value.startsWith(schemasPointerNamespace) 646 + value.startsWith(schemasPointerNamespace) && 647 + !visited.has(value) 637 648 ) { 638 649 // If we're in a schema with a defined context (read/write), follow the $ref 639 650 // to update nested $refs within the referenced schema 651 + // Use visited set to avoid infinite recursion on circular references 640 652 const schemasObj = getSchemasObject(spec); 641 653 if (schemasObj) { 642 654 const schemaName = value.substring( ··· 644 656 ); 645 657 const referencedSchema = schemasObj[schemaName]; 646 658 if (referencedSchema) { 659 + const newVisited = new Set(visited); 660 + newVisited.add(value); 647 661 walk({ 648 662 context: nextContext, 649 663 currentPointer: value, 650 664 inSchema: true, 651 665 node: referencedSchema, 652 666 path: value.split('/').filter(Boolean), 667 + visited: newVisited, 653 668 }); 654 669 } 655 670 } ··· 661 676 inSchema, 662 677 node: value, 663 678 path: [...path, key], 679 + visited, 664 680 }); 665 681 } 666 682 }