sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.77k stars 152 forks source link

Maximum call stack size exceeded. #750

Closed liyofx closed 7 months ago

liyofx commented 7 months ago
import { describe , test, expect} from "bun:test";
import { Type } from "@sinclair/typebox";
import { TypeCompiler } from '@sinclair/typebox/compiler'

const bookFindMany = () => Type.Object({
  select: Type.Optional(BookSelectObjectSchema()),
});

const UserSelectObjectSchema = () => Type.Object(
  {
    id: Type.Optional(Type.Boolean()),
    email: Type.Optional(Type.Boolean()),
    name: Type.Optional(Type.Boolean()),
    books: Type.Optional(
      Type.Union([Type.Optional(Type.Boolean()), Type.Optional(bookFindMany())]),
    ),
  },
  { additionalProperties: false,},
);

const UserArgsObjectSchema = () => Type.Object(
  {
    select: Type.Optional(UserSelectObjectSchema()),
  },
  { additionalProperties: false },
);
const BookSelectObjectSchema = () => Type.Object(
  {
    id: Type.Optional(Type.Boolean()),
    title: Type.Optional(Type.Boolean()),
    author: Type.Optional(
      Type.Union([Type.Optional(Type.Boolean()), Type.Optional(UserArgsObjectSchema())]),
    ),
    authorId: Type.Optional(Type.Boolean()),
  },
  { additionalProperties: false },
);

const userFindMany = () => Type.Object({
  select: Type.Optional(UserSelectObjectSchema()),
});

describe('', ()=>{
  test('', async ()=> {
    var select = {select: {id: true, name: true, email: true}};
    var typeCheck = TypeCompiler.Compile(userFindMany());

    var isVaild = typeCheck.Check(select);
    console.log(typeCheck.Errors(select).First());

    expect(isVaild).toBeTrue()
  })
});
# bun test test/generator1.test.ts
bun test v1.0.25 (a8ff7be6)

test/generator1.test.ts:
11 | function RegExpType(value) {
12 |     return new RegExp(value.source, value.flags);
13 | }
14 | function ObjectType(value) {
15 |     const clonedProperties = Object.getOwnPropertyNames(value).reduce((acc, key) => ({ ...acc, [key]: Visit(value[key]) }), {});
16 |     const clonedSymbols = Object.getOwnPropertySymbols(value).reduce((acc, key) => ({ ...acc, [key]: Visit(value[key]) }), {});
                                      ^
RangeError: Maximum call stack size exceeded.
      at /Users/z/Documents/web/prisma-zod-generator/node_modules/@sinclair/typebox/build/import/type/clone/value.mjs:16:34
      at reduce (:1:21)
      at ObjectType (/Users/z/Documents/web/prisma-zod-generator/node_modules/@sinclair/typebox/build/import/type/clone/value.mjs:15:30)
      at CloneType (/Users/z/Documents/web/prisma-zod-generator/node_modules/@sinclair/typebox/build/import/type/clone/type.mjs:8:17)
      at AddOptional (/Users/z/Documents/web/prisma-zod-generator/node_modules/@sinclair/typebox/build/import/type/optional/optional.mjs:10:17)
      at BookSelectObjectSchema (/Users/z/Documents/web/prisma-zod-generator/test/generator1.test.ts:31:9)
      at bookFindMany (/Users/z/Documents/web/prisma-zod-generator/test/generator1.test.ts:7:25)
      at UserSelectObjectSchema (/Users/z/Documents/web/prisma-zod-generator/test/generator1.test.ts:16:64)
      at UserArgsObjectSchema (/Users/z/Documents/web/prisma-zod-generator/test/generator1.test.ts:24:27)
      at BookSelectObjectSchema (/Users/z/Documents/web/prisma-zod-generator/test/generator1.test.ts:34:64)
✗ test [33.92ms]

 0 pass
 1 fail
Ran 1 tests across 1 files. [75.00ms]
sinclairzx81 commented 7 months ago

@liyofx Hiya,

TypeBox does not support cyclic types. The issue here is that these type functions are cyclically initializing each other. You can reproduce the issue by calling the userFindMany() function only.

userFindMany() // results in a cyclic type

// userFindMany -> UserSelectObjectSchema -> 
//   bookFindMany -> BookSelectObjectSchema -> UserArgsObjectSchema -> UserSelectObjectSchema -> 
//   bookFindMany -> BookSelectObjectSchema -> UserArgsObjectSchema -> UserSelectObjectSchema -> 
//   bookFindMany -> BookSelectObjectSchema -> UserArgsObjectSchema -> UserSelectObjectSchema -> 
//   bookFindMany -> ...infinite

You can somewhat approach cyclic types using Type.Ref however cyclic type inference is not well supported in TypeBox (due to difficulties getting TypeScript to handle infinite instantiations). Additionally, cyclic, self referential values are non-serializable in JSON.

The recommendation for handling these types would be use either use Type.Ref or just explicitly implement a custom type and register via the TypeRegistry. Another approach may be to use a generic recursive type, but would likely require refactoring (or restructuring the type to support)

Will close off this issue as this functionality is currently not supported. All the best S

liyofx commented 7 months ago

@sinclairzx81 I tried Type.Ref, but it still didn't work. I hope you can give me a simple example. Thank you.