typescriptts-morph

Type generation tool is not correctly handling nullable fields in TypeScript


I'm building a code generation tool that takes in an input file of my database schema as an interface and generates smaller types from that.

Input

export interface Database {
  public: {
    Tables: {
      profiles: {
        Row: {
          first_name: string | null;
          id: string;
          last_name: string | null;
        };
      };
    };
  };
}

Expected Output

export type Profile = {
  first_name: string | null;
  id: string;
  last_name: string | null;
};

Actual Output

export type Profile = {
  first_name: string;
  id: string;
  last_name: string;
};

I'm having trouble capturing the null parts of my output when generating my types.

What I've currently got

const project = new Project({
  compilerOptions: {
    allowSyntheticDefaultImports: true,
    esModuleInterop: true,
    module: ModuleKind.ESNext,
    target: ScriptTarget.ESNext,
  },
});

const sourceFile = project.addSourceFileAtPath(typesPath);

// Find the 'Tables' type alias
const databaseInterface = sourceFile.getInterfaceOrThrow('Database');
const publicProperty = databaseInterface.getPropertyOrThrow('public');
const publicType = publicProperty.getType();

const tablesProperty = publicType
  .getApparentProperties()
  .find((property) => property.getName() === 'Tables');

const tablesType = project
  .getProgram()
  .getTypeChecker()
  .getTypeAtLocation(tablesProperty.getValueDeclarationOrThrow());
const tablesProperties = tablesType.getProperties();

const types: string[] = [];

for (const table of tablesProperties) {
  const tableName = table.getName();
  types.push(...generateTypes(table, tableName));
}

...


export function generateTypes(table: Symbol, tableName: string): string[] {
  // Get the table type
  const tableType = table.getTypeAtLocation(table.getValueDeclarationOrThrow());

  // Find the 'Row' property within the table type
  const rowProperty = tableType.getProperty('Row');

  // Get the type of the 'Row' property
  const rowType = rowProperty.getTypeAtLocation(
    rowProperty.getValueDeclarationOrThrow()
  );

  const rowTypeString = rowType.getText();

  const types: string[] = [];

  types.push(
    `export type ${toTypeName(tableName)} = ${rowTypeString};`,
    ...
  );

  return types;
}

I've tried a lot of variations regarding what I've posted above but every single time I run my generate function I can't get it to print | null for those scenarios where properties could be null.

Using ts-morph v18.


Solution

  • Turn on the strictNullChecks compiler option:

    const project = new Project({
      compilerOptions: {
        allowSyntheticDefaultImports: true,
        esModuleInterop: true,
        module: ModuleKind.ESNext,
        target: ScriptTarget.ESNext,
        strictNullChecks: true, // <-- this
      },
    });
    
    

    Otherwise the type checker ignores undefined/null in union types and thinks all types are not nullable (ts-morph uses the same defaults found in the typescript compiler).