import type { AnyArray, AnyConstructor, AnyFunction, AnyObject, DeepReadonly, DeletableDotKeys, InexactPartial, NonNullish, } from "#utils"; import type Document from "../abstract/document.d.mts"; /** * Benchmark the performance of a function, calling it a requested number of iterations. * @param func - The function to benchmark * @param iterations - The number of iterations to test * @param args - Additional arguments passed to the benchmarked function */ export function benchmark(func: F, iterations: number, ...args: Parameters): Promise; /** * A debugging function to test latency or timeouts by forcibly locking the thread for an amount of time. * @param ms - A number of milliseconds to lock * @param debug - Log debugging information? (default: `false`) */ export function threadLock(ms: number, debug?: boolean): Promise; /** * Wrap a callback in a debounced timeout. * Delay execution of the callback function until the function has not been called for delay milliseconds * @param callback - A function to execute once the debounced threshold has been passed * @param delay - An amount of time in milliseconds to delay * @returns A wrapped function which can be called to debounce execution */ export function debounce(callback: T, delay: number): (...args: Parameters) => void; /** * Wrap a callback in a throttled timeout. * Delay execution of the callback function when the last time the function was called was delay milliseconds ago * @param callback - A function to execute once the throttled threshold has been passed * @param delay - A maximum amount of time in milliseconds between to execution * @returns A wrapped function which can be called to throttle execution */ export function throttle any>(callback: T, delay: number): T; /** * A utility function to reload the page with a debounce. */ export const debouncedReload: () => void; /** * Recursively freezes ({@linkcode Object.freeze}) the object (or value). * This method DOES NOT support cyclical data structures. * This method DOES NOT support advanced object types like Set, Map, or other specialized classes. * @param obj - The object (or value) * @param options - Options to configure the behaviour of deepFreeze * @returns The same object (or value) that was passed in * @remarks * @throws If passed an object with a depth over 100, or if it encounters anything but a plain object, array, or primitive */ export function deepFreeze(obj: T, options?: DeepFreezeOptions): DeepReadonly; /** @internal */ type _DeepFreezeOptions = InexactPartial<{ /** * Throw an Error if deepFreeze is unable to freeze something instead of returning the original * @defaultValue `false` */ strict: boolean; }>; export interface DeepFreezeOptions extends _DeepFreezeOptions {} /** * Recursively seals ({@linkcode Object.seal}) the object (or value). * This method DOES NOT support cyclical data structures. * This method DOES NOT support advanced object types like Set, Map, or other specialized classes. * @param obj - The object (or value) * @param options - Options to configure the behaviour of deepSeal * @returns The same object (or value) that was passed in * @remarks * @throws If passed an object with a depth over 100, or if it encounters anything but a plain object, array, or primitive */ export function deepSeal(obj: T, options?: DeepSealOptions): T; /** @internal */ type _DeepSealOptions = InexactPartial<{ /** * Throw an Error if deepSeal is unable to seal something * @defaultValue `false` */ strict: boolean; }>; export interface DeepSealOptions extends _DeepSealOptions {} /** * Quickly clone a simple piece of data, returning a copy which can be mutated safely. * This method DOES support recursive data structures containing inner objects or arrays. * This method DOES NOT support cyclical data structures. * This method DOES NOT support advanced object types like Set, Map, or other specialized classes. * @param original - Some sort of data * @param options - Options to configure the behaviour of deepClone * @returns The clone of that data * @remarks * @throws If passed an object with a depth over 100, or if it encounters anything but a plain object, array, `Date`, or primitive */ export function deepClone(original: T, options?: DeepCloneOptions): T; /** @internal */ type _DeepCloneOptions = InexactPartial<{ /** * Throw an Error if deepClone is unable to clone something instead of returning the original * @defaultValue `false` */ strict: boolean; }>; export interface DeepCloneOptions extends _DeepCloneOptions {} /** * Deeply difference an object against some other, returning the update keys and values. * @param original - An object comparing data against which to compare * @param other - An object containing potentially different data * @param options - Additional options which configure the diff operation * @returns An object of the data in other which differs from that in original */ export function diffObject(original: object, other: object, options?: DiffObjectOptions): object; /** @internal */ type _DiffObjectOptions = InexactPartial<{ /** * Only recognize differences in other for keys which also exist in original * @defaultValue `false` */ inner: boolean; /** * Apply special logic to deletion keys. They will only be kept if the original object has a * corresponding key that could be deleted. * @defaultValue `false` */ deletionKeys: boolean; /** * An internal depth tracker * @defaultValue `0` * @remarks Not intended to be passed externally. v13 converted the outer signature of `deepClone` to hide this param, * and that pattern was copied for `deepFreeze` and `deepSeal`; Not sure why they didn't use it here. */ _d: number; }>; export interface DiffObjectOptions extends _DiffObjectOptions {} /** * Recurse through an object, applying all special keys. * Deletion keys ("-=") are removed. * Forced replacement keys ("==") are assigned. * @remarks Returns the passed `obj` for anything but plain objects and arrays. */ // TODO: bespoke return type accounting for deletion keys recursively export function applySpecialKeys(obj: T): T; /** * Test if two objects contain the same enumerable keys and values. * @param a - The first object. * @param b - The second object. */ export function objectsEqual(a: object, b: B): a is B; /** * A cheap data duplication trick which is relatively robust. * For a subset of cases the deepClone function will offer better performance. * @param original - Some sort of data * @template T - Type of the original data. * * @remarks This function will actually convert any occurrences of `NaN` and `Infinity` to `null`. For ease of use, this * is _not_ reflected in the type. Be careful if your types might contain `NaN` or `Infinity`! */ export function duplicate(original: T): Duplicated; /** * The resulting type when using {@linkcode duplicate} on some data of type `T`. * * @template T - Original type. * @internal */ export type Duplicated = T extends NonStringifiable ? never : InnerDuplicated; /** * Is a string key of an object used for certain deletion or forced replacement operations. * @remarks Foundry seems to refer to both `-=...` and `==...` as "deletion keys" in this context */ export function isDeletionKey(key: string): key is DeletionKey; export type DeletionKey = `-=${string}` | `==${string}`; /** * Test whether some class is a subclass of a parent. * Returns true if the classes are identical. * @param cls - The class to test * @param parent - Some other class which may be a parent * @returns Is the class a subclass of the parent? */ export function isSubclass(cls: AnyConstructor, parent: Parent): cls is Parent; /** * Search up the prototype chain and return the class that defines the given property. * @param obj - A class instance or class definition which contains a property. * * If a class instance is passed the property is treated as an instance attribute. * * If a class constructor is passed the property is treated as a static attribute. * @param property - The property name * @returns The class that defines the property */ export function getDefiningClass(obj: AnyConstructor, property: string): AnyConstructor; /** * Encode an url-like string by replacing any characters which need encoding * @param path - A fully-qualified URL or url component (like a relative path) * @returns An encoded URL string */ export function encodeURL(path: string): string; /** * Expand a flattened object to be a standard multi-dimensional nested Object by converting all dot-notation keys to * inner objects. * * @param obj - The object to expand * @returns An expanded object * */ export function expandObject(obj: object): object; /** * Filter the contents of some source object using the structure of a template object. * Only keys which exist in the template are preserved in the source object. * * @param source - An object which contains the data you wish to filter * @param template - An object which contains the structure you wish to preserve * @param options - Additional options which customize the filtration (default: `{}`) * @returns The filtered object * * @example Filter an object * ```typescript * const source = {foo: {number: 1, name: "Tim", topping: "olives"}, bar: "baz"}; * const template = {foo: {number: 0, name: "Mit", style: "bold"}, other: 72}; * filterObject(source, template); // {foo: {number: 1, name: "Tim"}}; * filterObject(source, template, {templateValues: true}); // {foo: {number: 0, name: "Mit"}}; * ``` */ export function filterObject(source: object, template: object, options?: FilterObjectOptions): object; /** @internal */ type _FilterObjectOptions = InexactPartial<{ /** * Whether to keep deletion keys * @defaultValue `false` */ deletionKeys: boolean; /** * Instead of keeping values from the source, instead draw values from the template * @defaultValue `false` */ templateValues: boolean; }>; export interface FilterObjectOptions extends _FilterObjectOptions {} /** * Flatten a possibly multidimensional object to a one-dimensional one by converting all nested keys to dot notation * @param obj - The object to flatten * @param _d - Track the recursion depth to prevent overflow. (default: `0`) * @returns A flattened object */ export function flattenObject(obj: object, _d?: number): object; /** * Obtain references to the parent classes of a certain class. * @param cls - An ES6 Class definition * @returns An array of parent Classes which the provided class extends */ export function getParentClasses(cls: AnyConstructor): Array; /** * Get the URL route for a certain path which includes a path prefix, if one is set * @param path - The Foundry URL path * @returns The absolute URL path */ export function getRoute(path: string, { prefix }?: GetRouteOptions): string; /** @internal */ type _GetRouteOptions = InexactPartial<{ /** * A path prefix to apply * @defaultValue {@linkcode globalThis.ROUTE_PREFIX} */ prefix: string | null; }>; export interface GetRouteOptions extends _GetRouteOptions {} /** * Learn the underlying data type of some variable. Supported identifiable types include: * undefined, null, number, string, boolean, function, Array, Set, Map, Promise, Error, * HTMLElement (client side only), Object (plain objects). * If the type isn't identifiable, Unknown is returned. * @param variable - A provided variable * @returns The named type of the token */ export function getType(variable: unknown): DataType; export type DataType = | "Array" | "Error" | "HTMLElement" | "Map" | "Object" | "Promise" | "Set" | "bigint" | "boolean" | "function" | "null" | "number" | "string" | "symbol" | "undefined" | "Unknown"; export type ExpandKey = K extends `${infer First}.${infer Rest}` ? { [_ in First]: ExpandKey } : { [_ in K]: unknown }; /** * A helper function which tests whether an object has a property or nested property given a string key. * The string key supports the notation a.b.c which would return true if object[a][b][c] exists * @param object - The object to traverse * @param key - An object property with notation a.b.c * @returns An indicator for whether the property exists */ export function hasProperty(object: object, key: K): object is ExpandKey; /** * A helper function which searches through an object to retrieve a value by a string key. * The string key supports the notation a.b.c which would return object[a][b][c] * @param object - The object to traverse * @param key - An object property with notation a.b.c * @returns The value of the found property */ export function getProperty(object: object, key: PropertyKey): unknown; /** * A helper function which searches through an object to assign a value using a string key * This string key supports the notation a.b.c which would target object[a][b][c] * @param object - The object to update * @param key - The string key * @param value - The value to be assigned * @returns Whether the value was changed from its previous value * * @remarks - This type is likely to refuse to set a key that isn't known in the future. */ export function setProperty(object: object, key: PropertyKey, value: unknown): boolean; /** * A helper function which searches through an object to delete a value by a string key. * The string key supports the notation a.b.c which would delete object[a][b][c] * @param object - The object to traverse * @param key - An object property with notation a.b.c * @returns Was the property deleted? */ export function deleteProperty(object: T, key: DeletableDotKeys): boolean; /** * Invert an object by assigning its values as keys and its keys as values. * @param obj - The original object to invert * @returns The inverted object with keys and values swapped */ export function invertObject(obj: T): InvertObject; // Merging into this would be antithetical to its purpose. // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type InvertableObject = { readonly [K: PropertyKey]: PropertyKey; }; export type InvertObject = { -readonly [K in keyof T as T[K]]: K; }; /** * Return whether a target version (v1) is more advanced than some other reference version (v0). * Supports either numeric or string version comparison with version parts separated by periods. * @param v1 - The target version * @param v0 - The reference version * @returns Is v1 a more advanced version than v0? */ export function isNewerVersion(v1: number | string, v0: number | string): boolean; /** * Test whether a value is empty-like; either undefined or a content-less object. * @param value - The value to test * @returns Is the value empty-like? */ export function isEmpty( value: undefined | null | unknown[] | object | Set | Map | NonNullish, ): boolean; /** * Update a source object by replacing its keys and values with those from a target object. * * @param original - The initial object which should be updated with values from the target * @param other - A new object whose values should replace those in the source (default: `{}`) * @param options - Additional options which configure the merge (default: `{}`) * @param _d - A privately used parameter to track recursion depth. (default: `0`) * @returns The original source object including updated, inserted, or overwritten records. * * @example Control how new keys and values are added * ```typescript * mergeObject({k1: "v1"}, {k2: "v2"}, {insertKeys: false}); // {k1: "v1"} * mergeObject({k1: "v1"}, {k2: "v2"}, {insertKeys: true}); // {k1: "v1", k2: "v2"} * mergeObject({k1: {i1: "v1"}}, {k1: {i2: "v2"}}, {insertValues: false}); // {k1: {i1: "v1"}} * mergeObject({k1: {i1: "v1"}}, {k1: {i2: "v2"}}, {insertValues: true}); // {k1: {i1: "v1", i2: "v2"}} * ``` * * @example Control how existing data is overwritten * ```typescript * mergeObject({k1: "v1"}, {k1: "v2"}, {overwrite: true}); // {k1: "v2"} * mergeObject({k1: "v1"}, {k1: "v2"}, {overwrite: false}); // {k1: "v1"} * ``` * * @example Control whether merges are performed recursively * ```typescript * mergeObject({k1: {i1: "v1"}}, {k1: {i2: "v2"}}, {recursive: false}); // {k1: {i2: "v2"}} * mergeObject({k1: {i1: "v1"}}, {k1: {i2: "v2"}}, {recursive: true}); // {k1: {i1: "v1", i2: "v2"}} * ``` * * @example Deleting an existing object key * ```typescript * mergeObject({k1: "v1", k2: "v2"}, {"-=k1": null}, {performDeletions: true}); // {k2: "v2"} * ``` * * @example Explicitly replacing an inner object key * ```js * mergeObject({k1: {i1: "v1"}}, {"==k1": {i2: "v2"}}, {performDeletions: true}); // {k1: {i2: "v2"}} * ``` */ export function mergeObject( original: T, other?: U, options?: M, _d?: number, ): MergeObject; export type MergeObject = UpdateInsert< DeleteByObjectKeys, RemoveDeletingObjectKeys, M >; /** @internal */ type _MergeObjectOptions = InexactPartial<{ /** * Control whether to insert new top-level objects into the resulting structure which do not previously exist in the original object. * @defaultValue `true` */ insertKeys: boolean; /** * Control whether to insert new nested values into child objects in the resulting structure which did not previously exist in the original object. * @defaultValue `true` */ insertValues: boolean; /** * Control whether to replace existing values in the source, or only merge values which do not already exist in the original object. * @defaultValue `true` */ overwrite: boolean; /** * Control whether to merge inner-objects recursively (if true), or whether to simply replace inner objects with a provided new value. * @defaultValue `true` */ recursive: boolean; /** * Control whether to apply updates to the original object in-place (if true), otherwise the original object is duplicated and the copy is merged. * @defaultValue `true` */ inplace: boolean; /** * Control whether strict type checking requires that the value of a key in the other object must match the data type in the original data to be merged. * @defaultValue `false` */ enforceTypes: boolean; /** * Control whether to perform deletions on the original object if deletion keys are present in the other object. * @defaultValue `false` */ performDeletions: boolean; // TODO: implement this in the mergeObject return type }>; export interface MergeObjectOptions extends _MergeObjectOptions {} /** * Parse an S3 key to learn the bucket and the key prefix used for the request. * @param key - A fully qualified key name or prefix path. */ export function parseS3URL(key: string): ParseS3URLReturn; export interface ParseS3URLReturn { /** @remarks `null` only if the passed URL fails {@linkcode URL.parseSafe} */ bucket: string | null; /** @remarks `""` if the passed URL fails {@linkcode URL.parseSafe} */ keyPrefix: string; } /** * Generate a random alphanumeric string ID of a given requested length using `crypto.getRandomValues()`. * @param length - The length of the random string to generate, which must be at most 16384. (default: `16`) * @returns A string containing random letters (A-Z, a-z) and numbers (0-9). */ export function randomID(length?: number): string; /** * Format a file size to an appropriate order of magnitude. * @param size - The size in bytes. * @param options - Additional options. (default: `{}`) */ export function formatFileSize(size: number, options?: FormatFileSizeOptions): string; /** @internal */ type _FormatFileSizeOptions = InexactPartial<{ /** * The number of decimal places to round to. * @defaultValue `2` */ decimalPlaces: number; /** * The base to use. In base 10 a kilobyte is 1000 bytes. In base 2 it is 1024 bytes. * @defaultValue `10` * @remarks Foundry only actually checks if the base is `2` or not; all other values are treated as `10` */ base: 2 | 10; }>; export interface FormatFileSizeOptions extends _FormatFileSizeOptions {} export interface ResolvedUUID { /** * The original UUID. */ uuid: string; /** * The type of Document referenced. Legacy compendium UUIDs will not populate this field if the compendium is not active in the World. * @remarks This property is always present, the above caveat simply means it might be `undefined` */ type: Document.Type | undefined; /** * The ID of the Document referenced. */ id: string; /** * The primary Document type of this UUID. Only present if the Document is embedded. * @remarks This property is always present, the above caveat simply means it might be `undefined` */ primaryType: CONST.PRIMARY_DOCUMENT_TYPES | undefined; /** * The primary Document ID of this UUID. Only present if the Document is embedded. * @remarks This property is always present, the above caveat simply means it might be `undefined` */ primaryId: string | undefined; /** * The collection that the primary Document belongs to. */ collection: foundry.documents.abstract.DocumentCollection.Any | undefined; /** * Additional Embedded Document parts. */ embedded: string[]; /** * Either the document type or the parent type. Retained for backwards compatibility. */ documentType: Document.Type | undefined; /** * Either the document id or the parent id. Retained for backwards compatibility. */ documentId: string | undefined; } /** * Parse a UUID into its constituent parts, identifying the type and ID of the referenced document. * The ResolvedUUID result also identifies a "primary" document which is a root-level document either in the game * World or in a Compendium pack which is a parent of the referenced document. * @param uuid - The UUID to parse. * @param options - Options to configure parsing behavior. * @returns Returns, if possible, the Collection, Document Type, and Document ID to resolve the parent document, as well as the remaining Embedded Document parts, if any. */ export function parseUuid(uuid: string, options?: ParseUUIDOptions): ResolvedUUID; /** @internal */ type _ParseUUIDOptions = InexactPartial<{ /** * A document to resolve relative UUIDs against. */ relative: Document.Any; }>; export interface ParseUUIDOptions extends _ParseUUIDOptions {} /** * Escape the given unescaped string. * * Escaped strings are safe to use inside inner HTML of most tags and in most quoted HTML attributes. * They are not NOT safe to use in `