openapi-ts / openapi-typescript

Generate TypeScript types from OpenAPI 3 specs
https://openapi-ts.dev
MIT License
5.98k stars 473 forks source link

Injecting imports for transformed types #1997

Open gresnick opened 2 weeks ago

gresnick commented 2 weeks ago

Description

This is more a basic question than a bug.

I am following the Node.js API docs with the example of replacing Date and DateTime. I would like to use a custom library (e.g. luxon) to provide these types.

My script

import ts from "typescript";
import openapiTS, { astToString } from "openapi-typescript";
import fs from "node:fs";

const DATE_TIME = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("DateTime"));
const DATE = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Date")); 
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());

const localPath = new URL("../../api/openapi.yaml", import.meta.url); 
const output = await openapiTS(localPath,{
    transform(schemaObject, metadata) {
     if (["date-time", "date"].includes(schemaObject.format)) {
        return schemaObject.nullable
          ? ts.factory.createUnionTypeNode([DATE_TIME, NULL])
          : DATE_TIME;
      }
    },
  });
  fs.writeFileSync("interfaces.d.ts", astToString(output))

The resulting interfaces.d.ts file has the types mapped correctly

    schemas: {
        Id: string;
        String: string;
        /** Format: date-time */
        DateTime: DateTime;
      Date: DateTime;
      ...
   }

however DateTime is unresolved.

How do I inject import { DateTime } from 'luxon'; into interfaces.d.ts ?

duncanbeevers commented 1 week ago

There is an inject option for prefixing arbitrary text.

However, in our case we use the same tooling to generate types from different OpenAPI schemas, and don't know beforehand whether a given format is actually used in a schema, so a static inject isn't sufficient.

Instead, we track the usage out-of-band, and interpolate the openapi-typescript generated typesOutput string into the final output, and import the dependencies only when necessary.

// Track whether the import is necessary
let hasObjectId = false;

const typesOutput = await openapiTS(localPath, {
  // ...other options
  transform: (schema) => {
    if ('format' in schemaObject && schemaObject.format === 'mongo-objectid') {
      // This side-effect signals that the import is necessary
      hasObjectId = true;

      return ts.factory.createTypeReferenceNode('ObjectId');
    }
  }
});

const output = `${
  // include imports if necessary
  hasObjectId ?
    "import { ObjectId } from 'mongodb';\n\n" :
    ''
  }${
    // stringify the openapi-typescript nodes
    astToString(typesOutput)
  }`;

fs.writeFileSync("interfaces.d.ts", output);