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

TypeScriptToModel fails with type that contains a self reference #32

Open
jeffrey-peterson-vanna opened this issue Mar 6, 2024 · 3 comments

Comments

@jeffrey-peterson-vanna
Copy link

I'm working with TS files that contain references to types defined in the same interface. TypeScriptToModel seems to be using Recursive types for this scenario but fails when the nesting is more than one level deep.

Example:

import * as Codegen from '@sinclair/typebox-codegen';

const Code = `
  interface foo {
    bar: {
      ref: foo['bar']['baz'];
      baz: {};
    };
  }
`;

const boom = Codegen.TypeScriptToModel.Generate(Code);

TypeBox Workbench link

@jpoehnelt
Copy link

Running into this with Supabase generated types of the structure:

type Database = {
  public: {
    Tables: {
      foo: {
        Row: {
          bar: Database["public"]["Enums"]["Bar"];
        };
      };
    };
    Enums: {
      Bar: "baz" | "quz";
    };
  };
};

@florence-wolfe
Copy link
Contributor

So I did a decent amount of investigation here and it's a fairly challenging problem to solve. I took a bunch of stabs at modifying the generated code to make lazily evaluate with getters or closures with no success.

I ended up trying to modify the typebox library directly instead. Only the Type.Index method relies on references, and self-references so this was a good place to focus. I made the Type.Index accept a callback function so that it could be evaluated lazily and then modify the generated code in typebox-codegen in my local forks. This worked and compiled successfully, however, the result of the output for the recursive/self-referential types resolves those references with an empty object.

This is still a great improvement over the previous code as it doesn't throw, but it's still not generating all of the final model's types correctly.

I think the next steps are to keep a reference tree in the generator of all self-references and iterate over them continuously until the desired reference is available, while avoiding circular references.

@sinclairzx81 Do you have any thoughts on this? I'm not even sure if this is the correct approach since this would require modifications to both the typebox and codegen libs. Seems wrong 😓

@sinclairzx81
Copy link
Owner

sinclairzx81 commented Apr 14, 2024

@florence-wolfe Hi,

Unfortunately, there's no simple solution to this. With the exception of Type.Recursive, TypeBox self-referential types are generally not well supported with mapping types (like Index), and lazy types are generally considered to be out-of scope (as it primarily focuses on constructing immediate schematics).

The only way to really generate something like the proposed Database type would be to hoist the interior index type into it's own variable. The following layout should be supported with current library transforms, but the complexity to hoist the type may be a bit too prohibitively complex for this code generator.

Reference

import { Type } from '@sinclair/typebox'

// hoist the Bar out into it's own type
const ____Bar = Type.Union([
    Type.Literal('baz'),
    Type.Literal('bar')
])

const Database = Type.Object({
    public: Type.Object({
        Tables: Type.Object({
            foo: Type.Object({
                Row: Type.Object({
                    bar: ____Bar // js reference here
                })
            })
        }),
        Enums: Type.Object({
            Bar: ____Bar  // js reference here
        })
    })
})

There may be cases where the above may not work (specifically mutual recursive cases) but would be certainly be happy to review a PR if you wanted to try detect for self referential types. I would aim for the above layout as a possible starting point.

Hope this helps!
S

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

No branches or pull requests

4 participants