fvtt-types
Version:
TypeScript type definitions for Foundry VTT
1,248 lines (1,096 loc) • 215 kB
text/typescript
import type {
RemoveIndexSignatures,
SimpleMerge,
AnyObject,
EmptyObject,
NullishProps,
InexactPartial,
FixedInstanceType,
Identity,
PrettifyType,
InterfaceToObject,
AnyArray,
GetKey,
SplitString,
ValueOf,
AnyMutableObject,
ConcreteKeys,
} from "#utils";
import type { DataModel } from "../abstract/data.mts";
import type Document from "../abstract/document.mts";
import type { EmbeddedCollection, EmbeddedCollectionDelta, TypeDataModel } from "../abstract/_module.d.mts";
import type { DOCUMENT_OWNERSHIP_LEVELS } from "../constants.d.mts";
import type { CONST } from "#client/client.d.mts";
import type { DataModelValidationFailure } from "./validation-failure.mts";
import type {
_FormInputConfig,
FormGroupConfig,
FormInputConfig,
MultiSelectInputConfig,
SelectInputConfig,
TextAreaInputConfig,
} from "#client/applications/forms/fields.d.mts";
export type DataSchema = Record<string, DataField.Any>;
/**
* An abstract class that defines the base pattern for a data field within a data schema.
* @template Options - the options of the DataField instance
* @template AssignmentType - the type of the allowed assignment values of the DataField
* @template InitializedType - the type of the initialized values of the DataField
* @template PersistedType - the type of the persisted values of the DataField
* @remarks
* Defaults:
* - AssignmentType: `unknown | null | undefined`
* - InitializedType: `unknown | undefined`
* - PersistedType: `unknown | undefined`
* - InitialValue: `undefined`
*/
declare abstract class DataField<
const Options extends DataField.Options.Any = DataField.DefaultOptions,
// eslint-disable-next-line @typescript-eslint/no-deprecated
const AssignmentType = DataField.AssignmentType<Options>,
const InitializedType = DataField.InitializedType<Options>,
const PersistedType = InitializedType,
> {
// Prevent from being bivariant.
#assignmentType: AssignmentType;
/**
* @param options - Options which configure the behavior of the field
* @param context - Additional context which describes the field
*/
constructor(options?: Options, context?: DataField.ConstructionContext);
/** @internal */
" __fvtt_types_internal_source_data": PersistedType;
/** @internal */
" __fvtt_types_internal_assignment_data": AssignmentType;
/** @internal */
" __fvtt_types_internal_initialized_data": InitializedType;
/**
* The field name of this DataField instance.
* This is assigned by {@linkcode SchemaField.initialize | SchemaField#initialize}.
* @internal
*/
name: string | undefined;
/**
* A reference to the parent schema to which this DataField belongs.
* This is assigned by {@linkcode SchemaField.initialize | SchemaField#initialize}.
* @internal
*/
parent: DataField.Any | undefined;
/** The initially provided options which configure the data field */
options: Options;
/**
* Whether this field defines part of a Document/Embedded Document hierarchy.
* @defaultValue `false`
*/
static hierarchical: boolean;
/**
* Does this field type contain other fields in a recursive structure?
* Examples of recursive fields are SchemaField, ArrayField, or TypeDataField
* Examples of non-recursive fields are StringField, NumberField, or ObjectField
* @defaultValue `false`
*/
static recursive: boolean;
/**
* Is this field required to be populated?
* @defaultValue `false`
*/
required: boolean;
/**
* Can this field have null values?
* @defaultValue `false`
*/
nullable: boolean;
/**
* Can this field only be modified by a gamemaster or assistant gamemaster?
* @defaultValue `false`
*/
gmOnly: boolean;
/**
* The initial value of a field, or a function which assigns that initial value.
* @defaultValue `undefined`
*/
initial: DataField.Options.InitialType<InitializedType>;
/**
* Should the prepared value of the field be read-only, preventing it from being
* changed unless a change to the _source data is applied.
* @defaultValue `false`
*/
readonly: boolean;
/**
* A localizable label displayed on forms which render this field.
* @defaultValue `""`
*/
label: string;
/**
* Localizable help text displayed on forms which render this field.
* @defaultValue `""`
*/
hint: string;
/**
* A custom validation error string. When displayed will be prepended with the
* document name, field name, and candidate value. This error string is only
* used when the return type of the validate function is a boolean. If an Error
* is thrown in the validate function, the string message of that Error is used.
* @defaultValue `"is not a valid value"`
*/
validationError: string;
/**
* Default parameters for this field type
* @remarks This is not entirely type-safe, overrides should specify a more concrete return type.
*/
protected static get _defaults(): DataField.Options.Any;
/**
* A dot-separated string representation of the field path within the parent schema.
* @remarks Returns `""` if both `this.parent?.fieldPath` and `this.name` are falsey
*/
get fieldPath(): string;
/**
* Apply a function to this DataField which propagates through recursively to any contained data schema.
* @param fn - The function to apply
* @param value - The current value of this field
* @param options - Additional options passed to the applied function (default `{}`)
* @returns The results object
*/
// TODO: Determine `value` based upon the field metadata in fields-v2 (while allowing subclasses to narrow allowed values)
apply<Options, Return>(
fn: keyof this | ((this: this, value: unknown, options: Options) => Return),
value?: unknown,
options?: Options,
): Return;
/**
* Add types of the source to the data if they are missing.
* @param source - The source data
* @param changes - The partial data
* @param options - Additional options (default: `{}`)
* @internal
*
* @remarks
* Called externally by Foundry in `ClientDatabaseBackend##preUpdateDocumentArray`, {@link DataModel.validate | `DataModel#validate`},
* and {@link DataModel.updateSource | `DataModel#updateSource`}.
*
* The `options` arg is not expected to be passed, it's assembled using the passed `source` and `changes` then used internally for recursive calls.
*/
protected _addTypes(source?: AnyObject, changes?: AnyObject, options?: DataField.AddTypesOptions): void;
/**
* Recursively traverse a schema and retrieve a field specification by a given path
* @param path - The field path as an array of strings
* @returns The corresponding DataField definition for that field, or undefined
* @internal
*/
protected _getField(path: string[]): DataField.Any | undefined;
/**
* Coerce source data to ensure that it conforms to the correct data type for the field.
* Data coercion operations should be simple and synchronous as these are applied whenever a DataModel is constructed.
* For one-off cleaning of user-provided input the sanitize method should be used.
* @param value - An initial requested value
* @param options - Additional options for how the field is cleaned
* @returns The cast value
*/
// TODO (LukeAbby): Because `getInitialValue` trusts function `initial`s too much, this can actually return `| null | undefined` regardless of options, if `value === undefined`
clean(value: AssignmentType, options?: DataField.CleanOptions): InitializedType;
/**
* Apply any cleaning logic specific to this DataField type.
* @param value - The appropriately coerced value.
* @param options - Additional options for how the field is cleaned.
* @returns The cleaned value.
* @remarks Simply returns `value` in `DataField`. `options` is unused in `DataField`
*/
protected _cleanType(value: InitializedType, options?: DataField.CleanOptions): InitializedType;
/**
* Cast a non-default value to ensure it is the correct type for the field
* @param value - The provided non-default value
* @returns The standardized value
* @remarks No longer so effectively abstract in v13, `DataField`'s implementation now simply returns the provided value,
* but since subclasses *should* still implement an `_cast` that matches their `AssignmentType` and `InitializedType`, it
* remains `abstract` here
*/
protected abstract _cast(value: unknown): AssignmentType;
/**
* Attempt to retrieve a valid initial value for the DataField.
* @param data - The source data object for which an initial value is required
* @returns A valid initial value
* @remarks `data` is unused if the field's `initial` is not a function.
*/
getInitialValue(data?: unknown): InitializedType;
/**
* Export the current value of the field into a serializable object.
* @param value - The initialized value of the field
* @returns An exported representation of the field
*/
toObject(value: InitializedType): PersistedType;
/**
* Validate a candidate input for this field, ensuring it meets the field requirements.
* A validation failure can be provided as a raised Error (with a string message), by returning false, or by returning
* a DataModelValidationFailure instance.
* A validator which returns true denotes that the result is certainly valid and further validations are unnecessary.
* @param value - The initial value
* @param options - Options which affect validation behavior (default: `{}`)
* @returns Returns a ModelValidationError if a validation failure occurred
*/
validate(value: AssignmentType, options?: DataField.ValidateOptions<this>): DataModelValidationFailure | void;
/**
* Special validation rules which supersede regular field validation.
* This validator screens for certain values which are otherwise incompatible with this field like null or undefined.
* @param value - The candidate value
* @returns A boolean to indicate with certainty whether the value is valid.
* Otherwise, return void.
* @throws May throw a specific error if the value is not valid
*/
protected _validateSpecial(value: AssignmentType): boolean | void;
/**
* A default type-specific validator that can be overridden by child classes
* @param value - The candidate value
* @param options - Options which affect validation behavior
* @returns A boolean to indicate with certainty whether the value is valid, or specific DataModelValidationFailure information, otherwise void.
* @throws May throw a specific error if the value is not valid
*/
protected _validateType(
value: InitializedType,
options?: DataField.ValidateOptions<this>,
): boolean | DataModelValidationFailure | void;
/**
* Certain fields may declare joint data validation criteria.
* This method will only be called if the field is designated as recursive.
* @param data - Candidate data for joint model validation
* @param options - Options which modify joint model validation
* @throws An error if joint model validation fails
* @internal
*
* @remarks Core never checks the return of this, it should simply either `throw` or not `throw`
*
* The only place core checks the `options` for any property is in {@link TypeDataField._validateModel | `TypeDataField#_validateModel`},
* where it checks `options.source?.type`
*
* {@link SchemaField._validateModel | `SchemaField._validateModel`} enforces `source`'s existence for subsidiary calls
*
* The only place core *calls* this at a top level, it does not pass anything for `options`, relying on SchemaField above
* to make TypeDataField work
*/
protected _validateModel(data: AnyObject, options?: DataField.ValidateModelOptions): void;
/**
* Initialize the original source data into a mutable copy for the DataModel instance.
* @param value - The source value of the field
* @param model - The DataModel instance that this field belongs to
* @param options - Initialization options
* @returns An initialized copy of the source data
* @remarks Core fields that return a function:
* - {@link ForeignDocumentField | `ForeignDocumentField`}
* - `ActorDeltaField` (exported in the BaseToken file but not re-exported by the relevant `_module`, so unlinkable)
*/
// TODO: investigate narrowing return to just `InitializedType` on inheritance lines that don't possibly return one
// TODO: (everything except SchemaField and ObjectField and their descendants)
initialize(
value: PersistedType,
model: DataModel.Any,
options?: DataField.InitializeOptions,
): InitializedType | (() => InitializedType | null);
/**
* Update the source data for a DataModel which includes this DataField.
* This method is responsible for modifying the provided source data as well as updating the tracked diff included
* in provided metadata.
* @param source - Source data of the DataModel which should be updated. This object is always a partial node of source data, relative to which this field belongs.
* @param key - The name of this field within the context of the source data.
* @param value - The candidate value that should be applied as an update.
* @param difference - The accumulated diff that is recursively populated as the model traverses through its schema fields.
* @param options - Options which modify how this update workflow is performed.
* @throws An error if the requested update cannot be performed.
* @internal
* @remarks Only `recursive` is checked in `options` by any core fields. Mutates `source`.
*
* Called externally by Foundry in {@link DataModel.updateSource | `DataModel#updateSource`} and various core field class's overrides (`this.element._updateDiff()`, `field._updateDiff()` etc);
* it's been left public for use in user subclasses
*/
_updateDiff(
source: AnyMutableObject,
key: string,
value: unknown,
difference: AnyObject,
options?: DataModel.UpdateOptions,
): void;
/**
* Commit a prepared update to DataModel#_source.
* @param source - The parent source object within which the `key` field exists
* @param key - The named field in source to commit
* @param value - The new value of the field which should be committed to source
* @param diff - The reported change to the field
* @param options - Options which modify how this update workflow is performed.
* @internal
* @remarks Mutates `source`.
*
* Called externally by Foundry in {@link DataModel.updateSource | `DataModel#updateSource`} and various core field class's overrides (`this.element._updateCommit()`, `field._updateCommit()` etc);
* it's been left public for use in user subclasses
*/
_updateCommit(
source: AnyMutableObject,
key: string,
value: unknown,
diff: unknown,
options?: DataModel.UpdateOptions,
): void;
/**
* Does this form field class have defined form support?
*/
static get hasFormSupport(): boolean;
/**
* Render this DataField as an HTML element.
* @param config - Form element configuration parameters
* @throws An Error if this DataField subclass does not support input rendering
* @returns A rendered HTMLElement for the field
*/
toInput(config?: DataField.ToInputConfig<InitializedType>): HTMLElement | HTMLCollection;
/**
* Render this DataField as an HTML element.
* Subclasses should implement this method rather than the public toInput method which wraps it.
* @param config - Form element configuration parameters
* @throws An Error if this DataField subclass does not support input rendering
* @returns A rendered HTMLElement for the field
* @remarks Would be `abstract` except not all fields are designed to be used in forms
*/
protected _toInput(config: DataField.ToInputConfig<InitializedType>): HTMLElement | HTMLCollection;
/**
* Render this DataField as a standardized form-group element.
* @param groupConfig - Configuration options passed to the wrapping form-group
* @param inputConfig - Input element configuration options passed to DataField#toInput
* @returns The rendered form group element
*/
toFormGroup(
groupConfig?: DataField.GroupConfig,
inputConfig?: DataField.ToInputConfig<InitializedType>,
): HTMLDivElement;
/**
* Apply an ActiveEffectChange to this field.
* @param value - The field's current value.
* @param model - The model instance.
* @param change - The change to apply.
* @returns The updated value.
*/
applyChange(value: InitializedType, model: DataModel.Any, change: ActiveEffect.ChangeData): InitializedType;
/**
* Cast a change delta into an appropriate type to be applied to this field.
* @param delta - The change delta.
* @internal
*/
// Note(LukeAbby): Technically since this defers to `_cast` it should take whatever `_cast` can.
// But it always must be able to take a `string` because that's how `applyChange` calls it.
protected _castChangeDelta(delta: string): InitializedType;
/**
* Apply an ADD change to this field.
* @param value - The field's current value.
* @param delta - The change delta.
* @param model - The model instance.
* @param change - The original change data.
* @returns The updated value.
*
* @remarks Returns `value + delta`. `model` and `change` are unused in `DataField`
*/
protected _applyChangeAdd(
value: InitializedType,
delta: InitializedType,
model: DataModel.Any,
change: ActiveEffect.ChangeData,
): InitializedType;
/**
* Apply a MULTIPLY change to this field.
* @param value - The field's current value.
* @param delta - The change delta.
* @param model - The model instance.
* @param change - The original change data.
* @returns The updated value.
*
* @remarks No-op in `DataField`, returns `undefined` unless overridden
*/
protected _applyChangeMultiply(
value: InitializedType,
delta: InitializedType,
model: DataModel.Any,
change: ActiveEffect.ChangeData,
): InitializedType | undefined;
/**
* Apply an OVERRIDE change to this field.
* @param value - The field's current value.
* @param delta - The change delta.
* @param model - The model instance.
* @param change - The original change data.
* @returns The updated value.
*
* @returns Simply returns `delta`. `value`, `model`, and `change` are unused in `DataField`
*/
protected _applyChangeOverride(
value: InitializedType,
delta: InitializedType,
model: DataModel.Any,
change: ActiveEffect.ChangeData,
): InitializedType;
/**
* Apply an UPGRADE change to this field.
* @param value - The field's current value.
* @param delta - The change delta.
* @param model - The model instance.
* @param change - The original change data.
* @returns The updated value.
*
* @remarks No-op in `DataField`, returns `undefined` unless overridden
*/
protected _applyChangeUpgrade(
value: InitializedType,
delta: InitializedType,
model: DataModel.Any,
change: ActiveEffect.ChangeData,
): InitializedType | undefined;
/**
* Apply a DOWNGRADE change to this field.
* @param value - The field's current value.
* @param delta - The change delta.
* @param model - The model instance.
* @param change - The original change data.
* @returns The updated value.
*
* @remarks No-op in `DataField`, returns `undefined` unless overridden
*/
protected _applyChangeDowngrade(
value: InitializedType,
delta: InitializedType,
model: DataModel.Any,
change: ActiveEffect.ChangeData,
): InitializedType | undefined;
/**
* Apply a CUSTOM change to this field.
* @param value - The field's current value.
* @param delta - The change delta.
* @param model - The model instance.
* @param change - The original change data.
* @returns The updated value.
* @remarks Only returns a value if the target value of the change actually changed
*/
protected _applyChangeCustom(
value: InitializedType,
delta: InitializedType,
model: DataModel.Any,
change: ActiveEffect.ChangeData,
): InitializedType | undefined;
}
declare namespace DataField {
/** Any DataField. */
interface Any extends AnyDataField {}
interface AnyConstructor extends Identity<typeof AnyDataField> {}
/** A DataField with unknown inner types. */
type Unknown = DataField<any, unknown, unknown, unknown>;
namespace Internal {
interface ElementFieldImplementation<Element extends DataField.Any = DataField.Any> {
" __fvtt_types_get_field_element": Element;
}
interface NestedFieldImplementation<Schema extends DataSchema = DataSchema> {
" __fvtt_types_get_field_schema": Schema;
}
}
/**
* @deprecated AssignmentType is being deprecated. See {@linkcode SchemaField.AssignmentData}
* for more details.
*/
type AssignmentTypeFor<ConcreteDataField extends Any> = ConcreteDataField[" __fvtt_types_internal_assignment_data"];
type InitializedTypeFor<ConcreteDataField extends Any> = ConcreteDataField[" __fvtt_types_internal_initialized_data"];
type PersistedTypeFor<ConcreteDataField extends Any> = ConcreteDataField[" __fvtt_types_internal_source_data"];
/** The type of the default options for the {@linkcode DataField} class. */
interface DefaultOptions {
required: false;
nullable: false;
initial: undefined;
readonly: false;
gmOnly: false;
label: "";
hint: "";
validationError: "is not a valid value";
}
/** @internal */
type _Options<BaseAssignmentType> = InexactPartial<{
/**
* Is this field required to be populated?
* @defaultValue `false`
*/
required: boolean;
/**
* Can this field have null values?
* @defaultValue `false`
*/
nullable: boolean;
/**
* Can this field only be modified by a gamemaster or assistant gamemaster?
* @defaultValue `false`
*/
gmOnly: boolean;
/** The initial value of a field, or a function which assigns that initial value. */
initial: DataField.Options.InitialType<
// TODO(LukeAbby): Add a `ValidateOptions` type or something of that sort in order to
// catch incorrect initial types.
DataField.Options.InitialReturnType<BaseAssignmentType, boolean, boolean>
>;
/** A localizable label displayed on forms which render this field. */
label: string;
/** Localizable help text displayed on forms which render this field. */
hint: string;
/** A data validation function which accepts one argument with the current value. */
validate: DataField.Validator<DataField.Any, BaseAssignmentType>;
/**
* A custom validation error string. When displayed will be prepended with the
* document name, field name, and candidate value. This error string is only
* used when the return type of the validate function is a boolean. If an Error
* is thrown in the validate function, the string message of that Error is used.
*/
validationError: string;
}>;
interface Options<BaseAssignmentType> extends _Options<BaseAssignmentType> {}
namespace Options {
/** Any DataField.Options. */
// Note(LukeAbby): Extending `Identity<object>` is intentional. Its purpose is to allow options like `{ integer: true }` to be assigned.
// This is an issue because `{ integer: true }` does not extend `{ required?: boolean }` because they have no properties in common.
// Even though `{ integer: true, required: undefined }` would extend `{ required?: boolean }` following the regular rules of surplus properties being allowed.
// `object` was chosen over `AnyObject` so that people may pass in interfaces.
interface Any extends DataField.Options<any>, Identity<object> {}
/**
* A helper type for the {@linkcode DataField.Options.initial} option.
* @template ReturnType - the return type of the option
*/
type InitialType<ReturnType> = ReturnType | ((initialData: unknown) => ReturnType);
/**
* The decorated return type for the {@linkcode DataField.Options.initial} option.
* @template BaseAssignmentType - the base assignment type for a DataField
* @template NullableOption - the value of the nullable option
* @template RequiredOption - the value of the required option
*/
type InitialReturnType<BaseAssignmentType, NullableOption, RequiredOption> =
| Exclude<BaseAssignmentType, null | undefined>
| (NullableOption extends true ? null : never)
| (RequiredOption extends true ? never : undefined);
}
/**
* A helper type for the given options type merged into the default options of the DataField class.
* @template Options - the options that override the default options
*/
type MergedOptions<Options extends DataField.Options.Any> = SimpleMerge<DefaultOptions, Options>;
/**
* A type to decorate the base assignment type to a DataField, based on the options of the field.
* @template BaseAssignmentType - the base assignment type of the DataField, without null or undefined
* @template Options - the options of the DataField
*
* @deprecated This type is being phased out alongside the entirety of the concept of the
* `Assignment` type.
*/
type DerivedAssignmentType<BaseAssignmentType, Options extends DataField.Options.Any> =
| Exclude<BaseAssignmentType, null | undefined> // Always include the base type
| (Options["nullable"] extends true // determine whether null is in the union
? // when nullable, both `null` and `undefined` can safely be passed
null | undefined
: never)
| (Options["required"] extends true // determine whether undefined is in the union
? never
: // when not required, both `null` and `undefined` can safely be passed
null | undefined)
| ("initial" extends keyof Options
? // TODO(LukeAbby): This should possibly actually be distributive.
Options["initial"] extends undefined
? never
: null | undefined // when initial is not `undefined` then `null | undefined` are valid.
: never);
/** @internal */
type _Has<T, U> = U extends unknown ? (U extends T ? true : false) : never;
/**
* A type to decorate the base initialized type of a DataField, based on the options of the field.
* @template BaseInitializedType - the base initialized type of the DataField, without null or undefined
* @template Options - the options of the DataField
*/
type DerivedInitializedType<BaseInitializedType, Options extends DataField.Options.Any> =
| Exclude<BaseInitializedType, null | undefined>
| _DerivedUndefined<GetKey<Options, "initial", undefined>, GetKey<Options, "required", undefined>>
| (Options["nullable"] extends true ? null : never);
/** @internal */
type _DerivedUndefined<Initial, Required extends boolean | undefined> = Required extends true
? never // If `required: true` then `undefined` is not possible.
: Initial extends undefined
? undefined // If `required: false` and `initial: undefined` then it passes through.
: Initial extends (...args: never) => infer Result
? Result extends undefined
? undefined // Passes through `undefined` if the function returns `undefined`. Should this match the case of `() => unknown`?
: never
: never;
/**
* A shorthand for the assignment type of a DataField class.
* @template Options - the options overriding the defaults
*
* @deprecated AssignmentData is being phased out. See {@linkcode SchemaField.AssignmentData}
* for more details.
*/
// eslint-disable-next-line @typescript-eslint/no-deprecated
type AssignmentType<Options extends DataField.Options.Any> = DerivedAssignmentType<unknown, MergedOptions<Options>>;
/**
* A shorthand for the initialized type of a DataField class.
* @template Options - the options overriding the defaults
*/
type InitializedType<Options extends DataField.Options.Any> = DerivedInitializedType<unknown, MergedOptions<Options>>;
/** @internal */
type _ConstructionContext = InexactPartial<{
/** A field name to assign to the constructed field */
name: string;
/** Another data field which is a hierarchical parent of this one */
parent: DataField.Any;
}>;
interface ConstructionContext extends _ConstructionContext {}
/** @internal */
type _AddTypesOptions = InexactPartial<{
/**
* The root data model source
* @remarks Not expected to be passed externally, the top level `_addTypes` call sets this to the passed `source`,
* making it available to subsidiary calls
*/
source: AnyObject;
/**
* The root data model changes
* @remarks Not expected to be passed externally, the top level `_addTypes` call sets this to the passed `changes`,
* making it available to subsidiary calls
*/
changes: AnyObject;
}>;
interface AddTypesOptions extends _AddTypesOptions {}
/** @internal */
type _ValidationOptions = InexactPartial<{
/** Whether this is a partial schema validation, or a complete one. */
partial: boolean;
/** Whether to allow replacing invalid values with valid fallbacks. */
fallback: boolean;
/**
* 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.
*/
dropInvalidEmbedded: boolean;
/** The full source object being evaluated. */
source: AnyObject;
}>;
/**
* @remarks This is the type for the options for `#validate` and associate methods *without* the
* possible inclusion of a `validator` function.
*
* If you are looking for the type with a generic formerly under this name, see {@link ValidateOptions | `DataField.ValidateOptions`}
*/
interface ValidationOptions extends _ValidationOptions {}
/** @internal */
type _CleanOptions = InexactPartial<{
/** Whether to perform partial cleaning? */
partial: boolean;
/** The root data model being cleaned */
source: AnyObject;
}>;
/** An interface for the options of {@link DataField.clean | `DataField#clean`} and {@link DataField._cleanType | `DataField#_cleanType`}. */
interface CleanOptions extends _CleanOptions {}
/**
* @remarks The only place core checks the `options` for any property is in {@link TypeDataField._validateModel | `TypeDataField#_validateModel`},
* where it checks `options.source?.type`
*
* {@link SchemaField._validateModel | `SchemaField._validateModel`} enforces `source`'s existence for subsidiary calls
*
* The only place core *calls* this at a top level, it does not pass anything for `options`, relying on SchemaField above
* to make TypeDataField work
*/
interface ValidateModelOptions extends Pick<ValidationOptions, "source"> {}
/**
* A Custom DataField validator function.
*
* A boolean return value indicates that the value is valid (true) or invalid (false) with certainty. With an explicit
* boolean return value no further validation functions will be evaluated.
*
* An undefined return indicates that the value may be valid but further validation functions should be performed,
* if defined.
*
* An Error may be thrown which provides a custom error message explaining the reason the value is invalid.
*/
type Validator<CurrentField extends DataField.Any, BaseAssignmentType> =
| {
validate(
this: CurrentField,
value: unknown,
options: ValidateOptions<CurrentField>,
): value is BaseAssignmentType;
}["validate"]
| {
validate(
this: CurrentField,
value: unknown,
options: ValidateOptions<CurrentField>,
): asserts value is BaseAssignmentType;
}["validate"]
| {
validate(
this: CurrentField,
value: unknown,
options: ValidateOptions<CurrentField>,
): DataModelValidationFailure | boolean | void;
}["validate"];
/**
* An interface for the options of the {@linkcode DataField} validation functions.
* @template CurrentField - the type of the DataField, which is the receiver of the validate function
*/
interface ValidateOptions<CurrentField extends DataField.Any> extends ValidationOptions {
/**
* @remarks If {@link DataField.validate | `DataField#validate`} is called with a `validate: someFunc` in its `options`,
* it will then pass that `options` object on to that function when it calls it, without alteration.
* Nothing in core makes use of the fact that a reference to the function is available, this seems incidental.
*/
// eslint-disable-next-line @typescript-eslint/no-deprecated
validate?: Validator<CurrentField, DataField.AssignmentTypeFor<CurrentField>>;
}
/**
* @remarks The `options` passed to {@link DataField.initialize | `DataField#initialize`} exclusively (in core) come from
* {@link DataModel._initialize | `DataModel#_initialize`} or an override (meaning `parent` has been stripped from the
* interface), and eventually hits one of:
* 1. Document construction, in all cases with `parent` already provided
* 2. Gets fed back {@link DataModel._initialize | `DataModel#_initialize`} or an override
* 3. {@link Document.get | `Document.get`}, but the one place this happens, `pack` is already provided, and that's the only
* option that method cares about.
*
* This extends the `Document` interface because several core fields use the `pack` property, which isn't available on the
* `DataModel` interface
*/
interface InitializeOptions extends Document.InitializeOptions {}
interface ToInputConfig<InitializedType> extends FormInputConfig<InitializedType> {}
interface ToInputConfigWithOptions<InitializedType> extends FormInputConfig<InitializedType>, SelectInputConfig {}
type AnyChoices = StringField.Choices | NumberField.Choices;
type ToInputConfigWithChoices<InitializedType, Choices extends AnyChoices | undefined> = SimpleMerge<
Omit<ToInputConfigWithOptions<InitializedType>, "options">,
Choices extends undefined
? StringField.PrepareChoiceConfig
: Omit<StringField.PrepareChoiceConfig, "choices"> & {
choices?: StringField.PrepareChoiceConfig["choices"] | undefined;
}
>;
type SelectableToInputConfig<InitializedType, Choices extends StringField.Choices | undefined> =
| ToInputConfig<InitializedType>
| ToInputConfigWithOptions<InitializedType>
| ToInputConfigWithChoices<InitializedType, Choices>;
/**
* `label`, `hint`, and `input` are all provided defaults.
*/
interface GroupConfig extends Omit<FormGroupConfig, "label" | "hint" | "input"> {
label?: FormGroupConfig["label"] | undefined;
hint?: FormGroupConfig["hint"] | undefined;
input?: FormGroupConfig["input"] | undefined;
}
}
declare abstract class AnyDataField extends DataField<any, any, any, any> {
constructor(...args: never);
}
/**
* A special class of {@linkcode DataField} which defines a data schema.
* @template Fields - the DataSchema fields of the SchemaField
* @template Options - the options of the SchemaField instance
* @template AssignmentType - the type of the allowed assignment values of the SchemaField
* @template InitializedType - the type of the initialized values of the SchemaField
* @template PersistedType - the type of the persisted values of the SchemaField
* @remarks
* Defaults:
* - AssignmentType: `SchemaField.AssignmentType<Fields> | null | undefined`
* - InitializedType: `SchemaField.InitializedType<Fields>`
* - PersistedType: `SchemaField.PersistedType<Fields>`
*/
declare class SchemaField<
const Fields extends DataSchema,
const Options extends SchemaField.Options<Fields> = SchemaField.DefaultOptions,
// eslint-disable-next-line @typescript-eslint/no-deprecated
const AssignmentType = SchemaField.Internal.AssignmentType<Fields, Options>,
const InitializedType = SchemaField.Internal.InitializedType<Fields, Options>,
const PersistedType extends AnyObject | null | undefined = SchemaField.Internal.PersistedType<Fields, Options>,
>
extends DataField<Options, AssignmentType, InitializedType, PersistedType>
implements DataField.Internal.NestedFieldImplementation
{
/**
* @param fields - The contained field definitions
* @param options - Options which configure the behavior of the field
* @param context - Additional context which describes the field
*/
// Saying `fields: Fields` here causes the inference for the fields to be unnecessarily widened. This might effectively be a no-op but it fixes the inference.
// options: not null (unchecked `in` operation in super), context: not null (destructured in super)
constructor(fields: { [K in keyof Fields]: Fields[K] }, options?: Options, context?: DataField.ConstructionContext);
/** @internal */
" __fvtt_types_get_field_schema": Fields;
/** @defaultValue `true` */
override required: boolean;
/** @defaultValue `false` */
override nullable: boolean;
protected static override get _defaults(): SchemaField.Options<DataSchema>;
/** @defaultValue `true` */
static override recursive: boolean;
/**
* The contained field definitions.
*/
fields: Fields;
/**
* Initialize and validate the structure of the provided field definitions.
* @param fields - The provided field definitions
* @returns The validated schema
* @remarks
* @throws If any field is named `_source`
*/
protected _initialize(fields: Fields): Fields;
/**
* Iterate over a SchemaField by iterating over its fields.
*/
[Symbol.iterator](): Generator<DataField.Unknown, void, undefined>;
// TODO: see if its viable to narrow keys, values, entries, has, and get's types via the schema
/**
* An array of field names which are present in the schema.
*/
keys(): string[];
/**
* An array of DataField instances which are present in the schema.
*/
values(): DataField.Unknown[];
/**
* An array of [name, DataField] tuples which define the schema.
*/
entries(): [name: string, dataField: DataField.Unknown][];
/**
* Test whether a certain field name belongs to this schema definition.
* @param fieldName - The field name
* @returns Does the named field exist in this schema?
*/
has(fieldName: string): boolean;
/**
* Get a DataField instance from the schema by name
* @param fieldName - The field name
* @returns The DataField instance or undefined
*/
// TODO(LukeAbby): Enabling this signatures causes a circularity but it would be ideal.
// get<FieldName extends string>(fieldName: OverlapsWith<FieldName, keyof Fields>): SchemaField.Get<Fields, FieldName>;
get(fieldName: string): DataField.Unknown | void;
/**
* Traverse the schema, obtaining the DataField definition for a particular field.
* @param fieldName - A field path like ["abilities", "strength"] or "abilities.strength"
* @returns The corresponding DataField definition for that field, or undefined
*/
getField(fieldName: string | string[]): DataField.Unknown | undefined;
// TODO(LukeAbby): Enabling this signatures causes a circularity but it would be ideal.
// getField<FieldName extends SchemaField.FieldName<Fields>>(
// fieldName: FieldName,
// ): SchemaField.GetField<this, Fields, FieldName>;
protected override _getField(path: string[]): DataField.Any | undefined;
override getInitialValue(data?: unknown): InitializedType;
protected override _cast(value: unknown): AssignmentType;
/**
* @remarks Ensures `options.source` is set via effectively `||= data`, then forwards to each field's `#clean`
*
* Deletes any keys from `value` not in the schema, including `-=` and `==` keys
*/
protected override _cleanType(value: InitializedType, options?: DataField.CleanOptions): InitializedType;
override initialize(
value: PersistedType,
model: DataModel.Any,
options?: DataField.InitializeOptions,
): InitializedType | (() => InitializedType | null);
override _updateDiff(
source: AnyMutableObject,
key: string,
value: unknown,
difference: AnyObject,
options?: DataModel.UpdateOptions,
): void;
override _updateCommit(
source: AnyMutableObject,
key: string,
value: unknown,
diff: unknown,
options?: DataModel.UpdateOptions,
): void;
protected override _validateType(
value: InitializedType,
options?: DataField.ValidateOptions<this>,
): boolean | DataModelValidationFailure | void;
protected override _validateModel(data: AnyObject, options?: DataField.ValidateModelOptions): void;
override toObject(value: InitializedType): PersistedType;
override apply<Options, Return>(
fn: keyof this | ((this: this, value: AnyObject, options: Options) => Return),
value?: AnyObject,
options?: Options,
): Return;
protected override _addTypes(source?: AnyObject, changes?: AnyObject, options?: DataField.AddTypesOptions): void;
/**
* Migrate this field's candidate source data.
* @param sourceData - Candidate source data of the root model
* @param fieldData - The value of this field within the source data
*/
migrateSource(sourceData: AnyObject, fieldData: unknown): unknown;
}
declare namespace SchemaField {
/**
* A shorthand for the options of a SchemaField class.
* @template Fields - the DataSchema fields of the SchemaField
*/
// eslint-disable-next-line @typescript-eslint/no-deprecated
type Options<Fields extends DataSchema> = DataField.Options<AssignmentData<Fields>>;
/** Any SchemaField. */
interface Any extends SchemaField<any, any, any, any, any> {}
/**
* Get the constructor type for the given DataSchema.
* @template Fields - the DataSchema fields of the SchemaField
*/
type CreateData<Fields extends DataSchema> = PrettifyType<
RemoveIndexSignatures<
_OptionalIfNullish<{
[Key in keyof Fields]: Fields[Key][" __fvtt_types_internal_assignment_data"];
}>
>
>;
/** @internal */
type _OptionalIfNullish<T> = {
[K in keyof T as true extends _IsNullish<T[K]> ? K : never]?: T[K] | null | undefined;
} & {
[K in keyof T as true extends _IsNullish<T[K]> ? never : K]: T[K];
};
/** @internal */
type _IsNullish<T> = T extends null | undefined ? true : false;
/**
* Get the inner assignment type for the given DataSchema.
* @template Fields - the DataSchema fields of the SchemaField
*
* @deprecated This type is a relic of the early days of data models. It was meant to represent
* the types that would be valid for the expression `this.schemaProperty = ...`. Modern users will
* recognize that the only sane thing to do here is to use `InitializedData` but when data models
* were first being introduced there was an attempt to support a sort of strange compromise between
* `InitializedData`, `SourceData`, and even `CreateData` for backwards compatibility with existing patterns.
*
* You should instead use those types as appropriate.
*/
type AssignmentData<Fields extends DataSchema> = PrettifyType<
RemoveIndexSignatures<{
// Note(LukeAbby): Simply stripping off `readonly` may eventually need to be revisited.
-readonly [Key in keyof Fields]?: Fields[Key][" __fvtt_types_internal_assignment_data"];
}>
>;
/**
* The required type of data used when updating a document.
* @template Fields - the DataSchema fields of the SchemaField
*/
// Note(LukeAbby): Currently this is too close to `AssignmentData` but the intent is to make it
// more accurate in the future.
type UpdateData<Fields extends DataSchema> = _AddUpdateKeys<
RemoveIndexSignatures<{
-readonly [Key in keyof Fields]?: Fields[Key][" __fvtt_types_internal_assignment_data"];
}>
>;
/** @internal */
type _AddUpdateKeys<T> = PrettifyType<
T & {
[K in keyof T as K extends string ? (T[K] extends undefined ? `-=${K}` : never) : never]?: null;
} & {
// Note(LukeAbby): There's more work to be done here. For example `type` and `==system` must
// go together. This will be added once a performant validator type is created.
[K in keyof T as K extends string ? `==${K}` : never]?: T[K];
}
>;
/**
* Gets the initialized version of a schema. This means a
* @template Fields - the DataSchema fields of the SchemaField
*/
type InitializedData<Fields extends DataSchema> = PrettifyType<
RemoveIndexSignatures<{
-readonly [Key in keyof Fields]: Fields[Key][" __fvtt_types_internal_initialized_data"];
}>
>;
/**
* Get the persisted type for the given DataSchema. This is the type used for source.
* @template Fields - the DataSchema fields of the SchemaField
*/
type SourceData<Fields extends DataSchema> = PrettifyType<
RemoveIndexSignatures<{
-readonly [Key in keyof Fields]: Fields[Key][" __fvtt_types_internal_source_data"];
}>
>;
type UpdateSourceData<Fields extends DataSchema> = PrettifyType<
RemoveIndexSignatures<{
-readonly [Key in keyof Fields]: Fields[Key][" __fvtt_types_internal_initialized_data"];
}>
>;
/** The type of the default options for the {@linkcode SchemaField} class. */
type DefaultOptions = SimpleMerge<
DataField.DefaultOptions,
{
required: true;
nullable: false;
}
>;
/**
* A helper type for the given options type merged into the default options of the SchemaField class.
* @template Fields - the DataSchema fields of the SchemaField
* @template Opts - the options that override the default options
*/
type MergedOptions<Fields extends DataSchema, Opts extends Options<Fields>> = SimpleMerge<DefaultOptions, Opts>;
/**
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
type _EffectiveOptions<AssignmentData, Opts extends Options<any>> = [AssignmentData] extends [{}]
? // eslint-disable-next-line @typescript-eslint/no-empty-object-type
SimpleMerge<Opts, { initial: {} }> // If all fields are optional then the initial of `{}` is valid.
: Opts;
// These exist for calculating the type of schema field with options.
// This will be deleted once fields are refactored.
// The names are also confusing. Hence these it's put into `Internal.
namespace Internal {
// FIXME: null or undefined should be permissible, cast as the initialized type
/**
* A shorthand for the assignment type of a SchemaField class.
* @template Fields - the DataSchema fields of the SchemaField
* @template Opts - the options that override the default options
*
* @deprecated This type is a relic of the early days of data models. It was meant to represent
* the types that would be valid for the expression `this.schemaProperty = ...`. Modern users will
* recognize that the only sane thing to do here is to use `InitializedType` but when data models
* were first being introduced there was an attempt to support a sort of strange compromise between
* `InitializedType`, `SourceType`, and even `CreateType` to an extent.
*
* You should instead use those types as appropriate.
*/
type AssignmentType<
Fields extends DataSchema,
Opts extends Options<Fields> = DefaultOptions,
// eslint-disable-next-line @typescript-eslint/no-deprecated
> = DataField.DerivedAssignmentType<
// eslint-disable-next-line @typescript-eslint/no-deprecated
AssignmentData<Fields>,
// eslint-disable-next-line @typescript-eslint/no-deprecated
_EffectiveOptions<NonNullable<AssignmentData<Fields>>, MergedOptions<Fields, Opts>>
>;
/**
* A shorthand for the assignment type of a SchemaField class.
* @template Fields - the DataSchema fields of the SchemaField
* @template Opts - the options that override the default options
*/
type InitializedType<
Fields extends DataSchema,
Opts extends Options<Fields> = DefaultOptions,
> = DataField.DerivedInitializedType<InitializedData<Fields>, MergedOptions<Fields, Opts>>;
/**
* A shorthand for the assignment type of a SchemaField class.
* @template Fields - the DataSchema fields of the SchemaField
* @template Opts - the options that override the default options
*/
type PersistedType<
Fields extends DataSchema,
Opts extends Options<Fields> = DefaultOptions,
> = DataField.DerivedInitializedType<SourceData<Fields>, MergedOptions<Fields, Opts>>;
}
/**
* @deprecated This type is a relic of the early days of data models. It was meant to represent
* the types that would be valid for the expression `this.schemaProperty = ...`. Modern users will
* recognize that the only sane thing to do here is to use `InitializedData` but when data models
* were first being introduced there was an attempt to support a sort of strange compromise between
* `InitializedData`, `SourceData`, and even `CreateData` to an extent.
*
* You should instead use those types as appropriate.
*/
// eslint-disable-next-line @typescript-eslint/no-deprecated
type InnerAssignmentType<Fields extends DataSchema> = AssignmentData<Fields>;
type Get<Schema extends DataSchema, FieldName extends string> = GetKey<Schema, FieldName, undefined>;
type FieldName<Schema extends DataSchema> = "" | [] | _FieldName<Schema>;
/**
* Essentially sets a field depth of 10.
*
* @internal
*/