booster icon indicating copy to clipboard operation
booster copied to clipboard

Scoped classes incorrectly generate GraphQL schema as JSON

Open NickSeagull opened this issue 3 years ago • 2 comments

Bug Report

Current Behavior

If one tries to use a scoped class in a read model/command, Metadata Booster will incorrectly generate the type as JSON, instead of the proper schema. E.g.

import * as A from './a'
@ReadModel({
  authorize: 'all',
})
export class B {
  public constructor(public id: UUID, readonly list: Array<A.DTO>) {}
}

Will generate a the list type as:

list: [JSON!]!

Where as if we change the import to:

import { DTO } from './a'

It will generate the type properly.

Expected behavior

Metadata Booster should be able to generate the type correctly even if the class is scoped.

Possible Solution

Probably add a case to Metadata Booster to process scoped types properly.

Additional information

Environment

  • Booster version: 0.30.6
  • Node version: 16.14

Created via Raycast

NickSeagull avatar Sep 19 '22 11:09 NickSeagull

After experimenting with this for a while, it seems to me that the import (qualified or scoped) is not actually the issue. The issue is, that it seems to be mandatory to decorate the DTO classes with @ReadModel if you import it (in a qualified OR unqualified way).

// file: src/read-modles/a.ts
export class DTO_Undecorated {
  public constructor(id: UUID, propPublic: string)
}

@ReadModel()
export class DTO_Decorated {
  public constructor(id: UUID, propPublic: string)
}

// file: src/read-models/b.ts
import * as A from './a1'
import { DTO_Undecorated, DTO_Decorated } from './a2'

export class B1 { public constructor(x: A.DTO_Undecorated[]) } // type: JSON[]
export class B2 { public constructor(x: DTO_Undecorated[]) } // type: JSON[]
export class B3 { public constructor(x: A.DTO_Decorated[]) } // type: DTO_Decorated[]
export class B4 { public constructor(x: DTO_Decorated[]) } // type: DTO_Decorated[]

Now interestingly, it is only mandatory to decorate the class if it is imported, i.e. if the class is defined in the same file it does not have to decorated.

// file: src/read-models/b.ts
export class DTO_Undecorated {
  public constructor(id: UUID, propPublic: string)
}

@ReadModel()
export class DTO_Decorated {
  public constructor(id: UUID, propPublic: string)
}

export class B5 { public constructor(x: DTO_Undecorated[]) } // type: DTO_Undecorated[]
export class B6 { public constructor(x: DTO_Decorated[]) } // type: DTO_Decorated[]

BUT, this is an issue, because in practice if I have a shared class (GraphQL type) I don't want to specify that class in every file that exports a read model. At the same time I don't want to decorate this shared class with @ReadModel because it shouldn't be a table.

A possible workaround, i.e. being able to import the class from a shared file without having to decorate the class with @ReadModel, is by using a locally defined class that inherits everything from the shared, base class. For example:

// file: src/read-modles/a.ts
export class DTO_Undecorated {
  public constructor(id: UUID, propPublic: string)
}

// file: src/read-models/b.ts
import * as A from './a1'

class DTO_Undecorated_B extends A.DTO_Undecorated { }

export class B7 { public constructor(x: DTO_Undecorated_B[]) } // type: DTO_Undecorated_B[]

MichaelHirn avatar Sep 30 '22 11:09 MichaelHirn

This would also mean, that the ItemWithQuantity example in the docs [1] doesn't actually work. You would either have to decorate ItemWithQuantity with @ReadModel or create a local class that inherits everything from it.

MichaelHirn avatar Sep 30 '22 11:09 MichaelHirn