fvtt-types
Version:
TypeScript type definitions for Foundry VTT
517 lines (444 loc) • 21.9 kB
text/typescript
import type { AnyMutableObject, AnyObject, EmptyObject, Identity, InexactPartial, NullishProps } from "#utils";
import type { DataField, SchemaField } from "../data/fields.d.mts";
import type { fields } from "../data/_module.d.mts";
import type { DataModelValidationFailure } from "../data/validation-failure.d.mts";
type DataSchema = fields.DataSchema;
declare const DynamicClass: new <_Computed extends object>(...args: never) => _Computed;
// @ts-expect-error This is a workaround to allow for dynamic top level properties in a class.
declare class _InternalDataModel<
out Schema extends DataSchema,
// Do not inline. Being a type parameter is an important part of the circumvention of TypeScript's detection of dynamic classes.
out _Computed extends object = SchemaField.InitializedData<Schema>,
> extends DynamicClass<_Computed> {}
export default DataModel;
/**
* The abstract base class which defines the data schema contained within a Document.
*/
declare abstract class DataModel<
Schema extends DataSchema,
Parent extends DataModel.Any | null = null,
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
ExtraConstructorOptions extends AnyObject = {},
> extends _InternalDataModel<Schema> {
/**
* @param data - Initial data used to construct the data object. The provided object
* will be owned by the constructed model instance and may be mutated.
* @param options - Options which affect DataModel construction
*/
constructor(...args: DataModel.ConstructorArgs<Schema, Parent, ExtraConstructorOptions>);
/** @internal */
" __fvtt_types_internal_schema": Schema;
/** @internal */
" __fvtt_types_internal_source_data": SchemaField.SourceData<Schema>;
/** @internal */
// eslint-disable-next-line @typescript-eslint/no-deprecated
" __fvtt_types_internal_assignment_data": SchemaField.AssignmentData<Schema>;
/** @internal */
" __fvtt_types_internal_initialized_data": SchemaField.InitializedData<Schema>;
/**
* Configure the data model instance before validation and initialization workflows are performed.
*/
// options: not null (parameter default only, destructured in Document)
protected _configure(options?: DataModel.ConfigureOptions & ExtraConstructorOptions): void;
/**
* The source data object for this DataModel instance.
* Once constructed, the source object is sealed such that no keys may be added nor removed.
*/
readonly _source: fields.SchemaField.SourceData<Schema>;
/**
* The defined and cached Data Schema for all instances of this DataModel.
* @internal
*/
protected static _schema: SchemaField.Any;
/**
* An immutable reverse-reference to a parent DataModel to which this model belongs.
*/
readonly parent: Parent;
/**
* Define the data schema for documents of this type.
* The schema is populated the first time it is accessed and cached for future reuse.
* @remarks The returned value MUST be kept up to sync with the `Schema` type parameter.
*/
static defineSchema(): DataSchema;
/**
* Define the data schema for documents of this type.
*/
static get schema(): SchemaField.Any;
/**
* Define the data schema for this document instance.
*/
get schema(): SchemaField<Schema, EmptyObject>;
/**
* Is the current state of this DataModel invalid?
* The model is invalid if there is any unresolved failure.
*/
get invalid(): boolean;
/**
* An array of validation failure instances which may have occurred when this instance was last validated.
*/
// FIXME: Check if this is actually correct.
get validationFailures(): { fields: DataModelValidationFailure | null; joint: DataModelValidationFailure | null };
#validationFailures: { fields: DataModelValidationFailure | null; joint: DataModelValidationFailure | null };
/**
* A set of localization prefix paths which are used by this DataModel.
*/
static LOCALIZATION_PREFIXES: string[];
/**
* Initialize the source data for a new DataModel instance.
* One-time migrations and initial cleaning operations are applied to the source data.
* @param data - The candidate source data from which the model will be constructed
* @param options - Options provided to the model constructor
* (unused)
* @returns Migrated and cleaned source data which will be stored to the model instance
* @remarks `options` is unused in `DataModel`
*/
// options: not null (parameter default only)
protected _initializeSource(
data: fields.SchemaField.CreateData<Schema> | this,
options?: DataModel.InitializeSourceOptions & ExtraConstructorOptions,
): fields.SchemaField.SourceData<Schema>;
/**
* Clean a data source object to conform to a specific provided schema.
* @param source - The source data object
* @param options - Additional options which are passed to field cleaning methods
* @returns The cleaned source data
*/
static cleanData(source?: AnyMutableObject, options?: DataField.CleanOptions): AnyMutableObject;
/**
* A generator that orders the DataFields in the DataSchema into an expected initialization order.
*/
protected static _initializationOrder(): Generator<[string, DataField.Any], void, undefined>;
/**
* Initialize the instance by copying data from the source object to instance attributes.
* This mirrors the workflow of SchemaField#initialize but with some added functionality.
* @param options - Options provided to the model constructor
* @remarks `options` gets passed on to each field in the schema's `#initialize`
*/
// options: not null (parameter default only)
protected _initialize(options?: DataModel.InitializeOptions & ExtraConstructorOptions): void;
/**
* Reset the state of this data instance back to mirror the contained source data, erasing any changes.
*/
reset(): void;
/**
* Clone a model, creating a new data model by combining current data with provided overrides.
* @param data - Additional data which overrides current document data at the time of creation
* @param context - Context options passed to the data model constructor
* @returns The cloned Document [sic] instance
* @remarks Obviously returns not necessarily a `Document`, just a `DataModel`.
*
* **NOTE:** At the type level, the returned model will necessarily have the same parent as the instance `#clone()` is being called
* on; Accurate typing of `#parent` requires a cast.
* @privateRemarks Foundry types `context` as simply `object`, but going by core usage, it's a
* {@link DataModel.ConstructionContext | `DataModel.ConstructionContext`}. Both the implementation here
* and the override in `Document` provide `this.parent` to the construction context; Here, `context` is
* spread in, so providing a different parent is allowed. The `Document` override enforces `this.parent`
* with no opportunity to pass an alternative.
*/
// null would be fine for either/both params here, but it breaks Document, and its never *expected*, just incidentally doesn't break
// Note(LukeAbby): This is really like `DeepPartial<fields.SchemaField.UpdateData<Schema>>`.
// However the difference is subtle enough that it's unlikely to come into play in common usage.
clone(data?: fields.SchemaField.UpdateData<Schema>, context?: DataModel.CloneContext & ExtraConstructorOptions): this;
/**
* Validate the data contained in the document to check for type and content
* This function throws an error if data within the document is not valid
*
* @param options - Optional parameters which customize how validation occurs.
* @returns An indicator for whether the document contains valid data
*/
// options: not null (destructured)
validate(options?: DataModel.ValidateOptions<Schema>): boolean;
/**
* Evaluate joint validation rules which apply validation conditions across multiple fields of the model.
* Field-specific validation rules should be defined as part of the DataSchema for the model.
* This method allows for testing aggregate rules which impose requirements on the overall model.
* @param data - Candidate data for the model
* @throws An error if a validation failure is detected
* @remarks Other than posting a deprecation warning about and forwarding `data` to `this.prototype._validateModel` if defined,
* this is effectively abstract in `DataModel`. Subclasses implementing should type `data` as the `SchemaField.SourceData<>` of
* their schema.
*/
// TODO(esheyw): dep warning is gone in v13, clean up remarks
static validateJoint(data: never): void;
/**
* Update the DataModel locally by applying an object of changes to its source data.
* The provided changes are cleaned, validated, and stored to the source data object for this model.
* The source data is then re-initialized to apply those changes to the prepared data.
* The method returns an object of differential changes which modified the original data.
*
* @param changes - New values which should be applied to the data model
* @param options - Options which determine how the new data is merged
* @returns An object containing the changed keys and values
*/
// TODO: This should allow dotkeys to be passed
// TODO: Without widening changes and the return type, the ActorDelta override is impossible to make correct
// changes, options: not null (parameter default only)
updateSource(
changes?: fields.SchemaField.UpdateData<Schema>,
options?: DataModel.UpdateOptions,
): fields.SchemaField.UpdateData<Schema>;
/**
* Copy and transform the DataModel into a plain object.
* Draw the values of the extracted object from the data source (by default) otherwise from its transformed values.
* @param source - Draw values from the underlying data source rather than transformed values (default: `true`)
* @returns The extracted primitive object
*/
toObject(source?: boolean | null): SchemaField.SourceData<Schema>;
/**
* Extract the source data for the DataModel into a simple object format that can be serialized.
* @returns The document source data expressed as a plain object
*/
toJSON(): SchemaField.SourceData<Schema>;
/**
* Create a new instance of this DataModel from a source record.
* The source is presumed to be trustworthy and is not strictly validated.
* @param source - Initial document data which comes from a trusted source.
* @param context - Model construction context
* @remarks Returns `new this()` so needs an override per subclass:
* ```ts
* const mySchema = {
* // etc
* }
*
* type MySchema = typeof mySchema
*
* // most models likely wont be using this param at all, but its included for completeness
* interface MyExtraConstructorOptions {
* someProp: string
* }
*
* class MyDataModel extends DataModel<MySchema, DataModel.Any | null, MyExtraConstructorOptions> {
* static fromSource(
* source: foundry.data.fields.SchemaField.CreateData<MySchema>,
* context?: DataModel.FromSourceOptions<NewParent> & MyExtraConstructorOptions
* ): MyDataModel
* }
* ```
*/
// context: not null (destructured)
static fromSource(source: never, context?: DataModel.FromSourceOptions): DataModel.Any;
/**
* Create a DataModel instance using a provided serialized JSON string.
* @param json - Serialized document data in string format
* @returns A constructed data model instance
* @remarks
* Returns `new this()` so needs an override per subclass.
*
* ```ts
* class MyDataModel extends ... {
* static fromJSON(json: string): MyDataModel
* }
* ```
*/
static fromJSON(json: string): DataModel.Any;
/**
* Migrate candidate source data for this DataModel which may require initial cleaning or transformations.
* @param source - The candidate source data from which the model will be constructed
* @returns Migrated source data, if necessary
*/
static migrateData(source: AnyMutableObject): AnyMutableObject;
/**
* Wrap data migration in a try/catch which attempts it safely
* @param source - The candidate source data from which the model will be constructed
* @returns Migrated source data, if necessary
*/
static migrateDataSafe(source: AnyMutableObject): AnyMutableObject;
/**
* Take data which conforms to the current data schema and add backwards-compatible accessors to it in order to
* support older code which uses this data.
* @param data - Data which matches the current schema
* @param options - Additional shimming options
* @returns Data with added backwards-compatible properties
*/
// options: not null (destructured)
static shimData(data: AnyMutableObject, options?: DataModel.ShimDataOptions): AnyMutableObject;
}
declare namespace DataModel {
interface Any extends AnyDataModel {}
interface AnyConstructor extends Identity<typeof AnyDataModel> {}
/**
* This type is similar to `typeof DataModel` but isn't abstract.
* This is useful for code that does `new someDataModel(...)`.
*/
interface ConcreteConstructor extends Identity<typeof ConcreteDataModel> {}
// TODO(LukeAbby): see if `| DataModel<Schema, any>` needs to be added back.
// Seems rare in practice and causes terrible errors. May not need to be added back if `DataModel`.
// is always assignable.
type CreateData<Schema extends DataSchema> = fields.SchemaField.CreateData<Schema>;
// TODO(LukeAbby): Make optional only if `{}` is assignable to `CreateData`.
type ConstructorArgs<
Schema extends DataSchema,
Parent extends DataModel.Any | null = null,
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
ExtraConstructorOptions extends AnyObject = {},
> = [
data?: DataModel.CreateData<Schema>,
// Note(LukeAbby): `{ parent, strict, ...options }`
options?: DataModel.ConstructionContext<Parent> & ExtraConstructorOptions,
];
/**
* With the existence of custom module subtypes a system can no longer rely on their configured types being the only ones.
*
* `UnknownDataModel` covers the case where it's configured with a {@linkcode DataModel}.
* Using a {@linkcode TypeDataModel} is recommended by Foundry but a {@linkcode DataModel} is
* always possible.
* See {@linkcode UnknownSystem} for other possibilities.
*/
interface UnknownDataModel extends DataModel<any, any, any> {}
/**
* A helper type to extract the schema from a {@linkcode DataModel}.
* @template ModelType - the DataModel for the embedded data
*/
type SchemaOf<ModelType extends DataModel.Any> = ModelType["schema"]["fields"];
// Note(LukeAbby): This avoids writing `SchemaOf<InstanceType<ConcreteClass>>` to be robust to an issue with this snippet:
// ```ts
// EmbeddedDataField<typeof DataModel<{}>> extends SchemaField<infer SubSchema> ? SubSchema : never
// ```
type SchemaOfClass<ConcreteClass extends DataModel.AnyConstructor> = ReturnType<ConcreteClass["defineSchema"]>;
/**
* this is how 13.339 splits up the interfaces
*
* @internal
*/
type _ConstructionContext = Pick<ValidateOptions<DataSchema>, "strict" | "fallback" | "dropInvalidEmbedded">;
interface ConstructionContext<Parent extends Any | null = Any | null> extends _ConstructionContext {
/**
* A parent DataModel instance to which this DataModel belongs
* @defaultValue `null`
*/
parent?: Parent | undefined;
}
interface CloneContext extends _ConstructionContext {
/**
* A parent DataModel instance to which this DataModel belongs
* @defaultValue `this.parent`
* @remarks The above default is only applied if this parameter is omitted entirely. Passing `parent: undefined` will
* cause the returned DataModel's `parent` (*at runtime*) to be `null`, because it will override the spread-object
* default of `this.parent`, and then the parameter default in `new DataModel()` (`null`) will apply.
*
* **NOTE:** At the type level, the returned model will necessarily have the same parent as the instance `#clone()` is being called
* on; Accurate typing of `#parent` requires a cast.
*/
parent?: Any | null | undefined;
}
/**
* Updated to 13.339 wording, still accurate for v12
*
* @internal
*/
type _ValidateOptions<Schema extends DataSchema> = NullishProps<{
/**
* Validate each individual field?
* @defaultValue `true`
*/
fields: boolean;
/**
* Perform joint validation on the full data model?
* Joint validation will be performed by default if no changes are passed.
* Joint validation will be disabled by default if changes are passed.
* Joint validation can be performed on a complete set of changes (for example, testing a complete data model) by explicitly passing true.
* @remarks If nullish, defaults to `!changes`
*/
joint: boolean;
/**
* A specific set of proposed changes to validate, rather than the full source data of the model.
* @remarks If not passed or nullish, `validate` will operate on `this._source` instead
*/
// eslint-disable-next-line @typescript-eslint/no-deprecated
changes: fields.SchemaField.AssignmentData<Schema>;
/**
* If changes are provided, attempt to clean the changes before validating them?
* @defaultValue `false`
* @remarks Only has any effect if a `changes` has been passed with it
*/
clean: boolean;
/**
* Throw an error if validation fails.
* @defaultValue `true`
*/
strict: boolean;
/**
* Allow replacement of invalid values with valid defaults? This option mutates the provided changes.
* @defaultValue `false`
* @see {@link DataField.ValidateOptions.fallback | `DataField.DataValidationOptions.fallback`}
*/
fallback: boolean;
// Foundry describes a `partial` property here, but nothing in DataModel actually *takes* such; `#validate`
// generates a value for `partial` from the state of `changes` and `joint` before passing it on to either
// `DataModel.cleanData` or `SchemaField#validate`, both of which do have uses for that property downstream
// (in `SchemaField#_cleanType` and `#_validateType`). This is a documentation error in v12, fixed in v13.
/**
* If true, invalid embedded documents will emit a warning and be placed in the invalidDocuments collection rather
* than causing the parent to be considered invalid. This option mutates the provided changes.
* @defaultValue `false`
* @see {@link DataField.ValidateOptions.dropInvalidEmbedded | `DataField.DataValidationOptions.dropInvalidEmbedded`}
*/
dropInvalidEmbedded: boolean;
}>;
interface ValidateOptions<Schema extends DataSchema> extends _ValidateOptions<Schema> {}
/** `DataModel#constructor` pulls `parent` out of the passed `ConstructionContext` before forwarding to `#_initializeSource` */
interface InitializeSourceOptions extends _ConstructionContext {}
/** `DataModel#constructor` pulls `parent` and `strict` out of the passed `ConstructionContext` before forwarding to `#_configure` */
interface ConfigureOptions extends Omit<_ConstructionContext, "strict"> {}
/** `DataModel#constructor` pulls `parent` out of the passed `ConstructionContext` before forwarding to `#_initialize` */
interface InitializeOptions extends _ConstructionContext {}
type _UpdateOptions = InexactPartial<{
/** Do not finally apply the change, but instead simulate the update workflow */
dryRun: boolean;
/**
* Allow automatic fallback to a valid initial value if the value provided for a field
* in the model is invalid.
*/
fallback: boolean;
/**
* Apply changes to inner objects recursively rather than replacing the top-level object
* @defaultValue `true`
* @remarks No actual default is applied to this property anywhere in Foundry code, but behaviour depending on this option uses `=== false`
* checks, so it effectively is `true` by default
*/
recursive: boolean;
/** An advanced option used specifically and internally by the ActorDelta model */
restoreDelta: boolean;
}>;
interface UpdateOptions extends _UpdateOptions {}
/**
* Only necessary to change the default value of `strict`
*
* @internal
*/
type _FromSourceOptions = NullishProps<{
/**
* Models created from trusted source data are validated non-strictly
* @defaultValue `false`
* @remarks The property description is describing why the default is `false` here,
* rather than `true` in normal construction
*/
strict: boolean;
}>;
/**
* @remarks `.fromSource` could take and pass on a specific `Parent`, but this causes
* inheritance issues and complicates subclass overriding
*/
interface FromSourceOptions extends ConstructionContext {}
/** @internal */
type _ShimDataOptions = NullishProps<{
/**
* Apply shims to embedded models?
* @defaultValue `true`
*/
embedded: boolean;
}>;
interface ShimDataOptions extends _ShimDataOptions {}
/**
* @deprecated Replaced by {@linkcode DataModel.CreateData}
*/
type ConstructorDataFor<ConcreteDataModel extends DataModel.Any> = CreateData<SchemaOf<ConcreteDataModel>>;
}
// This uses `any` because `Schema` and `Parent` are invariant
declare abstract class AnyDataModel extends DataModel<DataSchema, any, AnyObject> {
constructor(...args: never);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-object-type
declare class ConcreteDataModel extends DataModel<DataSchema, DataModel.Any | null, {}> {}
// Matches foundry exporting class as both default and non-default
export { DataModel };