From 0f32afd25aab90644f9e78e880f8b3511bec5b15 Mon Sep 17 00:00:00 2001 From: ThaumRystra Date: Fri, 10 Jan 2025 09:35:30 +0200 Subject: [PATCH] Moved attributes and adjustments to typed schemas --- .../creatureProperties/CreatureProperties.ts | 23 +++-- app/imports/api/parenting/ChildSchema.ts | 7 +- .../api/parenting/SoftRemovableSchema.js | 3 +- app/imports/api/properties/Actions.ts | 91 ++++--------------- .../{Adjustments.js => Adjustments.ts} | 12 ++- app/imports/api/properties/Attributes.ts | 63 +++---------- .../properties/subSchemas/computedField.ts | 7 +- .../subSchemas/createPropertySchema.ts | 2 +- .../subSchemas/inlineCalculationField.ts | 7 +- app/imports/api/utility/TypedSimpleSchema.ts | 46 ++++------ 10 files changed, 84 insertions(+), 177 deletions(-) rename app/imports/api/properties/{Adjustments.js => Adjustments.ts} (70%) diff --git a/app/imports/api/creature/creatureProperties/CreatureProperties.ts b/app/imports/api/creature/creatureProperties/CreatureProperties.ts index ac4a062d..3d673359 100644 --- a/app/imports/api/creature/creatureProperties/CreatureProperties.ts +++ b/app/imports/api/creature/creatureProperties/CreatureProperties.ts @@ -1,7 +1,7 @@ import { Mongo } from 'meteor/mongo'; import SimpleSchema from 'simpl-schema'; import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema'; -import ChildSchema, { TreeDoc } from '/imports/api/parenting/ChildSchema'; +import ChildSchema from '/imports/api/parenting/ChildSchema'; import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema'; import propertySchemasIndex from '/imports/api/properties/computedPropertySchemasIndex'; import { storedIconsSchema } from '/imports/api/icons/Icons'; @@ -130,17 +130,6 @@ const DenormalisedOnlyCreaturePropertySchema = new TypedSimpleSchema({ const CreaturePropertySchema = PreComputeCreaturePropertySchema.extend(DenormalisedOnlyCreaturePropertySchema); -type CreaturePropertyByType = - InferType - & InferType - & InferType - & InferType - & InferType - -type ConvertToUnion = T[keyof T]; -type CreatureProperty = ConvertToUnion<{ [key in keyof typeof propertySchemasIndex]: CreaturePropertyByType }>; -type ActionProperty = CreaturePropertyByType<'action'>; - const CreatureProperties = new Mongo.Collection('creatureProperties'); let key: keyof typeof propertySchemasIndex; @@ -164,6 +153,16 @@ for (key in propertySchemasIndex) { } } +export type CreaturePropertyByType = + InferType + & InferType + & InferType + & InferType + & InferType + +type ConvertToUnion = T[keyof T]; +export type CreatureProperty = ConvertToUnion<{ [key in keyof typeof propertySchemasIndex]: CreaturePropertyByType }>; + export default CreatureProperties; export { DenormalisedOnlyCreaturePropertySchema, diff --git a/app/imports/api/parenting/ChildSchema.ts b/app/imports/api/parenting/ChildSchema.ts index 6c1d4e56..cac212ab 100644 --- a/app/imports/api/parenting/ChildSchema.ts +++ b/app/imports/api/parenting/ChildSchema.ts @@ -1,5 +1,6 @@ import SimpleSchema from 'simpl-schema'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS'; +import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema'; export interface Reference { collection: string, @@ -15,7 +16,7 @@ export interface TreeDoc { removed?: true, } -const RefSchema = new SimpleSchema({ +const RefSchema = new TypedSimpleSchema({ id: { type: String, regEx: SimpleSchema.RegEx.Id, @@ -26,7 +27,7 @@ const RefSchema = new SimpleSchema({ }, }); -const ChildSchema = new SimpleSchema({ +const ChildSchema = new TypedSimpleSchema({ root: { type: Object, }, @@ -47,7 +48,7 @@ const ChildSchema = new SimpleSchema({ optional: true, }, /** - * The tree structure goes as follows where the numbering follows a counterclockwise depth first + * The tree structure goes as follows where the numbering follows a counter-clockwise depth first * path around the tree. The canonical structure comes from the root and parentId references, * while the left and right numbering is used to optimize ancestor queries. * diff --git a/app/imports/api/parenting/SoftRemovableSchema.js b/app/imports/api/parenting/SoftRemovableSchema.js index 54c2f92c..82cd7e49 100644 --- a/app/imports/api/parenting/SoftRemovableSchema.js +++ b/app/imports/api/parenting/SoftRemovableSchema.js @@ -1,6 +1,7 @@ import SimpleSchema from 'simpl-schema'; +import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema'; -let SoftRemovableSchema = new SimpleSchema({ +let SoftRemovableSchema = new TypedSimpleSchema({ 'removed': { type: Boolean, optional: true, diff --git a/app/imports/api/properties/Actions.ts b/app/imports/api/properties/Actions.ts index 39d4c325..d3916d7e 100644 --- a/app/imports/api/properties/Actions.ts +++ b/app/imports/api/properties/Actions.ts @@ -3,64 +3,7 @@ import createPropertySchema from '/imports/api/properties/subSchemas/createPrope import { storedIconsSchema } from '/imports/api/icons/Icons'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS'; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX'; -import { CreatureProperty } from '/imports/api/creature/creatureProperties/CreatureProperties'; -import { InlineCalculation } from '/imports/api/properties/subSchemas/inlineCalculationField'; -import { CalculatedField } from '/imports/api/properties/subSchemas/computedField'; -import Property from '/imports/api/properties/Properties.type'; -import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema'; - -export type CreatureAction = Action & CreatureProperty & { - overridden?: boolean - insufficientResources?: boolean -} - -/* - * Actions are things a character can do - */ -export interface Action extends ActionBase { - type: 'action' -} - -/** - * Base property type for both spells and actions - */ -export interface ActionBase extends Property { - name?: string - summary?: InlineCalculation - description?: InlineCalculation - actionType: 'action' | 'bonus' | 'attack' | 'reaction' | 'free' | 'long' | 'event' - variableName?: string - target: 'self' | 'singleTarget' | 'multipleTargets' - attackRoll?: CalculatedField - uses?: CalculatedField - usesUsed?: number - reset?: string - silent?: boolean - usesLeft?: number - // Resources - resources: { - itemsConsumed: { - _id: string - tag?: string - itemName?: string - quantity?: CalculatedField - itemId?: string - available?: number - }[] - attributesConsumed: { - _id: string - variableName?: string - quantity?: CalculatedField - available?: number - statName?: string - }[] - conditions?: { - _id: string, - condition?: CalculatedField - conditionNote?: string, - }[] - } -} +import { Expand, InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema'; /* * Actions are things a character can do @@ -72,11 +15,11 @@ const ActionSchema = createPropertySchema({ max: STORAGE_LIMITS.name, }, summary: { - type: 'inlineCalculationFieldToCompute', + type: 'inlineCalculationFieldToCompute' as const, optional: true, }, description: { - type: 'inlineCalculationFieldToCompute', + type: 'inlineCalculationFieldToCompute' as const, optional: true, }, // What time-resource is used to take the action in combat @@ -102,16 +45,16 @@ const ActionSchema = createPropertySchema({ 'self', 'singleTarget', 'multipleTargets', - ], + ] as const, }, // Some actions have an attack roll attackRoll: { - type: 'fieldToCompute', + type: 'fieldToCompute' as const, optional: true, }, // Calculation of how many times this action can be used uses: { - type: 'fieldToCompute', + type: 'fieldToCompute' as const, optional: true, }, // Integer of how many times it has already been used @@ -152,7 +95,7 @@ const ActionSchema = createPropertySchema({ optional: true, }, 'resources.itemsConsumed.$.quantity': { - type: 'fieldToCompute', + type: 'fieldToCompute' as const, optional: true, }, 'resources.itemsConsumed.$.itemId': { @@ -181,7 +124,7 @@ const ActionSchema = createPropertySchema({ max: STORAGE_LIMITS.variableName, }, 'resources.attributesConsumed.$.quantity': { - type: 'fieldToCompute', + type: 'fieldToCompute' as const, optional: true, }, 'resources.conditions': { @@ -200,7 +143,7 @@ const ActionSchema = createPropertySchema({ } }, 'resources.conditions.$.condition': { - type: 'fieldToCompute', + type: 'fieldToCompute' as const, optional: true, }, 'resources.conditions.$.conditionNote': { @@ -217,11 +160,11 @@ const ActionSchema = createPropertySchema({ const ComputedOnlyActionSchema = createPropertySchema({ summary: { - type: 'computedOnlyInlineCalculationField', + type: 'computedOnlyInlineCalculationField' as const, optional: true, }, description: { - type: 'computedOnlyInlineCalculationField', + type: 'computedOnlyInlineCalculationField' as const, optional: true, }, // True if the uses left is zero, or any item or attribute consumed is @@ -232,12 +175,12 @@ const ComputedOnlyActionSchema = createPropertySchema({ removeBeforeCompute: true, }, attackRoll: { - type: 'computedOnlyField', + type: 'computedOnlyField' as const, optional: true, }, uses: { parseLevel: 'reduce', - type: 'computedOnlyField', + type: 'computedOnlyField' as const, optional: true, }, // Uses - usesUsed @@ -270,7 +213,7 @@ const ComputedOnlyActionSchema = createPropertySchema({ removeBeforeCompute: true, }, 'resources.itemsConsumed.$.quantity': { - type: 'computedOnlyField', + type: 'computedOnlyField' as const, optional: true, }, 'resources.itemsConsumed.$.itemName': { @@ -299,7 +242,7 @@ const ComputedOnlyActionSchema = createPropertySchema({ type: Object, }, 'resources.attributesConsumed.$.quantity': { - type: 'computedOnlyField', + type: 'computedOnlyField' as const, optional: true, }, 'resources.attributesConsumed.$.available': { @@ -319,4 +262,8 @@ const ComputedActionSchema = new TypedSimpleSchema({}) .extend(ActionSchema) .extend(ComputedOnlyActionSchema); +export type Action = InferType; +export type ComputedOnlyAction = InferType; +export type ComputedAction = Expand & InferType>; + export { ActionSchema, ComputedOnlyActionSchema, ComputedActionSchema }; diff --git a/app/imports/api/properties/Adjustments.js b/app/imports/api/properties/Adjustments.ts similarity index 70% rename from app/imports/api/properties/Adjustments.js rename to app/imports/api/properties/Adjustments.ts index 8521063c..f011a06d 100644 --- a/app/imports/api/properties/Adjustments.js +++ b/app/imports/api/properties/Adjustments.ts @@ -1,12 +1,12 @@ -import SimpleSchema from 'simpl-schema'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS'; import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema'; +import { Expand, InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema'; const AdjustmentSchema = createPropertySchema({ // The roll that determines how much to change the attribute // This can be simplified, but should only compute when activated amount: { - type: 'fieldToCompute', + type: 'fieldToCompute' as const, parseLevel: 'compile', optional: true, defaultValue: 1, @@ -40,14 +40,18 @@ const AdjustmentSchema = createPropertySchema({ const ComputedOnlyAdjustmentSchema = createPropertySchema({ amount: { - type: 'computedOnlyField', + type: 'computedOnlyField' as const, parseLevel: 'compile', optional: true, }, }); -const ComputedAdjustmentSchema = new SimpleSchema() +const ComputedAdjustmentSchema = new TypedSimpleSchema({}) .extend(AdjustmentSchema) .extend(ComputedOnlyAdjustmentSchema); +export type Adjustment = InferType; +export type ComputedOnlyAdjustment = InferType; +export type ComputedAdjustment = Expand & InferType>; + export { AdjustmentSchema, ComputedAdjustmentSchema, ComputedOnlyAdjustmentSchema }; diff --git a/app/imports/api/properties/Attributes.ts b/app/imports/api/properties/Attributes.ts index 88d0fcae..1256cfc5 100644 --- a/app/imports/api/properties/Attributes.ts +++ b/app/imports/api/properties/Attributes.ts @@ -2,52 +2,7 @@ import SimpleSchema from 'simpl-schema'; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS'; import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema'; -import { CreatureProperty } from '/imports/api/creature/creatureProperties/CreatureProperties'; -import { CalculatedField } from '/imports/api/properties/subSchemas/computedField'; -import { InlineCalculation } from '/imports/api/properties/subSchemas/inlineCalculationField'; -import { ConstantValueType } from '/imports/parser/parseTree/constant'; -import Property from '/imports/api/properties/Properties.type'; - -export type CreatureAttribute = Attribute & CreatureProperty & { - total?: ConstantValueType; - value?: ConstantValueType; - modifier?: number; - proficiency?: 0 | 0.49 | 0.5 | 1 | 2; - advantage?: -1 | 0 | 1; - constitutionMod?: number; - hide?: true; - overridden?: true; - effectIds?: string[]; - proficiencyIds?: string[]; - definitions?: { _id: string, type: string, row?: number }[]; -} - -export interface Attribute extends Property { - type: 'attribute'; - name?: string; - variableName?: string; - attributeType: 'ability' | 'stat' | 'modifier' | 'hitDice' | 'healthBar' | 'resource' | - 'spellSlot' | 'utility'; - hitDiceSize?: 'd1' | 'd2' | 'd4' | 'd6' | 'd8' | 'd10' | 'd12' | 'd20' | 'd100'; - spellSlotLevel?: CalculatedField; - healthBarColorMid?: string; - healthBarColorLow?: string; - healthBarNoDamage?: true; - healthBarNoHealing?: true; - healthBarNoDamageOverflow?: true; - healthBarNoHealingOverflow?: true; - healthBarDamageOrder?: number; - healthBarHealingOrder?: number; - baseValue?: CalculatedField; - description?: InlineCalculation; - damage?: number; - decimal?: true; - ignoreLowerLimit?: true; - ignoreUpperLimit?: true; - hideWhenTotalZero?: true; - hideWhenValueZero?: true; - reset?: string; -} +import { Expand, InferType } from '/imports/api/utility/TypedSimpleSchema'; /* * Attributes are numbered stats of a character @@ -90,7 +45,7 @@ const AttributeSchema = createPropertySchema({ }, // For type spellSlot, the level needs to be stored separately spellSlotLevel: { - type: 'fieldToCompute', + type: 'fieldToCompute' as const, optional: true, }, // For type healthBar midColor, and lowColor can be set separately from the @@ -134,12 +89,12 @@ const AttributeSchema = createPropertySchema({ }, // The starting value, before effects baseValue: { - type: 'fieldToCompute', + type: 'fieldToCompute' as const, optional: true, }, // Description of what the attribute is used for description: { - type: 'inlineCalculationFieldToCompute', + type: 'inlineCalculationFieldToCompute' as const, optional: true, }, // The damage done to the attribute, should always compute as positive @@ -182,15 +137,15 @@ const AttributeSchema = createPropertySchema({ const ComputedOnlyAttributeSchema = createPropertySchema({ description: { - type: 'computedOnlyInlineCalculationField', + type: 'computedOnlyInlineCalculationField' as const, optional: true, }, baseValue: { - type: 'computedOnlyField', + type: 'computedOnlyField' as const, optional: true, }, spellSlotLevel: { - type: 'computedOnlyField', + type: 'computedOnlyField' as const, optional: true, }, // The computed value of the attribute @@ -346,4 +301,8 @@ const ComputedAttributeSchema = new SimpleSchema({}) .extend(ComputedOnlyAttributeSchema) .extend(AttributeSchema); +export type Attribute = InferType; +export type ComputedOnlyAttribute = InferType; +export type ComputedAttribute = Expand & InferType>; + export { AttributeSchema, ComputedOnlyAttributeSchema, ComputedAttributeSchema }; diff --git a/app/imports/api/properties/subSchemas/computedField.ts b/app/imports/api/properties/subSchemas/computedField.ts index 515613e1..397708c8 100644 --- a/app/imports/api/properties/subSchemas/computedField.ts +++ b/app/imports/api/properties/subSchemas/computedField.ts @@ -4,8 +4,11 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS'; import ParseNode from '/imports/parser/parseTree/ParseNode'; import { ConstantValueType } from '/imports/parser/parseTree/constant'; -export interface CalculatedField { +export type FieldToCalculate = { calculation?: string; +} + +export type CalculatedOnlyField = { value?: ConstantValueType; valueNode: ParseNode; effectIds?: string[]; @@ -17,6 +20,8 @@ export interface CalculatedField { errors?: any[]; } +export type CalculatedField = FieldToCalculate & CalculatedOnlyField; + // Get schemas that apply fields directly so they can be gracefully extended // because {type: Schema} fields can't be extended function fieldToCompute(field) { diff --git a/app/imports/api/properties/subSchemas/createPropertySchema.ts b/app/imports/api/properties/subSchemas/createPropertySchema.ts index 34a6cde7..801443ca 100644 --- a/app/imports/api/properties/subSchemas/createPropertySchema.ts +++ b/app/imports/api/properties/subSchemas/createPropertySchema.ts @@ -11,7 +11,7 @@ import { Definition, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleS // Search through the schema for keys whose type is 'fieldToCompute' etc. // replace the type with Object and attach extend the schema with // the required fields to make the computation work -export default function createPropertySchema(definition: Definition) { +export default function createPropertySchema(definition: T): TypedSimpleSchema { const computationFields = { inlineCalculationFieldToCompute: [], computedOnlyInlineCalculationField: [], diff --git a/app/imports/api/properties/subSchemas/inlineCalculationField.ts b/app/imports/api/properties/subSchemas/inlineCalculationField.ts index c50ea24d..6826a78b 100644 --- a/app/imports/api/properties/subSchemas/inlineCalculationField.ts +++ b/app/imports/api/properties/subSchemas/inlineCalculationField.ts @@ -3,7 +3,11 @@ import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS'; import { CalculatedField } from './computedField'; -export interface InlineCalculation { +export type InlineCalculationFieldToCompute = { + text?: string, +} + +export type ComputedOnlyInlineCalculationField = { text?: string, hash?: number, value?: string, @@ -45,6 +49,7 @@ function computedOnlyInlineCalculationField(field) { type: String, optional: true, max: STORAGE_LIMITS.inlineCalculationField, + // @ts-expect-error removeBeforeCompute is an extension of SimpleSchema removeBeforeCompute: true, }, [`${field}.inlineCalculations`]: { diff --git a/app/imports/api/utility/TypedSimpleSchema.ts b/app/imports/api/utility/TypedSimpleSchema.ts index 0db730c2..0f6e65e8 100644 --- a/app/imports/api/utility/TypedSimpleSchema.ts +++ b/app/imports/api/utility/TypedSimpleSchema.ts @@ -1,4 +1,10 @@ import SimpleSchema, { SimpleSchemaDefinition } from 'simpl-schema'; +import type { + FieldToCalculate, CalculatedOnlyField +} from '/imports/api/properties/subSchemas/computedField'; +import type { + InlineCalculationFieldToCompute, ComputedOnlyInlineCalculationField +} from '/imports/api/properties/subSchemas/inlineCalculationField'; // It DOES NOT support a constructor with multiple schemas. export type Definition = Exclude; @@ -27,7 +33,7 @@ type NotImplementedMarker = { readonly NotImplementedMarker: unique symbol }; type ArrayMarker = { readonly ArrayMarker: unique symbol }; type ObjectMarker = { readonly ObjectMarker: unique symbol }; -export type InferType = ExpandRecursively>>; +export type InferType = Expand>>; // Infer TypeScript type from SimpleSchema type. type InferTypeInner = @@ -36,8 +42,10 @@ type InferTypeInner = // eslint-disable-next-line @typescript-eslint/ban-types T extends typeof Function ? Function : T extends typeof Number ? number : + T extends typeof SimpleSchema.Integer ? number : T extends typeof Object ? ObjectMarker : T extends typeof String ? string : + T extends typeof Date ? Date : T extends RegExp ? string : T extends TypedSimpleSchema ? InferSchema : NotImplementedMarker; @@ -50,8 +58,11 @@ export type InferField = ? Array> : ObjectMarker extends InferTypeInner ? { [L in keyof Def as L extends `${Key}.${infer SubKey}` ? SubKey extends `${string}.${string}` ? never : SubKey : never]: InferField } - : Def[Key] extends { allowedValues: infer Allowed extends string[] } - ? InferOptional> + : Def[Key] extends { allowedValues: infer Allowed extends string[] } ? InferOptional> + : Def[Key] extends { type: 'fieldToCompute' } ? FieldToCalculate + : Def[Key] extends { type: 'computedOnlyField' } ? CalculatedOnlyField + : Def[Key] extends { type: 'inlineCalculationFieldToCompute' } ? InlineCalculationFieldToCompute + : Def[Key] extends { type: 'computedOnlyInlineCalculationField' } ? ComputedOnlyInlineCalculationField : InferOptional> : NotImplementedMarker : NotImplementedMarker @@ -71,34 +82,9 @@ export type InferSchema = InferField< & { [Key in keyof Def as Key extends string ? `.${Key}` : never]: Def[Key] }, '' >; -const testSchema = new TypedSimpleSchema({ - name: { - type: String, - optional: true, - }, - age: { - type: Number, - }, - children: { - type: Array, - optional: true, - defaultValue: [], - }, - 'children.$': { - type: String, - }, - type: { - type: String, - allowedValues: ['cat', 'dog'] as const, - optional: true, - } -}); - // expands object types recursively -type ExpandRecursively = T extends object +export type ExpandRecursively = T extends object ? T extends infer O ? { [K in keyof O]: ExpandRecursively } : never : T; -type testType = InferType; - -type subType = ExpandRecursively +export type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never;