UNPKG

@resk/core

Version:

An innovative TypeScript framework that empowers developers to build applications with a fully decorator-based architecture for efficient resource management. By combining the power of decorators with a resource-oriented design, DecorRes enhances code cla

568 lines (567 loc) 23 kB
import { IPrimitive } from "../types"; /** * Determines whether a value is a plain object (POJO - Plain Old JavaScript Object). * * A plain object is an object created by the Object constructor or one with a null prototype. * This function performs comprehensive checks to distinguish plain objects from other object types * like arrays, dates, DOM elements, regular expressions, class instances, and functions. * It also handles cross-frame compatibility where objects might be created in different execution contexts. * * @template T - The type of the value being checked * @param {T} obj - The value to test for being a plain object * * @returns {obj is (T extends (Record<any, any> | object) ? T : T extends string | undefined | null | boolean | Array<any> ? never : any)} * Type predicate that narrows the type to a plain object if the check passes, or never for non-object types * * @example * ```typescript * // Basic plain object detection * const plainObj = { name: "John", age: 30 }; * console.log(isObj(plainObj)); // true * * // Object created with Object.create(null) * const nullProtoObj = Object.create(null); * nullProtoObj.prop = "value"; * console.log(isObj(nullProtoObj)); // true * * // Arrays are not plain objects * const array = [1, 2, 3]; * console.log(isObj(array)); // false * * // Dates are not plain objects * const date = new Date(); * console.log(isObj(date)); // false * * // Regular expressions are not plain objects * const regex = /pattern/; * console.log(isObj(regex)); // false * * // Class instances are not plain objects * class MyClass { * constructor(public value: string) {} * } * const instance = new MyClass("test"); * console.log(isObj(instance)); // false * * // Functions are not plain objects * const func = () => {}; * console.log(isObj(func)); // false * * // Primitives are not plain objects * console.log(isObj("string")); // false * console.log(isObj(42)); // false * console.log(isObj(true)); // false * console.log(isObj(null)); // false * console.log(isObj(undefined)); // false * * // DOM elements are not plain objects (in browser environment) * const element = document.createElement('div'); * console.log(isObj(element)); // false * ``` * * @example * ```typescript * // Type narrowing with TypeScript * function processValue<T>(value: T): void { * if (isObj(value)) { * // TypeScript now knows 'value' is a plain object * // Safe to access object properties * Object.keys(value).forEach(key => { * console.log(`${key}: ${value[key]}`); * }); * } else { * // Handle non-object values * console.log("Not a plain object:", value); * } * } * * // Cross-frame compatibility example * // Works even when objects are created in different iframes * const iframe = document.createElement('iframe'); * document.body.appendChild(iframe); * const iframeWindow = iframe.contentWindow; * const crossFrameObj = new iframeWindow.Object(); * crossFrameObj.prop = "value"; * console.log(isObj(crossFrameObj)); // true (handles cross-frame objects) * ``` * * @remarks * This function is particularly useful for: * - Object serialization/deserialization logic * - Deep cloning or merging operations where you need to distinguish plain objects * - API response validation where you expect plain data objects * - Library functions that need to handle various object types differently * - Cross-frame scenarios in browser environments * * The function performs several layers of validation: * 1. **Early rejection**: Filters out null, primitives, arrays, dates, regex, and DOM elements * 2. **Null prototype check**: Accepts objects created with `Object.create(null)` * 3. **Constructor validation**: Verifies the object's constructor is a function * 4. **Standard Object check**: Accepts objects with `Object` as constructor * 5. **Prototype chain validation**: Ensures the prototype chain is standard * 6. **Cross-frame compatibility**: Handles objects from different execution contexts * * @since 1.0.0 * @category Type Guards * @see {@link cloneObject} - For cloning plain objects * @see {@link extendObj} - For merging plain objects * @see {@link defaultObj} - For providing default plain objects */ export declare function isObj<T = any>(obj: T): obj is T extends Record<any, any> | object ? T : T extends string | undefined | null | boolean | Array<any> ? never : any; /** * Clones a source object by returning a non-reference copy of the object. * * This function creates a deep copy of the provided object. * Any nested objects or arrays within the source will also be cloned, ensuring that the * returned object does not reference the original object. * * @template T - The type of the object to clone. This can be any type, including arrays and nested objects. * @param {T} source - The object to clone. This can be any type, including arrays and nested objects. * * @returns {T} A deep cloned copy of the source object. The return type can be * either an object or an array, depending on the input. * * @example * ```ts * // Example with a simple object * const original = { a: 1, b: { c: 2 } }; * const cloned = cloneObject(original); * console.log(cloned); // Outputs: { a: 1, b: { c: 2 } } * * // Modifying the cloned object does not affect the original * cloned.b.c = 3; * console.log(original.b.c); // Outputs: 2 (original remains unchanged) * * // Example with an array * const originalArray = [1, 2, { a: 3 }]; * const clonedArray = cloneObject(originalArray); * console.log(clonedArray); // Outputs: [1, 2, { a: 3 }] * * // Modifying the cloned array does not affect the original * clonedArray[2].a = 4; * console.log(originalArray[2].a); // Outputs: 3 (original remains unchanged) * * // Example with a nested structure * const complexObject = { a: 1, b: [2, { c: 3 }] }; * const clonedComplex = cloneObject(complexObject); * console.log(clonedComplex); // Outputs: { a: 1, b: [2, { c: 3 }] } * ``` */ export declare function cloneObject<T = any>(source: T): T; /** * Calculates the size of an object or array. * * This function returns the number of own properties of an object or the length of an array. * If the input is null or not an object, it returns 0. * * @param {any} obj - The object or array whose size is to be calculated. * @param {boolean} [breakOnFirstElementFound=false] - Optional flag to determine if the function should * return the size immediately upon finding the first property or element. * * @returns {number} The size of the object or array. Returns 0 if the input is null or not an object. * * @example * // Example with an object * const exampleObject = { a: 1, b: 2, c: 3 }; * const size = objectSize(exampleObject); * console.log(size); // Outputs: 3 * * @example * // Example with an array * const exampleArray = [1, 2, 3, 4]; * const arraySize = objectSize(exampleArray); * console.log(arraySize); // Outputs: 4 * * @example * // Example with breakOnFirstElementFound * const earlyExitSize = objectSize(exampleObject, true); * console.log(earlyExitSize); // Outputs: 1, as it returns after the first property * * @example * // Example with a null input * const nullSize = objectSize(null); * console.log(nullSize); // Outputs: 0 * * @example * // Example with a non-object input * const nonObjectSize = objectSize(42); * console.log(nonObjectSize); // Outputs: 0 */ export declare const objectSize: (obj: any, breakOnFirstElementFound?: boolean) => number; /** * Returns a default object based on the provided arguments. * * This function takes multiple arguments and returns the first non-empty object found. * If only one argument is provided, it returns that argument if it is an object; otherwise, it returns an empty object. * If no valid object is found among the arguments, it returns an empty object. * * @param {...any[]} args - The arguments to check for objects. * @template T - The type of the object to return. * * @returns {object} The first non-empty object found among the arguments, or an empty object if none is found. * * @example * ```ts * // Example with one valid object * const result1 = defaultObj({ a: 1 }); * console.log(result1); // Outputs: { a: 1 } * * // Example with one invalid argument * const result2 = defaultObj("not an object"); * console.log(result2); // Outputs: {} * * // Example with multiple arguments, returning the first non-empty object * const result3 = defaultObj({}, { b: 2 }, { c: 3 }); * console.log(result3); // Outputs: { b: 2 } * * // Example with multiple arguments, returning the last valid object * const result4 = defaultObj({}, {}, { d: 4 }); * console.log(result4); // Outputs: { d: 4 } * * // Example with no valid objects * const result5 = defaultObj(null, undefined, "string"); * console.log(result5); // Outputs: {} * ``` */ export declare function defaultObj<T extends object = any>(...args: any[]): T; /** * Declares a global interface extension for the built-in `Object` type. */ declare global { /** * Interface extension for the built-in `Object` type. */ interface Object { /** * Clones a source object by returning a non-reference copy of the object. * * This method creates a deep clone of the provided object, ensuring that nested objects * are also cloned, and modifications to the cloned object do not affect the original. * * @param {any} obj - The object to clone. This can be any type, including arrays and nested objects. * @returns {any} The cloned object, which can be an object or an array. * * @example * ```ts * const original = { a: 1, b: { c: 2 } }; * const cloned = Object.clone(original); * console.log(cloned); // Outputs: { a: 1, b: { c: 2 } } * * cloned.b.c = 3; * console.log(original.b.c); // Outputs: 2 (original remains unchanged) * ``` * @template T - The type of the object to clone. * @param {T} obj - The object to clone. * @returns {T} A clone of the object. */ clone: <T = any>(obj: T) => T; /** * Determines the size of an object or array. * * This method calculates the number of own enumerable properties in an object or * the number of elements in an array. If the `breakOnFirstElementFound` parameter * is set to true, it will return 1 immediately upon finding the first element or property. * * @param {any} obj - The object or array to determine the size of. * @param {boolean} [breakOnFirstElementFound=false] - Whether to return immediately after the first element is found. * @returns {number} The size of the object or array. Returns 0 if the input is not an object or array. * * @example * ```ts * const obj = { a: 1, b: 2, c: 3 }; * const size = Object.getSize(obj); * console.log(size); // Outputs: 3 * * const arr = [1, 2, 3]; * const arrSize = Object.getSize(arr); * console.log(arrSize); // Outputs: 3 * * const singleElementSize = Object.getSize(arr, true); * console.log(singleElementSize); // Outputs: 1 (returns immediately) * * const emptyObjSize = Object.getSize({}); * console.log(emptyObjSize); // Outputs: 0 * ``` */ getSize: (obj: any, breakOnFirstElementFound?: boolean) => number; /** * Flattens a nested object structure into a single-level object with dot/bracket notation keys. * Handles various data structures including Arrays, Sets, Maps, and plain objects. * Skips non-primitive values like functions, class instances. * * @param {any} obj - The object to flatten * @param {string} [prefix=''] - The prefix to use for nested keys * @returns {Record<string, Primitive>} A flattened object with primitive values * * @example * // Basic object flattening * _flattenObject({ * a: { * b: 'value', * c: 42 * } * }) * // Returns: { 'a.b': 'value', 'a.c': 42 } * * @example * // Array handling * _flattenObject({ * items: ['a', 'b', { nested: 'value' }] * }) * // Returns: { 'items[0]': 'a', 'items[1]': 'b', 'items[2].nested': 'value' } * * @example * // Map handling * _flattenObject({ * map: new Map([ * ['key1', 'value1'], * ['key2', { nested: 'value2' }] * ]) * }) * // Returns: { 'map[key1]': 'value1', 'map[key2].nested': 'value2' } * * @example * // Complex nested structure * _flattenObject({ * array: [1, { a: 2 }], * set: new Set(['x', { b: 'y' }]), * map: new Map([['k', { c: 'v' }]]), * obj: { * deep: { * nested: 'value', * fn: () => {}, // Will be skipped * date: new Date() // Will be skipped * } * } * }) * // Returns: { * // 'array[0]': 1, * // 'array[1].a': 2, * // 'set[0]': 'x', * // 'set[1].b': 'y', * // 'map[k].c': 'v', * // 'obj.deep.nested': 'value' * // } * * @throws {Error} Will not throw errors, but silently skips non-primitive values * * @category Utilities * @since 1.0.0 */ flatten(obj: any): Record<string, IPrimitive>; /** * Enhanced version of Object.entries that preserves key types in TypeScript inference. * * Unlike the standard Object.entries which types all keys as string, this method * maintains the original key types (string | number | symbol) in TypeScript's type system * while still following JavaScript's runtime behavior where all keys are strings. * * @template T - The object type extending Record<string | number, any> * @param obj - The object to extract entries from * @returns Array of key-value tuples with preserved key types in TypeScript * * @example * ```typescript * const obj = { 1: "one", "foo": "bar", 42: "answer" } as const; * * // Standard Object.entries - all keys typed as string * const standard = Object.entries(obj); // [string, string][] * * // Enhanced Object.typedEntries - preserves original key types * const typed = Object.typedEntries(obj); * // Type: (["1", "one"] | ["foo", "bar"] | ["42", "answer"])[] * ``` * * @remarks * - Runtime behavior is identical to Object.entries (all keys are strings) * - Only TypeScript inference is enhanced to remember original key types * - Works best with objects declared with 'as const' for literal type inference * - Particularly useful for objects with mixed string and numeric keys * * @since 1.0.0 */ typedEntries<T extends Record<any, unknown> = any>(obj: T): Array<{ [K in keyof T]: [K, T[K]]; }[keyof T]>; } } /** * Extends an object by merging properties from one or more source objects. * * This function takes a target object and one or more source objects, merging the properties * from the source objects into the target object. If a property exists in both the target * and a source object, the value from the source object will overwrite the target's value. * For arrays, the function merges the contents of the arrays, preserving the original order of the elements. *@template T - The type of the target object. * @param {T} target - The object to extend. It will receive the new properties. * @param {...any[]} sources - The source objects to merge into the target. These objects will not be modified. * @returns {T} The extended target object, which contains properties from the source objects. * * @example * ```ts * const target = { a: 1, b: 2 }; * const source1 = { b: 3, c: 4 }; * const source2 = { d: 5 }; * * const extended = extendObj(target, source1, source2); * console.log(extended); // Outputs: { a: 1, b: 3, c: 4, d: 5 } * console.log(target); // Outputs: { a: 1, b: 3 } (target is modified) * ``` * Merges the contents of two or more objects together into the first object. * Similar to jQuery's $.extend function. * @remarks * This function is used to merge the contents of multiple objects into a single object. It takes an optional target object as the first argument and one or more source objects as additional arguments. The function returns the merged object, which is a new object that contains all the properties from the source objects. * * If the target object is not provided or is not a plain object, an empty object is returned. * * If the target object is a plain object, the function iterates over the sources and copies the properties from each source object to the target object. If a property with the same name already exists in the target object, it is overwritten with the corresponding value from the source object. * * If the target object is a plain object, the function iterates over the sources and copies the properties from each source object to the target object. If a property with the same name already exists in the target object, it is overwritten with the corresponding value from the source object. * For arrays, The function replaces the contents of the arrays, preserving the original order of the elements. * Empty values like null, undefined, and empty strings are ignored. */ export declare function extendObj<T extends Record<string, any> = any>(target: any, ...sources: any[]): T; /** * Flattens a nested object structure into a single-level object with dot/bracket notation keys. * Handles various data structures including Arrays, Sets, Maps, and plain objects. * Skips non-primitive values like functions, class instances. * * @param {any} obj - The object to flatten * @param {string} [prefix=''] - The prefix to use for nested keys * @returns {Record<string, Primitive>} A flattened object with primitive values * * @example * // Basic object flattening * _flattenObject({ * a: { * b: 'value', * c: 42 * } * }) * // Returns: { 'a.b': 'value', 'a.c': 42 } * * @example * // Array handling * _flattenObject({ * items: ['a', 'b', { nested: 'value' }] * }) * // Returns: { 'items[0]': 'a', 'items[1]': 'b', 'items[2].nested': 'value' } * * @example * // Map handling * _flattenObject({ * map: new Map([ * ['key1', 'value1'], * ['key2', { nested: 'value2' }] * ]) * }) * // Returns: { 'map[key1]': 'value1', 'map[key2].nested': 'value2' } * * @example * // Complex nested structure * _flattenObject({ * array: [1, { a: 2 }], * set: new Set(['x', { b: 'y' }]), * map: new Map([['k', { c: 'v' }]]), * obj: { * deep: { * nested: 'value', * fn: () => {}, // Will be skipped * date: new Date() // Will be skipped * } * } * }) * // Returns: { * // 'array[0]': 1, * // 'array[1].a': 2, * // 'set[0]': 'x', * // 'set[1].b': 'y', * // 'map[k].c': 'v', * // 'obj.deep.nested': 'value' * // } * * @throws {Error} Will not throw errors, but silently skips non-primitive values * * @category Utilities * @since 1.0.0 */ export declare function flattenObject(obj: any, options?: FlattenObjectOptions): Record<string, any>; /** * Checks if a value is an iterable data structure (Array, Set, Map, WeakMap, WeakSet). * * @param {any} value - The value to check * @returns {boolean} True if the value is an iterable structure, false otherwise * * @example * isIterableStructure([1, 2, 3]) // returns true * isIterableStructure(new Set([1, 2, 3])) // returns true * isIterableStructure(new Map()) // returns true * isIterableStructure({}) // returns false */ export declare function isIterableStructure(value: any): boolean; /** * Enhanced version of Object.entries that preserves key types in TypeScript inference. * * Unlike the standard Object.entries which types all keys as string, this method * maintains the original key types (string | number | symbol) in TypeScript's type system * while still following JavaScript's runtime behavior where all keys are strings. * * @template T - The object type extending Record<string | number, any> * @param obj - The object to extract entries from * @returns Array of key-value tuples with preserved key types in TypeScript * * @example * ```typescript * const obj = { 1: "one", "foo": "bar", 42: "answer" } as const; * * // Standard Object.entries - all keys typed as string * const standard = Object.entries(obj); // [string, string][] * * // Enhanced Object.typedEntries - preserves original key types * const typed = Object.typedEntries(obj); * // Type: (["1", "one"] | ["foo", "bar"] | ["42", "answer"])[] * ``` * * @remarks * - Runtime behavior is identical to Object.entries (all keys are strings) * - Only TypeScript inference is enhanced to remember original key types * - Works best with objects declared with 'as const' for literal type inference * - Particularly useful for objects with mixed string and numeric keys * * @since 1.0.0 */ export declare function typedEntries<T extends Record<any, unknown> = any>(obj: T): Array<{ [K in keyof T]: [K, T[K]]; }[keyof T]>; export interface FlattenObjectOptions { /** * Whether to skip arrays during the flattening process. * * When set to `true`, arrays will be treated as primitive values and included * directly in the flattened result instead of being recursively flattened. * This can be useful for performance optimization or when you want to preserve * array structures in your flattened output. * * @default false - Arrays are recursively flattened by default * * @example * ```typescript * const obj = { * name: 'John', * tags: ['developer', 'typescript'], * scores: [85, 92, 78] * }; * * // Default behavior (skipArrays: false) * flattenObject(obj); * // Result: { 'name': 'John', 'tags[0]': 'developer', 'tags[1]': 'typescript', 'scores[0]': 85, 'scores[1]': 92, 'scores[2]': 78 } * * // With skipArrays: true * flattenObject(obj, { skipArrays: true }); * // Result: { 'name': 'John', 'tags': ['developer', 'typescript'], 'scores': [85, 92, 78] } * ``` * * @since 1.0.0 */ skipArrays?: boolean; }