From ddb259b0e241baabf7fe9448c1d08706c25b3bbb Mon Sep 17 00:00:00 2001 From: faewd Date: Wed, 18 Jun 2025 19:25:05 +0100 Subject: [PATCH 1/7] Abstract model decorators into single utility decorator --- src/models/2014/abilityScore.ts | 43 +++++++----- src/models/2014/alignment.ts | 34 ++++----- src/models/2014/background.ts | 64 +++++++++-------- src/models/2014/class.ts | 119 ++++++++++++++------------------ src/util/fieldDectorator.ts | 117 +++++++++++++++++++++++++++++++ 5 files changed, 246 insertions(+), 131 deletions(-) create mode 100644 src/util/fieldDectorator.ts diff --git a/src/models/2014/abilityScore.ts b/src/models/2014/abilityScore.ts index 6810daa86..644debcef 100644 --- a/src/models/2014/abilityScore.ts +++ b/src/models/2014/abilityScore.ts @@ -1,11 +1,12 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { srdModelOptions } from '@/util/modelOptions' import { Skill } from './skill' +import { field, T } from '@/util/fieldDectorator' @ObjectType({ description: @@ -13,33 +14,43 @@ import { Skill } from './skill' }) @srdModelOptions('2014-ability-scores') export class AbilityScore { - @Field(() => [String], { - description: 'A description of the ability score and its applications.' + @field({ + description: 'A description of the ability score and its applications.', + type: T.String }) - @prop({ required: true, index: true, type: () => [String] }) public desc!: string[] - @Field(() => String, { description: 'The full name of the ability score (e.g., Strength).' }) - @prop({ required: true, index: true, type: () => String }) + @field({ + description: 'The full name of the ability score (e.g., Strength).', + type: T.String + }) public full_name!: string - @Field(() => String, { description: 'The unique identifier for this ability score (e.g., str).' }) - @prop({ required: true, index: true, type: () => String }) + @field({ + description: 'The unique identifier for this ability score (e.g., str).', + type: T.String + }) public index!: string - @Field(() => String, { description: 'The abbreviated name of the ability score (e.g., STR).' }) - @prop({ required: true, index: true, type: () => String }) + @field({ + description: 'The abbreviated name of the ability score (e.g., STR).', + type: T.String + }) public name!: string - @Field(() => [Skill], { description: 'Skills associated with this ability score.' }) - @prop({ type: () => [APIReference] }) + @field({ + description: 'Skills associated with this ability score.', + type: T.RefList(Skill) + }) public skills!: APIReference[] - @prop({ required: true, index: true, type: () => String }) + @field({ + description: 'The canonical path of this resource in the REST API.', + type: T.String + }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'Timestamp of the last update.', type: T.String }) public updated_at!: string } diff --git a/src/models/2014/alignment.ts b/src/models/2014/alignment.ts index 96323d49a..b91648d1d 100644 --- a/src/models/2014/alignment.ts +++ b/src/models/2014/alignment.ts @@ -1,39 +1,41 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { srdModelOptions } from '@/util/modelOptions' +import { field, T } from '@/util/fieldDectorator' @ObjectType({ description: "Represents a creature's moral and ethical outlook." }) @srdModelOptions('2014-alignments') export class Alignment { - @Field(() => String, { description: 'A brief description of the alignment.' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'A brief description of the alignment.', type: T.String }) public desc!: string - @Field(() => String, { - description: 'A shortened representation of the alignment (e.g., LG, CE).' + @field({ + description: 'A shortened representation of the alignment (e.g., LG, CE).', + type: T.String }) - @prop({ required: true, index: true, type: () => String }) public abbreviation!: string - @Field(() => String, { - description: 'The unique identifier for this alignment (e.g., lawful-good).' + @field({ + description: 'The unique identifier for this alignment (e.g., lawful-good).', + type: T.String }) - @prop({ required: true, index: true, type: () => String }) public index!: string - @Field(() => String, { - description: 'The name of the alignment (e.g., Lawful Good, Chaotic Evil).' + @field({ + description: 'The name of the alignment (e.g., Lawful Good, Chaotic Evil).', + type: T.String }) - @prop({ required: true, index: true, type: () => String }) public name!: string - @prop({ required: true, index: true, type: () => String }) + @field({ + description: 'The canonical path of this resource in the REST API.', + type: T.String + }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'Timestamp of the last update.', type: T.String }) public updated_at!: string } diff --git a/src/models/2014/background.ts b/src/models/2014/background.ts index 32df27eb1..ba2f90fbf 100644 --- a/src/models/2014/background.ts +++ b/src/models/2014/background.ts @@ -1,6 +1,6 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { Choice } from '@/models/common/choice' @@ -8,26 +8,23 @@ import { srdModelOptions } from '@/util/modelOptions' import { Equipment } from './equipment' import { Proficiency } from './proficiency' +import { field, T } from '@/util/fieldDectorator' @ObjectType({ description: 'Reference to a piece of equipment with a quantity.' }) export class EquipmentRef { - @Field(() => Equipment, { description: 'The specific equipment referenced.' }) - @prop({ type: () => APIReference }) + @field({ description: 'The specific equipment referenced.', type: T.Ref(Equipment) }) public equipment!: APIReference - @Field(() => Int, { description: 'The quantity of the referenced equipment.' }) - @prop({ required: true, index: true, type: () => Number }) + @field({ description: 'The quantity of the referenced equipment.', type: T.Int }) public quantity!: number } @ObjectType({ description: 'A special feature granted by the background.' }) class BackgroundFeature { - @Field(() => String, { description: 'The name of the background feature.' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'The name of the background feature.', type: T.String }) public name!: string - @Field(() => [String], { description: 'The description of the background feature.' }) - @prop({ required: true, index: true, type: () => [String] }) + @field({ description: 'The description of the background feature.', type: T.StringList }) public desc!: string[] } @@ -36,57 +33,64 @@ class BackgroundFeature { }) @srdModelOptions('2014-backgrounds') export class Background { - @Field(() => String, { - description: 'The unique identifier for this background (e.g., acolyte).' + @field({ + description: 'The unique identifier for this background (e.g., acolyte).', + type: T.String }) - @prop({ required: true, index: true, type: () => String }) public index!: string - @Field(() => String, { description: 'The name of the background (e.g., Acolyte).' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'The name of the background (e.g., Acolyte).', type: T.String }) public name!: string - @Field(() => [Proficiency], { description: 'Proficiencies granted by this background at start.' }) - @prop({ type: () => [APIReference] }) + @field({ + description: 'Proficiencies granted by this background at start.', + type: T.RefList(Proficiency) + }) public starting_proficiencies!: APIReference[] // Handled by BackgroundResolver - @prop({ type: () => Choice }) + @field({ type: T.Model(Choice), skipResolver: true }) public language_options!: Choice - @prop({ required: true, index: true, type: () => String }) + @field({ + description: 'The canonical path of this resource in the REST API.', + type: T.String + }) public url!: string - @Field(() => [EquipmentRef], { description: 'Equipment received when choosing this background.' }) - @prop({ type: () => [EquipmentRef] }) + @field({ + description: 'Equipment received when choosing this background.', + type: T.List(EquipmentRef) + }) public starting_equipment!: EquipmentRef[] // Handled by BackgroundResolver - @prop({ type: () => [Choice], index: true }) + @field({ type: T.List(Choice), skipResolver: true }) public starting_equipment_options!: Choice[] - @Field(() => BackgroundFeature, { description: 'The feature associated with this background.' }) - @prop({ type: () => BackgroundFeature }) + @field({ + description: 'The feature associated with this background.', + type: T.Model(BackgroundFeature) + }) public feature!: BackgroundFeature // Handled by BackgroundResolver - @prop({ type: () => Choice }) + @field({ type: T.Model(Choice), skipResolver: true }) public personality_traits!: Choice // Handled by BackgroundResolver - @prop({ type: () => Choice }) + @field({ type: T.Model(Choice), skipResolver: true }) public ideals!: Choice // Handled by BackgroundResolver - @prop({ type: () => Choice }) + @field({ type: T.Model(Choice), skipResolver: true }) public bonds!: Choice // Handled by BackgroundResolver - @prop({ type: () => Choice }) + @field({ type: T.Model(Choice), skipResolver: true }) public flaws!: Choice - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'Timestamp of the last update.', type: T.String }) public updated_at!: string } diff --git a/src/models/2014/class.ts b/src/models/2014/class.ts index 3914e089f..878e44c95 100644 --- a/src/models/2014/class.ts +++ b/src/models/2014/class.ts @@ -1,6 +1,6 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { Choice } from '@/models/common/choice' @@ -11,157 +11,138 @@ import { Level } from './level' import { Proficiency } from './proficiency' import { Spell } from './spell' import { Subclass } from './subclass' +import { field, T } from '@/util/fieldDectorator' +import { Equipment } from './equipment' @ObjectType({ description: 'Starting equipment item for a class' }) export class ClassEquipment { // Handled by ClassEquipmentResolver - @prop({ type: () => APIReference }) + @field({ type: T.Ref(Equipment), skipResolver: true }) public equipment!: APIReference - @Field(() => Int, { description: 'Quantity of the equipment item.' }) - @prop({ required: true, index: true, type: () => Number }) + @field({ description: 'Quantity of the equipment item.', type: T.Int }) public quantity!: number } @ObjectType({ description: "Information about a class's spellcasting ability" }) export class SpellcastingInfo { - @Field(() => [String], { description: 'Description of the spellcasting ability.' }) - @prop({ required: true, index: true, type: () => [String] }) + @field({ description: 'Description of the spellcasting ability.', type: T.StringList }) public desc!: string[] - @Field(() => String, { description: 'Name of the spellcasting ability.' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'Name of the spellcasting ability.', type: T.String }) public name!: string } @ObjectType({ description: 'Spellcasting details for a class' }) export class Spellcasting { - @Field(() => [SpellcastingInfo], { description: 'Spellcasting details for the class.' }) - @prop({ type: () => [SpellcastingInfo] }) + @field({ description: 'Spellcasting details for the class.', type: T.List(SpellcastingInfo) }) public info!: SpellcastingInfo[] - @Field(() => Int, { description: 'Level of the spellcasting ability.' }) - @prop({ required: true, index: true, type: () => Number }) + @field({ description: 'Level of the spellcasting ability.', type: T.Int }) public level!: number - @Field(() => AbilityScore, { description: 'Ability score used for spellcasting.' }) - @prop({ type: () => APIReference }) + @field({ description: 'Ability score used for spellcasting.', type: T.Ref(AbilityScore) }) public spellcasting_ability!: APIReference } @ObjectType({ description: 'Prerequisite for multi-classing' }) export class MultiClassingPrereq { - @Field(() => AbilityScore, { nullable: true, description: 'The ability score required.' }) - @prop({ type: () => APIReference }) + @field({ description: 'The ability score required.', type: T.Ref(AbilityScore) }) public ability_score!: APIReference - @Field(() => Int, { description: 'The minimum score required.' }) - @prop({ required: true, index: true, type: () => Number }) + @field({ description: 'The minimum score required.', type: T.Int }) public minimum_score!: number } @ObjectType({ description: 'Multi-classing requirements and features for a class' }) export class MultiClassing { - @Field(() => [MultiClassingPrereq], { - nullable: true, - description: 'Ability score prerequisites for multi-classing.' + @field({ + description: 'Ability score prerequisites for multi-classing.', + type: T.List(MultiClassingPrereq), + optional: true }) - @prop({ type: () => [MultiClassingPrereq], default: undefined }) public prerequisites?: MultiClassingPrereq[] // Handled by MultiClassingResolver - @prop({ type: () => Choice, default: undefined }) + @field({ type: T.Model(Choice), optional: true, skipResolver: true }) public prerequisite_options?: Choice - @Field(() => [Proficiency], { - nullable: true, - description: 'Proficiencies gained when multi-classing into this class.' + @field({ + description: 'Proficiencies gained when multi-classing into this class.', + type: T.RefList(Proficiency), + optional: true }) - @prop({ type: () => [APIReference], default: undefined }) public proficiencies?: APIReference[] // Handled by MultiClassingResolver - @prop({ type: () => [Choice], default: undefined }) + @field({ type: T.Model(Choice), optional: true, skipResolver: true }) public proficiency_choices?: Choice[] } @ObjectType({ description: 'Represents a character class (e.g., Barbarian, Wizard)' }) @srdModelOptions('2014-classes') export class Class { - @Field(() => [Level], { - description: 'All levels for this class, detailing features and abilities gained.' + @field({ + description: 'All levels for this class, detailing features and abilities gained.', + type: T.Link([[Level]]) }) - @prop({ required: true, index: true, type: () => String }) public class_levels!: string - @Field(() => MultiClassing, { - nullable: true, - description: 'Multi-classing requirements and features for this class.' + @field({ + description: 'Multi-classing requirements and features for this class.', + type: T.Model(MultiClassing) }) - @prop({ type: () => MultiClassing }) public multi_classing!: MultiClassing - @Field(() => Int, { description: 'Hit die size for the class (e.g., 6, 8, 10, 12)' }) - @prop({ required: true, index: true, type: () => Number }) + @field({ description: 'Hit die size for the class (e.g., 6, 8, 10, 12)', type: T.Int }) public hit_die!: number - @Field(() => String, { description: 'Unique identifier for the class' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'Unique identifier for the class', type: T.String }) public index!: string - @Field(() => String, { description: 'Name of the class' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'Name of the class', type: T.String }) public name!: string - @Field(() => [Proficiency], { - nullable: true, - description: 'Base proficiencies granted by this class.' - }) - @prop({ type: () => [APIReference] }) + @field({ description: 'Base proficiencies granted by this class.', type: T.RefList(Proficiency) }) public proficiencies!: APIReference[] // Handled by ClassResolver - @prop({ type: () => [Choice] }) + @field({ type: T.List(Choice), skipResolver: true }) public proficiency_choices!: Choice[] - @Field(() => [AbilityScore], { - nullable: true, - description: 'Saving throw proficiencies granted by this class.' + @field({ + description: 'Saving throw proficiencies granted by this class.', + type: T.RefList(AbilityScore) }) - @prop({ type: () => [APIReference] }) public saving_throws!: APIReference[] - @Field(() => Spellcasting, { - nullable: true, - description: 'Spellcasting details for the class.' + @field({ + description: 'Spellcasting details for the class.', + type: T.Model(Spellcasting), + optional: true }) - @prop({ type: () => Spellcasting }) public spellcasting?: Spellcasting - @Field(() => [Spell], { description: 'Spells available to this class.' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'Spells available to this class.', type: T.Link([Spell]) }) public spells!: string - @Field(() => [ClassEquipment], { - nullable: true, - description: 'Starting equipment for the class.' - }) - @prop({ type: () => [ClassEquipment] }) + @field({ description: 'Starting equipment for the class.', type: T.List(ClassEquipment) }) public starting_equipment!: ClassEquipment[] // Handled by ClassResolver - @prop({ type: () => [Choice] }) + @field({ type: T.List(Choice), skipResolver: true }) public starting_equipment_options!: Choice[] - @Field(() => [Subclass], { nullable: true, description: 'Available subclasses for this class.' }) - @prop({ type: () => [APIReference] }) + @field({ description: 'Available subclasses for this class.', type: T.RefList(Subclass) }) public subclasses!: APIReference[] - @prop({ required: true, index: true, type: () => String }) + @field({ + description: 'The canonical path of this resource in the REST API.', + type: T.String + }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update' }) - @prop({ required: true, index: true, type: () => String }) + @field({ description: 'Timestamp of the last update', type: T.String }) public updated_at!: string } diff --git a/src/util/fieldDectorator.ts b/src/util/fieldDectorator.ts new file mode 100644 index 000000000..4b4726ae2 --- /dev/null +++ b/src/util/fieldDectorator.ts @@ -0,0 +1,117 @@ +import { APIReference } from '@/models/common/apiReference' +import { prop } from '@typegoose/typegoose' +import { + BasePropOptions, + ArrayPropOptions, + MapPropOptions, + PropOptionsForNumber, + PropOptionsForString, + VirtualOptions +} from '@typegoose/typegoose/lib/types' +import { SchemaTypes } from 'mongoose' +import { ReturnTypeFuncValue } from 'node_modules/type-graphql/build/typings/decorators/types' +import { Field, Int as GqlInt, FieldOptions as TypeGraphQLFieldOptions } from 'type-graphql' + +type TypegooseOptions = + | BasePropOptions + | ArrayPropOptions + | MapPropOptions + | PropOptionsForNumber + | PropOptionsForString + | VirtualOptions + +type FieldOptions = AutoResolvedFieldOptions | ManualResolvedFieldOptions + +type BaseFieldOptions = { + type: TypeObject + optional?: boolean + index?: boolean + typegoose?: TypegooseOptions +} + +type AutoResolvedFieldOptions = BaseFieldOptions & { + description: string + skipResolver?: false + typeGraphQL?: TypeGraphQLFieldOptions +} + +type ManualResolvedFieldOptions = BaseFieldOptions & { + skipResolver: true +} + +function isAutoResolvedFieldOptions(options: FieldOptions): options is AutoResolvedFieldOptions { + return options.skipResolver !== true +} + +export function field(options: FieldOptions): PropertyDecorator { + const required = options.optional !== true + const index = options.index ?? true + + const { db: dbType, gql: gqlType } = options.type + + return (...args) => { + prop({ type: () => dbType, index, required, ...options.typegoose })(...args) + + if (isAutoResolvedFieldOptions(options)) { + Field(() => gqlType, { + description: options.description, + nullable: !required, + ...options.typeGraphQL + })(...args) + } + } +} + +type TypeObject = { db: BasePropOptions['type']; gql: ReturnTypeFuncValue } + +/** + * Creates a field type that is represented as a single APIReference object in the database, but as + * an object of the given type in the GraphQL API + */ +function Ref(type: ReturnTypeFuncValue): TypeObject { + return { db: APIReference, gql: type } +} + +/** + * Creates a field type that is represented as an array of APIReference objects in the database, but + * as an array of the given type in the GraphQL API + */ +function RefList(type: ReturnTypeFuncValue): TypeObject { + return { db: [APIReference], gql: [type] } +} + +/** + * Creates a field type that is represented as an array of a given type in both the database and the + * GraphQL API + */ +function List(type: ReturnTypeFuncValue): TypeObject { + return { db: [type], gql: [type] } +} + +/** + * Creates a field type that has the same representation in the database & GraphQL API, i.e. a + * nested model/schema. + */ +function Model(model: ReturnTypeFuncValue): TypeObject { + return { db: model, gql: model } +} + +/** + * Creates a field type that is represented as a string in the database and REST API, but resolves + * to a given type in the GraphQL API + */ +function Link(type: ReturnTypeFuncValue): TypeObject { + return { db: String, gql: type } +} + +export const T = { + String: { db: String, gql: String }, + StringList: { db: [String], gql: [String] }, + Int: { db: SchemaTypes.Int32, gql: GqlInt }, + IntList: { db: [SchemaTypes.Int32], gql: [GqlInt] }, + Ref, + RefList, + List, + Model, + Link +} From 15eee3efceac6bb74c0b172a5c1ba4dfcf7208b5 Mon Sep 17 00:00:00 2001 From: faewd Date: Wed, 18 Jun 2025 22:13:20 +0100 Subject: [PATCH 2/7] Fix linter errors, type mismatches & minor refactor --- src/models/2014/abilityScore.ts | 6 +++--- src/models/2014/alignment.ts | 2 +- src/models/2014/background.ts | 6 +++--- src/models/2014/class.ts | 19 +++++++++++-------- src/util/fieldDectorator.ts | 28 +++++++++++++++------------- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/models/2014/abilityScore.ts b/src/models/2014/abilityScore.ts index 644debcef..6ae65a6da 100644 --- a/src/models/2014/abilityScore.ts +++ b/src/models/2014/abilityScore.ts @@ -3,10 +3,10 @@ import { DocumentType } from '@typegoose/typegoose/lib/types' import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { Skill } from './skill' -import { field, T } from '@/util/fieldDectorator' @ObjectType({ description: @@ -16,7 +16,7 @@ import { field, T } from '@/util/fieldDectorator' export class AbilityScore { @field({ description: 'A description of the ability score and its applications.', - type: T.String + type: T.List(T.String) }) public desc!: string[] @@ -40,7 +40,7 @@ export class AbilityScore { @field({ description: 'Skills associated with this ability score.', - type: T.RefList(Skill) + type: T.List(T.Ref(Skill)) }) public skills!: APIReference[] diff --git a/src/models/2014/alignment.ts b/src/models/2014/alignment.ts index b91648d1d..b13f4edb5 100644 --- a/src/models/2014/alignment.ts +++ b/src/models/2014/alignment.ts @@ -2,8 +2,8 @@ import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' import { ObjectType } from 'type-graphql' -import { srdModelOptions } from '@/util/modelOptions' import { field, T } from '@/util/fieldDectorator' +import { srdModelOptions } from '@/util/modelOptions' @ObjectType({ description: "Represents a creature's moral and ethical outlook." }) @srdModelOptions('2014-alignments') diff --git a/src/models/2014/background.ts b/src/models/2014/background.ts index ba2f90fbf..0f63ca11d 100644 --- a/src/models/2014/background.ts +++ b/src/models/2014/background.ts @@ -4,11 +4,11 @@ import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { Choice } from '@/models/common/choice' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { Equipment } from './equipment' import { Proficiency } from './proficiency' -import { field, T } from '@/util/fieldDectorator' @ObjectType({ description: 'Reference to a piece of equipment with a quantity.' }) export class EquipmentRef { @@ -24,7 +24,7 @@ class BackgroundFeature { @field({ description: 'The name of the background feature.', type: T.String }) public name!: string - @field({ description: 'The description of the background feature.', type: T.StringList }) + @field({ description: 'The description of the background feature.', type: T.List(T.String) }) public desc!: string[] } @@ -44,7 +44,7 @@ export class Background { @field({ description: 'Proficiencies granted by this background at start.', - type: T.RefList(Proficiency) + type: T.List(T.Ref(Proficiency)) }) public starting_proficiencies!: APIReference[] diff --git a/src/models/2014/class.ts b/src/models/2014/class.ts index 878e44c95..b85f89770 100644 --- a/src/models/2014/class.ts +++ b/src/models/2014/class.ts @@ -4,15 +4,15 @@ import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { Choice } from '@/models/common/choice' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { AbilityScore } from './abilityScore' +import { Equipment } from './equipment' import { Level } from './level' import { Proficiency } from './proficiency' import { Spell } from './spell' import { Subclass } from './subclass' -import { field, T } from '@/util/fieldDectorator' -import { Equipment } from './equipment' @ObjectType({ description: 'Starting equipment item for a class' }) export class ClassEquipment { @@ -26,7 +26,7 @@ export class ClassEquipment { @ObjectType({ description: "Information about a class's spellcasting ability" }) export class SpellcastingInfo { - @field({ description: 'Description of the spellcasting ability.', type: T.StringList }) + @field({ description: 'Description of the spellcasting ability.', type: T.List(T.String) }) public desc!: string[] @field({ description: 'Name of the spellcasting ability.', type: T.String }) @@ -69,13 +69,13 @@ export class MultiClassing { @field({ description: 'Proficiencies gained when multi-classing into this class.', - type: T.RefList(Proficiency), + type: T.List(T.Ref(Proficiency)), optional: true }) public proficiencies?: APIReference[] // Handled by MultiClassingResolver - @field({ type: T.Model(Choice), optional: true, skipResolver: true }) + @field({ type: T.List(Choice), optional: true, skipResolver: true }) public proficiency_choices?: Choice[] } @@ -103,7 +103,10 @@ export class Class { @field({ description: 'Name of the class', type: T.String }) public name!: string - @field({ description: 'Base proficiencies granted by this class.', type: T.RefList(Proficiency) }) + @field({ + description: 'Base proficiencies granted by this class.', + type: T.List(T.Ref(Proficiency)) + }) public proficiencies!: APIReference[] // Handled by ClassResolver @@ -112,7 +115,7 @@ export class Class { @field({ description: 'Saving throw proficiencies granted by this class.', - type: T.RefList(AbilityScore) + type: T.List(T.Ref(AbilityScore)) }) public saving_throws!: APIReference[] @@ -133,7 +136,7 @@ export class Class { @field({ type: T.List(Choice), skipResolver: true }) public starting_equipment_options!: Choice[] - @field({ description: 'Available subclasses for this class.', type: T.RefList(Subclass) }) + @field({ description: 'Available subclasses for this class.', type: T.List(T.Ref(Subclass)) }) public subclasses!: APIReference[] @field({ diff --git a/src/util/fieldDectorator.ts b/src/util/fieldDectorator.ts index 4b4726ae2..8fe509040 100644 --- a/src/util/fieldDectorator.ts +++ b/src/util/fieldDectorator.ts @@ -1,4 +1,3 @@ -import { APIReference } from '@/models/common/apiReference' import { prop } from '@typegoose/typegoose' import { BasePropOptions, @@ -12,6 +11,8 @@ import { SchemaTypes } from 'mongoose' import { ReturnTypeFuncValue } from 'node_modules/type-graphql/build/typings/decorators/types' import { Field, Int as GqlInt, FieldOptions as TypeGraphQLFieldOptions } from 'type-graphql' +import { APIReference } from '@/models/common/apiReference' + type TypegooseOptions = | BasePropOptions | ArrayPropOptions @@ -64,6 +65,15 @@ export function field(options: FieldOptions): PropertyDecorator { type TypeObject = { db: BasePropOptions['type']; gql: ReturnTypeFuncValue } +function isTypeObject(type: unknown): type is TypeObject { + return ( + type !== null && + typeof type === 'object' && + Object.hasOwn(type, 'gql') && + Object.hasOwn(type, 'db') + ) +} + /** * Creates a field type that is represented as a single APIReference object in the database, but as * an object of the given type in the GraphQL API @@ -72,19 +82,14 @@ function Ref(type: ReturnTypeFuncValue): TypeObject { return { db: APIReference, gql: type } } -/** - * Creates a field type that is represented as an array of APIReference objects in the database, but - * as an array of the given type in the GraphQL API - */ -function RefList(type: ReturnTypeFuncValue): TypeObject { - return { db: [APIReference], gql: [type] } -} - /** * Creates a field type that is represented as an array of a given type in both the database and the * GraphQL API */ -function List(type: ReturnTypeFuncValue): TypeObject { +function List(type: ReturnTypeFuncValue | TypeObject): TypeObject { + if (isTypeObject(type)) { + return { db: [type.db], gql: [type.gql] } + } return { db: [type], gql: [type] } } @@ -106,11 +111,8 @@ function Link(type: ReturnTypeFuncValue): TypeObject { export const T = { String: { db: String, gql: String }, - StringList: { db: [String], gql: [String] }, Int: { db: SchemaTypes.Int32, gql: GqlInt }, - IntList: { db: [SchemaTypes.Int32], gql: [GqlInt] }, Ref, - RefList, List, Model, Link From b148014e005f904cb45a02ef0d6b15904e4627ed Mon Sep 17 00:00:00 2001 From: faewd Date: Fri, 20 Jun 2025 09:51:34 +0100 Subject: [PATCH 3/7] Extract type from options object & lazily evaluate it --- src/models/2014/abilityScore.ts | 32 ++++++-------- src/models/2014/alignment.ts | 24 +++++------ src/models/2014/background.ts | 49 ++++++++++----------- src/models/2014/class.ts | 76 +++++++++++++++------------------ src/util/fieldDectorator.ts | 9 ++-- 5 files changed, 83 insertions(+), 107 deletions(-) diff --git a/src/models/2014/abilityScore.ts b/src/models/2014/abilityScore.ts index 6ae65a6da..cacf5a52d 100644 --- a/src/models/2014/abilityScore.ts +++ b/src/models/2014/abilityScore.ts @@ -14,43 +14,37 @@ import { Skill } from './skill' }) @srdModelOptions('2014-ability-scores') export class AbilityScore { - @field({ - description: 'A description of the ability score and its applications.', - type: T.List(T.String) + @field(() => T.List(T.String), { + description: 'A description of the ability score and its applications.' }) public desc!: string[] - @field({ - description: 'The full name of the ability score (e.g., Strength).', - type: T.String + @field(() => T.String, { + description: 'The full name of the ability score (e.g., Strength).' }) public full_name!: string - @field({ - description: 'The unique identifier for this ability score (e.g., str).', - type: T.String + @field(() => T.String, { + description: 'The unique identifier for this ability score (e.g., str).' }) public index!: string - @field({ - description: 'The abbreviated name of the ability score (e.g., STR).', - type: T.String + @field(() => T.String, { + description: 'The abbreviated name of the ability score (e.g., STR).' }) public name!: string - @field({ - description: 'Skills associated with this ability score.', - type: T.List(T.Ref(Skill)) + @field(() => T.List(T.Ref(Skill)), { + description: 'Skills associated with this ability score.' }) public skills!: APIReference[] - @field({ - description: 'The canonical path of this resource in the REST API.', - type: T.String + @field(() => T.String, { + description: 'The canonical path of this resource in the REST API.' }) public url!: string - @field({ description: 'Timestamp of the last update.', type: T.String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/alignment.ts b/src/models/2014/alignment.ts index b13f4edb5..2c0089743 100644 --- a/src/models/2014/alignment.ts +++ b/src/models/2014/alignment.ts @@ -8,34 +8,30 @@ import { srdModelOptions } from '@/util/modelOptions' @ObjectType({ description: "Represents a creature's moral and ethical outlook." }) @srdModelOptions('2014-alignments') export class Alignment { - @field({ description: 'A brief description of the alignment.', type: T.String }) + @field(() => T.String, { description: 'A brief description of the alignment.' }) public desc!: string - @field({ - description: 'A shortened representation of the alignment (e.g., LG, CE).', - type: T.String + @field(() => T.String, { + description: 'A shortened representation of the alignment (e.g., LG, CE).' }) public abbreviation!: string - @field({ - description: 'The unique identifier for this alignment (e.g., lawful-good).', - type: T.String + @field(() => T.String, { + description: 'The unique identifier for this alignment (e.g., lawful-good).' }) public index!: string - @field({ - description: 'The name of the alignment (e.g., Lawful Good, Chaotic Evil).', - type: T.String + @field(() => T.String, { + description: 'The name of the alignment (e.g., Lawful Good, Chaotic Evil).' }) public name!: string - @field({ - description: 'The canonical path of this resource in the REST API.', - type: T.String + @field(() => T.String, { + description: 'The canonical path of this resource in the REST API.' }) public url!: string - @field({ description: 'Timestamp of the last update.', type: T.String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/background.ts b/src/models/2014/background.ts index 0f63ca11d..31812b524 100644 --- a/src/models/2014/background.ts +++ b/src/models/2014/background.ts @@ -12,19 +12,19 @@ import { Proficiency } from './proficiency' @ObjectType({ description: 'Reference to a piece of equipment with a quantity.' }) export class EquipmentRef { - @field({ description: 'The specific equipment referenced.', type: T.Ref(Equipment) }) + @field(() => T.Ref(Equipment), { description: 'The specific equipment referenced.' }) public equipment!: APIReference - @field({ description: 'The quantity of the referenced equipment.', type: T.Int }) + @field(() => T.Int, { description: 'The quantity of the referenced equipment.' }) public quantity!: number } @ObjectType({ description: 'A special feature granted by the background.' }) class BackgroundFeature { - @field({ description: 'The name of the background feature.', type: T.String }) + @field(() => T.String, { description: 'The name of the background feature.' }) public name!: string - @field({ description: 'The description of the background feature.', type: T.List(T.String) }) + @field(() => T.List(T.String), { description: 'The description of the background feature.' }) public desc!: string[] } @@ -33,64 +33,59 @@ class BackgroundFeature { }) @srdModelOptions('2014-backgrounds') export class Background { - @field({ - description: 'The unique identifier for this background (e.g., acolyte).', - type: T.String + @field(() => T.String, { + description: 'The unique identifier for this background (e.g., acolyte).' }) public index!: string - @field({ description: 'The name of the background (e.g., Acolyte).', type: T.String }) + @field(() => T.String, { description: 'The name of the background (e.g., Acolyte).' }) public name!: string - @field({ - description: 'Proficiencies granted by this background at start.', - type: T.List(T.Ref(Proficiency)) + @field(() => T.List(T.Ref(Proficiency)), { + description: 'Proficiencies granted by this background at start.' }) public starting_proficiencies!: APIReference[] // Handled by BackgroundResolver - @field({ type: T.Model(Choice), skipResolver: true }) + @field(() => T.Model(Choice), { skipResolver: true }) public language_options!: Choice - @field({ - description: 'The canonical path of this resource in the REST API.', - type: T.String + @field(() => T.String, { + description: 'The canonical path of this resource in the REST API.' }) public url!: string - @field({ - description: 'Equipment received when choosing this background.', - type: T.List(EquipmentRef) + @field(() => T.List(EquipmentRef), { + description: 'Equipment received when choosing this background.' }) public starting_equipment!: EquipmentRef[] // Handled by BackgroundResolver - @field({ type: T.List(Choice), skipResolver: true }) + @field(() => T.List(Choice), { skipResolver: true }) public starting_equipment_options!: Choice[] - @field({ - description: 'The feature associated with this background.', - type: T.Model(BackgroundFeature) + @field(() => T.Model(BackgroundFeature), { + description: 'The feature associated with this background.' }) public feature!: BackgroundFeature // Handled by BackgroundResolver - @field({ type: T.Model(Choice), skipResolver: true }) + @field(() => T.Model(Choice), { skipResolver: true }) public personality_traits!: Choice // Handled by BackgroundResolver - @field({ type: T.Model(Choice), skipResolver: true }) + @field(() => T.Model(Choice), { skipResolver: true }) public ideals!: Choice // Handled by BackgroundResolver - @field({ type: T.Model(Choice), skipResolver: true }) + @field(() => T.Model(Choice), { skipResolver: true }) public bonds!: Choice // Handled by BackgroundResolver - @field({ type: T.Model(Choice), skipResolver: true }) + @field(() => T.Model(Choice), { skipResolver: true }) public flaws!: Choice - @field({ description: 'Timestamp of the last update.', type: T.String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/class.ts b/src/models/2014/class.ts index b85f89770..6ee0eeafd 100644 --- a/src/models/2014/class.ts +++ b/src/models/2014/class.ts @@ -17,135 +17,129 @@ import { Subclass } from './subclass' @ObjectType({ description: 'Starting equipment item for a class' }) export class ClassEquipment { // Handled by ClassEquipmentResolver - @field({ type: T.Ref(Equipment), skipResolver: true }) + @field(() => T.Ref(Equipment), { skipResolver: true }) public equipment!: APIReference - @field({ description: 'Quantity of the equipment item.', type: T.Int }) + @field(() => T.Int, { description: 'Quantity of the equipment item.' }) public quantity!: number } @ObjectType({ description: "Information about a class's spellcasting ability" }) export class SpellcastingInfo { - @field({ description: 'Description of the spellcasting ability.', type: T.List(T.String) }) + @field(() => T.List(T.String), { description: 'Description of the spellcasting ability.' }) public desc!: string[] - @field({ description: 'Name of the spellcasting ability.', type: T.String }) + @field(() => T.String, { description: 'Name of the spellcasting ability.' }) public name!: string } @ObjectType({ description: 'Spellcasting details for a class' }) export class Spellcasting { - @field({ description: 'Spellcasting details for the class.', type: T.List(SpellcastingInfo) }) + @field(() => T.List(SpellcastingInfo), { description: 'Spellcasting details for the class.' }) public info!: SpellcastingInfo[] - @field({ description: 'Level of the spellcasting ability.', type: T.Int }) + @field(() => T.Int, { description: 'Level of the spellcasting ability.' }) public level!: number - @field({ description: 'Ability score used for spellcasting.', type: T.Ref(AbilityScore) }) + @field(() => T.Ref(AbilityScore), { description: 'Ability score used for spellcasting.' }) public spellcasting_ability!: APIReference } @ObjectType({ description: 'Prerequisite for multi-classing' }) export class MultiClassingPrereq { - @field({ description: 'The ability score required.', type: T.Ref(AbilityScore) }) + @field(() => T.Ref(AbilityScore), { description: 'The ability score required.' }) public ability_score!: APIReference - @field({ description: 'The minimum score required.', type: T.Int }) + @field(() => T.Int, { description: 'The minimum score required.' }) public minimum_score!: number } @ObjectType({ description: 'Multi-classing requirements and features for a class' }) export class MultiClassing { - @field({ + @field(() => T.List(MultiClassingPrereq), { description: 'Ability score prerequisites for multi-classing.', - type: T.List(MultiClassingPrereq), optional: true }) public prerequisites?: MultiClassingPrereq[] // Handled by MultiClassingResolver - @field({ type: T.Model(Choice), optional: true, skipResolver: true }) + @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public prerequisite_options?: Choice - @field({ + @field(() => T.List(T.Ref(Proficiency)), { description: 'Proficiencies gained when multi-classing into this class.', - type: T.List(T.Ref(Proficiency)), optional: true }) public proficiencies?: APIReference[] // Handled by MultiClassingResolver - @field({ type: T.List(Choice), optional: true, skipResolver: true }) + @field(() => T.List(Choice), { optional: true, skipResolver: true }) public proficiency_choices?: Choice[] } @ObjectType({ description: 'Represents a character class (e.g., Barbarian, Wizard)' }) @srdModelOptions('2014-classes') export class Class { - @field({ - description: 'All levels for this class, detailing features and abilities gained.', - type: T.Link([[Level]]) + @field(() => T.Link([[Level]]), { + description: 'All levels for this class, detailing features and abilities gained.' }) public class_levels!: string - @field({ - description: 'Multi-classing requirements and features for this class.', - type: T.Model(MultiClassing) + @field(() => T.Model(MultiClassing), { + description: 'Multi-classing requirements and features for this class.' }) public multi_classing!: MultiClassing - @field({ description: 'Hit die size for the class (e.g., 6, 8, 10, 12)', type: T.Int }) + @field(() => T.Int, { description: 'Hit die size for the class (e.g., 6, 8, 10, 12)' }) public hit_die!: number - @field({ description: 'Unique identifier for the class', type: T.String }) + @field(() => T.String, { description: 'Unique identifier for the class' }) public index!: string - @field({ description: 'Name of the class', type: T.String }) + @field(() => T.String, { description: 'Name of the class' }) public name!: string - @field({ - description: 'Base proficiencies granted by this class.', - type: T.List(T.Ref(Proficiency)) + @field(() => T.List(T.Ref(Proficiency)), { + description: 'Base proficiencies granted by this class.' }) public proficiencies!: APIReference[] // Handled by ClassResolver - @field({ type: T.List(Choice), skipResolver: true }) + @field(() => T.List(Choice), { skipResolver: true }) public proficiency_choices!: Choice[] - @field({ - description: 'Saving throw proficiencies granted by this class.', - type: T.List(T.Ref(AbilityScore)) + @field(() => T.List(T.Ref(AbilityScore)), { + description: 'Saving throw proficiencies granted by this class.' }) public saving_throws!: APIReference[] - @field({ + @field(() => T.Model(Spellcasting), { description: 'Spellcasting details for the class.', - type: T.Model(Spellcasting), optional: true }) public spellcasting?: Spellcasting - @field({ description: 'Spells available to this class.', type: T.Link([Spell]) }) + @field(() => T.Link([Spell]), { description: 'Spells available to this class.' }) public spells!: string - @field({ description: 'Starting equipment for the class.', type: T.List(ClassEquipment) }) + @field(() => T.List(ClassEquipment), { description: 'Starting equipment for the class.' }) public starting_equipment!: ClassEquipment[] // Handled by ClassResolver - @field({ type: T.List(Choice), skipResolver: true }) + @field(() => T.List(Choice), { skipResolver: true }) public starting_equipment_options!: Choice[] - @field({ description: 'Available subclasses for this class.', type: T.List(T.Ref(Subclass)) }) + @field(() => T.List(T.Ref(Subclass)), { + description: 'Available subclasses for this class.' + }) public subclasses!: APIReference[] - @field({ - description: 'The canonical path of this resource in the REST API.', - type: T.String + @field(() => T.String, { + description: 'The canonical path of this resource in the REST API.' }) public url!: string - @field({ description: 'Timestamp of the last update', type: T.String }) + @field(() => T.String, { description: 'Timestamp of the last update' }) public updated_at!: string } diff --git a/src/util/fieldDectorator.ts b/src/util/fieldDectorator.ts index 8fe509040..beff7488b 100644 --- a/src/util/fieldDectorator.ts +++ b/src/util/fieldDectorator.ts @@ -24,7 +24,6 @@ type TypegooseOptions = type FieldOptions = AutoResolvedFieldOptions | ManualResolvedFieldOptions type BaseFieldOptions = { - type: TypeObject optional?: boolean index?: boolean typegoose?: TypegooseOptions @@ -44,17 +43,15 @@ function isAutoResolvedFieldOptions(options: FieldOptions): options is AutoResol return options.skipResolver !== true } -export function field(options: FieldOptions): PropertyDecorator { +export function field(type: () => TypeObject, options: FieldOptions): PropertyDecorator { const required = options.optional !== true const index = options.index ?? true - const { db: dbType, gql: gqlType } = options.type - return (...args) => { - prop({ type: () => dbType, index, required, ...options.typegoose })(...args) + prop({ type: () => type().db, index, required, ...options.typegoose })(...args) if (isAutoResolvedFieldOptions(options)) { - Field(() => gqlType, { + Field(() => type().gql, { description: options.description, nullable: !required, ...options.typeGraphQL From f916ab64de5061f29c5c8de57176bf9f8c8ea04f Mon Sep 17 00:00:00 2001 From: faewd Date: Fri, 20 Jun 2025 17:34:20 +0100 Subject: [PATCH 4/7] Use new decorator for Collection through Proficiency (except Monster) --- src/models/2014/abilityScore.ts | 16 +- src/models/2014/alignment.ts | 4 +- src/models/2014/background.ts | 8 +- src/models/2014/class.ts | 18 +- src/models/2014/collection.ts | 5 +- src/models/2014/condition.ts | 21 +- src/models/2014/damageType.ts | 21 +- src/models/2014/equipment.ts | 123 +++++------ src/models/2014/equipmentCategory.ts | 20 +- src/models/2014/feat.ts | 38 ++-- src/models/2014/feature.ts | 83 +++---- src/models/2014/language.ts | 32 ++- src/models/2014/level.ts | 317 +++++++++++---------------- src/models/2014/magicItem.ts | 51 ++--- src/models/2014/magicSchool.ts | 21 +- src/models/2014/proficiency.ts | 33 +-- src/util/fieldDectorator.ts | 20 +- 17 files changed, 367 insertions(+), 464 deletions(-) diff --git a/src/models/2014/abilityScore.ts b/src/models/2014/abilityScore.ts index cacf5a52d..8cb11e051 100644 --- a/src/models/2014/abilityScore.ts +++ b/src/models/2014/abilityScore.ts @@ -14,14 +14,12 @@ import { Skill } from './skill' }) @srdModelOptions('2014-ability-scores') export class AbilityScore { - @field(() => T.List(T.String), { + @field(() => T.List(String), { description: 'A description of the ability score and its applications.' }) public desc!: string[] - @field(() => T.String, { - description: 'The full name of the ability score (e.g., Strength).' - }) + @field(() => T.String, { description: 'The full name of the ability score (e.g., Strength).' }) public full_name!: string @field(() => T.String, { @@ -29,19 +27,15 @@ export class AbilityScore { }) public index!: string - @field(() => T.String, { - description: 'The abbreviated name of the ability score (e.g., STR).' - }) + @field(() => T.String, { description: 'The abbreviated name of the ability score (e.g., STR).' }) public name!: string - @field(() => T.List(T.Ref(Skill)), { + @field(() => T.RefList(Skill), { description: 'Skills associated with this ability score.' }) public skills!: APIReference[] - @field(() => T.String, { - description: 'The canonical path of this resource in the REST API.' - }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string @field(() => T.String, { description: 'Timestamp of the last update.' }) diff --git a/src/models/2014/alignment.ts b/src/models/2014/alignment.ts index 2c0089743..f86260ed2 100644 --- a/src/models/2014/alignment.ts +++ b/src/models/2014/alignment.ts @@ -26,9 +26,7 @@ export class Alignment { }) public name!: string - @field(() => T.String, { - description: 'The canonical path of this resource in the REST API.' - }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string @field(() => T.String, { description: 'Timestamp of the last update.' }) diff --git a/src/models/2014/background.ts b/src/models/2014/background.ts index 31812b524..2001a0267 100644 --- a/src/models/2014/background.ts +++ b/src/models/2014/background.ts @@ -24,7 +24,7 @@ class BackgroundFeature { @field(() => T.String, { description: 'The name of the background feature.' }) public name!: string - @field(() => T.List(T.String), { description: 'The description of the background feature.' }) + @field(() => T.List(String), { description: 'The description of the background feature.' }) public desc!: string[] } @@ -41,7 +41,7 @@ export class Background { @field(() => T.String, { description: 'The name of the background (e.g., Acolyte).' }) public name!: string - @field(() => T.List(T.Ref(Proficiency)), { + @field(() => T.RefList(Proficiency), { description: 'Proficiencies granted by this background at start.' }) public starting_proficiencies!: APIReference[] @@ -50,9 +50,7 @@ export class Background { @field(() => T.Model(Choice), { skipResolver: true }) public language_options!: Choice - @field(() => T.String, { - description: 'The canonical path of this resource in the REST API.' - }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string @field(() => T.List(EquipmentRef), { diff --git a/src/models/2014/class.ts b/src/models/2014/class.ts index 6ee0eeafd..f64701287 100644 --- a/src/models/2014/class.ts +++ b/src/models/2014/class.ts @@ -26,7 +26,7 @@ export class ClassEquipment { @ObjectType({ description: "Information about a class's spellcasting ability" }) export class SpellcastingInfo { - @field(() => T.List(T.String), { description: 'Description of the spellcasting ability.' }) + @field(() => T.List(String), { description: 'Description of the spellcasting ability.' }) public desc!: string[] @field(() => T.String, { description: 'Name of the spellcasting ability.' }) @@ -66,7 +66,7 @@ export class MultiClassing { @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public prerequisite_options?: Choice - @field(() => T.List(T.Ref(Proficiency)), { + @field(() => T.RefList(Proficiency), { description: 'Proficiencies gained when multi-classing into this class.', optional: true }) @@ -80,7 +80,7 @@ export class MultiClassing { @ObjectType({ description: 'Represents a character class (e.g., Barbarian, Wizard)' }) @srdModelOptions('2014-classes') export class Class { - @field(() => T.Link([[Level]]), { + @field(() => T.Link([Level]), { description: 'All levels for this class, detailing features and abilities gained.' }) public class_levels!: string @@ -99,7 +99,7 @@ export class Class { @field(() => T.String, { description: 'Name of the class' }) public name!: string - @field(() => T.List(T.Ref(Proficiency)), { + @field(() => T.RefList(Proficiency), { description: 'Base proficiencies granted by this class.' }) public proficiencies!: APIReference[] @@ -108,7 +108,7 @@ export class Class { @field(() => T.List(Choice), { skipResolver: true }) public proficiency_choices!: Choice[] - @field(() => T.List(T.Ref(AbilityScore)), { + @field(() => T.RefList(AbilityScore), { description: 'Saving throw proficiencies granted by this class.' }) public saving_throws!: APIReference[] @@ -129,14 +129,10 @@ export class Class { @field(() => T.List(Choice), { skipResolver: true }) public starting_equipment_options!: Choice[] - @field(() => T.List(T.Ref(Subclass)), { - description: 'Available subclasses for this class.' - }) + @field(() => T.RefList(Subclass), { description: 'Available subclasses for this class.' }) public subclasses!: APIReference[] - @field(() => T.String, { - description: 'The canonical path of this resource in the REST API.' - }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string @field(() => T.String, { description: 'Timestamp of the last update' }) diff --git a/src/models/2014/collection.ts b/src/models/2014/collection.ts index 0d0ce92f7..ea7474b46 100644 --- a/src/models/2014/collection.ts +++ b/src/models/2014/collection.ts @@ -1,11 +1,12 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' @srdModelOptions('2014-collections') export class Collection { - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { skipResolver: true }) public index!: string } diff --git a/src/models/2014/condition.ts b/src/models/2014/condition.ts index 31ca8b15f..49e304cf1 100644 --- a/src/models/2014/condition.ts +++ b/src/models/2014/condition.ts @@ -1,29 +1,28 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' @ObjectType({ description: 'A state that can affect a creature, such as Blinded or Prone.' }) @srdModelOptions('2014-conditions') export class Condition { - @Field(() => String, { description: 'The unique identifier for this condition (e.g., blinded).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { + description: 'The unique identifier for this condition (e.g., blinded).' + }) public index!: string - @Field(() => String, { description: 'The name of the condition (e.g., Blinded).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the condition (e.g., Blinded).' }) public name!: string - @Field(() => [String], { description: 'A description of the effects of the condition.' }) - @prop({ required: true, type: () => [String] }) + @field(() => T.List(String), { description: 'A description of the effects of the condition.' }) public desc!: string[] - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/damageType.ts b/src/models/2014/damageType.ts index 6c34aa3e9..04c849cdb 100644 --- a/src/models/2014/damageType.ts +++ b/src/models/2014/damageType.ts @@ -1,29 +1,28 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' @ObjectType({ description: 'Represents a type of damage (e.g., Acid, Bludgeoning, Fire).' }) @srdModelOptions('2014-damage-types') export class DamageType { - @Field(() => String, { description: 'The unique identifier for this damage type (e.g., acid).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { + description: 'The unique identifier for this damage type (e.g., acid).' + }) public index!: string - @Field(() => String, { description: 'The name of the damage type (e.g., Acid).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the damage type (e.g., Acid).' }) public name!: string - @Field(() => [String], { description: 'A description of the damage type.' }) - @prop({ required: true, type: () => [String] }) + @field(() => T.List(String), { description: 'A description of the damage type.' }) public desc!: string[] - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/equipment.ts b/src/models/2014/equipment.ts index c7f8ae841..f633e02d2 100644 --- a/src/models/2014/equipment.ts +++ b/src/models/2014/equipment.ts @@ -1,80 +1,71 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Float, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { IEquipment } from '@/graphql/2014/common/interfaces' import { EquipmentCategory } from '@/models/2014/equipmentCategory' import { APIReference } from '@/models/common/apiReference' import { Damage } from '@/models/common/damage' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' +import { WeaponProperty } from './weaponProperty' + @ObjectType({ description: 'Details about armor class.' }) export class ArmorClass { - @Field(() => Int, { description: 'Base armor class value.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Base armor class value.' }) public base!: number - @Field(() => Boolean, { description: 'Indicates if Dexterity bonus applies.' }) - @prop({ required: true, index: true, type: () => Boolean }) + @field(() => T.Bool, { description: 'Indicates if Dexterity bonus applies.' }) public dex_bonus!: boolean - @Field(() => Int, { nullable: true, description: 'Maximum Dexterity bonus allowed.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Maximum Dexterity bonus allowed.', optional: true }) public max_bonus?: number } @ObjectType({ description: 'An item and its quantity within a container or bundle.' }) export class Content { // Handled by ContentFieldResolver - @prop({ type: () => APIReference }) + @field(() => T.Ref(Equipment), { skipResolver: true }) public item!: APIReference - @Field(() => Int, { description: 'The quantity of the item.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The quantity of the item.' }) public quantity!: number } @ObjectType({ description: 'Cost of an item in coinage.' }) export class Cost { - @Field(() => Int, { description: 'The quantity of coins.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The quantity of coins.' }) public quantity!: number - @Field(() => String, { description: 'The unit of coinage (e.g., gp, sp, cp).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The unit of coinage (e.g., gp, sp, cp).' }) public unit!: string } @ObjectType({ description: 'Range of a weapon (normal and long).' }) export class Range { - @Field(() => Int, { nullable: true, description: 'The long range of the weapon.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'The long range of the weapon.', optional: true }) public long?: number - @Field(() => Int, { description: 'The normal range of the weapon.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The normal range of the weapon.' }) public normal!: number } @ObjectType({ description: 'Speed of a mount or vehicle.' }) export class Speed { - @Field(() => Float, { description: 'The speed quantity.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Float, { description: 'The speed quantity.' }) public quantity!: number - @Field(() => String, { description: 'The unit of speed (e.g., ft./round).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The unit of speed (e.g., ft./round).' }) public unit!: string } @ObjectType({ description: 'Range for a thrown weapon.' }) export class ThrowRange { - @Field(() => Int, { description: 'The long range when thrown.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The long range when thrown.' }) public long!: number - @Field(() => Int, { description: 'The normal range when thrown.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The normal range when thrown.' }) public normal!: number } @@ -85,104 +76,96 @@ export class ThrowRange { export class Equipment implements IEquipment { // General fields - @Field(() => String, { description: 'The unique identifier for this equipment.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The unique identifier for this equipment.' }) public index!: string - @Field(() => String, { description: 'The name of the equipment.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the equipment.' }) public name!: string - @Field(() => [String], { nullable: true, description: 'Description of the equipment.' }) - @prop({ required: true, index: true, type: () => [String] }) + @field(() => T.List(String), { description: 'Description of the equipment.', optional: true }) public desc?: string[] - @Field(() => EquipmentCategory, { description: 'The category this equipment belongs to.' }) - @prop({ type: () => APIReference }) + @field(() => T.Ref(EquipmentCategory), { description: 'The category this equipment belongs to.' }) public equipment_category!: APIReference - @Field(() => EquipmentCategory, { - nullable: true, - description: 'Category if the equipment is gear.' + @field(() => T.Ref(EquipmentCategory), { + description: 'Category if the equipment is gear.', + optional: true }) - @prop({ type: () => APIReference }) public gear_category?: APIReference - @Field(() => Cost, { description: 'Cost of the equipment in coinage.' }) - @prop({ type: () => Cost }) + @field(() => T.Model(Cost), { description: 'Cost of the equipment in coinage.' }) public cost!: Cost - @Field(() => Float, { nullable: true, description: 'Weight of the equipment in pounds.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Float, { description: 'Weight of the equipment in pounds.', optional: true }) public weight?: number + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) + public url!: string + + @field(() => T.String, { description: 'Timestamp of the last update.' }) + public updated_at!: string + // Specific fields - @prop({ index: true, type: () => String }) + @field(() => T.String, { skipResolver: true, optional: true }) public armor_category?: string - @prop({ type: () => ArmorClass }) + @field(() => T.Model(ArmorClass), { skipResolver: true, optional: true }) public armor_class?: ArmorClass - @prop({ index: true, type: () => String }) + @field(() => T.String, { skipResolver: true, optional: true }) public capacity?: string - @prop({ index: true, type: () => String }) + @field(() => T.String, { skipResolver: true, optional: true }) public category_range?: string - @prop({ type: () => [Content] }) + @field(() => T.List(Content), { skipResolver: true, optional: true }) public contents?: Content[] - @prop({ type: () => Damage }) + @field(() => T.Model(Damage), { skipResolver: true, optional: true }) public damage?: Damage - @prop({ index: true, type: () => String }) + @field(() => T.String, { skipResolver: true, optional: true }) public image?: string - @prop({ type: () => [APIReference] }) + @field(() => T.Ref(WeaponProperty), { skipResolver: true, optional: true }) public properties?: APIReference[] - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { skipResolver: true, optional: true }) public quantity?: number - @prop({ type: () => Range }) + @field(() => T.Model(Range), { skipResolver: true, optional: true }) public range?: Range - @prop({ index: true, type: () => [String] }) + @field(() => T.List(String), { skipResolver: true, optional: true }) public special?: string[] - @prop({ type: () => Speed }) + @field(() => T.Model(Speed), { skipResolver: true, optional: true }) public speed?: Speed - @prop({ index: true, type: () => Boolean }) + @field(() => T.Bool, { skipResolver: true, optional: true }) public stealth_disadvantage?: boolean - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { skipResolver: true, optional: true }) public str_minimum?: number - @prop({ type: () => ThrowRange }) + @field(() => T.Model(ThrowRange), { skipResolver: true, optional: true }) public throw_range?: ThrowRange - @prop({ index: true, type: () => String }) + @field(() => T.String, { skipResolver: true, optional: true }) public tool_category?: string - @prop({ type: () => Damage }) + @field(() => T.Model(Damage), { skipResolver: true, optional: true }) public two_handed_damage?: Damage - @prop({ required: true, index: true, type: () => String }) - public url!: string - - @prop({ index: true, type: () => String }) + @field(() => T.String, { skipResolver: true, optional: true }) public vehicle_category?: string - @prop({ index: true, type: () => String }) + @field(() => T.String, { skipResolver: true, optional: true }) public weapon_category?: string - @prop({ index: true, type: () => String }) + @field(() => T.String, { skipResolver: true, optional: true }) public weapon_range?: string - - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) - public updated_at!: string } export type EquipmentDocument = DocumentType diff --git a/src/models/2014/equipmentCategory.ts b/src/models/2014/equipmentCategory.ts index f11239ae7..cb4ea4c3c 100644 --- a/src/models/2014/equipmentCategory.ts +++ b/src/models/2014/equipmentCategory.ts @@ -1,32 +1,32 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' +import { Equipment } from './equipment' + @ObjectType({ description: 'A category for grouping equipment (e.g., Weapon, Armor, Adventuring Gear).' }) @srdModelOptions('2014-equipment-categories') export class EquipmentCategory { // Handled by EquipmentCategoryResolver - @prop({ type: () => [APIReference], index: true }) + @field(() => T.RefList(Equipment), { skipResolver: true }) public equipment!: APIReference[] - @Field(() => String, { description: 'The unique identifier for this category (e.g., weapon).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The unique identifier for this category (e.g., weapon).' }) public index!: string - @Field(() => String, { description: 'The name of the category (e.g., Weapon).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the category (e.g., Weapon).' }) public name!: string - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/feat.ts b/src/models/2014/feat.ts index 107d1a19e..5c9136c72 100644 --- a/src/models/2014/feat.ts +++ b/src/models/2014/feat.ts @@ -1,23 +1,24 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { AbilityScore } from './abilityScore' @ObjectType({ description: 'A prerequisite for taking a feat, usually a minimum ability score.' }) export class Prerequisite { - @Field(() => AbilityScore, { - nullable: true, - description: 'The ability score required for this prerequisite.' + @field(() => T.Ref(AbilityScore), { + description: 'The ability score required for this prerequisite.', + optional: true }) - @prop({ type: () => APIReference }) public ability_score!: APIReference - @Field(() => Int, { description: 'The minimum score required in the referenced ability score.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { + description: 'The minimum score required in the referenced ability score.' + }) public minimum_score!: number } @@ -26,27 +27,26 @@ export class Prerequisite { }) @srdModelOptions('2014-feats') export class Feat { - @Field(() => String, { description: 'The unique identifier for this feat (e.g., grappler).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The unique identifier for this feat (e.g., grappler).' }) public index!: string - @Field(() => String, { description: 'The name of the feat (e.g., Grappler).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the feat (e.g., Grappler).' }) public name!: string - @Field(() => [Prerequisite], { description: 'Prerequisites that must be met to take the feat.' }) - @prop({ type: () => [Prerequisite] }) + @field(() => T.List(Prerequisite), { + description: 'Prerequisites that must be met to take the feat.' + }) public prerequisites!: Prerequisite[] - @Field(() => [String], { description: 'A description of the benefits conferred by the feat.' }) - @prop({ required: true, index: true, type: () => [String] }) + @field(() => T.List(String), { + description: 'A description of the benefits conferred by the feat.' + }) public desc!: string[] - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/feature.ts b/src/models/2014/feature.ts index 3eeadd28c..648fb3226 100644 --- a/src/models/2014/feature.ts +++ b/src/models/2014/feature.ts @@ -1,9 +1,10 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { Choice } from '@/models/common/choice' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { Class } from './class' @@ -13,34 +14,28 @@ import { Subclass } from './subclass' // Export nested classes @ObjectType({ description: 'Prerequisite based on character level' }) export class LevelPrerequisite { - @Field(() => String, { description: 'Type indicator for this prerequisite.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Type indicator for this prerequisite.' }) public type!: string - @Field(() => Int, { description: 'The character level required.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The character level required.' }) public level!: number } @ObjectType({ description: 'Prerequisite based on having another feature' }) export class FeaturePrerequisite { - @Field(() => String, { description: 'Type indicator for this prerequisite.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Type indicator for this prerequisite.' }) public type!: string - @Field(() => Feature, { description: 'The specific feature required.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.Link(Feature), { description: 'The specific feature required.' }) public feature!: string } @ObjectType({ description: 'Prerequisite based on knowing a specific spell' }) export class SpellPrerequisite { - @Field(() => String, { description: 'Type indicator for this prerequisite.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Type indicator for this prerequisite.' }) public type!: string - @Field(() => Spell, { description: 'The specific spell required.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.Link(Spell), { description: 'The specific spell required.' }) public spell!: string } @@ -48,80 +43,72 @@ export type Prerequisite = LevelPrerequisite | FeaturePrerequisite | SpellPrereq @ObjectType({ description: 'Specific details related to a feature' }) export class FeatureSpecific { - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { skipResolver: true, optional: true }) public subfeature_options?: Choice - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { skipResolver: true, optional: true }) public expertise_options?: Choice - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { skipResolver: true, optional: true }) public terrain_type_options?: Choice - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { skipResolver: true, optional: true }) public enemy_type_options?: Choice - @Field(() => [Feature], { nullable: true, description: 'Invocations related to this feature.' }) - @prop({ type: () => [APIReference] }) + @field(() => T.RefList(Feature), { + description: 'Invocations related to this feature.', + optional: true + }) public invocations?: APIReference[] } @ObjectType({ description: 'Represents a class or subclass feature.' }) @srdModelOptions('2014-features') export class Feature { - @Field(() => Class, { nullable: true, description: 'The class that gains this feature.' }) - @prop({ type: () => APIReference }) + @field(() => T.Ref(Class), { description: 'The class that gains this feature.', optional: true }) public class!: APIReference - @Field(() => [String], { description: 'Description of the feature.' }) - @prop({ required: true, index: true, type: () => [String] }) + @field(() => T.List(String), { description: 'Description of the feature.' }) public desc!: string[] - @Field(() => Feature, { nullable: true, description: 'A parent feature, if applicable.' }) - @prop({ type: () => APIReference }) + @field(() => T.Ref(Feature), { description: 'A parent feature, if applicable.', optional: true }) public parent?: APIReference - @Field(() => String, { description: 'Unique identifier for this feature.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Unique identifier for this feature.' }) public index!: string - @Field(() => Int, { description: 'Level at which the feature is gained.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Level at which the feature is gained.' }) public level!: number - @Field(() => String, { description: 'Name of the feature.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Name of the feature.' }) public name!: string // Handled by FeatureResolver - @prop({ type: () => [Object] }) + @field(() => T.List(Object), { skipResolver: true }) public prerequisites?: Prerequisite[] - @Field(() => String, { - nullable: true, - description: 'Reference information (e.g., book and page number).' + @field(() => T.String, { + description: 'Reference information (e.g., book and page number).', + optional: true }) - @prop({ index: true, type: () => String }) public reference?: string - @Field(() => Subclass, { - nullable: true, - description: 'The subclass that gains this feature, if applicable.' + @field(() => T.Ref(Subclass), { + description: 'The subclass that gains this feature, if applicable.', + optional: true }) - @prop({ type: () => APIReference }) public subclass?: APIReference - @Field(() => FeatureSpecific, { - nullable: true, - description: 'Specific details for this feature, if applicable.' + @field(() => T.Model(FeatureSpecific), { + description: 'Specific details for this feature, if applicable.', + optional: true }) - @prop({ type: () => FeatureSpecific }) public feature_specific?: FeatureSpecific - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/language.ts b/src/models/2014/language.ts index 11a49031b..e6992e7ad 100644 --- a/src/models/2014/language.ts +++ b/src/models/2014/language.ts @@ -1,44 +1,38 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' @ObjectType({ description: 'Represents a language spoken in the D&D world.' }) @srdModelOptions('2014-languages') export class Language { - @Field(() => String, { nullable: true, description: 'A brief description of the language.' }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: 'A brief description of the language.', optional: true }) public desc?: string - @Field(() => String, { description: 'The unique identifier for this language (e.g., common).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The unique identifier for this language (e.g., common).' }) public index!: string - @Field(() => String, { description: 'The name of the language (e.g., Common).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the language (e.g., Common).' }) public name!: string - @Field(() => String, { - nullable: true, - description: 'The script used to write the language (e.g., Common, Elvish).' + @field(() => T.String, { + description: 'The script used to write the language (e.g., Common, Elvish).', + optional: true }) - @prop({ index: true, type: () => String }) public script?: string - @Field(() => String, { description: 'The type of language (e.g., Standard, Exotic).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The type of language (e.g., Standard, Exotic).' }) public type!: string - @Field(() => [String], { description: 'Typical speakers of the language.' }) - @prop({ type: () => [String], index: true }) + @field(() => T.List(String), { description: 'Typical speakers of the language.' }) public typical_speakers!: string[] - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/level.ts b/src/models/2014/level.ts index 081e79e51..3f87667b8 100644 --- a/src/models/2014/level.ts +++ b/src/models/2014/level.ts @@ -1,8 +1,9 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Float, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { Class } from './class' @@ -12,295 +13,250 @@ import { Subclass } from './subclass' // Export nested classes @ObjectType({ description: 'Spell slot creation details for Sorcerer levels' }) export class ClassSpecificCreatingSpellSlot { - @Field(() => Int, { description: 'Cost in sorcery points.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Cost in sorcery points.' }) public sorcery_point_cost!: number - @Field(() => Int, { description: 'Level of the spell slot created.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Level of the spell slot created.' }) public spell_slot_level!: number } @ObjectType({ description: 'Martial arts details for Monk levels' }) export class ClassSpecificMartialArt { - @Field(() => Int, { description: 'Number of dice for martial arts damage.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of dice for martial arts damage.' }) public dice_count!: number - @Field(() => Int, { description: 'Value of the dice used (e.g., 4 for d4).' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Value of the dice used (e.g., 4 for d4).' }) public dice_value!: number } @ObjectType({ description: 'Sneak attack details for Rogue levels' }) export class ClassSpecificSneakAttack { - @Field(() => Int, { description: 'Number of dice for sneak attack damage.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of dice for sneak attack damage.' }) public dice_count!: number - @Field(() => Int, { description: 'Value of the dice used (e.g., 6 for d6).' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Value of the dice used (e.g., 6 for d6).' }) public dice_value!: number } @ObjectType({ description: 'Class-specific features and values gained at a level' }) export class ClassSpecific { - @Field(() => Int, { nullable: true, description: 'Number of Action Surges available.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of Action Surges available.', optional: true }) public action_surges?: number - @Field(() => Int, { - nullable: true, - description: 'Maximum spell level recoverable via Arcane Recovery.' + @field(() => T.Int, { + description: 'Maximum spell level recoverable via Arcane Recovery.', + optional: true }) - @prop({ index: true, type: () => Number }) public arcane_recovery_levels?: number - @Field(() => Int, { nullable: true, description: 'Range of Paladin auras in feet.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Range of Paladin auras in feet.', optional: true }) public aura_range?: number - @Field(() => Int, { - nullable: true, - description: 'Die size for Bardic Inspiration (e.g., 6 for d6).' + @field(() => T.Int, { + description: 'Die size for Bardic Inspiration (e.g., 6 for d6).', + optional: true }) - @prop({ index: true, type: () => Number }) public bardic_inspiration_die?: number - @Field(() => Int, { - nullable: true, - description: "Number of extra damage dice for Barbarian's Brutal Critical." + @field(() => T.Int, { + description: "Number of extra damage dice for Barbarian's Brutal Critical.", + optional: true }) - @prop({ index: true, type: () => Number }) public brutal_critical_dice?: number - @Field(() => Int, { nullable: true, description: 'Number of uses for Channel Divinity.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of uses for Channel Divinity.', optional: true }) public channel_divinity_charges?: number - @Field(() => [ClassSpecificCreatingSpellSlot], { - nullable: true, - description: 'Sorcerer spell slot creation options.' + @field(() => T.List(ClassSpecificCreatingSpellSlot), { + description: 'Sorcerer spell slot creation options.', + optional: true }) - @prop({ type: () => [ClassSpecificCreatingSpellSlot], default: undefined }) public creating_spell_slots?: ClassSpecificCreatingSpellSlot[] - @Field(() => Float, { - nullable: true, - description: 'Maximum Challenge Rating of undead that can be destroyed by Channel Divinity.' + @field(() => T.Float, { + description: 'Maximum Challenge Rating of undead that can be destroyed by Channel Divinity.', + optional: true }) - @prop({ index: true, type: () => Number }) public destroy_undead_cr?: number - @Field(() => Int, { nullable: true, description: 'Number of extra attacks granted.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of extra attacks granted.', optional: true }) public extra_attacks?: number - @Field(() => Int, { nullable: true, description: 'Number of favored enemies known by Ranger.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of favored enemies known by Ranger.', optional: true }) public favored_enemies?: number - @Field(() => Int, { nullable: true, description: 'Number of favored terrains known by Ranger.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { + description: 'Number of favored terrains known by Ranger.', + optional: true + }) public favored_terrain?: number - @Field(() => Int, { - nullable: true, - description: "Number of uses for Fighter's Indomitable feature." + @field(() => T.Int, { + description: "Number of uses for Fighter's Indomitable feature.", + optional: true }) - @prop({ index: true, type: () => Number }) public indomitable_uses?: number - @Field(() => Int, { nullable: true, description: 'Number of Warlock invocations known.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of Warlock invocations known.', optional: true }) public invocations_known?: number - @Field(() => Int, { nullable: true, description: 'Number of Monk ki points.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of Monk ki points.', optional: true }) public ki_points?: number - @Field(() => Int, { - nullable: true, - description: "Maximum level of spells gained via Bard's Magical Secrets (up to level 5)." + @field(() => T.Int, { + description: "Maximum level of spells gained via Bard's Magical Secrets (up to level 5).", + optional: true }) - @prop({ index: true, type: () => Number }) public magical_secrets_max_5?: number - @Field(() => Int, { - nullable: true, - description: "Maximum level of spells gained via Bard's Magical Secrets (up to level 7)." + @field(() => T.Int, { + description: "Maximum level of spells gained via Bard's Magical Secrets (up to level 7).", + optional: true }) - @prop({ index: true, type: () => Number }) public magical_secrets_max_7?: number - @Field(() => Int, { - nullable: true, - description: "Maximum level of spells gained via Bard's Magical Secrets (up to level 9)." + @field(() => T.Int, { + description: "Maximum level of spells gained via Bard's Magical Secrets (up to level 9).", + optional: true }) - @prop({ index: true, type: () => Number }) public magical_secrets_max_9?: number - @Field(() => ClassSpecificMartialArt, { - nullable: true, - description: 'Monk martial arts damage progression.' + @field(() => T.Model(ClassSpecificMartialArt), { + description: 'Monk martial arts damage progression.', + optional: true }) - @prop({ type: () => ClassSpecificMartialArt }) public martial_arts?: ClassSpecificMartialArt - @Field(() => Int, { nullable: true, description: 'Number of Sorcerer metamagic options known.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { + description: 'Number of Sorcerer metamagic options known.', + optional: true + }) public metamagic_known?: number - @Field(() => Int, { - nullable: true, - description: 'Indicates if Warlock gained level 6 Mystic Arcanum (1 = yes).' + @field(() => T.Int, { + description: 'Indicates if Warlock gained level 6 Mystic Arcanum (1 = yes).', + optional: true }) - @prop({ index: true, type: () => Number }) public mystic_arcanum_level_6?: number - @Field(() => Int, { - nullable: true, - description: 'Indicates if Warlock gained level 7 Mystic Arcanum (1 = yes).' + @field(() => T.Int, { + description: 'Indicates if Warlock gained level 7 Mystic Arcanum (1 = yes).', + optional: true }) - @prop({ index: true, type: () => Number }) public mystic_arcanum_level_7?: number - @Field(() => Int, { - nullable: true, - description: 'Indicates if Warlock gained level 8 Mystic Arcanum (1 = yes).' + @field(() => T.Int, { + description: 'Indicates if Warlock gained level 8 Mystic Arcanum (1 = yes).', + optional: true }) - @prop({ index: true, type: () => Number }) public mystic_arcanum_level_8?: number - @Field(() => Int, { - nullable: true, - description: 'Indicates if Warlock gained level 9 Mystic Arcanum (1 = yes).' + @field(() => T.Int, { + description: 'Indicates if Warlock gained level 9 Mystic Arcanum (1 = yes).', + optional: true }) - @prop({ index: true, type: () => Number }) public mystic_arcanum_level_9?: number - @Field(() => Int, { nullable: true, description: 'Number of Barbarian rages per long rest.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of Barbarian rages per long rest.', optional: true }) public rage_count?: number - @Field(() => Int, { - nullable: true, - description: 'Damage bonus added to Barbarian rage attacks.' + @field(() => T.Int, { + description: 'Damage bonus added to Barbarian rage attacks.', + optional: true }) - @prop({ index: true, type: () => Number }) public rage_damage_bonus?: number - @Field(() => ClassSpecificSneakAttack, { - nullable: true, - description: 'Rogue sneak attack damage progression.' + @field(() => T.Model(ClassSpecificSneakAttack), { + description: 'Rogue sneak attack damage progression.', + optional: true }) - @prop({ type: () => ClassSpecificSneakAttack }) public sneak_attack?: ClassSpecificSneakAttack - @Field(() => Int, { - nullable: true, - description: "Die size for Bard's Song of Rest (e.g., 6 for d6)." + @field(() => T.Int, { + description: "Die size for Bard's Song of Rest (e.g., 6 for d6).", + optional: true }) - @prop({ index: true, type: () => Number }) public song_of_rest_die?: number - @Field(() => Int, { nullable: true, description: 'Number of Sorcerer sorcery points.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of Sorcerer sorcery points.', optional: true }) public sorcery_points?: number - @Field(() => Int, { - nullable: true, - description: "Bonus speed for Monk's Unarmored Movement in feet." + @field(() => T.Int, { + description: "Bonus speed for Monk's Unarmored Movement in feet.", + optional: true }) - @prop({ index: true, type: () => Number }) public unarmored_movement?: number - @Field(() => Boolean, { - nullable: true, - description: "Indicates if Druid's Wild Shape allows flying." + @field(() => T.Bool, { + description: "Indicates if Druid's Wild Shape allows flying.", + optional: true }) - @prop({ index: true, type: () => Boolean }) public wild_shape_fly?: boolean - @Field(() => Float, { - nullable: true, - description: "Maximum Challenge Rating for Druid's Wild Shape form." + @field(() => T.Float, { + description: "Maximum Challenge Rating for Druid's Wild Shape form.", + optional: true }) - @prop({ index: true, type: () => Number }) public wild_shape_max_cr?: number - @Field(() => Boolean, { - nullable: true, - description: "Indicates if Druid's Wild Shape allows swimming." + @field(() => T.Bool, { + description: "Indicates if Druid's Wild Shape allows swimming.", + optional: true }) - @prop({ index: true, type: () => Boolean }) public wild_shape_swim?: boolean } @ObjectType({ description: 'Spellcasting details for a class at a specific level' }) export class LevelSpellcasting { - @Field(() => Int, { nullable: true, description: 'Number of cantrips known.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of cantrips known.', optional: true }) public cantrips_known?: number - @Field(() => Int, { description: 'Number of level 1 spell slots.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of level 1 spell slots.' }) public spell_slots_level_1!: number - @Field(() => Int, { description: 'Number of level 2 spell slots.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of level 2 spell slots.' }) public spell_slots_level_2!: number - @Field(() => Int, { description: 'Number of level 3 spell slots.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of level 3 spell slots.' }) public spell_slots_level_3!: number - @Field(() => Int, { description: 'Number of level 4 spell slots.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of level 4 spell slots.' }) public spell_slots_level_4!: number - @Field(() => Int, { description: 'Number of level 5 spell slots.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of level 5 spell slots.' }) public spell_slots_level_5!: number - @Field(() => Int, { nullable: true, description: 'Number of level 6 spell slots.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of level 6 spell slots.', optional: true }) public spell_slots_level_6?: number - @Field(() => Int, { nullable: true, description: 'Number of level 7 spell slots.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of level 7 spell slots.', optional: true }) public spell_slots_level_7?: number - @Field(() => Int, { nullable: true, description: 'Number of level 8 spell slots.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of level 8 spell slots.', optional: true }) public spell_slots_level_8?: number - @Field(() => Int, { nullable: true, description: 'Number of level 9 spell slots.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of level 9 spell slots.', optional: true }) public spell_slots_level_9?: number - @Field(() => Int, { - nullable: true, - description: 'Total number of spells known (for certain classes like Sorcerer).' + @field(() => T.Int, { + description: 'Total number of spells known (for certain classes like Sorcerer).', + optional: true }) - @prop({ index: true, type: () => Number }) public spells_known?: number } @ObjectType({ description: 'Subclass-specific features and values gained at a level' }) export class SubclassSpecific { - @Field(() => Int, { - nullable: true, - description: "Maximum level of spells gained via Bard's Additional Magical Secrets." + @field(() => T.Int, { + description: "Maximum level of spells gained via Bard's Additional Magical Secrets.", + optional: true }) - @prop({ index: true, type: () => Number }) public additional_magical_secrets_max_lvl?: number - @Field(() => Int, { - nullable: true, - description: 'Range of subclass-specific auras (e.g., Paladin) in feet.' + @field(() => T.Int, { + description: 'Range of subclass-specific auras (e.g., Paladin) in feet.', + optional: true }) - @prop({ index: true, type: () => Number }) public aura_range?: number } @@ -309,69 +265,60 @@ export class SubclassSpecific { }) @srdModelOptions('2014-levels') export class Level { - @Field(() => Int, { - nullable: true, - description: 'Number of ability score bonuses gained at this level' + @field(() => T.Int, { + description: 'Number of ability score bonuses gained at this level', + optional: true }) - @prop({ index: true, type: () => Number }) public ability_score_bonuses?: number - @Field(() => Class, { nullable: true, description: 'The class this level belongs to.' }) - @prop({ type: () => APIReference }) + @field(() => T.Ref(Class), { description: 'The class this level belongs to.' }) public class!: APIReference - @Field(() => ClassSpecific, { - nullable: true, - description: 'Class-specific details for this level.' + @field(() => T.Model(ClassSpecific), { + description: 'Class-specific details for this level.', + optional: true }) - @prop({ type: () => ClassSpecific }) public class_specific?: ClassSpecific - @Field(() => [Feature], { nullable: true, description: 'Features gained at this level.' }) - @prop({ type: () => [APIReference] }) + @field(() => T.RefList(Feature), { + description: 'Features gained at this level.', + optional: true + }) public features?: APIReference[] - @Field(() => String, { + @field(() => T.String, { description: 'Unique identifier for this level (e.g., barbarian-1, rogue-20)' }) - @prop({ required: true, index: true, type: () => String }) public index!: string - @Field(() => Int, { description: 'The class level (1-20)' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The class level (1-20)' }) public level!: number - @Field(() => Int, { nullable: true, description: 'Proficiency bonus gained at this level' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'Proficiency bonus gained at this level', optional: true }) public prof_bonus?: number - @Field(() => LevelSpellcasting, { - nullable: true, - description: 'Spellcasting progression details for this level.' + @field(() => T.Model(LevelSpellcasting), { + description: 'Spellcasting progression details for this level.', + optional: true }) - @prop({ type: () => LevelSpellcasting }) public spellcasting?: LevelSpellcasting - @Field(() => Subclass, { - nullable: true, - description: 'The subclass this level relates to, if applicable.' + @field(() => T.Ref(Subclass), { + description: 'The subclass this level relates to, if applicable.', + optional: true }) - @prop({ type: () => APIReference }) public subclass?: APIReference - @Field(() => SubclassSpecific, { - nullable: true, - description: 'Subclass-specific details for this level.' + @field(() => T.Model(SubclassSpecific), { + description: 'Subclass-specific details for this level.', + optional: true }) - @prop({ type: () => SubclassSpecific }) public subclass_specific?: SubclassSpecific // url field is not exposed via GraphQL - @prop({ required: true, index: true, type: () => String }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update' }) public updated_at!: string } diff --git a/src/models/2014/magicItem.ts b/src/models/2014/magicItem.ts index 7715724be..950f3c836 100644 --- a/src/models/2014/magicItem.ts +++ b/src/models/2014/magicItem.ts @@ -1,76 +1,67 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { EquipmentCategory } from './equipmentCategory' @ObjectType({ description: 'Rarity level of a magic item.' }) export class Rarity { - @Field(() => String, { + @field(() => T.String, { description: 'The name of the rarity level (e.g., Common, Uncommon, Rare).' }) - @prop({ required: true, index: true, type: () => String }) public name!: string } @ObjectType({ description: 'An item imbued with magical properties.' }) @srdModelOptions('2014-magic-items') export class MagicItem { - @Field(() => [String], { + @field(() => T.List(String), { description: 'A description of the magic item, including its effects and usage.' }) - @prop({ type: () => [String], index: true }) public desc!: string[] - @Field(() => EquipmentCategory, { + @field(() => T.Ref(EquipmentCategory), { description: 'The category of equipment this magic item belongs to.' }) - @prop({ type: () => APIReference, index: true }) public equipment_category!: APIReference - @Field(() => String, { - nullable: true, - description: 'URL of an image for the magic item, if available.' + @field(() => T.String, { + description: 'URL of an image for the magic item, if available.', + optional: true }) - @prop({ type: () => String, index: true }) public image?: string - @Field(() => String, { + @field(() => T.String, { description: 'The unique identifier for this magic item (e.g., adamantite-armor).' }) - @prop({ required: true, index: true, type: () => String }) public index!: string - @Field(() => String, { description: 'The name of the magic item (e.g., Adamantite Armor).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the magic item (e.g., Adamantite Armor).' }) public name!: string - @Field(() => Rarity, { description: 'The rarity of the magic item.' }) - @prop({ required: true, index: true, type: () => Rarity }) + @field(() => T.Model(Rarity), { description: 'The rarity of the magic item.' }) public rarity!: Rarity - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => [MagicItem], { - nullable: true, - description: 'Other magic items that are variants of this item.' + @field(() => T.String, { description: 'Timestamp of the last update.' }) + public updated_at!: string + + @field(() => T.RefList(MagicItem), { + description: 'Other magic items that are variants of this item.', + optional: true }) - @prop({ type: () => [APIReference], index: true }) - public variants!: APIReference[] + public variants?: APIReference[] - @Field(() => Boolean, { + @field(() => T.Bool, { description: 'Indicates if this magic item is a variant of another item.' }) - @prop({ required: true, index: true, type: () => Boolean }) public variant!: boolean - - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) - public updated_at!: string } export type MagicItemDocument = DocumentType diff --git a/src/models/2014/magicSchool.ts b/src/models/2014/magicSchool.ts index 9aa97e924..09ecd922e 100644 --- a/src/models/2014/magicSchool.ts +++ b/src/models/2014/magicSchool.ts @@ -1,7 +1,8 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' @ObjectType({ @@ -9,23 +10,21 @@ import { srdModelOptions } from '@/util/modelOptions' }) @srdModelOptions('2014-magic-schools') export class MagicSchool { - @Field(() => String, { description: 'A brief description of the school of magic.' }) - @prop({ type: () => String, index: true }) + @field(() => T.String, { description: 'A brief description of the school of magic.' }) public desc!: string - @Field(() => String, { description: 'The unique identifier for this school (e.g., evocation).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { + description: 'The unique identifier for this school (e.g., evocation).' + }) public index!: string - @Field(() => String, { description: 'The name of the school (e.g., Evocation).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the school (e.g., Evocation).' }) public name!: string - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/proficiency.ts b/src/models/2014/proficiency.ts index d4b62b905..0a3dcfca8 100644 --- a/src/models/2014/proficiency.ts +++ b/src/models/2014/proficiency.ts @@ -1,8 +1,9 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { Class } from './class' @@ -13,36 +14,36 @@ import { Race } from './race' }) @srdModelOptions('2014-proficiencies') export class Proficiency { - @Field(() => [Class], { nullable: true, description: 'Classes that grant this proficiency.' }) - @prop({ type: () => [APIReference] }) + @field(() => T.RefList(Class), { + description: 'Classes that grant this proficiency.', + optional: true + }) public classes?: APIReference[] - @Field(() => String, { description: 'Unique identifier for this proficiency.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Unique identifier for this proficiency.' }) public index!: string - @Field(() => String, { description: 'Name of the proficiency.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Name of the proficiency.' }) public name!: string - @Field(() => [Race], { nullable: true, description: 'Races that grant this proficiency.' }) - @prop({ type: () => [APIReference] }) + @field(() => T.RefList(Race), { + description: 'Races that grant this proficiency.', + optional: true + }) public races?: APIReference[] - @prop({ type: () => APIReference }) + @field(() => T.Ref(Proficiency), { skipResolver: true }) public reference!: APIReference - @Field(() => String, { + @field(() => T.String, { description: 'Category of proficiency (e.g., Armor, Weapons, Saving Throws, Skills).' }) - @prop({ required: true, index: true, type: () => String }) public type!: string - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update' }) public updated_at!: string } diff --git a/src/util/fieldDectorator.ts b/src/util/fieldDectorator.ts index beff7488b..8d6458940 100644 --- a/src/util/fieldDectorator.ts +++ b/src/util/fieldDectorator.ts @@ -9,7 +9,12 @@ import { } from '@typegoose/typegoose/lib/types' import { SchemaTypes } from 'mongoose' import { ReturnTypeFuncValue } from 'node_modules/type-graphql/build/typings/decorators/types' -import { Field, Int as GqlInt, FieldOptions as TypeGraphQLFieldOptions } from 'type-graphql' +import { + Field, + Int as GqlInt, + Float as GqlFloat, + FieldOptions as TypeGraphQLFieldOptions +} from 'type-graphql' import { APIReference } from '@/models/common/apiReference' @@ -79,6 +84,14 @@ function Ref(type: ReturnTypeFuncValue): TypeObject { return { db: APIReference, gql: type } } +/** + * Creates a field type that is represented as an array of APIReference objects in the database, but + * as a list of the given type in the GraphQL API + */ +function RefList(type: ReturnTypeFuncValue): TypeObject { + return { db: [APIReference], gql: [type] } +} + /** * Creates a field type that is represented as an array of a given type in both the database and the * GraphQL API @@ -107,9 +120,12 @@ function Link(type: ReturnTypeFuncValue): TypeObject { } export const T = { - String: { db: String, gql: String }, + String: { db: SchemaTypes.String, gql: String }, Int: { db: SchemaTypes.Int32, gql: GqlInt }, + Float: { db: SchemaTypes.Double, gql: GqlFloat }, + Bool: { db: SchemaTypes.Boolean, gql: Boolean }, Ref, + RefList, List, Model, Link From 3a6fa3aa0a2a98a5693810815003220eb0929b2b Mon Sep 17 00:00:00 2001 From: faewd Date: Sat, 21 Jun 2025 10:57:09 +0100 Subject: [PATCH 5/7] Fix failing tests --- src/models/2014/equipment.ts | 2 +- src/models/2014/level.ts | 2 +- src/tests/controllers/api/2014/subclassController.test.ts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/models/2014/equipment.ts b/src/models/2014/equipment.ts index f633e02d2..0dade8ef3 100644 --- a/src/models/2014/equipment.ts +++ b/src/models/2014/equipment.ts @@ -128,7 +128,7 @@ export class Equipment implements IEquipment { @field(() => T.String, { skipResolver: true, optional: true }) public image?: string - @field(() => T.Ref(WeaponProperty), { skipResolver: true, optional: true }) + @field(() => T.RefList(WeaponProperty), { skipResolver: true, optional: true }) public properties?: APIReference[] @field(() => T.Int, { skipResolver: true, optional: true }) diff --git a/src/models/2014/level.ts b/src/models/2014/level.ts index 3f87667b8..56a9cb84b 100644 --- a/src/models/2014/level.ts +++ b/src/models/2014/level.ts @@ -315,7 +315,7 @@ export class Level { }) public subclass_specific?: SubclassSpecific - // url field is not exposed via GraphQL + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string @field(() => T.String, { description: 'Timestamp of the last update' }) diff --git a/src/tests/controllers/api/2014/subclassController.test.ts b/src/tests/controllers/api/2014/subclassController.test.ts index f1516f1d2..9d75bae67 100644 --- a/src/tests/controllers/api/2014/subclassController.test.ts +++ b/src/tests/controllers/api/2014/subclassController.test.ts @@ -178,6 +178,8 @@ describe('SubclassController', () => { expect(responseData).toBeInstanceOf(Array) expect(responseData).toHaveLength(3) // Only the 3 levels for this subclass // Check if the returned levels match the ones created for the subclass + console.log("##DATA") + console.log(responseData) expect(responseData).toEqual( expect.arrayContaining([ expect.objectContaining({ From ee35fa8b11489ffb2d274d8da5ce1e808732ec9f Mon Sep 17 00:00:00 2001 From: faewd Date: Sat, 21 Jun 2025 19:41:34 +0100 Subject: [PATCH 6/7] The rest of the frickin owl --- src/models/2014/monster.ts | 461 ++++++++---------- src/models/2014/race.ts | 77 ++- src/models/2014/rule.ts | 24 +- src/models/2014/ruleSection.ts | 19 +- src/models/2014/skill.ts | 25 +- src/models/2014/spell.ts | 113 ++--- src/models/2014/subclass.ts | 49 +- src/models/2014/subrace.ts | 63 +-- src/models/2014/trait.ts | 109 ++--- src/models/2014/weaponProperty.ts | 19 +- .../api/2014/subclassController.test.ts | 2 - src/util/fieldDectorator.ts | 2 +- 12 files changed, 413 insertions(+), 550 deletions(-) diff --git a/src/models/2014/monster.ts b/src/models/2014/monster.ts index 8f49ac771..842d8e0c9 100644 --- a/src/models/2014/monster.ts +++ b/src/models/2014/monster.ts @@ -1,580 +1,519 @@ -import { getModelForClass, modelOptions, prop, Severity } from '@typegoose/typegoose' +import { getModelForClass, modelOptions, Severity } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Float, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { Choice } from '@/models/common/choice' import { Damage } from '@/models/common/damage' import { DifficultyClass } from '@/models/common/difficultyClass' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { AbilityScore } from './abilityScore' import { Condition } from './condition' +import { Equipment } from './equipment' import { Proficiency } from './proficiency' import { Spell } from './spell' // Export all nested classes/types @ObjectType({ description: 'Option within a monster action' }) export class ActionOption { - @Field(() => String, { description: 'The name of the action.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the action.' }) public action_name!: string - @Field(() => String, { description: 'Number of times the action can be used.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Number of times the action can be used.' }) public count!: number | string - @Field(() => String, { description: 'The type of action.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The type of action.' }) public type!: 'melee' | 'ranged' | 'ability' | 'magic' } @ObjectType({ description: 'Usage details for a monster action or ability' }) export class ActionUsage { - @Field(() => String, { description: 'The type of action usage.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The type of action usage.' }) public type!: string - @Field(() => String, { nullable: true, description: 'The dice roll for the action usage.' }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: 'The dice roll for the action usage.', optional: true }) public dice?: string - @Field(() => Int, { nullable: true, description: 'The minimum value for the action usage.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.String, { description: 'The minimum value for the action usage.', optional: true }) public min_value?: number } @ObjectType({ description: 'An action a monster can perform' }) export class MonsterAction { - @Field(() => String, { description: 'The name of the action.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the action.' }) public name!: string - @Field(() => String, { description: 'The description of the action.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The description of the action.' }) public desc!: string - @Field(() => Int, { nullable: true, description: 'The attack bonus for the action.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'The attack bonus for the action.', optional: true }) public attack_bonus?: number // Handled by MonsterActionResolver - @prop({ type: () => [Object] }) + @field(() => T.List(Object), { skipResolver: true }) public damage?: (Damage | Choice)[] - @Field(() => DifficultyClass, { - nullable: true, - description: 'The difficulty class for the action.' + @field(() => T.Model(DifficultyClass), { + description: 'The difficulty class for the action.', + optional: true }) - @prop({ type: () => DifficultyClass }) public dc?: DifficultyClass // Handled by MonsterActionResolver - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public options?: Choice - @Field(() => ActionUsage, { nullable: true, description: 'The usage for the action.' }) - @prop({ type: () => ActionUsage }) + @field(() => T.Model(ActionUsage), { description: 'The usage for the action.', optional: true }) public usage?: ActionUsage - @Field(() => String, { nullable: true, description: 'The type of multiattack for the action.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The type of multiattack for the action.', optional: true }) public multiattack_type?: 'actions' | 'action_options' - @Field(() => [ActionOption], { nullable: true, description: 'The actions for the action.' }) - @prop({ type: () => [ActionOption] }) + @field(() => T.List(ActionOption), { description: 'The actions for the action.', optional: true }) public actions?: ActionOption[] // Handled by MonsterActionResolver - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { skipResolver: true }) public action_options?: Choice } +type ArmorClass = + | ArmorClassDex + | ArmorClassNatural + | ArmorClassArmor + | ArmorClassSpell + | ArmorClassCondition + @ObjectType({ description: 'Monster Armor Class component: Dexterity based' }) export class ArmorClassDex { - @Field(() => String, { description: "Type of AC component: 'dex'" }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "Type of AC component: 'dex'" }) public type!: 'dex' - @Field(() => Int, { description: 'AC value from dexterity.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'AC value from dexterity.' }) public value!: number - @Field(() => String, { - nullable: true, - description: 'Optional description for this AC component.' + @field(() => T.String, { + description: 'Optional description for this AC component.', + optional: true }) - @prop({ index: true, type: () => String }) public desc?: string } @ObjectType({ description: 'Monster Armor Class component: Natural armor' }) export class ArmorClassNatural { - @Field(() => String, { description: "Type of AC component: 'natural'" }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "Type of AC component: 'natural'" }) public type!: 'natural' - @Field(() => Int, { description: 'AC value from natural armor.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'AC value from natural armor.' }) public value!: number - @Field(() => String, { - nullable: true, - description: 'Optional description for this AC component.' + @field(() => T.String, { + description: 'Optional description for this AC component.', + optional: true }) - @prop({ index: true, type: () => String }) public desc?: string } @ObjectType({ description: 'Monster Armor Class component: Armor worn' }) export class ArmorClassArmor { - @Field(() => String, { description: "Type of AC component: 'armor'" }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "Type of AC component: 'armor'" }) public type!: 'armor' - @Field(() => Int, { description: 'AC value from worn armor.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'AC value from worn armor.' }) public value!: number // Handled by MonsterArmorClassResolver - @prop({ type: () => [APIReference] }) + @field(() => T.Ref(Equipment), { skipResolver: true }) public armor?: APIReference[] - @Field(() => String, { - nullable: true, - description: 'Optional description for this AC component.' + @field(() => T.String, { + description: 'Optional description for this AC component.', + optional: true }) - @prop({ index: true, type: () => String }) public desc?: string } @ObjectType({ description: 'Monster Armor Class component: Spell effect' }) export class ArmorClassSpell { - @Field(() => String, { description: "Type of AC component: 'spell'" }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "Type of AC component: 'spell'" }) public type!: 'spell' - @Field(() => Int, { description: 'AC value from spell effect.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'AC value from spell effect.' }) public value!: number - @Field(() => Spell, { + @field(() => T.Ref(Spell), { description: 'The spell providing the AC bonus. Resolved via resolver.' }) - @prop({ type: () => APIReference }) public spell!: APIReference - @Field(() => String, { - nullable: true, - description: 'Optional description for this AC component.' + @field(() => T.String, { + description: 'Optional description for this AC component.', + optional: true }) - @prop({ index: true, type: () => String }) public desc?: string } @ObjectType({ description: 'Monster Armor Class component: Condition effect' }) export class ArmorClassCondition { - @Field(() => String, { description: "Type of AC component: 'condition'" }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "Type of AC component: 'condition'" }) public type!: 'condition' - @Field(() => Int, { description: 'AC value from condition effect.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'AC value from condition effect.' }) public value!: number - @Field(() => Condition, { + @field(() => T.Ref(Condition), { description: 'The condition providing the AC bonus. Resolved via resolver.' }) - @prop({ type: () => APIReference }) public condition!: APIReference - @Field(() => String, { - nullable: true, - description: 'Optional description for this AC component.' + @field(() => T.String, { + description: 'Optional description for this AC component.', + optional: true }) - @prop({ index: true, type: () => String }) public desc?: string } @ObjectType({ description: 'A legendary action a monster can perform' }) export class LegendaryAction { - @Field(() => String, { description: 'The name of the legendary action.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the legendary action.' }) public name!: string - @Field(() => String, { description: 'The description of the legendary action.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The description of the legendary action.' }) public desc!: string - @Field(() => Int, { nullable: true, description: 'The attack bonus for the legendary action.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'The attack bonus for the legendary action.', optional: true }) public attack_bonus?: number - @Field(() => [Damage], { nullable: true, description: 'The damage for the legendary action.' }) - @prop({ type: () => [Damage] }) + @field(() => T.List(Damage), { + description: 'The damage for the legendary action.', + optional: true + }) public damage?: Damage[] - @Field(() => DifficultyClass, { - nullable: true, - description: 'The difficulty class for the legendary action.' + @field(() => T.Model(DifficultyClass), { + description: 'The difficulty class for the legendary action.', + optional: true }) - @prop({ type: () => DifficultyClass }) public dc?: DifficultyClass } @ObjectType({ description: "A monster's specific proficiency and its bonus value." }) export class MonsterProficiency { - @Field(() => Proficiency, { + @field(() => T.Ref(Proficiency), { description: 'The specific proficiency (e.g., Saving Throw: STR, Skill: Athletics).' }) - @prop({ type: () => APIReference }) public proficiency!: APIReference - @Field(() => Int, { description: 'The proficiency bonus value for this monster.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The proficiency bonus value for this monster.' }) public value!: number } @ObjectType({ description: 'A reaction a monster can perform' }) export class Reaction { - @Field(() => String, { description: 'The name of the reaction.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the reaction.' }) public name!: string - @Field(() => String, { description: 'The description of the reaction.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The description of the reaction.' }) public desc!: string - @Field(() => DifficultyClass, { - nullable: true, - description: 'The difficulty class for the reaction.' + @field(() => T.Model(DifficultyClass), { + description: 'The difficulty class for the reaction.', + optional: true }) - @prop({ type: () => DifficultyClass }) public dc?: DifficultyClass } @ObjectType({ description: 'Monster senses details' }) export class Sense { - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { + description: "The creature's blindsight range, e.g. '30 ft.'.", + optional: true + }) public blindsight?: string - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { + description: "The creature's darkvision range, e.g. '120 ft.'.", + optional: true + }) public darkvision?: string - @Field(() => Int) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: "The creature's passive Wisdom (Perception) score." }) public passive_perception!: number - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { + description: "The creature's tremorsense range, e.g. '60 ft.'.", + optional: true + }) public tremorsense?: string - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { + description: "The creature's truesight range, e.g. '120 ft.'.", + optional: true + }) public truesight?: string } @ObjectType({ description: 'Usage details for a special ability' }) export class SpecialAbilityUsage { - @Field(() => String, { description: 'The type of usage for the special ability.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The type of usage for the special ability.' }) public type!: string - @Field(() => Int, { - nullable: true, - description: 'The number of times the special ability can be used.' + @field(() => T.Int, { + description: 'The number of times the special ability can be used.', + optional: true }) - @prop({ index: true, type: () => Number }) public times?: number - @Field(() => [String], { - nullable: true, - description: 'The types of rest the special ability can be used on.' + @field(() => T.List(String), { + description: 'The types of rest the special ability can be used on.', + optional: true }) - @prop({ type: () => [String] }) public rest_types?: string[] } @ObjectType({ description: "A spell within a monster's special ability spellcasting" }) export class SpecialAbilitySpell { - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the spell.' }) public name!: string - @Field(() => Int, { description: 'The level of the spell.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The level of the spell.' }) public level!: number - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { + description: 'The canonical path of the spell resource in the REST API.' + }) public url!: string - @Field(() => String, { nullable: true, description: 'The notes for the spell.' }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: 'The notes for the spell.', optional: true }) public notes?: string - @Field(() => SpecialAbilityUsage, { nullable: true, description: 'The usage for the spell.' }) - @prop({ type: () => SpecialAbilityUsage }) + @field(() => T.Model(SpecialAbilityUsage), { + description: 'The usage for the spell.', + optional: true + }) public usage?: SpecialAbilityUsage } @ObjectType({ description: 'Spellcasting details for a monster special ability' }) @modelOptions({ options: { allowMixed: Severity.ALLOW } }) export class SpecialAbilitySpellcasting { - @Field(() => Int, { nullable: true, description: 'The level of the spellcasting.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'The level of the spellcasting.', optional: true }) public level?: number - @Field(() => AbilityScore, { description: 'The ability for the spellcasting.' }) - @prop({ type: () => APIReference }) + @field(() => T.Ref(AbilityScore), { description: 'The ability for the spellcasting.' }) public ability!: APIReference - @Field(() => Int, { nullable: true, description: 'The difficulty class for the spellcasting.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'The difficulty class for the spellcasting.', optional: true }) public dc?: number - @Field(() => Int, { nullable: true, description: 'The modifier for the spellcasting.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'The modifier for the spellcasting.', optional: true }) public modifier?: number - @Field(() => [String], { description: 'The components required for the spellcasting.' }) - @prop({ type: () => [String] }) + @field(() => T.List(String), { description: 'The components required for the spellcasting.' }) public components_required!: string[] - @Field(() => String, { nullable: true, description: 'The school of the spellcasting.' }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: 'The school of the spellcasting.', optional: true }) public school?: string // Handled by MonsterSpellcastingResolver - @prop({ type: () => Object, default: undefined }) + @field(() => T.Model(Object), { skipResolver: true }) public slots?: Record - @Field(() => [SpecialAbilitySpell], { description: 'The spells for the spellcasting.' }) - @prop({ type: () => [SpecialAbilitySpell] }) + @field(() => T.List(SpecialAbilitySpell), { description: 'The spells for the spellcasting.' }) public spells!: SpecialAbilitySpell[] } @ObjectType({ description: 'A special ability of the monster' }) export class SpecialAbility { - @Field(() => String, { description: 'The name of the special ability.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the special ability.' }) public name!: string - @Field(() => String, { description: 'The description of the special ability.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The description of the special ability.' }) public desc!: string - @Field(() => Int, { nullable: true, description: 'The attack bonus for the special ability.' }) - @prop({ index: true, type: () => Number }) + @field(() => T.Int, { description: 'The attack bonus for the special ability.', optional: true }) public attack_bonus?: number - @Field(() => [Damage], { nullable: true, description: 'The damage for the special ability.' }) - @prop({ type: () => [Damage] }) + @field(() => T.List(Damage), { + description: 'The damage for the special ability.', + optional: true + }) public damage?: Damage[] - @Field(() => DifficultyClass, { - nullable: true, - description: 'The difficulty class for the special ability.' + @field(() => T.Model(DifficultyClass), { + description: 'The difficulty class for the special ability.', + optional: true }) - @prop({ type: () => DifficultyClass }) public dc?: DifficultyClass - @Field(() => SpecialAbilitySpellcasting, { - nullable: true, - description: 'The spellcasting for the special ability.' + @field(() => T.Model(SpecialAbilitySpellcasting), { + description: 'The spellcasting for the special ability.', + optional: true }) - @prop({ type: () => SpecialAbilitySpellcasting }) public spellcasting?: SpecialAbilitySpellcasting - @Field(() => SpecialAbilityUsage, { - nullable: true, - description: 'The usage for the special ability.' + @field(() => T.Model(SpecialAbilityUsage), { + description: 'The usage for the special ability.', + optional: true }) - @prop({ type: () => SpecialAbilityUsage }) public usage?: SpecialAbilityUsage } @ObjectType({ description: 'Monster movement speeds' }) export class MonsterSpeed { - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: "The creature's burrowing speed.", optional: true }) public burrow?: string - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: "The creature's climbing speed.", optional: true }) public climb?: string - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: "The creature's flying speed.", optional: true }) public fly?: string - @Field(() => Boolean, { nullable: true }) - @prop({ index: true, type: () => Boolean }) + @field(() => T.String, { description: 'Whether the creature can hover or not.', optional: true }) public hover?: boolean - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: "The creature's swimming speed.", optional: true }) public swim?: string - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: "The creature's walking speed.", optional: true }) public walk?: string } @ObjectType({ description: 'A D&D monster.' }) @srdModelOptions('2014-monsters') export class Monster { - @Field(() => [MonsterAction], { nullable: true, description: 'The actions for the monster.' }) - @prop({ type: () => [MonsterAction] }) + @field(() => T.List(MonsterAction), { + description: 'The actions for the monster.', + optional: true + }) public actions?: MonsterAction[] - @Field(() => String) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "The monster's alignment." }) public alignment!: string // Handled by MonsterArmorClassResolver - @prop({ - type: () => - Array< - ArmorClassDex | ArmorClassNatural | ArmorClassArmor | ArmorClassSpell | ArmorClassCondition - >, - required: true - }) - public armor_class!: Array< - ArmorClassDex | ArmorClassNatural | ArmorClassArmor | ArmorClassSpell | ArmorClassCondition - > - - @Field(() => Float) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.List(Object), { skipResolver: true }) + public armor_class!: ArmorClass[] + + @field(() => T.Float, { description: "The monster's alignment." }) public challenge_rating!: number - @Field(() => Int) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: "The monster's Charisma score." }) public charisma!: number - @Field(() => [Condition], { nullable: true, description: 'Conditions the monster is immune to.' }) - @prop({ type: () => [APIReference] }) + @field(() => T.RefList(Condition), { + description: 'Conditions the monster is immune to.', + optional: true + }) public condition_immunities!: APIReference[] - @Field(() => Int) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: "The monster's Constitution score." }) public constitution!: number - @Field(() => [String]) - @prop({ type: () => [String] }) + @field(() => T.List(String), { description: 'Damage types the monster is immune to.' }) public damage_immunities!: string[] - @Field(() => [String]) - @prop({ type: () => [String] }) + @field(() => T.List(String), { description: 'Damage types the monster is resistant to.' }) public damage_resistances!: string[] - @Field(() => [String]) - @prop({ type: () => [String] }) + @field(() => T.List(String), { description: 'Damage types the monster is vulnerable to.' }) public damage_vulnerabilities!: string[] - @Field(() => Int) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: "The monster's Dexterity score." }) public dexterity!: number - @Field(() => [Monster], { nullable: true, description: 'Other forms the monster can assume.' }) - @prop({ type: () => [APIReference] }) + @field(() => T.RefList(Monster), { + description: 'Other forms the monster can assume.', + optional: true + }) public forms?: APIReference[] - @Field(() => String) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "The number and size of the monster's hit dice." }) public hit_dice!: string - @Field(() => Int) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: "The monster's average Hit Point maximum." }) public hit_points!: number - @Field(() => String) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "Dice to determine the monster's Hit Point maximum." }) public hit_points_roll!: string - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { + description: + "The path of an image depicting the monster, relative to the API's base URL, e.g. '/api/images/monsters/aboleth.png'", + optional: true + }) public image?: string - @Field(() => String) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { + description: 'Unique identifer for this monster (e.g. aboleth, young-black-dragon).' + }) public index!: string - @Field(() => Int) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: "The monster's Intelligence score." }) public intelligence!: number - @Field(() => String) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Languages that the monster speaks and/or understands.' }) public languages!: string - @Field(() => [LegendaryAction], { - nullable: true, - description: 'The legendary actions for the monster.' + @field(() => T.List(LegendaryAction), { + description: 'The legendary actions for the monster.', + optional: true }) - @prop({ type: () => [LegendaryAction] }) public legendary_actions?: LegendaryAction[] - @Field(() => String) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "The monster's name." }) public name!: string - @Field(() => [MonsterProficiency], { - nullable: true, - description: 'The proficiencies for the monster.' + @field(() => T.List(MonsterProficiency), { + description: 'The proficiencies for the monster.', + optional: true }) - @prop({ type: () => [MonsterProficiency] }) public proficiencies!: MonsterProficiency[] - @Field(() => [Reaction], { nullable: true, description: 'The reactions for the monster.' }) - @prop({ type: () => [Reaction] }) + @field(() => T.List(Reaction), { description: 'The reactions for the monster.', optional: true }) public reactions?: Reaction[] - @Field(() => Sense) - @prop({ type: () => Sense }) + @field(() => T.Model(Sense), { description: "The monster's senses." }) public senses!: Sense - @Field(() => String) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "The monster's size category." }) public size!: string - @Field(() => [SpecialAbility], { - nullable: true, - description: 'The special abilities for the monster.' + @field(() => T.List(SpecialAbility), { + description: 'The special abilities for the monster.', + optional: true }) - @prop({ type: () => [SpecialAbility] }) public special_abilities?: SpecialAbility[] - @Field(() => MonsterSpeed) - @prop({ type: () => MonsterSpeed }) + @field(() => T.Model(MonsterSpeed), { description: "The monster's movement information." }) public speed!: MonsterSpeed - @Field(() => Int) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: "The monster's Strength score." }) public strength!: number - @Field(() => String, { nullable: true }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { + description: 'The subtype of the monster (e.g. goblinoid, shapechanger).', + optional: true + }) public subtype?: string - @Field(() => String) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The type of the monster (e.g. beast, monstrosity).' }) public type!: string - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update' }) + public updated_at!: string + + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => Int) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: "The monster's Wisdom score." }) public wisdom!: number - @Field(() => Int) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The Experience Points rewarded for slaying the monster.' }) public xp!: number - - @Field(() => String) - @prop({ required: true, index: true, type: () => String }) - public updated_at!: string } export type MonsterDocument = DocumentType diff --git a/src/models/2014/race.ts b/src/models/2014/race.ts index eb4a5b52d..642cc591b 100644 --- a/src/models/2014/race.ts +++ b/src/models/2014/race.ts @@ -1,9 +1,10 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { Choice } from '@/models/common/choice' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { AbilityScore } from './abilityScore' @@ -14,96 +15,82 @@ import { Trait } from './trait' @ObjectType({ description: 'Ability score bonus provided by a race' }) export class RaceAbilityBonus { - @Field(() => AbilityScore, { - nullable: true, - description: 'The ability score that receives the bonus.' + @field(() => T.Ref(AbilityScore), { + description: 'The ability score that receives the bonus.', + optional: true }) - @prop({ type: () => APIReference, required: true }) public ability_score!: APIReference - @Field(() => Int, { description: 'The bonus value for the ability score' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The bonus value for the ability score' }) public bonus!: number } @ObjectType({ description: 'Represents a playable race in D&D' }) @srdModelOptions('2014-races') export class Race { - @Field(() => String, { description: 'The index of the race.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The index of the race.' }) public index!: string // Handled by RaceResolver - @prop({ type: () => Choice, required: false, index: true }) + @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public ability_bonus_options?: Choice - @Field(() => [RaceAbilityBonus], { description: 'Ability score bonuses granted by this race.' }) - @prop({ type: () => [RaceAbilityBonus], required: true }) + @field(() => T.List(RaceAbilityBonus), { + description: 'Ability score bonuses granted by this race.' + }) public ability_bonuses!: RaceAbilityBonus[] - @Field(() => String, { description: 'Typical age range and lifespan for the race' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Typical age range and lifespan for the race' }) public age!: string - @Field(() => String, { description: 'Typical alignment tendencies for the race' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Typical alignment tendencies for the race' }) public alignment!: string - @Field(() => String, { description: 'Description of languages typically spoken by the race' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Description of languages typically spoken by the race' }) public language_desc!: string // Handled by RaceResolver - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public language_options?: Choice - @Field(() => [Language], { - nullable: true, - description: 'Languages typically spoken by this race.' - }) - @prop({ type: () => [APIReference], required: true }) + @field(() => T.RefList(Language), { description: 'Languages typically spoken by this race.' }) public languages!: APIReference[] - @Field(() => String, { description: 'The name of the race.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the race.' }) public name!: string - @Field(() => String, { description: 'Size category (e.g., Medium, Small)' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Size category (e.g., Medium, Small)' }) public size!: string - @Field(() => String, { description: "Description of the race's size" }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "Description of the race's size" }) public size_description!: string - @Field(() => Int, { description: 'Base walking speed in feet' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Base walking speed in feet' }) public speed!: number - @Field(() => [Proficiency], { - nullable: true, - description: 'Proficiencies granted by this race at start.' + @field(() => T.RefList(Proficiency), { + description: 'Proficiencies granted by this race at start.', + optional: true }) - @prop({ type: () => [APIReference] }) public starting_proficiencies?: APIReference[] // Handled by RaceResolver - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public starting_proficiency_options?: Choice - @Field(() => [Subrace], { nullable: true, description: 'Subraces available for this race.' }) - @prop({ type: () => [APIReference] }) + @field(() => T.RefList(Subrace), { + description: 'Subraces available for this race.', + optional: true + }) public subraces?: APIReference[] - @Field(() => [Trait], { nullable: true, description: 'Traits common to this race.' }) - @prop({ type: () => [APIReference] }) + @field(() => T.RefList(Trait), { description: 'Traits common to this race.', optional: true }) public traits?: APIReference[] - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/rule.ts b/src/models/2014/rule.ts index 679fff7a0..4ab5d5ca5 100644 --- a/src/models/2014/rule.ts +++ b/src/models/2014/rule.ts @@ -1,8 +1,9 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { RuleSection } from './ruleSection' @@ -10,29 +11,26 @@ import { RuleSection } from './ruleSection' @ObjectType({ description: 'A specific rule from the SRD.' }) @srdModelOptions('2014-rules') export class Rule { - @Field(() => String, { description: 'A description of the rule.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'A description of the rule.' }) public desc!: string - @Field(() => String, { description: 'The unique identifier for this rule (e.g., adventuring).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { + description: 'The unique identifier for this rule (e.g., adventuring).' + }) public index!: string - @Field(() => String, { description: 'The name of the rule (e.g., Adventuring).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the rule (e.g., Adventuring).' }) public name!: string - @Field(() => [RuleSection], { + @field(() => T.RefList(RuleSection), { description: 'Subsections clarifying or detailing this rule.' }) - @prop({ type: () => [APIReference], index: true }) public subsections!: APIReference[] - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/ruleSection.ts b/src/models/2014/ruleSection.ts index 1dab22e64..29242cd40 100644 --- a/src/models/2014/ruleSection.ts +++ b/src/models/2014/ruleSection.ts @@ -1,31 +1,28 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' @ObjectType({ description: 'Represents a named section of the SRD rules document.' }) @srdModelOptions('2014-rule-sections') export class RuleSection { - @Field(() => String, { description: 'A description of the rule section.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'A description of the rule section.' }) public desc!: string - @Field(() => String, { + @field(() => T.String, { description: 'The unique identifier for this rule section (e.g., ability-checks).' }) - @prop({ required: true, index: true, type: () => String }) public index!: string - @Field(() => String, { description: 'The name of the rule section (e.g., Ability Checks).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the rule section (e.g., Ability Checks).' }) public name!: string - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/skill.ts b/src/models/2014/skill.ts index 4618dc989..25d50f542 100644 --- a/src/models/2014/skill.ts +++ b/src/models/2014/skill.ts @@ -1,8 +1,9 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { AbilityScore } from './abilityScore' @@ -12,28 +13,24 @@ import { AbilityScore } from './abilityScore' }) @srdModelOptions('2014-skills') export class Skill { - @Field(() => AbilityScore, { description: 'The ability score associated with this skill.' }) - @prop({ type: () => APIReference, required: true }) + @field(() => T.Ref(AbilityScore), { + description: 'The ability score associated with this skill.' + }) public ability_score!: APIReference - @Field(() => [String], { description: 'A description of the skill.' }) - @prop({ required: true, index: true, type: () => [String] }) + @field(() => T.List(String), { description: 'A description of the skill.' }) public desc!: string[] - @Field(() => String, { description: 'The unique identifier for this skill (e.g., athletics).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The unique identifier for this skill (e.g., athletics).' }) public index!: string - @Field(() => String, { description: 'The name of the skill (e.g., Athletics).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the skill (e.g., Athletics).' }) public name!: string - // url is intentionally not decorated with @Field - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/spell.ts b/src/models/2014/spell.ts index e57aecaa4..76ad4d887 100644 --- a/src/models/2014/spell.ts +++ b/src/models/2014/spell.ts @@ -1,9 +1,10 @@ -import { getModelForClass, modelOptions, prop, Severity } from '@typegoose/typegoose' +import { getModelForClass, modelOptions, Severity } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { AreaOfEffect } from '@/models/common/areaOfEffect' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { AbilityScore } from './abilityScore' @@ -15,137 +16,119 @@ import { Subclass } from './subclass' @ObjectType({ description: 'Details about spell damage' }) @modelOptions({ options: { allowMixed: Severity.ALLOW } }) export class SpellDamage { - @Field(() => DamageType, { nullable: true, description: 'Type of damage dealt.' }) - @prop({ type: () => APIReference }) + @field(() => T.Ref(DamageType), { description: 'Type of damage dealt.', optional: true }) public damage_type?: APIReference // Handled by SpellDamageResolver - @prop({ mapProp: true, type: () => Object, default: undefined }) + @field(() => T.Model(Object), { optional: true, skipResolver: true }) public damage_at_slot_level?: Record // Handled by SpellDamageResolver - @prop({ mapProp: true, type: () => Object, default: undefined }) + @field(() => T.Model(Object), { optional: true, skipResolver: true }) public damage_at_character_level?: Record } @ObjectType({ description: "Details about a spell's saving throw" }) export class SpellDC { - @Field(() => AbilityScore, { description: 'The ability score used for the saving throw.' }) - @prop({ type: () => APIReference, required: true }) + @field(() => T.Ref(AbilityScore), { description: 'The ability score used for the saving throw.' }) public dc_type!: APIReference - @Field(() => String, { description: "The result of a successful save (e.g., 'half', 'none')." }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "The result of a successful save (e.g., 'half', 'none')." }) public dc_success!: string - @Field(() => String, { - nullable: true, - description: 'Additional description for the saving throw.' + @field(() => T.String, { + description: 'Additional description for the saving throw.', + optional: true }) - @prop({ index: true, type: () => String }) public desc?: string } @ObjectType({ description: 'Represents a spell in D&D' }) @srdModelOptions('2014-spells') export class Spell { - @Field(() => AreaOfEffect, { - nullable: true, - description: 'Area of effect details, if applicable.' + @field(() => T.Model(AreaOfEffect), { + description: 'Area of effect details, if applicable.', + optional: true }) - @prop({ type: () => AreaOfEffect }) public area_of_effect?: AreaOfEffect - @Field(() => String, { - nullable: true, - description: 'Type of attack associated with the spell (e.g., Melee, Ranged)' + @field(() => T.String, { + description: 'Type of attack associated with the spell (e.g., Melee, Ranged)', + optional: true }) - @prop({ index: true, type: () => String }) public attack_type?: string - @Field(() => String, { description: 'Time required to cast the spell' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Time required to cast the spell' }) public casting_time!: string - @Field(() => [Class], { nullable: true, description: 'Classes that can cast this spell.' }) - @prop({ type: () => [APIReference], required: true }) + @field(() => T.RefList(Class), { description: 'Classes that can cast this spell.' }) public classes!: APIReference[] - @Field(() => [String], { description: 'Components required for the spell (V, S, M)' }) - @prop({ type: () => [String], required: true }) + @field(() => T.List(String), { description: 'Components required for the spell (V, S, M)' }) public components!: string[] - @Field(() => Boolean, { description: 'Indicates if the spell requires concentration' }) - @prop({ index: true, type: () => Boolean }) + @field(() => T.Bool, { description: 'Indicates if the spell requires concentration' }) public concentration!: boolean - @Field(() => SpellDamage, { nullable: true, description: 'Damage details, if applicable.' }) - @prop({ type: () => SpellDamage }) + @field(() => T.Model(SpellDamage), { + description: 'Damage details, if applicable.', + optional: true + }) public damage?: SpellDamage - @Field(() => SpellDC, { nullable: true, description: 'Saving throw details, if applicable.' }) - @prop({ type: () => SpellDC }) + @field(() => T.Model(SpellDC), { + description: 'Saving throw details, if applicable.', + optional: true + }) public dc?: SpellDC - @Field(() => [String], { description: "Description of the spell's effects" }) - @prop({ required: true, index: true, type: () => [String] }) + @field(() => T.List(String), { description: "Description of the spell's effects" }) public desc!: string[] - @Field(() => String, { description: 'Duration of the spell' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Duration of the spell' }) public duration!: string // Handled by SpellResolver - @prop({ type: () => Object }) + @field(() => T.Model(Object), { optional: true, skipResolver: true }) public heal_at_slot_level?: Record - @Field(() => [String], { - nullable: true, - description: 'Description of effects when cast at higher levels' + @field(() => T.List(String), { + description: 'Description of effects when cast at higher levels', + optional: true }) - @prop({ type: () => [String] }) public higher_level?: string[] - @Field(() => String, { description: 'Unique identifier for this spell' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Unique identifier for this spell' }) public index!: string - @Field(() => Int, { description: 'Level of the spell (0 for cantrips)' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Level of the spell (0 for cantrips)' }) public level!: number - @Field(() => String, { nullable: true, description: 'Material components required, if any' }) - @prop({ index: true, type: () => String }) + @field(() => T.String, { description: 'Material components required, if any', optional: true }) public material?: string - @Field(() => String, { description: 'Name of the spell' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Name of the spell' }) public name!: string - @Field(() => String, { description: 'Range of the spell' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Range of the spell' }) public range!: string - @Field(() => Boolean, { description: 'Indicates if the spell can be cast as a ritual' }) - @prop({ required: true, index: true, type: () => Boolean }) + @field(() => T.Bool, { description: 'Indicates if the spell can be cast as a ritual' }) public ritual!: boolean - @Field(() => MagicSchool, { - nullable: true, - description: 'The school of magic this spell belongs to.' - }) - @prop({ type: () => APIReference, required: true }) + @field(() => T.Ref(MagicSchool), { description: 'The school of magic this spell belongs to.' }) public school!: APIReference - @Field(() => [Subclass], { nullable: true, description: 'Subclasses that can cast this spell.' }) - @prop({ type: () => [APIReference], required: true }) + @field(() => T.RefList(Subclass), { + description: 'Subclasses that can cast this spell.', + optional: true + }) public subclasses?: APIReference[] - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/subclass.ts b/src/models/2014/subclass.ts index db6f7e99b..611c900d3 100644 --- a/src/models/2014/subclass.ts +++ b/src/models/2014/subclass.ts @@ -1,8 +1,9 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { Class } from './class' @@ -11,27 +12,26 @@ import { Spell } from './spell' @ObjectType({ description: 'Prerequisite for a subclass spell' }) export class Prerequisite { - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { skipResolver: true }) public index!: string - @prop({ required: true, type: () => String }) + @field(() => T.String, { skipResolver: true }) public name!: string - @prop({ required: true, type: () => String }) + @field(() => T.String, { skipResolver: true }) public type!: string - @prop({ required: true, type: () => String }) + @field(() => T.String, { skipResolver: true }) public url!: string } @ObjectType({ description: 'Spell gained by a subclass' }) export class SubclassSpell { // Handled by SubclassSpellResolver - @prop({ type: () => [Prerequisite], required: true }) + @field(() => T.List(Prerequisite), { skipResolver: true }) public prerequisites!: Prerequisite[] - @Field(() => Spell, { description: 'The spell gained.' }) - @prop({ type: () => APIReference, required: true }) + @field(() => T.Ref(Spell), { description: 'The spell gained.' }) public spell!: APIReference } @@ -40,45 +40,36 @@ export class SubclassSpell { }) @srdModelOptions('2014-subclasses') export class Subclass { - @Field(() => Class, { nullable: true, description: 'The parent class for this subclass.' }) - @prop({ type: () => APIReference, required: true }) + @field(() => T.Ref(Class), { description: 'The parent class for this subclass.', optional: true }) public class!: APIReference - @Field(() => [String], { description: 'Description of the subclass' }) - @prop({ required: true, index: true, type: () => [String] }) + @field(() => T.List(String), { description: 'Description of the subclass' }) public desc!: string[] - @Field(() => String, { description: 'Unique identifier for the subclass' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Unique identifier for the subclass' }) public index!: string - @Field(() => String, { description: 'Name of the subclass' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Name of the subclass' }) public name!: string - @Field(() => [SubclassSpell], { - nullable: true, - description: 'Spells specific to this subclass.' + @field(() => T.List(SubclassSpell), { + description: 'Spells specific to this subclass.', + optional: true }) - @prop({ type: () => [SubclassSpell] }) public spells?: SubclassSpell[] - @Field(() => String, { description: 'Flavor text describing the subclass' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Flavor text describing the subclass' }) public subclass_flavor!: string - @Field(() => [Level], { - nullable: true, + @field(() => T.Link([Level]), { description: 'Features and abilities gained by level for this subclass.' }) - @prop({ required: true, index: true, type: () => String }) public subclass_levels!: string - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/subrace.ts b/src/models/2014/subrace.ts index 1224dc142..79ec9f16c 100644 --- a/src/models/2014/subrace.ts +++ b/src/models/2014/subrace.ts @@ -1,10 +1,11 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { LanguageChoice } from '@/graphql/2014/common/choiceTypes' import { APIReference } from '@/models/common/apiReference' import { Choice } from '@/models/common/choice' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { AbilityScore } from './abilityScore' @@ -15,76 +16,60 @@ import { Trait } from './trait' @ObjectType({ description: 'Bonus to an ability score provided by a subrace.' }) export class SubraceAbilityBonus { - @Field(() => AbilityScore, { - nullable: true, - description: 'The ability score receiving the bonus.' - }) - @prop({ type: () => APIReference, required: true }) + @field(() => T.Ref(AbilityScore), { description: 'The ability score receiving the bonus.' }) public ability_score!: APIReference - @Field(() => Int, { description: 'The bonus value to the ability score.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'The bonus value to the ability score.' }) public bonus!: number } @ObjectType({ description: 'A subrace representing a specific heritage within a larger race.' }) @srdModelOptions('2014-subraces') export class Subrace { - @Field(() => [SubraceAbilityBonus], { + @field(() => T.List(SubraceAbilityBonus), { description: 'Ability score bonuses granted by this subrace.' }) - @prop({ type: () => [SubraceAbilityBonus], required: true }) public ability_bonuses!: SubraceAbilityBonus[] - @Field(() => String, { description: 'A description of the subrace.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'A description of the subrace.' }) public desc!: string - @Field(() => String, { description: 'The unique identifier for this subrace (e.g., high-elf).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { + description: 'The unique identifier for this subrace (e.g., high-elf).' + }) public index!: string - @Field(() => [Language], { - nullable: true, - description: 'Additional languages granted by this subrace.' + @field(() => T.RefList(Language), { + description: 'Additional languages granted by this subrace.', + optional: true }) - @prop({ type: () => [APIReference] }) public languages?: APIReference[] - @Field(() => LanguageChoice, { - nullable: true, - description: 'Languages typically spoken by this subrace.' + @field(() => T.Model(LanguageChoice), { + description: 'Languages typically spoken by this subrace.', + optional: true }) - @prop({ type: () => Choice }) public language_options?: Choice - @Field(() => String, { description: 'The name of the subrace (e.g., High Elf).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the subrace (e.g., High Elf).' }) public name!: string - @Field(() => Race, { nullable: true, description: 'The parent race for this subrace.' }) - @prop({ type: () => APIReference, required: true }) + @field(() => T.Ref(Race), { description: 'The parent race for this subrace.', optional: true }) public race!: APIReference - @Field(() => [Trait], { - nullable: true, - description: 'Racial traits associated with this subrace.' - }) - @prop({ type: () => [APIReference] }) + @field(() => T.RefList(Trait), { description: 'Racial traits associated with this subrace.' }) public racial_traits!: APIReference[] - @Field(() => [Proficiency], { - nullable: true, - description: 'Proficiencies granted by this subrace.' + @field(() => T.RefList(Proficiency), { + description: 'Proficiencies granted by this subrace.', + optional: true }) - @prop({ type: () => [APIReference] }) public starting_proficiencies?: APIReference[] - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/trait.ts b/src/models/2014/trait.ts index 355cd5b19..f5e60d62c 100644 --- a/src/models/2014/trait.ts +++ b/src/models/2014/trait.ts @@ -1,9 +1,10 @@ -import { getModelForClass, modelOptions, prop, Severity } from '@typegoose/typegoose' +import { getModelForClass, modelOptions, Severity } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, Int, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' import { APIReference } from '@/models/common/apiReference' import { AreaOfEffect } from '@/models/common/areaOfEffect' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' import { AbilityScore } from './abilityScore' @@ -16,87 +17,78 @@ import { Choice } from '../common/choice' @ObjectType({ description: 'Damage details for an action' }) @modelOptions({ options: { allowMixed: Severity.ALLOW } }) export class ActionDamage { - @Field(() => DamageType, { nullable: true, description: 'The type of damage dealt.' }) - @prop({ type: () => APIReference }) + @field(() => T.Ref(DamageType), { description: 'The type of damage dealt.', optional: true }) public damage_type!: APIReference // Handled by ActionDamageResolver - @prop({ type: () => Object }) + @field(() => T.Model(Object), { optional: true, skipResolver: true }) public damage_at_character_level?: Record } @ObjectType({ description: 'Usage limit details for an action' }) export class Usage { - @Field(() => String, { description: "Type of usage limit (e.g., 'per day')." }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: "Type of usage limit (e.g., 'per day')." }) public type!: string - @Field(() => Int, { description: 'Number of times the action can be used.' }) - @prop({ required: true, index: true, type: () => Number }) + @field(() => T.Int, { description: 'Number of times the action can be used.' }) public times!: number } @ObjectType({ description: 'DC details for a trait action (lacks dc_value).' }) export class TraitActionDC { - @Field(() => AbilityScore, { description: 'The ability score associated with this DC.' }) - @prop({ type: () => APIReference, required: true }) + @field(() => T.Ref(AbilityScore), { description: 'The ability score associated with this DC.' }) public dc_type!: APIReference - @Field(() => String, { description: 'The result of a successful save against this DC.' }) - @prop({ type: () => String, required: true }) + @field(() => T.String, { description: 'The result of a successful save against this DC.' }) public success_type!: 'none' | 'half' | 'other' } @ObjectType({ description: 'Represents an action associated with a trait (like a breath weapon).' }) export class Action { - @Field(() => String, { description: 'The name of the action.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the action.' }) public name!: string - @Field(() => String, { description: 'Description of the action.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Description of the action.' }) public desc!: string - @Field(() => Usage, { nullable: true, description: 'Usage limitations for the action.' }) - @prop({ type: () => Usage }) + @field(() => T.Model(Usage), { description: 'Usage limitations for the action.', optional: true }) public usage?: Usage - @Field(() => TraitActionDC, { - nullable: true, + @field(() => T.Model(TraitActionDC), { description: - 'The Difficulty Class (DC) associated with the action (value may not be applicable).' + 'The Difficulty Class (DC) associated with the action (value may not be applicable).', + optional: true }) - @prop({ type: () => TraitActionDC }) public dc?: TraitActionDC - @Field(() => [ActionDamage], { nullable: true, description: 'Damage dealt by the action.' }) - @prop({ type: () => [ActionDamage] }) + @field(() => T.List(ActionDamage), { description: 'Damage dealt by the action.', optional: true }) public damage?: ActionDamage[] - @Field(() => AreaOfEffect, { nullable: true, description: 'The area of effect for the action.' }) - @prop({ type: () => AreaOfEffect }) + @field(() => T.Model(AreaOfEffect), { + description: 'The area of effect for the action.', + optional: true + }) public area_of_effect?: AreaOfEffect } @ObjectType({ description: 'Details specific to certain traits.' }) export class TraitSpecific { // Handled by TraitSpecificResolver - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public subtrait_options?: Choice // Handled by TraitSpecificResolver - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public spell_options?: Choice // Handled by TraitSpecificResolver - @prop({ type: () => APIReference }) + @field(() => T.Ref(DamageType), { optional: true, skipResolver: true }) public damage_type?: APIReference - @Field(() => Action, { - nullable: true, - description: 'Breath weapon action details, if applicable.' + @field(() => T.Model(Action), { + description: 'Breath weapon action details, if applicable.', + optional: true }) - @prop({ type: () => Action }) public breath_weapon?: Action } @@ -105,57 +97,56 @@ export class TraitSpecific { }) @srdModelOptions('2014-traits') export class Trait { - @Field(() => [String], { description: 'A description of the trait.' }) - @prop({ required: true, index: true, type: () => [String] }) + @field(() => T.List(String), { description: 'A description of the trait.' }) public desc!: string[] - @Field(() => String, { description: 'The unique identifier for this trait (e.g., darkvision).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { + description: 'The unique identifier for this trait (e.g., darkvision).' + }) public index!: string - @Field(() => String, { description: 'The name of the trait (e.g., Darkvision).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the trait (e.g., Darkvision).' }) public name!: string - @Field(() => [Proficiency], { - nullable: true, - description: 'Proficiencies granted by this trait.' + @field(() => T.RefList(Proficiency), { + description: 'Proficiencies granted by this trait.', + optional: true }) - @prop({ type: () => [APIReference] }) public proficiencies?: APIReference[] // Handled by TraitResolver - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public proficiency_choices?: Choice // Handled by TraitResolver - @prop({ type: () => Choice }) + @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public language_options?: Choice - @Field(() => [Race], { nullable: true, description: 'Races that possess this trait.' }) - @prop({ type: () => [APIReference], required: true }) + @field(() => T.RefList(Race), { description: 'Races that possess this trait.', optional: true }) public races!: APIReference[] - @Field(() => [Subrace], { nullable: true, description: 'Subraces that possess this trait.' }) - @prop({ type: () => [APIReference], required: true }) + @field(() => T.RefList(Subrace), { + description: 'Subraces that possess this trait.', + optional: true + }) public subraces!: APIReference[] - @Field(() => Trait, { nullable: true, description: 'A parent trait, if this is a sub-trait.' }) - @prop({ type: () => APIReference }) + @field(() => T.Ref(Trait), { + description: 'A parent trait, if this is a sub-trait.', + optional: true + }) public parent?: APIReference - @Field(() => TraitSpecific, { - nullable: true, - description: 'Specific details for this trait, if applicable.' + @field(() => T.Model(TraitSpecific), { + description: 'Specific details for this trait, if applicable.', + optional: true }) - @prop({ type: () => TraitSpecific }) public trait_specific?: TraitSpecific - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/models/2014/weaponProperty.ts b/src/models/2014/weaponProperty.ts index f59e90f65..20f1eecf1 100644 --- a/src/models/2014/weaponProperty.ts +++ b/src/models/2014/weaponProperty.ts @@ -1,7 +1,8 @@ -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass } from '@typegoose/typegoose' import { DocumentType } from '@typegoose/typegoose/lib/types' -import { Field, ObjectType } from 'type-graphql' +import { ObjectType } from 'type-graphql' +import { field, T } from '@/util/fieldDectorator' import { srdModelOptions } from '@/util/modelOptions' @ObjectType({ @@ -9,25 +10,21 @@ import { srdModelOptions } from '@/util/modelOptions' }) @srdModelOptions('2014-weapon-properties') export class WeaponProperty { - @Field(() => [String], { description: 'A description of the weapon property.' }) - @prop({ required: true, index: true, type: () => [String] }) + @field(() => T.List(String), { description: 'A description of the weapon property.' }) public desc!: string[] - @Field(() => String, { + @field(() => T.String, { description: 'The unique identifier for this property (e.g., versatile).' }) - @prop({ required: true, index: true, type: () => String }) public index!: string - @Field(() => String, { description: 'The name of the property (e.g., Versatile).' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The name of the property (e.g., Versatile).' }) public name!: string - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'The canonical path of this resource in the REST API.' }) public url!: string - @Field(() => String, { description: 'Timestamp of the last update.' }) - @prop({ required: true, index: true, type: () => String }) + @field(() => T.String, { description: 'Timestamp of the last update.' }) public updated_at!: string } diff --git a/src/tests/controllers/api/2014/subclassController.test.ts b/src/tests/controllers/api/2014/subclassController.test.ts index 9d75bae67..f1516f1d2 100644 --- a/src/tests/controllers/api/2014/subclassController.test.ts +++ b/src/tests/controllers/api/2014/subclassController.test.ts @@ -178,8 +178,6 @@ describe('SubclassController', () => { expect(responseData).toBeInstanceOf(Array) expect(responseData).toHaveLength(3) // Only the 3 levels for this subclass // Check if the returned levels match the ones created for the subclass - console.log("##DATA") - console.log(responseData) expect(responseData).toEqual( expect.arrayContaining([ expect.objectContaining({ diff --git a/src/util/fieldDectorator.ts b/src/util/fieldDectorator.ts index 8d6458940..783c18047 100644 --- a/src/util/fieldDectorator.ts +++ b/src/util/fieldDectorator.ts @@ -122,7 +122,7 @@ function Link(type: ReturnTypeFuncValue): TypeObject { export const T = { String: { db: SchemaTypes.String, gql: String }, Int: { db: SchemaTypes.Int32, gql: GqlInt }, - Float: { db: SchemaTypes.Double, gql: GqlFloat }, + Float: { db: Number, gql: GqlFloat }, Bool: { db: SchemaTypes.Boolean, gql: Boolean }, Ref, RefList, From 2b11d44914a97092e20ebf8b7dc95500fb54e37f Mon Sep 17 00:00:00 2001 From: faewd Date: Thu, 26 Jun 2025 18:41:51 +0100 Subject: [PATCH 7/7] Fix some mismatched optional fields --- src/models/2014/feat.ts | 3 +-- src/models/2014/feature.ts | 2 +- src/models/2014/trait.ts | 9 +++------ 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/models/2014/feat.ts b/src/models/2014/feat.ts index 5c9136c72..c358978d8 100644 --- a/src/models/2014/feat.ts +++ b/src/models/2014/feat.ts @@ -11,8 +11,7 @@ import { AbilityScore } from './abilityScore' @ObjectType({ description: 'A prerequisite for taking a feat, usually a minimum ability score.' }) export class Prerequisite { @field(() => T.Ref(AbilityScore), { - description: 'The ability score required for this prerequisite.', - optional: true + description: 'The ability score required for this prerequisite.' }) public ability_score!: APIReference diff --git a/src/models/2014/feature.ts b/src/models/2014/feature.ts index 648fb3226..0ccb9079e 100644 --- a/src/models/2014/feature.ts +++ b/src/models/2014/feature.ts @@ -65,7 +65,7 @@ export class FeatureSpecific { @ObjectType({ description: 'Represents a class or subclass feature.' }) @srdModelOptions('2014-features') export class Feature { - @field(() => T.Ref(Class), { description: 'The class that gains this feature.', optional: true }) + @field(() => T.Ref(Class), { description: 'The class that gains this feature.' }) public class!: APIReference @field(() => T.List(String), { description: 'Description of the feature.' }) diff --git a/src/models/2014/trait.ts b/src/models/2014/trait.ts index f5e60d62c..433dfbb7c 100644 --- a/src/models/2014/trait.ts +++ b/src/models/2014/trait.ts @@ -17,7 +17,7 @@ import { Choice } from '../common/choice' @ObjectType({ description: 'Damage details for an action' }) @modelOptions({ options: { allowMixed: Severity.ALLOW } }) export class ActionDamage { - @field(() => T.Ref(DamageType), { description: 'The type of damage dealt.', optional: true }) + @field(() => T.Ref(DamageType), { description: 'The type of damage dealt.' }) public damage_type!: APIReference // Handled by ActionDamageResolver @@ -122,13 +122,10 @@ export class Trait { @field(() => T.Model(Choice), { optional: true, skipResolver: true }) public language_options?: Choice - @field(() => T.RefList(Race), { description: 'Races that possess this trait.', optional: true }) + @field(() => T.RefList(Race), { description: 'Races that possess this trait.' }) public races!: APIReference[] - @field(() => T.RefList(Subrace), { - description: 'Subraces that possess this trait.', - optional: true - }) + @field(() => T.RefList(Subrace), { description: 'Subraces that possess this trait.' }) public subraces!: APIReference[] @field(() => T.Ref(Trait), {