Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v7 Preview 🎉 #1368

Open
drwpow opened this issue Oct 6, 2023 · 51 comments · Fixed by #1669
Open

v7 Preview 🎉 #1368

drwpow opened this issue Oct 6, 2023 · 51 comments · Fixed by #1669

Comments

@drwpow
Copy link
Owner

drwpow commented Oct 6, 2023

v7 Preview

openapi-typescript@7 is now in preview! 🎉 The major additions to this library are:

  • ✨ Built-in validation using the Redoc CLI — no more needing an external tool
  • ✨ Respecting redocly.yaml for API config (define your schemas there in one place, and use openapi-typescript to generate TypeScript without having to manage a separate config)
  • ✨ Addition of the --enum flag for generating true TypeScript enums instead of unions (default behavior still unions)
  • 📘 A mini-rewrite to use the TypeScript AST instead of string mashing

Full Changelog

Features

  • Feature: automatically validate schemas with Redocly CLI (docs). No more need for external tools to report errors! 🎉

    • By default, it will only throw on actual schema errors (uses Redocly’s default settings)
    • For stricter linting or custom rules, you can create a redocly.yaml config
  • Feature: bundle schemas with Redocly CLI

  • Feature: add enum option to export top-level enums from schemas ([Feature request] Generate Enums AND Union types #941)

  • Feature: add formatOptions to allow formatting TS output

  • Feature: header responses add [key: string]: unknown index type to allow for additional untyped headers

  • Feature: allow configuration of schemas via apis key in redocly.config.yaml. See docs for more info.

Breaking Changes

  • ⚠️ Breaking: The Node.js API now returns the TypeScript AST for the main method as well as transform() and postTransform(). To migrate, you’ll have to use the typescript compiler API:

    + import ts from "typescript";
    
    + const DATE = ts.factory.createIdentifier("Date");
    + const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
    
      const ast = await openapiTS(mySchema, {
        transform(schemaObject, metadata) {
        if (schemaObject.format === "date-time") {
    -       return schemaObject.nullable ? "Date | null" : "Date";
    +       return schemaObject.nullable
    +         ? ts.factory.createUnionTypeNode([DATE, NULL])
    +         : DATE;
          }
        },
      };

    Though it’s more verbose, it’s also more powerful, as now you have access to additional properties of the generated code you didn’t before (such as injecting comments).

    For example syntax, search this codebae to see how the TypeScript AST is used.

    Also see AST Explorer’s typescript parser to inspect how TypeScript is interpreted as an AST.

  • ⚠️ Breaking: Changing of several CLI flags and Node.js API options

    • The --auth, --httpHeaders, --httpMethod, and fetch (Node.js-only) options were all removed from the CLI and Node.js API
    • You can also set your fetch client in redocly.yaml as well.
    • --immutable-types has been renamed to --immutable
    • --support-array-length has been renamed to --array-length
  • ⚠️ Breaking: Most optional objects are now always present in types, just typed as :never. This includes keys of the Components Object as well as HTTP methods.

  • ⚠️ Breaking: No more external export in schemas anymore. Everything gets flattened into the components object instead (if referencing a schema object from a remote partial, note it may have had a minor name change to avoid conflict).

  • ⚠️ Breaking defaultNonNullable option now defaults to true. You’ll now need to manually set false to return to old behavior.

  • ⚠️ Breaking: Remove globbing schemas in favor of redocly.yaml config. Specify multiple schemas with outputs in there instead. See Multiple schemas for more info.

And dozens of small performance improvements and quality fixes.

Give it a try today with the @next flag:

npm i openapi-typescript@next

Testers wanted 🙏

Will pin this issue for a while while people kick the tires and report bugs while a release candidate is developed. For many people, it should be a drop-in replacement; for others, it will only require a few minor changes.

If people could report any errors/issues they face while trying this, that’d be much appreciated. Any issues posted here or as separate issues will help patch holes in the docs site.

@drwpow drwpow pinned this issue Oct 6, 2023
@drwpow drwpow changed the title v7 Preview v7 Preview 🎉 Oct 6, 2023
@luchsamapparat
Copy link
Contributor

Great to hear that openapi-typescript will support external refs! Thanks a lot for that :)

I did quick test with our project and ran into a typing issue.

When extending a model using allOf...

    PurchasableProduct:
      type: object
      properties:
        price:
          $ref: '#/components/schemas/Price'
      required:
        - price
      allOf:
        - $ref: '#/components/schemas/Product'

... the generated code does not compile:

image

Our backend colleagues use the same OpenAPI file using the openapi-generator kotlin-spring plugin, so it seems to a be valid spec.

Here's an example repo to reproduce the issue:

https://github.com/luchsamapparat/openapi-typescript-7-bug

@drwpow
Copy link
Owner Author

drwpow commented Oct 6, 2023

@luchsamapparat the generated code seems to be working correctly; the error seems to be coming from the fact your schema specifies price as a required property but no such property exists. Does removing that fix the error?

@luchsamapparat
Copy link
Contributor

If you have another look at my example, the price property is not only defined as required, but also as a property directly below the properties: line.

You can copy and paste the full example OpenAPI YAML from my repo into the Swagger Editor which parses it correctly:

image

@ElForastero
Copy link
Contributor

ElForastero commented Oct 7, 2023

Would be nice to support x-enum-varnames property.

UPD:
Well, I think I did it. Check the PR #1374

@drwpow drwpow mentioned this issue Oct 8, 2023
3 tasks
@luchsamapparat
Copy link
Contributor

Thanks to @joostme who pointed out that there's a workaround for my problem:

image

luchsamapparat/openapi-typescript-7-bug@638870c

@drwpow drwpow mentioned this issue Oct 9, 2023
3 tasks
@stefanprobst
Copy link

running with --immutable flag seems to generate invalid ts: export readonly interface paths {}

see:

npx openapi-typescript@next https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml --immutable | head

error:

'readonly' modifier can only appear on a property declaration or index signature.ts(1024)

@drwpow
Copy link
Owner Author

drwpow commented Oct 19, 2023

@stefanprobst good catch 🙏! Will add some immutable tests to make sure the types are valid.

Sidenote: I did learn through the v7 rewrite that TypeScript’s AST does let you generate invalid TS without stopping you. Which is fun 🙃

@maslennikov
Copy link

Upgraded to v7 without issues. Generated file looks neat and tidy - especially when using external file refs. Thank you for this awesome job!

@maslennikov
Copy link

@drwpow may it be the case that there were some changes in handling of the | undefined portion of additionalProperties typings? I think, I am noticing now something of it.

Related issues: #1018, #1267

Input:

    CityLinks:
      type: object
      required: [socials]
      properties:
        socials:
          type: object
          additionalProperties:
            type: string

Output

         CityLinks: {
            socials: {
                // There was no `| undefined` in later v6 versions
                [key: string]: string | undefined;
            };
        };

@tonyxiao
Copy link

tonyxiao commented Nov 3, 2023

Why is it that we have a ton of never now in v7 whereas they did not exist in v6? it makes the generated types a fair bit more verbose to read.

CleanShot 2023-11-02 at 22 08 55@2x

@drwpow
Copy link
Owner Author

drwpow commented Nov 3, 2023

Why is it that we have a ton of never now in v7 whereas they did not exist in v6?

Great question! That actually has to do with how TS handles discriminated unions within other tools like openapi-fetch. For example, In v6, comparing the response objects between 2 or more endpoints would yield a very complex union because each would have different combinations of request methods—often each would be a completely different permutation of keys. And to TS, comparing objects with different keys are “apples to oranges” even if we don’t intend for them to be.

But when we add never in v7 and TS sees every request object has the same keys, it now knows it’s comparing “apples to apples” and can make better inference decisions (example). And as a result, writing type utilities becomes easier and safer to do (some edge cases were fixed in openapi-fetch by this change).

So it not only helps TS do its job better, and makes more advanced utilities built off openapi-typescript better, but I think the never additions are a more “correct” type in general—they make it clear where there could be an addition to your schema according to the spec, but it has been decidedly opted out of (or is not used yet).

@tonyxiao
Copy link

tonyxiao commented Nov 3, 2023

Gotcha, this is super helpful to know, thanks for the link to the reference article too! does this result also in a speed up in the auto complete type hints as a result?

@WickyNilliams
Copy link
Contributor

WickyNilliams commented Nov 6, 2023

hey i think i have found an issue with v7. i've dug into it and i have found the source of the issue...

a number of my endpoints can return a 204 status, which has no content. for context, my API is following the JSON:API standard: https://jsonapi.org/format/#crud-creating-responses-204.

so the generated types look like this:

responses: {
  201: {
    headers: {
      [name: string]: unknown;
    };
    content: {
      // ...
    };
  };
  204: {
    headers: {
      [name: string]: unknown;
    };
    content?: never; // <-- this is the source of the issue
  };
  // ...
};

the potentially undefined content for 204 trips up the SuccessResponse type utility, causing it to resolve to never.

Here's a typescript playground which contains an abridged version of my generated schema and some code at the bottom to demonstrate the issue. if you remove the ? from the 204 content you will see that the you get the correct type. I am guessing a NonNullable somewhere in the type utilities will fix this

I do not have this issue with v6.7.0

Edit: for completeness, the 204 response looks like this when generated with 6.7.0. here it is not potentially undefined:

204: {
  content: never;
};

@WickyNilliams
Copy link
Contributor

Another issue sorry!

In v7, if a request body parameter has a default value, it is treated as required/non-optional in the generated types.

requestBody:
  content:
    application/vnd.api+json:
      schema:
        required:
          - data
        properties:
          data:
            type: object
            required:
              - type
            additionalProperties: false
            properties:
              type:
                type: string
              id:
                type: string
              attributes:
                type: object
                properties:
                  first_name:
                    type: string
                    maxLength: 100
                  last_name:
                    type: string
                    maxLength: 100
                  email:
                    type: string
                    format: email
                    maxLength: 100
                  language:
                    type: string
                    nullable: true
                    default: en
                    maxLength: 20
                required:
                  - email
                  - initials

this will generate a type like:

requestBody?: {
  content: {
      "application/vnd.api+json": {
          data: {
              type: string;
              id?: string;
              attributes?: {
                  first_name?: string;
                  last_name?: string;
                  /** Format: email */
                  email: string;
                  /** @default en */
                  language: string | null; // <-- ❌ not optional?
              };
          };
      };
  };
};

i believe this is incorrect, given the openapi spec on default (emphasis mine):

Use the default keyword in the parameter schema to specify the default value for an optional parameter. The default value is the one that the server uses if the client does not supply the parameter value in the request

in v6, the generated type is as expected:

requestBody?: {
  content: {
    "application/vnd.api+json": {
      data: {
        type: string;
        id?: string;
        attributes?: {
          first_name?: string;
          last_name?: string;
          /** Format: email */
          email: string;
          /** @default en */
          language?: string | null;
        };
      };
    };
  };
};

here's a stackblitz demonstrating the issue. it will output to schema.d.ts when it starts up https://stackblitz.com/edit/stackblitz-starters-dh6zug

@drwpow
Copy link
Owner Author

drwpow commented Nov 6, 2023

@WickyNilliams thanks for flagging! I will investigate that specifically. There are other breaking changes I’m tracking that will require a breaking version change for openapi-fetch (but fortunately the breaking change is less code since 7.x makes inference simpler/safer). Right now it’s known openapi-fetch only works with 6.x, and an upcoming breaking version of openapi-fetch will be released for 7.x (as a fast follow)

@drwpow
Copy link
Owner Author

drwpow commented Nov 6, 2023

@WickyNilliams also for the default issue, that seems like a bug. 7.x enables --default-non-nullable by default so it’s intended to have that behavior. But is just missing some relevant tests it seems.

@WickyNilliams
Copy link
Contributor

Right now it’s known openapi-fetch only works with 6.x,

is this mentioned in the docs? i looked in multiple before trying out v7, and since i didn't spot anything i assumed that they were compatible. might be worth sticking a big banner somewhere :D or perhaps list openapi-typescript as a peer dependency with a v6 range? but i guess peer deps can be their own can of worms

i have pinned to v6 in the meanwhile. glad to hear v7 enables some simplification of the fetcher!

7.x enables --default-non-nullable by default so it’s intended to have that behavior. But is just missing some relevant tests it seems.

ah i see that in the full changelog above now. the docs site itself still says it defaults to false by the way. is there some explanation somewhere for that change? in my (lacking-in-context and likely uninformed) opinion anything that diverges from what the schema explicitly defines should be opt-in. so i'd be curious on the background.

@drwpow
Copy link
Owner Author

drwpow commented Nov 6, 2023

might be worth sticking a big banner somewhere :D

A note on the docs would be a good idea! Will add that.

is there some explanation somewhere for that change?

Mostly from this issue, and if you search you’ll find other instances where it seemed like more users were expecting this behavior than not. And it seems like the spec suggests this should be the default behavior? Open to feedback if I’m misunderstanding something; I had just flagged this as a deviation from the specification (and with a spec deviation + multiple issues raised about the default behavior seems like a good hint to change it).

@WickyNilliams
Copy link
Contributor

in my reading i think there is a sutble difference between inputs and outputs. for outputs/responses, the set of non-optional fields should be the union of required and those with defaults. for inputs/requests, it is only those which are required.

reading the linked issue, i think that's what they're asking also. see the second bullet point (emphasis theirs):

But, when the consumer is writing the value can be provided or not so that the generated type should also be optional instead of required

i found this related issue #584 which i think is talking about default values in responses being required.

there's also this comment which says:

So if a default value is set on a request parameter, it cannot be interpreted as a required parameter.

@drwpow
Copy link
Owner Author

drwpow commented Nov 6, 2023

Ah thank you that is a really great distinction. Will also add that to the test suite. That may mean some nuances of --default-non-nullable may change (maybe; maybe not). But breaking versions are the opportunities to make these improvements and dig deeper on how everything works.

@WickyNilliams
Copy link
Contributor

For sure! Please post back here as and when something changes

@ogjonixyz
Copy link

I need to to create types from multiple remote sources but I keep getting TypeError [ERR_INVALID_FILE_URL_HOST]: File URL host must be "localhost" or empty on linux error. Tried with local files and still the same issue. This is the redocly.yaml :
image

@drwpow
Copy link
Owner Author

drwpow commented Nov 8, 2023

@ogjonixyz Thank you! Will track that as well. Appreciate all the testing and reporting issues while it’s still in beta.

@omgpiu
Copy link

omgpiu commented Nov 8, 2023

Hello. Got some issue.
In yaml we have refs with schema field. And lib try to parse schema as a valid key to get data, but should ignore it in response

responses:
    '200':
      description: Info about foo
      content:
        application/json:
          schema:
            $ref: '../../schemas/foo-api/foo
responses: {
                200: {
                    headers: {
                        [name: string]: unknown;
                    };
                    content: {
                        "application/json": {
                            content?: paths["/foo/{fooId}"]["get"]["responses"]["200"]["content"]["application/json"]["schema"][];
                        } & {
                            count?: number;
                        };
                    };
                };
}

@psychedelicious
Copy link
Contributor

I've encountered two issues with v7.

  1. I'm getting an unhandled exception on openapi-typescript@next and on main. v6 works.

The exception is coming from redocly whenever it calls its own function isMappingRef(). I suppose I'll need to take this up with redocly, but posting here in case the issue is obvious and/or related to openapi-typescript.

If I return document.parsed early on L120 of redoc.ts I get types!

Call stack
isMappingRef (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/ref-utils.js:69)
additionalProperties (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/types/oas3.js:367)
<anonymous> (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/types/index.js:65)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:225)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:243)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:243)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:243)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:243)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:243)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:243)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:243)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:243)
walk (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:243)
resolveRefsInParallel (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:194)
<anonymous> (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:185)
<anonymous> (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:8)
__awaiter (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:4)
resolveDocument (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/resolve.js:180)
<anonymous> (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/bundle.js:78)
<anonymous> (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/bundle.js:8)
__awaiter (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/bundle.js:4)
bundleDocument (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/bundle.js:55)
<anonymous> (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/bundle.js:42)
<anonymous> (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/bundle.js:8)
__awaiter (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/bundle.js:4)
bundle (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/@redocly/openapi-core/lib/bundle.js:33)
validateAndBundle (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/openapi-typescript/dist/lib/redoc.js:92)
process.processTicksAndRejections (internal/process/task_queues:95)
await (Unknown Source:0)
openapiTS (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/node_modules/openapi-typescript/dist/index.js:38)
await (Unknown Source:0)
main (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/scripts/typegen.js:11)
<anonymous> (/home/bat/Documents/Code/InvokeAI/invokeai/frontend/web/scripts/typegen.js:34)
run (internal/modules/esm/module_job:194)
await (Unknown Source:0)
<anonymous> (internal/modules/esm/loader:525)
processTicksAndRejections (internal/process/task_queues:95)
Promise.then (Unknown Source:0)
import (internal/modules/esm/loader:525)
<anonymous> (internal/modules/run_main:58)
loadESM (internal/process/esm_loader:91)
processTicksAndRejections (internal/process/task_queues:95)
await (Unknown Source:0)
runMainESM (internal/modules/run_main:55)
executeUserEntryPoint (internal/modules/run_main:78)
<anonymous> (internal/main/run_main_module:23)
Problematic part of the schema
      "patch": {
        "tags": [
          "models"
        ],
        "summary": "Update Model Record",
        "description": "Update model contents with a new config. If the model name or base fields are changed, then the model is renamed.",
        "operationId": "update_model_record",
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Unique key of model",
              "title": "Key"
            },
            "description": "Unique key of model"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/MainDiffusersConfig"
                      },
                      {
                        "$ref": "#/components/schemas/MainCheckpointConfig"
                      }
                    ],
                    "discriminator": {
                      "propertyName": "format",
                      "mapping": {
                        "diffusers": "#/components/schemas/MainDiffusersConfig",
                        "checkpoint": "#/components/schemas/MainCheckpointConfig"
                      }
                    }
                  },
                  {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/ONNXSD1Config"
                      },
                      {
                        "$ref": "#/components/schemas/ONNXSD2Config"
                      }
                    ],
                    "discriminator": {
                      "propertyName": "base",
                      "mapping": {
                        "sd-1": "#/components/schemas/ONNXSD1Config",
                        "sd-2": "#/components/schemas/ONNXSD2Config"
                      }
                    }
                  },
                  {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/VaeDiffusersConfig"
                      },
                      {
                        "$ref": "#/components/schemas/VaeCheckpointConfig"
                      }
                    ],
                    "discriminator": {
                      "propertyName": "format",
                      "mapping": {
                        "diffusers": "#/components/schemas/VaeDiffusersConfig",
                        "checkpoint": "#/components/schemas/VaeCheckpointConfig"
                      }
                    }
                  },
                  {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/ControlNetDiffusersConfig"
                      },
                      {
                        "$ref": "#/components/schemas/ControlNetCheckpointConfig"
                      }
                    ],
                    "discriminator": {
                      "propertyName": "format",
                      "mapping": {
                        "diffusers": "#/components/schemas/ControlNetDiffusersConfig",
                        "checkpoint": "#/components/schemas/ControlNetCheckpointConfig"
                      }
                    }
                  },
                  {
                    "$ref": "#/components/schemas/LoRAConfig"
                  },
                  {
                    "$ref": "#/components/schemas/TextualInversionConfig"
                  },
                  {
                    "$ref": "#/components/schemas/IPAdapterConfig"
                  },
                  {
                    "$ref": "#/components/schemas/CLIPVisionDiffusersConfig"
                  },
                  {
                    "$ref": "#/components/schemas/T2IConfig"
                  }
                ],
                "discriminator": {
                  "propertyName": "type",
                  "mapping": {
                    "main": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/MainDiffusersConfig"
                        },
                        {
                          "$ref": "#/components/schemas/MainCheckpointConfig"
                        }
                      ],
                      "discriminator": {
                        "propertyName": "format",
                        "mapping": {
                          "diffusers": "#/components/schemas/MainDiffusersConfig",
                          "checkpoint": "#/components/schemas/MainCheckpointConfig"
                        }
                      }
                    },
                    "onnx": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/ONNXSD1Config"
                        },
                        {
                          "$ref": "#/components/schemas/ONNXSD2Config"
                        }
                      ],
                      "discriminator": {
                        "propertyName": "base",
                        "mapping": {
                          "sd-1": "#/components/schemas/ONNXSD1Config",
                          "sd-2": "#/components/schemas/ONNXSD2Config"
                        }
                      }
                    },
                    "vae": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/VaeDiffusersConfig"
                        },
                        {
                          "$ref": "#/components/schemas/VaeCheckpointConfig"
                        }
                      ],
                      "discriminator": {
                        "propertyName": "format",
                        "mapping": {
                          "diffusers": "#/components/schemas/VaeDiffusersConfig",
                          "checkpoint": "#/components/schemas/VaeCheckpointConfig"
                        }
                      }
                    },
                    "controlnet": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/ControlNetDiffusersConfig"
                        },
                        {
                          "$ref": "#/components/schemas/ControlNetCheckpointConfig"
                        }
                      ],
                      "discriminator": {
                        "propertyName": "format",
                        "mapping": {
                          "diffusers": "#/components/schemas/ControlNetDiffusersConfig",
                          "checkpoint": "#/components/schemas/ControlNetCheckpointConfig"
                        }
                      }
                    },
                    "lora": "#/components/schemas/LoRAConfig",
                    "embedding": "#/components/schemas/TextualInversionConfig",
                    "ip_adapter": "#/components/schemas/IPAdapterConfig",
                    "clip_vision": "#/components/schemas/CLIPVisionDiffusersConfig",
                    "t2i_adapter": "#/components/schemas/T2IConfig"
                  }
                },
                "description": "Model config",
                "title": "Info"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The model was updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/MainDiffusersConfig"
                        },
                        {
                          "$ref": "#/components/schemas/MainCheckpointConfig"
                        }
                      ],
                      "discriminator": {
                        "propertyName": "format",
                        "mapping": {
                          "diffusers": "#/components/schemas/MainDiffusersConfig",
                          "checkpoint": "#/components/schemas/MainCheckpointConfig"
                        }
                      }
                    },
                    {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/ONNXSD1Config"
                        },
                        {
                          "$ref": "#/components/schemas/ONNXSD2Config"
                        }
                      ],
                      "discriminator": {
                        "propertyName": "base",
                        "mapping": {
                          "sd-1": "#/components/schemas/ONNXSD1Config",
                          "sd-2": "#/components/schemas/ONNXSD2Config"
                        }
                      }
                    },
                    {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/VaeDiffusersConfig"
                        },
                        {
                          "$ref": "#/components/schemas/VaeCheckpointConfig"
                        }
                      ],
                      "discriminator": {
                        "propertyName": "format",
                        "mapping": {
                          "diffusers": "#/components/schemas/VaeDiffusersConfig",
                          "checkpoint": "#/components/schemas/VaeCheckpointConfig"
                        }
                      }
                    },
                    {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/ControlNetDiffusersConfig"
                        },
                        {
                          "$ref": "#/components/schemas/ControlNetCheckpointConfig"
                        }
                      ],
                      "discriminator": {
                        "propertyName": "format",
                        "mapping": {
                          "diffusers": "#/components/schemas/ControlNetDiffusersConfig",
                          "checkpoint": "#/components/schemas/ControlNetCheckpointConfig"
                        }
                      }
                    },
                    {
                      "$ref": "#/components/schemas/LoRAConfig"
                    },
                    {
                      "$ref": "#/components/schemas/TextualInversionConfig"
                    },
                    {
                      "$ref": "#/components/schemas/IPAdapterConfig"
                    },
                    {
                      "$ref": "#/components/schemas/CLIPVisionDiffusersConfig"
                    },
                    {
                      "$ref": "#/components/schemas/T2IConfig"
                    }
                  ],
                  "title": "Response Update Model Record"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "404": {
            "description": "The model could not be found"
          },
          "409": {
            "description": "There is already a model corresponding to the new name"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
  1. With the first issue resolved (?) as described above, and defaultNonNullable: false, I have one unexpectedly different type. (there are other differences in the file but structurally the types appear to be equivalent)
// v6
type V6 = {
  ui_choice_labels: {
    [key: string]: string;
  } | null;
}

// v7
type V7 = {
  ui_choice_labels: {
    [key: string]: string | undefined; // union with undefined is unexpected
  } | null;
}

The relevant part of the schema is:

  "ui_choice_labels": {
    "anyOf": [
      {
        "additionalProperties": {
          "type": "string"
        },
        "type": "object"
      },
      {
        "type": "null"
      }
    ],
    "default": null,
    "title": "Ui Choice Labels"
  }

@davidleger95 davidleger95 unpinned this issue Nov 30, 2023
@psychedelicious
Copy link
Contributor

I noticed this was unpinned as I checked back for updates. Should outstanding issues reported in this thread be moved to their own issues?

@liangskyli
Copy link
Contributor

liangskyli commented Dec 13, 2023

openapi-typescript@7.0.0-next.5 bug:
require('openapi-typescript') cannot export the astToString method
for example: var { astToString } = require('openapi-typescript');

@MrLoh
Copy link

MrLoh commented Jan 10, 2024

openapi-typescript@7.0.0-next.5 bug: require('openapi-typescript') cannot export the astToString method for example: var { astToString } = require('openapi-typescript');

I'm running into the same issue, plus when running a script with ts-node, I get:

Could not find a declaration file for module 'openapi-typescript'. './node_modules/openapi-typescript/dist/index.cjs' implicitly has an 'any' type.

@drwpow
Copy link
Owner Author

drwpow commented Jan 19, 2024

I noticed this was unpinned as I checked back for updates. Should outstanding issues reported in this thread be moved to their own issues?

Ah it was unpinned by mistake, but hopefully can resume getting the final tests in and releasing an RFC sometime soon.

That said, new issues are probably preferred, only for the reason to keep the notifications down for the people that commented here but weren’t expecting to see bug reports 🙂

@rookie-luochao
Copy link

If you need a beautiful and easy-to-use openapi ui document, you can take a look at this openapi-ui

@FirstWhack
Copy link

FirstWhack commented Feb 7, 2024

There are some great features here, specifically --enum.

I can't get this working with Redocly in @7.0.0-next.5 when using the resolve headers for basic auth

# used for API type generation
extends:
  - minimal

resolve:
  http:
    headers:
      - matches: https://raw.githubusercontent.com/myorg/**
        name: Authorization
        value: "Basic MYTOKEN"

I stepped through the Redocly code and there are multiple resolvers that make requests for the same file, kind of weird, one (or more) resolver instances isn't passed the config so it makes a unauthorized request and bombs the build.

I am not creating an issue since I don't know how to isolate this to openapi-typescript's use of Redocly or Redocly itself, just thought I would note somewhere since I'm not finding any search results.

Aside from this, I really don't want validation from Redoc forced in the same command where I generate types. I'm not sure why these operations are being combined.

@psychedelicious
Copy link
Contributor

@FirstWhack

Aside from this, I really don't want validation from Redoc forced in the same command where I generate types. I'm not sure why these operations are being combined.

FWIW, I couldn't get v7 to work when I last tried due to a problem with Redocly. If I manually commented out some code in redocly itself, everything worked and I got types that were identical to v6. Well, nearly identical - it's not clear if my change to redocly was the reason the types changed or not.

@FirstWhack
Copy link

FirstWhack commented Feb 7, 2024

@FirstWhack

Aside from this, I really don't want validation from Redoc forced in the same command where I generate types. I'm not sure why these operations are being combined.

FWIW, I couldn't get v7 to work when I last tried due to a problem with Redocly. If I manually commented out some code in redocly itself, everything worked and I got types that were identical to v6. Well, nearly identical - it's not clear if my change to redocly was the reason the types changed or not.

Did you ever try just passing the contents as a string? I'm thinking of doing that. Redoc wants me to have a env var with an entire base64 encoded username/password prepared just to use Basic Auth. So I need to script before I call openapi-typescript to assemble such a thing from an actual token, inject as a env var. It's not very palatable even if it was working.

@psychedelicious
Copy link
Contributor

@FirstWhack

Did you ever try just passing the contents as a string? I'm thinking of doing that. Redoc wants me to have a env var with an entire base64 encoded username/password prepared just to use Basic Auth. So I need to script before I call openapi-typescript to assemble such a thing from an actual token, inject as a env var. It's not very palatable even if it was working.

I didn't, is this what you mean?

import fs from 'node:fs';

import openapiTS from 'openapi-typescript';

const schema = fs.readFileSync('openapi.json', 'utf8');

async function main() {
  const types = await openapiTS(schema);
  fs.writeFileSync('schema.ts', types);
}

main();

That gives me the same error as before from redocly: TypeError: mapping.startsWith is not a function

@FirstWhack
Copy link

FirstWhack commented Feb 8, 2024

That gives me the same error as before from redocly: TypeError: mapping.startsWith is not a function

Yep that's what I meant. For me the problem is just when retrieving the spec via URL... But if I don't retrieve it this way I don't think I can use any of the other redoc features? All I want from v7 is --enum, might look at a custom transformer for now.

@gduliscouet-ubitransport

Thank you for the great project @drwpow !
I am trying to migrate to the v7, I only noticed an issue with nullable enums.

When I run npx -y openapi-typescript@next test.json > test.ts on this spec:

{
    "openapi": "3.1.0",
    "info": {
      "title": "Minimal API with Nullable Enum",
      "version": "1.0.0"
    },
    "paths": {
      "/example": {
        "get": {
          "summary": "Get Example Value",
          "responses": {
            "200": {
              "description": "Successful response",
              "content": {
                "application/json": {
                  "schema": {
                    "type": "object",
                    "properties": {
                      "value": {
                        "type": "string",
                        "enum": ["option1", "option2", "option3"],
                        "nullable": true
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }

I get:
image

For me value should be nullable: value?: "option1" | "option2" | "option3" | null;

Is it a bug ?

@drwpow
Copy link
Owner Author

drwpow commented Feb 23, 2024

@gduliscouet-ubitransport yes that’s a bug!

@JeanRemiDelteil
Copy link
Contributor

JeanRemiDelteil commented Mar 28, 2024

Testing the 7.0.0-next.8, with the --immutable --enum CLI parameters, the enums are generated with a readonly modifiers:

export readonly enum OrderStatus { // TS1024: readonly modifier can only appear on a property declaration or index signature.
    placed = "placed",
    approved = "approved",
    delivered = "delivered"
}

But enum can not have the readonly modifier (as far as I know 😃 )

@drwpow
Copy link
Owner Author

drwpow commented Mar 31, 2024

@JeanRemiDelteil great catch! Would love a PR to fix this (no need to open a separate issue)

@JeanRemiDelteil
Copy link
Contributor

JeanRemiDelteil commented Apr 2, 2024

I will see what I can do with the time I have at hand ! (Ok, let's discuss more in the PR if there is the need to.)

@JeanRemiDelteil
Copy link
Contributor

Another issue with v7 compared to v6 seems to be the handling of the --immutable flag for arrays.

with v7 I got:

    readonly Pet: {
        readonly photoUrls: string[];
    }

where in v6 it gives:

    readonly Pet: {
        readonly photoUrls: readonly string[];
    }

Is it by design ?

@drwpow
Copy link
Owner Author

drwpow commented Apr 29, 2024

Another issue with v7 compared to v6 seems to be the handling of the --immutable flag for arrays.

with v7 I got:

    readonly Pet: {
        readonly photoUrls: string[];
    }

where in v6 it gives:

    readonly Pet: {
        readonly photoUrls: readonly string[];
    }

Is it by design ?

Nope! Not intentional. Fixing this bug now, and going through all the reported issues so far to work toward a stable release soon.

@drwpow drwpow mentioned this issue Apr 29, 2024
4 tasks
@toomuchdesign
Copy link
Contributor

Upgraded to v7 on a pretty complex project with several openAPI definition involved without a single type error 👍 Thanks for the great job!

@drwpow
Copy link
Owner Author

drwpow commented May 19, 2024

Thanks everyone for your patience! 7.x has a release candidate up at next! 🎉 With a stable release to follow soon!

This one took a little longer to bake than normal, but is also one of the biggest releases of this library when you factor in a complete AST rewrite, addition of schema validation via Redocly (along with a ton of functionality/features like $ref resolution that come from folding Redocly’s OpenAPI engine into the project), and more. There may be some minor lingering bugs if any, but they should be minor thanks to everyone’s feedback in this issue and others. For those that are ready to update, be sure to consult the migration guide.

Now the only thing blocking a 7.0.0 stable version is openapi-fetch’s breaking 0.10.0 version that will use the new generated types. The two will have to be released in lockstep, and unfortunately there wasn’t a way to have openapi-fetch be backwards compatible with both 6.x and 7.x simultaneously. But in good news, openapi-fetch will be more reliable (and faster) than ever (7.x is the first major release of openapi-typescript since openapi-fetch was released, and the improved type generation takes its existence into account).

So look forward to openapi-typescript 7.0.0 and openapi-fetch 0.10.0 soon!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.