UNPKG

@league-of-foundry-developers/foundry-vtt-types

Version:
355 lines (316 loc) 14 kB
import type { MustConform, AnyObject, EmptyObject, DeepPartial, IsObject, Merge, RemoveIndexSignatures, SimpleMerge, Identity, } from "#utils"; import type { SchemaField } from "../data/fields.d.mts"; import type { DatabaseCreateOperation, DatabaseDeleteOperation, DatabaseUpdateOperation } from "./_types.d.mts"; import type { DataModel } from "./data.d.mts"; import type Document from "./document.d.mts"; type DataSchema = foundry.data.fields.DataSchema; interface _InternalTypeDataModelInterface extends DataModel.AnyConstructor { new <Schema extends DataSchema, Parent extends Document.Any, _ComputedInstance extends object>( ...args: DataModel.ConstructorArgs<Schema, Parent> // Note(LukeAbby): This seemingly redundant `DataModel<Schema, Parent>` is to // Ensure that TypeScript allows overriding `protected` methods in subclasses. // See: https://gist.github.com/LukeAbby/b9fd57eeba778a25297721e88b3e6bdd ): DataModel<Schema, Parent> & _ComputedInstance; } declare const _InternalTypeDataModelConst: _InternalTypeDataModelInterface; // @ts-expect-error Ignore the error, this is a workaround for a dynamic class. declare class _InternalTypeDataModel< Schema extends DataSchema, Parent extends Document<Document.Type, DataSchema, Document.Any | null>, BaseData extends AnyObject = EmptyObject, DerivedData extends AnyObject = EmptyObject, // This does not work if inlined. It's weird to put it here but it works. _ComputedInstance extends object = Merge<RemoveIndexSignatures<BaseData>, RemoveIndexSignatures<DerivedData>>, > extends _InternalTypeDataModelConst<Schema, Parent, _ComputedInstance> {} declare const __TypeDataModelBrand: unique symbol; // These properties are used to give a performant way of inferring types like `BaseData` and `DerivedData` types. // This avoids checking the entire constraint `T extends TypeDataModel.Any` to infer out the types. // To prevent adding properties that could appear at runtime they are unique symbols. // This makes them uniterable and means that the only way to access them is to have a reference to the variables which will be impossible outside of this file. declare const __Schema: unique symbol; declare const __Parent: unique symbol; declare const __BaseModel: unique symbol; declare const __BaseData: unique symbol; declare const __DerivedData: unique symbol; type _ClassMustBeAssignableToInternal = MustConform<typeof Document, Document.Internal.Constructor>; type _InstanceMustBeAssignableToInternal = MustConform<Document.Any, Document.Internal.Instance.Any>; // Removes the base and derived data from the type. // Has no extends bounds to simplify any checking logic. type RemoveDerived<BaseThis, BaseModel, BaseData, DerivedData> = SimpleMerge< Omit<BaseThis, keyof BaseData | keyof DerivedData>, BaseModel >; // Merges U into T but makes the appropriate keys partial. // This is similar to `Merge<T, DeepPartial<U>>` but only makes the deepest keys in `U` optional. type MergePartial<T, U> = Omit<T, keyof U> & { [K in keyof U as K extends PartialMergeKeys<T, U> ? K : never]?: InnerMerge<U, K, T>; } & { [K in keyof U as K extends PartialMergeKeys<T, U> ? never : K]: InnerMerge<U, K, T>; }; // Note(LukeAbby): The addition of `PropertyKey` seems to help simplify tsc's type checking. // This will likely not be necessary after type RequiredKeys<T> = PropertyKey & { [K in keyof T]-?: T extends { readonly [_ in K]: any } ? K : never; }[keyof T]; // Returns all the keys of U that should be partial when merged into T. // Only if both `T[K]` and `U[K]` are required and both are objects should a key be required. // This is because essentially only the most deep keys that are merged in need to be optional. // // Note(LukeAbby): The addition of `PropertyKey` seems to help simplify tsc's type checking. type PartialMergeKeys<T, U> = PropertyKey & { [K in keyof U]-?: K extends RequiredKeys<U> ? K extends RequiredKeys<T> ? IsObject<T[K]> extends true ? IsObject<U[K]> extends true ? never : K : K : K : K; }[keyof U]; // Merges `U[K]` into `T[K]` if they're both objects, returns `U[K]` otherwise. type InnerMerge<U, K extends keyof U, T> = T extends { readonly [_ in K]?: infer V } ? IsObject<U[K]> extends true ? IsObject<V> extends true ? MergePartial<V, U[K]> : Partial<U[K]> : Partial<U[K]> : U[K]; declare namespace TypeDataModel { interface Any extends AnyTypeDataModel {} interface AnyConstructor extends Identity<typeof AnyTypeDataModel> {} type ConfigurationFailureInstance = ConfigurationFailure; type ConfigurationFailureClass = typeof ConfigurationFailure; // Documented at https://gist.github.com/LukeAbby/c7420b053d881db4a4d4496b95995c98 namespace Internal { type Constructor = (abstract new (...args: never) => Instance.Any) & { [__TypeDataModelBrand]: never; }; // This still is only allows instances descended from `TypeDataField` because these unique symbols aren't used elsewhere. // These generic parameters seem to be required. This is likely because of a TypeScript soundness holes in which concrete types like `any` or `unknown` // will get treated bivariantly whereas type parameters get treated more safely. interface Instance< out Schema extends DataSchema, out Parent extends Document.Any, out BaseModel, out BaseData, out DerivedData, > { [__Schema]: Schema; [__Parent]: Parent; [__BaseModel]: BaseModel; [__BaseData]: BaseData; [__DerivedData]: DerivedData; } namespace Instance { interface Any extends Instance<any, any, any, any, any> {} } } type PrepareBaseDataThis<BaseThis extends Internal.Instance.Any> = BaseThis extends Internal.Instance<infer _1, infer _2, infer BaseModel, infer BaseData, infer DerivedData> ? MergePartial<Omit<RemoveDerived<BaseThis, BaseModel, BaseData, DerivedData>, "prepareBaseData">, BaseData> : never; type PrepareDerivedDataThis<BaseThis extends Internal.Instance.Any> = BaseThis extends Internal.Instance<infer _1, infer _2, infer BaseModel, infer BaseData, infer DerivedData> ? MergePartial< SimpleMerge<Omit<RemoveDerived<BaseThis, BaseModel, BaseData, DerivedData>, "prepareDerivedData">, BaseData>, DerivedData > : never; type ParentAssignmentType<Schema extends DataSchema, Parent extends Document.Internal.Instance.Any> = SimpleMerge< SchemaField.InitializedData<Document.SchemaFor<Parent>>, { // FIXME(LukeAbby): Callers handle making this partial when obvious. // However also should make system partial using the regular rules: if `initial` is assignable to the field or if `required` is false etc. system: SchemaField.InitializedData<Schema>; } >; } declare abstract class AnyTypeDataModel extends TypeDataModel<any, any, any, any> { constructor(...args: never); } /** * A specialized subclass of DataModel, intended to represent a Document's type-specific data. * Systems or Modules that provide DataModel implementations for sub-types of Documents (such as Actors or Items) * should subclass this class instead of the base DataModel class. * * * @example Registering a custom sub-type for a Module. * * **module.json** * ```json * { * "id": "my-module", * "esmodules": ["main.mjs"], * "documentTypes": { * "Actor": { * "sidekick": {}, * "villain": {} * }, * "JournalEntryPage": { * "dossier": {}, * "quest": { * "htmlFields": ["description"] * } * } * } * } * ``` * * **main.mjs** * ```js * Hooks.on("init", () => { * Object.assign(CONFIG.Actor.dataModels, { * "my-module.sidekick": SidekickModel, * "my-module.villain": VillainModel * }); * Object.assign(CONFIG.JournalEntryPage.dataModels, { * "my-module.dossier": DossierModel, * "my-module.quest": QuestModel * }); * }); * * class QuestModel extends foundry.abstract.TypeDataModel { * static defineSchema() { * const fields = foundry.data.fields; * return { * description: new fields.HTMLField({required: false, blank: true, initial: ""}), * steps: new fields.ArrayField(new fields.StringField()) * }; * } * * prepareDerivedData() { * this.totalSteps = this.steps.length; * } * } * ``` */ declare abstract class TypeDataModel< Schema extends DataSchema, Parent extends Document.Any, BaseData extends AnyObject = EmptyObject, DerivedData extends AnyObject = EmptyObject, > extends _InternalTypeDataModel<Schema, Parent, BaseData, DerivedData> { static [__TypeDataModelBrand]: never; [__Schema]: Schema; [__Parent]: Parent; [__BaseModel]: DataModel<Schema, Parent>; [__BaseData]: RemoveIndexSignatures<BaseData>; [__DerivedData]: RemoveIndexSignatures<DerivedData>; modelProvider: System | Module | null; /** * A set of localization prefix paths which are used by this data model. */ static LOCALIZATION_PREFIXES: string[]; /** * Prepare data related to this DataModel itself, before any derived data is computed. * * Called before {@link ClientDocument.prepareBaseData | `ClientDocument#prepareBaseData`} in {@link ClientDocument.prepareData | `ClientDocument#prepareData`}. */ prepareBaseData(this: TypeDataModel.PrepareBaseDataThis<this>): void; /** * Apply transformations of derivations to the values of the source data object. * Compute data fields whose values are not stored to the database. * * Called before {@link ClientDocument.prepareDerivedData | `ClientDocument#prepareDerivedData`} in {@link ClientDocument.prepareData | `ClientDocument#prepareData`}. */ prepareDerivedData(this: TypeDataModel.PrepareDerivedDataThis<this>): void; /** * Convert this Document to some HTML display for embedding purposes. * @param config - Configuration for embedding behavior. * @param options - The original enrichment options for cases where the Document embed content * also contains text that must be enriched. */ toEmbed( config: TextEditor.DocumentHTMLEmbedConfig, options: TextEditor.EnrichmentOptions, ): Promise<HTMLElement | HTMLCollection | null>; /* -------------------------------------------- */ /* Database Operations */ /* -------------------------------------------- */ /** * Called by {@link ClientDocument._preCreate | `ClientDocument#_preCreate`}. * * @param data - The initial data object provided to the document creation request * @param options - Additional options which modify the creation request * @param user - The User requesting the document creation * @returns Return false to exclude this Document from the creation operation */ protected _preCreate( data: TypeDataModel.ParentAssignmentType<Schema, Parent>, options: Document.Database.PreCreateOptions<DatabaseCreateOperation>, user: User.Implementation, ): Promise<boolean | void>; /** * Called by {@link ClientDocument._onCreate | `ClientDocument#_onCreate`}. * * @param data - The initial data object provided to the document creation request * @param options - Additional options which modify the creation request * @param userId - The id of the User requesting the document update */ // TODO: should be `MaybePromise<void>` to allow async subclassing? protected _onCreate( data: TypeDataModel.ParentAssignmentType<Schema, Parent>, options: Document.Database.CreateOptions<DatabaseCreateOperation>, userId: string, ): void; /** * Called by {@link ClientDocument._preUpdate | `ClientDocument#_preUpdate`}. * * @param changes - The candidate changes to the Document * @param options - Additional options which modify the update request * @param user - The User requesting the document update * @returns A return value of false indicates the update operation should be cancelled. */ protected _preUpdate( changes: DeepPartial<TypeDataModel.ParentAssignmentType<Schema, Parent>>, options: Document.Database.PreUpdateOptions<DatabaseUpdateOperation>, user: User.Implementation, ): Promise<boolean | void>; /** * Called by {@link ClientDocument._onUpdate | `ClientDocument#_onUpdate`}. * * @param changed - The differential data that was changed relative to the documents prior values * @param options - Additional options which modify the update request * @param userId - The id of the User requesting the document update */ // TODO: should be `MaybePromise<void>` to allow async subclassing? protected _onUpdate( changed: DeepPartial<TypeDataModel.ParentAssignmentType<Schema, Parent>>, options: Document.Database.UpdateOptions<DatabaseUpdateOperation>, userId: string, ): void; /** * Called by {@link ClientDocument._preDelete | `ClientDocument#_preDelete`}. * * @param options - Additional options which modify the deletion request * @param user - The User requesting the document deletion * @returns A return value of false indicates the deletion operation should be cancelled. */ protected _preDelete( options: Document.Database.PreDeleteOperationInstance<DatabaseDeleteOperation>, user: User.Implementation, ): Promise<boolean | void>; /** * Called by {@link ClientDocument._onDelete | `ClientDocument#_onDelete`}. * * @param options - Additional options which modify the deletion request * @param userId - The id of the User requesting the document update */ // TODO: should be `MaybePromise<void>` to allow async subclassing? protected _onDelete(options: Document.Database.DeleteOptions<DatabaseDeleteOperation>, userId: string): void; } declare class ConfigurationFailure extends TypeDataModel<any, any, any, any> {} export default TypeDataModel;