vega / ts-json-schema-generator

Generate JSON schema from your Typescript sources
MIT License
1.38k stars 189 forks source link

Invalid index in type #1974

Open navalex opened 1 month ago

navalex commented 1 month ago

Hello,

I'm trying to use your module to progamaticly generate JSON Schema from my defined types. Most of my custom types extends from packages types, such as Prisma. I don't have a lot of types, for here there are:

import { Prisma, Challenge } from "@prisma/client"
import { SessionRequest } from "supertokens-node/framework/fastify"
import { Certificate } from "tls"

export interface ChallengeWithStats extends Challenge {
    currentValue: number
}

export interface ExoCertificate extends Certificate {
    DC: string
}

interface RouteInterface {
    Params?: unknown
    Body?: unknown
}

export interface FastifyRequestSession<T extends RouteInterface> extends SessionRequest {
    body: T["Body"]
    params: T["Params"]
}

export interface UserWithRoles extends Prisma.UserGetPayload<{ include: { preferences: true } }>{
    roles: string[]
}

export interface SessionJsonData {
    sessionId: string
    start_date: Date
    end_date: Date
    steps: number
    distance: number
    verticalDuration: number
    walkDuration: number
    totalDuration: number
}

export interface SessionStatistics {
    total: {
        steps: number
        distance: number
        verticalDuration: number
        walkDuration: number
        totalDuration: number
    }
}

Note that all those types are located in ./src/types/**.ts, and all exported in ./src/types/index.ts.

Here is my script I'm trying to run to make my schema:

const fs = require("fs")

import("ts-json-schema-generator").then(async tsj => {
    const config = {
        path: "./src/types/index.ts",
        tsconfig: "./tsconfig.json",
        type: "*",
    }

    const outputPath = "./testSchema.json"

    const schema = tsj.createGenerator(config).createSchema(config.type)
    const schemaString = JSON.stringify(schema, null, 2)
    fs.writeFile(outputPath, schemaString, err => {
        if (err) throw err
    })
})

This file is located in ./src/generateSchema.js and i'm running the script like node ./src/schemaGenerator.js

And the output throw me this error:

file:///src/api/node_modules/ts-json-schema-generator/dist/src/NodeParser/IndexedAccessTypeNodeParser.js:60
                    throw new LogicError(`Invalid index "${type.getValue()}" in type "${objectType.getId()}"`);
                          ^

LogicError: Invalid index "exoskeleton" in type "indexed-type-1777104669-59854-59949-1777104669-59841-59950-1777104669-59839-60229-1777104669-59543-60230-1777104669-0-108983<structure-1777104669-16731-16733-1777104669-16717-16746-1777104669-16680-16747-1777104669-0-108983,structure-1777104669-16734-16737-1777104669-16717-16746-1777104669-16680-16747-1777104669-0-108983,structure-1777104669-16738-16741-1777104669-16717-16746-1777104669-16680-16747-1777104669-0-108983,structure-1777104669-16742-16745-1777104669-16717-16746-1777104669-16680-16747-1777104669-0-108983>"
    at file:///src/api/node_modules/ts-json-schema-generator/dist/src/NodeParser/IndexedAccessTypeNodeParser.js:60:27
    at Array.map (<anonymous>)
    at IndexedAccessTypeNodeParser.createType (file:///src/api/node_modules/ts-json-schema-generator/dist/src/NodeParser/IndexedAccessTypeNodeParser.js:47:42)
    at ChainNodeParser.createType (file:///src/api/node_modules/ts-json-schema-generator/dist/src/ChainNodeParser.js:27:54)
    at TypeReferenceNodeParser.createSubContext (file:///src/api/node_modules/ts-json-schema-generator/dist/src/NodeParser/TypeReferenceNodeParser.js:60:62)
    at TypeReferenceNodeParser.createType (file:///src/api/node_modules/ts-json-schema-generator/dist/src/NodeParser/TypeReferenceNodeParser.js:30:70)
    at ChainNodeParser.createType (file:///src/api/node_modules/ts-json-schema-generator/dist/src/ChainNodeParser.js:27:54)
    at AnnotatedNodeParser.createType (file:///src/api/node_modules/ts-json-schema-generator/dist/src/NodeParser/AnnotatedNodeParser.js:25:47)
    at file:///src/api/node_modules/ts-json-schema-generator/dist/src/NodeParser/TypeLiteralNodeParser.js:35:117
    at Array.map (<anonymous>) {
  msg: 'Invalid index "exoskeleton" in type "indexed-type-1777104669-59854-59949-1777104669-59841-59950-1777104669-59839-60229-1777104669-59543-60230-1777104669-0-108983<structure-1777104669-16731-16733-1777104669-16717-16746-1777104669-16680-16747-1777104669-0-108983,structure-1777104669-16734-16737-1777104669-16717-16746-1777104669-16680-16747-1777104669-0-108983,structure-1777104669-16738-16741-1777104669-16717-16746-1777104669-16680-16747-1777104669-0-108983,structure-1777104669-16742-16745-1777104669-16717-16746-1777104669-16680-16747-1777104669-0-108983>"'
}

Node.js v20.13.1

And to be honest, I have no clue about how to read and correct this at this point!

Do you guys have any clue on how to fix this ?

Thanks

domoritz commented 1 month ago

Thanks for the report. Can you provide a minimal reproducible example that demonstrates the issue?

navalex commented 1 month ago

Yeah sorry, here is a little project I made up to reproduce this:

package.json

{
  "name": "test",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@types/node": "^20.12.12",
    "prisma": "^5.14.0",
    "ts-json-schema-generator": "^2.2.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.4.5"
  },
  "dependencies": {
    "@prisma/client": "^5.14.0"
  }
}

prisma/schema.prisma

generator client {
    provider = "prisma-client-js"
}

datasource db {
    provider = "sqlite"
    url      = env("DATABASE_URL")
}

model User {
    id    Int     @id @default(autoincrement())
    email String  @unique
    name  String?
    posts Post[]
}

model Post {
    id        Int     @id @default(autoincrement())
    title     String
    content   String?
    published Boolean @default(false)
    author    User    @relation(fields: [authorId], references: [id])
    authorId  Int
}

types/index.ts

import { User } from '@prisma/client';

export interface UserTest extends User {
    newField: string;
}

schemaGenerator.js

const fs = require("fs")

import("ts-json-schema-generator").then(async tsj => {
    const config = {
        path: "./types/user.ts",
        tsconfig: "./tsconfig.json",
        type: "*",
        schemaId: "WdcTypes",
    }

    const outputPath = "./schemas"

    const schema = tsj.createGenerator(config).createSchema(config.type)
    const schemaString = JSON.stringify(schema, null, 2)
    fs.writeFile(outputPath, schemaString, err => {
        if (err) throw err
    })
})

This example + my personnal tests seems to show that any import from the @prisma/client seems to make the generator crash.

domoritz commented 1 month ago

That's not yet minimal if you can remove anything and the error state does not change. Oncer you identify the offending change, you can see how it's causing the issues you see.

navalex commented 1 month ago

Okay,

I edited my schema:

generator client {
    provider = "prisma-client-js"
}

datasource db {
    provider = "sqlite"
    url      = env("DATABASE_URL")
}

model User {
    name String @unique
}

That make all my files as minimal as possible (not that the error also trigger with the CLI, without using my .js file). The error occur when I extends any Prisma type. The moment I remove Prisma imports, everything works, if I add a Prisma type, it triggers this error

arthurfiorette commented 1 month ago

This might be fixed by #1924

arthurfiorette commented 1 month ago

https://github.com/vega/ts-json-schema-generator/pull/343 could also help here

arthurfiorette commented 1 month ago

@navalex can you try with v2.3.0-next.2?

After https://github.com/vega/ts-json-schema-generator/pull/1963 added diagnostics, the error thrown will have more info about it.

arthurfiorette commented 1 month ago

Probably duplicate of #542

navalex commented 1 month ago

@navalex can you try with v2.3.0-next.2?

After #1963 added diagnostics, the error thrown will have more info about it.

Okay so, after testing, it does fix the issue on my basic exemple, but does not on my main project. I tried both 2.2.0-next.0 and 2.3.0-next.2, the last one give an enormous output that seems to dump both a ts-json-schema-generator file and probably my file that throw the error (the Prisma generated client, so a big one).

I don't know if I'll have enough time to do more tests this week, so here is at least the output error, may give you more infos: Here is the full log: https://logpaste.com/OQTlH3wj

I'll also let my package.json and prisma.schema

package.json:

{
    "name": "eveapp-api-core",
    "version": "1.0.1",
    "description": "",
    "scripts": {
        "start:dev": "nodemon --exec 'ts-node -r tsconfig-paths/register' src/server.ts",
        "start:prod": "node -r tsconfig-paths/register dist/server.js",
        "build": "tsc && tsc-alias",
        "test:watch": "jest --watchAll --collectCoverage",
        "test:ci": "jest --collectCoverage",
        "test:manual": "jest",
        "format:check": "prettier --check \"src/**/*.ts\"",
        "format:write": "prettier --write \"src/**/*.ts\"",
        "lint:check": "eslint \"src/**/*.ts\"",
        "lint:fix": "eslint --fix \"src/**/*.ts\"",
        "schema:generate": "node bin/schemaGenerator.js"
    },
    "nodemonConfig": {
        "watch": [
            "src"
        ],
        "ext": "ts",
        "exec": "ts-node -r tsconfig-paths/register"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "@fastify/cors": "^9.0.1",
        "@fastify/formbody": "^7.4.0",
        "@fastify/multipart": "^8.2.0",
        "@fastify/sensible": "^5.6.0",
        "@fastify/swagger": "^8.14.0",
        "@fastify/swagger-ui": "^3.0.0",
        "@prisma/client": "^5.14.0",
        "fastify": "^4.27.0",
        "pino-loki": "^2.3.0",
        "pino-pretty": "^11.0.0",
        "prom-client": "^15.1.2",
        "socket.io": "^4.7.5",
        "supertokens-node": "^17.1.2"
    },
    "devDependencies": {
        "@types/jest": "^29.5.12",
        "@types/node": "^20.12.12",
        "eslint": "^9.3.0",
        "eslint-config-prettier": "^9.1.0",
        "jest": "^29.7.0",
        "jest-mock-extended": "^3.0.7",
        "nodemon": "^3.1.0",
        "prettier": "^3.2.5",
        "prisma": "^5.14.0",
        "prisma-json-schema-generator": "5.1.1",
        "prisma-json-types-generator": "^3.0.4",
        "ts-jest": "^29.1.3",
        "ts-json-schema-generator": "^2.3.0-next.2",
        "ts-node": "^10.9.2",
        "tsc-alias": "^1.8.10",
        "tsconfig-paths": "^4.2.0",
        "typescript": "^5.4.5"
    }
}

prisma.schema:

// ############################################
// #
// #           DATABASE DEFINITION
// #
// ############################################

datasource db {
    provider = "postgresql"
    url      = env("API_POSTGRES_URL")
}

generator client {
    provider        = "prisma-client-js"
    previewFeatures = ["fullTextSearch"]
}

generator jsonTypes {
    provider = "prisma-json-types-generator"
}

generator jsonSchema {
    provider   = "prisma-json-schema-generator"
    schemaId   = "PrismaSchema"
    forceAnyOf = true
}

// ############################################
// #
// #                MODELS
// #
// ############################################

model User {
    id                 String                   @id @default(uuid())
    auth_id            String                   @unique
    firstname          String
    lastname           String
    data               Json
    activationToken    String?                  @default(uuid())
    createdAt          DateTime                 @default(now())
    updatedAt          DateTime                 @updatedAt
    following          User[]                   @relation("Follow")
    followers          User[]                   @relation("Follow")
    exoskeletons       ExoskeletonAttribution[]
    challenges         Challenge[]
    authoredChallenges Challenge[]              @relation(name: "challengeAuthor")
    surveys            Survey[]
    preferences        Preferences?
}

model Preferences {
    id        String       @id @default(uuid())
    userId    String       @unique
    user      User         @relation(fields: [userId], references: [id])
    weekStart WeekStartDay @default(MONDAY)
    darkMode  Boolean      @default(false)
    unitType  UnitType     @default(METRIC)
    createdAt DateTime     @default(now())
    updatedAt DateTime     @updatedAt
}

model Exoskeleton {
    serial       String                   @id
    version      String?
    createdAt    DateTime                 @default(now())
    updatedAt    DateTime                 @updatedAt
    attributions ExoskeletonAttribution[]
}

model ExoskeletonAttribution {
    id            String      @id @default(uuid())
    createdAt     DateTime    @default(now())
    revokedAt     DateTime?
    exoskeletonId String
    userId        String
    exoskeleton   Exoskeleton @relation(fields: [exoskeletonId], references: [serial])
    user          User        @relation(fields: [userId], references: [id])
    sessions      Session[]
}

model Session {
    id            String                 @id @default(uuid())
    /// [SessionData]
    data          Json
    createdAt     DateTime               @default(now())
    updatedAt     DateTime               @updatedAt
    attributionId String
    attribution   ExoskeletonAttribution @relation(fields: [attributionId], references: [id])
}

model Challenge {
    id             String             @default(uuid())
    targetValue    Float
    metric         ChallengeMetric
    frequency      ChallengeFrequency
    repeate        Boolean            @default(false)
    weekRepetition Int?
    createdAt      DateTime           @default(now())
    archivedAt     DateTime?
    userId         String
    authorId       String
    user           User               @relation(fields: [userId], references: [id])
    author         User               @relation(name: "challengeAuthor", fields: [authorId], references: [id])

    @@id(name: "challengeId", [id, createdAt])
}

model ChallengeTemplate {
    id          String             @id @default(uuid())
    description String?
    targetValue Float
    metric      ChallengeMetric
    frequency   ChallengeFrequency
}

model Survey {
    id        String     @id @default(uuid())
    type      SurveyType
    data      Json
    createdAt DateTime   @default(now())
    userId    String
    user      User       @relation(fields: [userId], references: [id])
}

// ############################################
// #
// #                 ENUMS
// #
// ############################################

enum ChallengeMetric {
    STEP_COUNT
    DISTANCE
    VERTICAL_DURATION
    WALK_DURATION
}

enum ChallengeFrequency {
    DAILY
    WEEKLY
}

enum SurveyType {
    DAILY_MOOD
}

enum WeekStartDay {
    MONDAY
    SUNDAY
}

enum UnitType {
    METRIC
    IMPERIAL
}
arthurfiorette commented 1 week ago

Hey @navalex what about our latest release?