UNPKG

@tldraw/utils

Version:

tldraw infinite canvas SDK (private utilities).

8 lines (7 loc) • 15.1 kB
{ "version": 3, "sources": ["../../src/lib/object.ts"], "sourcesContent": ["import isEqualWith from 'lodash.isequalwith'\n\n/**\n * Safely checks if an object has a specific property as its own property (not inherited).\n * Uses Object.prototype.hasOwnProperty.call to avoid issues with objects that have null prototype\n * or have overridden the hasOwnProperty method.\n *\n * @param obj - The object to check\n * @param key - The property key to check for\n * @returns True if the object has the property as its own property, false otherwise\n * @example\n * ```ts\n * const obj = { name: 'Alice', age: 30 }\n * hasOwnProperty(obj, 'name') // true\n * hasOwnProperty(obj, 'toString') // false (inherited)\n * hasOwnProperty(obj, 'unknown') // false\n * ```\n * @internal\n */\nexport function hasOwnProperty(obj: object, key: string): boolean {\n\treturn Object.prototype.hasOwnProperty.call(obj, key)\n}\n\n/**\n * Safely gets an object's own property value (not inherited). Returns undefined if the property\n * doesn't exist as an own property. Provides type-safe access with proper TypeScript inference.\n *\n * @param obj - The object to get the property from\n * @param key - The property key to retrieve\n * @returns The property value if it exists as an own property, undefined otherwise\n * @example\n * ```ts\n * const user = { name: 'Alice', age: 30 }\n * const name = getOwnProperty(user, 'name') // 'Alice'\n * const missing = getOwnProperty(user, 'unknown') // undefined\n * const inherited = getOwnProperty(user, 'toString') // undefined (inherited)\n * ```\n * @internal\n */\nexport function getOwnProperty<K extends string, V>(\n\tobj: Partial<Record<K, V>>,\n\tkey: K\n): V | undefined\n/** @internal */\nexport function getOwnProperty<O extends object>(obj: O, key: string): O[keyof O] | undefined\n/** @internal */\nexport function getOwnProperty(obj: object, key: string): unknown\n/** @internal */\nexport function getOwnProperty(obj: object, key: string): unknown {\n\tif (!hasOwnProperty(obj, key)) {\n\t\treturn undefined\n\t}\n\t// @ts-expect-error we know the property exists\n\treturn obj[key]\n}\n\n/**\n * An alias for `Object.keys` that treats the object as a map and so preserves the type of the keys.\n * Unlike standard Object.keys which returns string[], this maintains the specific string literal types.\n *\n * @param object - The object to get keys from\n * @returns Array of keys with preserved string literal types\n * @example\n * ```ts\n * const config = { theme: 'dark', lang: 'en' } as const\n * const keys = objectMapKeys(config)\n * // keys is Array<'theme' | 'lang'> instead of string[]\n * ```\n * @internal\n */\nexport function objectMapKeys<Key extends string>(object: {\n\treadonly [K in Key]: unknown\n}): Array<Key> {\n\treturn Object.keys(object) as Key[]\n}\n\n/**\n * An alias for `Object.values` that treats the object as a map and so preserves the type of the\n * values. Unlike standard Object.values which returns unknown[], this maintains the specific value types.\n *\n * @param object - The object to get values from\n * @returns Array of values with preserved types\n * @example\n * ```ts\n * const scores = { alice: 85, bob: 92, charlie: 78 }\n * const values = objectMapValues(scores)\n * // values is Array<number> instead of unknown[]\n * ```\n * @internal\n */\nexport function objectMapValues<Key extends string, Value>(object: {\n\t[K in Key]: Value\n}): Array<Value> {\n\treturn Object.values(object) as Value[]\n}\n\n/**\n * An alias for `Object.entries` that treats the object as a map and so preserves the type of the\n * keys and values. Unlike standard Object.entries which returns `Array<[string, unknown]>`, this maintains specific types.\n *\n * @param object - The object to get entries from\n * @returns Array of key-value pairs with preserved types\n * @example\n * ```ts\n * const user = { name: 'Alice', age: 30 }\n * const entries = objectMapEntries(user)\n * // entries is Array<['name' | 'age', string | number]>\n * ```\n * @internal\n */\nexport function objectMapEntries<Obj extends object>(\n\tobject: Obj\n): Array<[keyof Obj, Obj[keyof Obj]]> {\n\treturn Object.entries(object) as [keyof Obj, Obj[keyof Obj]][]\n}\n\n/**\n * Returns the entries of an object as an iterable iterator.\n * Useful when working with large collections, to avoid allocating an array.\n * Only yields own properties (not inherited ones).\n *\n * @param object - The object to iterate over\n * @returns Iterator yielding key-value pairs with preserved types\n * @example\n * ```ts\n * const largeMap = { a: 1, b: 2, c: 3 } // Imagine thousands of entries\n * for (const [key, value] of objectMapEntriesIterable(largeMap)) {\n * // Process entries one at a time without creating a large array\n * console.log(key, value)\n * }\n * ```\n * @internal\n */\nexport function* objectMapEntriesIterable<Key extends string, Value>(object: {\n\t[K in Key]: Value\n}): IterableIterator<[Key, Value]> {\n\tfor (const key in object) {\n\t\tif (!Object.prototype.hasOwnProperty.call(object, key)) continue\n\t\tyield [key, object[key]]\n\t}\n}\n\n/**\n * An alias for `Object.fromEntries` that treats the object as a map and so preserves the type of the\n * keys and values. Creates an object from key-value pairs with proper TypeScript typing.\n *\n * @param entries - Array of key-value pairs to convert to an object\n * @returns Object with preserved key and value types\n * @example\n * ```ts\n * const pairs: Array<['name' | 'age', string | number]> = [['name', 'Alice'], ['age', 30]]\n * const obj = objectMapFromEntries(pairs)\n * // obj is { name: string | number, age: string | number }\n * ```\n * @internal\n */\nexport function objectMapFromEntries<Key extends string, Value>(\n\tentries: ReadonlyArray<readonly [Key, Value]>\n): { [K in Key]: Value } {\n\treturn Object.fromEntries(entries) as { [K in Key]: Value }\n}\n\n/**\n * Filters an object using a predicate function, returning a new object with only the entries\n * that pass the predicate. Optimized to return the original object if no changes are needed.\n *\n * @param object - The object to filter\n * @param predicate - Function that tests each key-value pair\n * @returns A new object with only the entries that pass the predicate, or the original object if unchanged\n * @example\n * ```ts\n * const scores = { alice: 85, bob: 92, charlie: 78 }\n * const passing = filterEntries(scores, (name, score) => score >= 80)\n * // { alice: 85, bob: 92 }\n * ```\n * @internal\n */\nexport function filterEntries<Key extends string, Value>(\n\tobject: { [K in Key]: Value },\n\tpredicate: (key: Key, value: Value) => boolean\n): { [K in Key]: Value } {\n\tconst result: { [K in Key]?: Value } = {}\n\tlet didChange = false\n\tfor (const [key, value] of objectMapEntries(object)) {\n\t\tif (predicate(key, value)) {\n\t\t\tresult[key] = value\n\t\t} else {\n\t\t\tdidChange = true\n\t\t}\n\t}\n\treturn didChange ? (result as { [K in Key]: Value }) : object\n}\n\n/**\n * Maps the values of an object to new values using a mapper function, preserving keys.\n * The mapper function receives both the key and value for each entry.\n *\n * @param object - The object whose values to transform\n * @param mapper - Function that transforms each value (receives key and value)\n * @returns A new object with the same keys but transformed values\n * @example\n * ```ts\n * const prices = { apple: 1.50, banana: 0.75, orange: 2.00 }\n * const withTax = mapObjectMapValues(prices, (fruit, price) => price * 1.08)\n * // { apple: 1.62, banana: 0.81, orange: 2.16 }\n * ```\n * @internal\n */\nexport function mapObjectMapValues<Key extends string, ValueBefore, ValueAfter>(\n\tobject: { readonly [K in Key]: ValueBefore },\n\tmapper: (key: Key, value: ValueBefore) => ValueAfter\n): { [K in Key]: ValueAfter } {\n\tconst result = {} as { [K in Key]: ValueAfter }\n\tfor (const key in object) {\n\t\tif (!Object.prototype.hasOwnProperty.call(object, key)) continue\n\t\tresult[key] = mapper(key, object[key])\n\t}\n\treturn result\n}\n\n/**\n * Performs a shallow equality check between two objects. Compares all enumerable own properties\n * using Object.is for value comparison. Returns true if both objects have the same keys and values.\n *\n * @param obj1 - First object to compare\n * @param obj2 - Second object to compare\n * @returns True if objects are shallow equal, false otherwise\n * @example\n * ```ts\n * const a = { x: 1, y: 2 }\n * const b = { x: 1, y: 2 }\n * const c = { x: 1, y: 3 }\n * areObjectsShallowEqual(a, b) // true\n * areObjectsShallowEqual(a, c) // false\n * areObjectsShallowEqual(a, a) // true (same reference)\n * ```\n * @internal\n */\nexport function areObjectsShallowEqual<T extends object>(obj1: T, obj2: T): boolean {\n\tif (obj1 === obj2) return true\n\tconst keys1 = new Set(Object.keys(obj1))\n\tconst keys2 = new Set(Object.keys(obj2))\n\tif (keys1.size !== keys2.size) return false\n\tfor (const key of keys1) {\n\t\tif (!keys2.has(key)) return false\n\t\tif (!Object.is((obj1 as any)[key], (obj2 as any)[key])) return false\n\t}\n\treturn true\n}\n\n/**\n * Groups an array of values into a record by a key extracted from each value.\n * The key selector function is called for each element to determine the grouping key.\n *\n * @param array - The array to group\n * @param keySelector - Function that extracts the grouping key from each value\n * @returns A record where keys are the extracted keys and values are arrays of grouped items\n * @example\n * ```ts\n * const people = [\n * { name: 'Alice', age: 25 },\n * { name: 'Bob', age: 30 },\n * { name: 'Charlie', age: 25 }\n * ]\n * const byAge = groupBy(people, person => `age-${person.age}`)\n * // { 'age-25': [Alice, Charlie], 'age-30': [Bob] }\n * ```\n * @internal\n */\nexport function groupBy<K extends string, V>(\n\tarray: ReadonlyArray<V>,\n\tkeySelector: (value: V) => K\n): Record<K, V[]> {\n\tconst result: Record<K, V[]> = {} as any\n\tfor (const value of array) {\n\t\tconst key = keySelector(value)\n\t\tif (!result[key]) result[key] = []\n\t\tresult[key].push(value)\n\t}\n\treturn result\n}\n\n/**\n * Creates a new object with specified keys omitted from the original object.\n * Uses shallow copying and then deletes the unwanted keys.\n *\n * @param obj - The source object\n * @param keys - Array of key names to omit from the result\n * @returns A new object without the specified keys\n * @example\n * ```ts\n * const user = { id: '123', name: 'Alice', password: 'secret', email: 'alice@example.com' }\n * const publicUser = omit(user, ['password'])\n * // { id: '123', name: 'Alice', email: 'alice@example.com' }\n * ```\n * @internal\n */\nexport function omit(\n\tobj: Record<string, unknown>,\n\tkeys: ReadonlyArray<string>\n): Record<string, unknown> {\n\tconst result = { ...obj }\n\tfor (const key of keys) {\n\t\tdelete result[key]\n\t}\n\treturn result\n}\n\n/**\n * Compares two objects and returns an array of keys where the values differ.\n * Uses Object.is for comparison, which handles NaN and -0/+0 correctly.\n * Only checks keys present in the first object.\n *\n * @param obj1 - The first object (keys to check come from this object)\n * @param obj2 - The second object to compare against\n * @returns Array of keys where values differ between the objects\n * @example\n * ```ts\n * const before = { name: 'Alice', age: 25, city: 'NYC' }\n * const after = { name: 'Alice', age: 26, city: 'NYC' }\n * const changed = getChangedKeys(before, after)\n * // ['age']\n * ```\n * @internal\n */\nexport function getChangedKeys<T extends object>(obj1: T, obj2: T): (keyof T)[] {\n\tconst result: (keyof T)[] = []\n\tfor (const key in obj1) {\n\t\tif (!Object.is(obj1[key], obj2[key])) {\n\t\t\tresult.push(key)\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Deep equality comparison that allows for floating-point precision errors.\n * Numbers are considered equal if they differ by less than the threshold.\n * Uses lodash.isequalwith internally for the deep comparison logic.\n *\n * @param obj1 - First object to compare\n * @param obj2 - Second object to compare\n * @param threshold - Maximum difference allowed between numbers (default: 0.000001)\n * @returns True if objects are deeply equal with floating-point tolerance\n * @example\n * ```ts\n * const a = { x: 0.1 + 0.2 } // 0.30000000000000004\n * const b = { x: 0.3 }\n * isEqualAllowingForFloatingPointErrors(a, b) // true\n *\n * const c = { coords: [1.0000001, 2.0000001] }\n * const d = { coords: [1.0000002, 2.0000002] }\n * isEqualAllowingForFloatingPointErrors(c, d) // true\n * ```\n * @internal\n */\nexport function isEqualAllowingForFloatingPointErrors(\n\tobj1: object,\n\tobj2: object,\n\tthreshold = 0.000001\n): boolean {\n\treturn isEqualWith(obj1, obj2, (value1, value2) => {\n\t\tif (typeof value1 === 'number' && typeof value2 === 'number') {\n\t\t\treturn Math.abs(value1 - value2) < threshold\n\t\t}\n\t\treturn undefined\n\t})\n}\n"], "mappings": "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", "names": [] }