@tldraw/utils 4.1.0-next.b6dfe9bccde9 → 4.1.0-next.b73a0d46b63f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +1350 -80
- package/dist-cjs/index.js +5 -5
- package/dist-cjs/lib/ExecutionQueue.js +79 -0
- package/dist-cjs/lib/ExecutionQueue.js.map +2 -2
- package/dist-cjs/lib/PerformanceTracker.js +43 -0
- package/dist-cjs/lib/PerformanceTracker.js.map +2 -2
- package/dist-cjs/lib/array.js +3 -1
- package/dist-cjs/lib/array.js.map +2 -2
- package/dist-cjs/lib/bind.js.map +2 -2
- package/dist-cjs/lib/cache.js +27 -5
- package/dist-cjs/lib/cache.js.map +2 -2
- package/dist-cjs/lib/control.js +12 -0
- package/dist-cjs/lib/control.js.map +2 -2
- package/dist-cjs/lib/debounce.js.map +2 -2
- package/dist-cjs/lib/error.js.map +2 -2
- package/dist-cjs/lib/file.js +76 -11
- package/dist-cjs/lib/file.js.map +2 -2
- package/dist-cjs/lib/function.js.map +2 -2
- package/dist-cjs/lib/hash.js.map +2 -2
- package/dist-cjs/lib/id.js.map +2 -2
- package/dist-cjs/lib/iterable.js.map +2 -2
- package/dist-cjs/lib/json-value.js.map +1 -1
- package/dist-cjs/lib/media/apng.js.map +2 -2
- package/dist-cjs/lib/media/avif.js.map +2 -2
- package/dist-cjs/lib/media/gif.js.map +2 -2
- package/dist-cjs/lib/media/media.js +130 -4
- package/dist-cjs/lib/media/media.js.map +2 -2
- package/dist-cjs/lib/media/png.js +141 -0
- package/dist-cjs/lib/media/png.js.map +2 -2
- package/dist-cjs/lib/media/webp.js +1 -0
- package/dist-cjs/lib/media/webp.js.map +2 -2
- package/dist-cjs/lib/network.js.map +2 -2
- package/dist-cjs/lib/number.js.map +2 -2
- package/dist-cjs/lib/object.js +1 -1
- package/dist-cjs/lib/object.js.map +2 -2
- package/dist-cjs/lib/perf.js.map +2 -2
- package/dist-cjs/lib/reordering.js.map +2 -2
- package/dist-cjs/lib/retry.js.map +2 -2
- package/dist-cjs/lib/sort.js.map +2 -2
- package/dist-cjs/lib/storage.js.map +2 -2
- package/dist-cjs/lib/stringEnum.js.map +2 -2
- package/dist-cjs/lib/throttle.js.map +2 -2
- package/dist-cjs/lib/timers.js +103 -4
- package/dist-cjs/lib/timers.js.map +2 -2
- package/dist-cjs/lib/types.js.map +1 -1
- package/dist-cjs/lib/url.js.map +2 -2
- package/dist-cjs/lib/value.js.map +2 -2
- package/dist-cjs/lib/version.js.map +2 -2
- package/dist-cjs/lib/warn.js.map +2 -2
- package/dist-esm/index.d.mts +1350 -80
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/ExecutionQueue.mjs +79 -0
- package/dist-esm/lib/ExecutionQueue.mjs.map +2 -2
- package/dist-esm/lib/PerformanceTracker.mjs +43 -0
- package/dist-esm/lib/PerformanceTracker.mjs.map +2 -2
- package/dist-esm/lib/array.mjs +3 -1
- package/dist-esm/lib/array.mjs.map +2 -2
- package/dist-esm/lib/bind.mjs.map +2 -2
- package/dist-esm/lib/cache.mjs +27 -5
- package/dist-esm/lib/cache.mjs.map +2 -2
- package/dist-esm/lib/control.mjs +12 -0
- package/dist-esm/lib/control.mjs.map +2 -2
- package/dist-esm/lib/debounce.mjs.map +2 -2
- package/dist-esm/lib/error.mjs.map +2 -2
- package/dist-esm/lib/file.mjs +76 -11
- package/dist-esm/lib/file.mjs.map +2 -2
- package/dist-esm/lib/function.mjs.map +2 -2
- package/dist-esm/lib/hash.mjs.map +2 -2
- package/dist-esm/lib/id.mjs.map +2 -2
- package/dist-esm/lib/iterable.mjs.map +2 -2
- package/dist-esm/lib/media/apng.mjs.map +2 -2
- package/dist-esm/lib/media/avif.mjs.map +2 -2
- package/dist-esm/lib/media/gif.mjs.map +2 -2
- package/dist-esm/lib/media/media.mjs +130 -4
- package/dist-esm/lib/media/media.mjs.map +2 -2
- package/dist-esm/lib/media/png.mjs +141 -0
- package/dist-esm/lib/media/png.mjs.map +2 -2
- package/dist-esm/lib/media/webp.mjs +1 -0
- package/dist-esm/lib/media/webp.mjs.map +2 -2
- package/dist-esm/lib/network.mjs.map +2 -2
- package/dist-esm/lib/number.mjs.map +2 -2
- package/dist-esm/lib/object.mjs.map +2 -2
- package/dist-esm/lib/perf.mjs.map +2 -2
- package/dist-esm/lib/reordering.mjs.map +2 -2
- package/dist-esm/lib/retry.mjs.map +2 -2
- package/dist-esm/lib/sort.mjs.map +2 -2
- package/dist-esm/lib/storage.mjs.map +2 -2
- package/dist-esm/lib/stringEnum.mjs.map +2 -2
- package/dist-esm/lib/throttle.mjs.map +2 -2
- package/dist-esm/lib/timers.mjs +103 -4
- package/dist-esm/lib/timers.mjs.map +2 -2
- package/dist-esm/lib/url.mjs.map +2 -2
- package/dist-esm/lib/value.mjs.map +2 -2
- package/dist-esm/lib/version.mjs.map +2 -2
- package/dist-esm/lib/warn.mjs.map +2 -2
- package/package.json +1 -1
- package/src/lib/ExecutionQueue.test.ts +162 -20
- package/src/lib/ExecutionQueue.ts +110 -1
- package/src/lib/PerformanceTracker.test.ts +124 -0
- package/src/lib/PerformanceTracker.ts +63 -1
- package/src/lib/array.test.ts +263 -1
- package/src/lib/array.ts +183 -14
- package/src/lib/bind.test.ts +47 -0
- package/src/lib/bind.ts +69 -4
- package/src/lib/cache.test.ts +73 -0
- package/src/lib/cache.ts +47 -6
- package/src/lib/control.test.ts +50 -0
- package/src/lib/control.ts +198 -9
- package/src/lib/debounce.ts +28 -3
- package/src/lib/error.test.ts +60 -0
- package/src/lib/error.ts +27 -1
- package/src/lib/file.test.ts +49 -0
- package/src/lib/file.ts +117 -12
- package/src/lib/function.ts +11 -0
- package/src/lib/hash.test.ts +99 -0
- package/src/lib/hash.ts +69 -2
- package/src/lib/id.test.ts +32 -0
- package/src/lib/id.ts +53 -5
- package/src/lib/iterable.test.ts +25 -0
- package/src/lib/iterable.ts +4 -5
- package/src/lib/json-value.ts +71 -4
- package/src/lib/media/apng.test.ts +67 -0
- package/src/lib/media/apng.ts +38 -21
- package/src/lib/media/avif.test.ts +26 -0
- package/src/lib/media/avif.ts +34 -0
- package/src/lib/media/gif.test.ts +52 -0
- package/src/lib/media/gif.ts +25 -2
- package/src/lib/media/media.test.ts +58 -0
- package/src/lib/media/media.ts +220 -11
- package/src/lib/media/png.ts +162 -1
- package/src/lib/media/webp.test.ts +81 -0
- package/src/lib/media/webp.ts +33 -1
- package/src/lib/network.test.ts +38 -0
- package/src/lib/network.ts +6 -0
- package/src/lib/number.test.ts +74 -0
- package/src/lib/number.ts +29 -5
- package/src/lib/object.test.ts +236 -0
- package/src/lib/object.ts +194 -14
- package/src/lib/perf.ts +75 -3
- package/src/lib/reordering.test.ts +168 -0
- package/src/lib/reordering.ts +62 -4
- package/src/lib/retry.test.ts +77 -0
- package/src/lib/retry.ts +47 -1
- package/src/lib/sort.test.ts +36 -0
- package/src/lib/sort.ts +22 -1
- package/src/lib/storage.test.ts +130 -0
- package/src/lib/storage.tsx +54 -8
- package/src/lib/stringEnum.ts +20 -1
- package/src/lib/throttle.ts +46 -8
- package/src/lib/timers.test.ts +75 -0
- package/src/lib/timers.ts +124 -5
- package/src/lib/types.ts +126 -4
- package/src/lib/url.test.ts +44 -0
- package/src/lib/url.ts +40 -1
- package/src/lib/value.test.ts +102 -0
- package/src/lib/value.ts +67 -3
- package/src/lib/version.test.ts +494 -56
- package/src/lib/version.ts +36 -1
- package/src/lib/warn.test.ts +64 -0
- package/src/lib/warn.ts +43 -2
package/src/lib/object.ts
CHANGED
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
import isEqualWith from 'lodash.isequalwith'
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Safely checks if an object has a specific property as its own property (not inherited).
|
|
5
|
+
* Uses Object.prototype.hasOwnProperty.call to avoid issues with objects that have null prototype
|
|
6
|
+
* or have overridden the hasOwnProperty method.
|
|
7
|
+
*
|
|
8
|
+
* @param obj - The object to check
|
|
9
|
+
* @param key - The property key to check for
|
|
10
|
+
* @returns True if the object has the property as its own property, false otherwise
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const obj = { name: 'Alice', age: 30 }
|
|
14
|
+
* hasOwnProperty(obj, 'name') // true
|
|
15
|
+
* hasOwnProperty(obj, 'toString') // false (inherited)
|
|
16
|
+
* hasOwnProperty(obj, 'unknown') // false
|
|
17
|
+
* ```
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
4
20
|
export function hasOwnProperty(obj: object, key: string): boolean {
|
|
5
21
|
return Object.prototype.hasOwnProperty.call(obj, key)
|
|
6
22
|
}
|
|
7
23
|
|
|
8
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Safely gets an object's own property value (not inherited). Returns undefined if the property
|
|
26
|
+
* doesn't exist as an own property. Provides type-safe access with proper TypeScript inference.
|
|
27
|
+
*
|
|
28
|
+
* @param obj - The object to get the property from
|
|
29
|
+
* @param key - The property key to retrieve
|
|
30
|
+
* @returns The property value if it exists as an own property, undefined otherwise
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const user = { name: 'Alice', age: 30 }
|
|
34
|
+
* const name = getOwnProperty(user, 'name') // 'Alice'
|
|
35
|
+
* const missing = getOwnProperty(user, 'unknown') // undefined
|
|
36
|
+
* const inherited = getOwnProperty(user, 'toString') // undefined (inherited)
|
|
37
|
+
* ```
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
9
40
|
export function getOwnProperty<K extends string, V>(
|
|
10
41
|
obj: Partial<Record<K, V>>,
|
|
11
42
|
key: K
|
|
@@ -25,7 +56,16 @@ export function getOwnProperty(obj: object, key: string): unknown {
|
|
|
25
56
|
|
|
26
57
|
/**
|
|
27
58
|
* An alias for `Object.keys` that treats the object as a map and so preserves the type of the keys.
|
|
59
|
+
* Unlike standard Object.keys which returns string[], this maintains the specific string literal types.
|
|
28
60
|
*
|
|
61
|
+
* @param object - The object to get keys from
|
|
62
|
+
* @returns Array of keys with preserved string literal types
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const config = { theme: 'dark', lang: 'en' } as const
|
|
66
|
+
* const keys = objectMapKeys(config)
|
|
67
|
+
* // keys is Array<'theme' | 'lang'> instead of string[]
|
|
68
|
+
* ```
|
|
29
69
|
* @internal
|
|
30
70
|
*/
|
|
31
71
|
export function objectMapKeys<Key extends string>(object: {
|
|
@@ -36,8 +76,16 @@ export function objectMapKeys<Key extends string>(object: {
|
|
|
36
76
|
|
|
37
77
|
/**
|
|
38
78
|
* An alias for `Object.values` that treats the object as a map and so preserves the type of the
|
|
39
|
-
*
|
|
79
|
+
* values. Unlike standard Object.values which returns unknown[], this maintains the specific value types.
|
|
40
80
|
*
|
|
81
|
+
* @param object - The object to get values from
|
|
82
|
+
* @returns Array of values with preserved types
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const scores = { alice: 85, bob: 92, charlie: 78 }
|
|
86
|
+
* const values = objectMapValues(scores)
|
|
87
|
+
* // values is Array<number> instead of unknown[]
|
|
88
|
+
* ```
|
|
41
89
|
* @internal
|
|
42
90
|
*/
|
|
43
91
|
export function objectMapValues<Key extends string, Value>(object: {
|
|
@@ -48,8 +96,16 @@ export function objectMapValues<Key extends string, Value>(object: {
|
|
|
48
96
|
|
|
49
97
|
/**
|
|
50
98
|
* An alias for `Object.entries` that treats the object as a map and so preserves the type of the
|
|
51
|
-
* keys.
|
|
99
|
+
* keys and values. Unlike standard Object.entries which returns `Array<[string, unknown]>`, this maintains specific types.
|
|
52
100
|
*
|
|
101
|
+
* @param object - The object to get entries from
|
|
102
|
+
* @returns Array of key-value pairs with preserved types
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const user = { name: 'Alice', age: 30 }
|
|
106
|
+
* const entries = objectMapEntries(user)
|
|
107
|
+
* // entries is Array<['name' | 'age', string | number]>
|
|
108
|
+
* ```
|
|
53
109
|
* @internal
|
|
54
110
|
*/
|
|
55
111
|
export function objectMapEntries<Key extends string, Value>(object: {
|
|
@@ -61,7 +117,18 @@ export function objectMapEntries<Key extends string, Value>(object: {
|
|
|
61
117
|
/**
|
|
62
118
|
* Returns the entries of an object as an iterable iterator.
|
|
63
119
|
* Useful when working with large collections, to avoid allocating an array.
|
|
120
|
+
* Only yields own properties (not inherited ones).
|
|
64
121
|
*
|
|
122
|
+
* @param object - The object to iterate over
|
|
123
|
+
* @returns Iterator yielding key-value pairs with preserved types
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const largeMap = { a: 1, b: 2, c: 3 } // Imagine thousands of entries
|
|
127
|
+
* for (const [key, value] of objectMapEntriesIterable(largeMap)) {
|
|
128
|
+
* // Process entries one at a time without creating a large array
|
|
129
|
+
* console.log(key, value)
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
65
132
|
* @internal
|
|
66
133
|
*/
|
|
67
134
|
export function* objectMapEntriesIterable<Key extends string, Value>(object: {
|
|
@@ -75,8 +142,16 @@ export function* objectMapEntriesIterable<Key extends string, Value>(object: {
|
|
|
75
142
|
|
|
76
143
|
/**
|
|
77
144
|
* An alias for `Object.fromEntries` that treats the object as a map and so preserves the type of the
|
|
78
|
-
* keys.
|
|
145
|
+
* keys and values. Creates an object from key-value pairs with proper TypeScript typing.
|
|
79
146
|
*
|
|
147
|
+
* @param entries - Array of key-value pairs to convert to an object
|
|
148
|
+
* @returns Object with preserved key and value types
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const pairs: Array<['name' | 'age', string | number]> = [['name', 'Alice'], ['age', 30]]
|
|
152
|
+
* const obj = objectMapFromEntries(pairs)
|
|
153
|
+
* // obj is { name: string | number, age: string | number }
|
|
154
|
+
* ```
|
|
80
155
|
* @internal
|
|
81
156
|
*/
|
|
82
157
|
export function objectMapFromEntries<Key extends string, Value>(
|
|
@@ -86,8 +161,18 @@ export function objectMapFromEntries<Key extends string, Value>(
|
|
|
86
161
|
}
|
|
87
162
|
|
|
88
163
|
/**
|
|
89
|
-
* Filters an object using a predicate function
|
|
90
|
-
*
|
|
164
|
+
* Filters an object using a predicate function, returning a new object with only the entries
|
|
165
|
+
* that pass the predicate. Optimized to return the original object if no changes are needed.
|
|
166
|
+
*
|
|
167
|
+
* @param object - The object to filter
|
|
168
|
+
* @param predicate - Function that tests each key-value pair
|
|
169
|
+
* @returns A new object with only the entries that pass the predicate, or the original object if unchanged
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* const scores = { alice: 85, bob: 92, charlie: 78 }
|
|
173
|
+
* const passing = filterEntries(scores, (name, score) => score >= 80)
|
|
174
|
+
* // { alice: 85, bob: 92 }
|
|
175
|
+
* ```
|
|
91
176
|
* @internal
|
|
92
177
|
*/
|
|
93
178
|
export function filterEntries<Key extends string, Value>(
|
|
@@ -107,8 +192,18 @@ export function filterEntries<Key extends string, Value>(
|
|
|
107
192
|
}
|
|
108
193
|
|
|
109
194
|
/**
|
|
110
|
-
* Maps the values of
|
|
111
|
-
*
|
|
195
|
+
* Maps the values of an object to new values using a mapper function, preserving keys.
|
|
196
|
+
* The mapper function receives both the key and value for each entry.
|
|
197
|
+
*
|
|
198
|
+
* @param object - The object whose values to transform
|
|
199
|
+
* @param mapper - Function that transforms each value (receives key and value)
|
|
200
|
+
* @returns A new object with the same keys but transformed values
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* const prices = { apple: 1.50, banana: 0.75, orange: 2.00 }
|
|
204
|
+
* const withTax = mapObjectMapValues(prices, (fruit, price) => price * 1.08)
|
|
205
|
+
* // { apple: 1.62, banana: 0.81, orange: 2.16 }
|
|
206
|
+
* ```
|
|
112
207
|
* @internal
|
|
113
208
|
*/
|
|
114
209
|
export function mapObjectMapValues<Key extends string, ValueBefore, ValueAfter>(
|
|
@@ -123,7 +218,24 @@ export function mapObjectMapValues<Key extends string, ValueBefore, ValueAfter>(
|
|
|
123
218
|
return result
|
|
124
219
|
}
|
|
125
220
|
|
|
126
|
-
/**
|
|
221
|
+
/**
|
|
222
|
+
* Performs a shallow equality check between two objects. Compares all enumerable own properties
|
|
223
|
+
* using Object.is for value comparison. Returns true if both objects have the same keys and values.
|
|
224
|
+
*
|
|
225
|
+
* @param obj1 - First object to compare
|
|
226
|
+
* @param obj2 - Second object to compare
|
|
227
|
+
* @returns True if objects are shallow equal, false otherwise
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* const a = { x: 1, y: 2 }
|
|
231
|
+
* const b = { x: 1, y: 2 }
|
|
232
|
+
* const c = { x: 1, y: 3 }
|
|
233
|
+
* areObjectsShallowEqual(a, b) // true
|
|
234
|
+
* areObjectsShallowEqual(a, c) // false
|
|
235
|
+
* areObjectsShallowEqual(a, a) // true (same reference)
|
|
236
|
+
* ```
|
|
237
|
+
* @internal
|
|
238
|
+
*/
|
|
127
239
|
export function areObjectsShallowEqual<T extends object>(obj1: T, obj2: T): boolean {
|
|
128
240
|
if (obj1 === obj2) return true
|
|
129
241
|
const keys1 = new Set(Object.keys(obj1))
|
|
@@ -136,7 +248,25 @@ export function areObjectsShallowEqual<T extends object>(obj1: T, obj2: T): bool
|
|
|
136
248
|
return true
|
|
137
249
|
}
|
|
138
250
|
|
|
139
|
-
/**
|
|
251
|
+
/**
|
|
252
|
+
* Groups an array of values into a record by a key extracted from each value.
|
|
253
|
+
* The key selector function is called for each element to determine the grouping key.
|
|
254
|
+
*
|
|
255
|
+
* @param array - The array to group
|
|
256
|
+
* @param keySelector - Function that extracts the grouping key from each value
|
|
257
|
+
* @returns A record where keys are the extracted keys and values are arrays of grouped items
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* const people = [
|
|
261
|
+
* { name: 'Alice', age: 25 },
|
|
262
|
+
* { name: 'Bob', age: 30 },
|
|
263
|
+
* { name: 'Charlie', age: 25 }
|
|
264
|
+
* ]
|
|
265
|
+
* const byAge = groupBy(people, person => `age-${person.age}`)
|
|
266
|
+
* // { 'age-25': [Alice, Charlie], 'age-30': [Bob] }
|
|
267
|
+
* ```
|
|
268
|
+
* @internal
|
|
269
|
+
*/
|
|
140
270
|
export function groupBy<K extends string, V>(
|
|
141
271
|
array: ReadonlyArray<V>,
|
|
142
272
|
keySelector: (value: V) => K
|
|
@@ -150,7 +280,21 @@ export function groupBy<K extends string, V>(
|
|
|
150
280
|
return result
|
|
151
281
|
}
|
|
152
282
|
|
|
153
|
-
/**
|
|
283
|
+
/**
|
|
284
|
+
* Creates a new object with specified keys omitted from the original object.
|
|
285
|
+
* Uses shallow copying and then deletes the unwanted keys.
|
|
286
|
+
*
|
|
287
|
+
* @param obj - The source object
|
|
288
|
+
* @param keys - Array of key names to omit from the result
|
|
289
|
+
* @returns A new object without the specified keys
|
|
290
|
+
* @example
|
|
291
|
+
* ```ts
|
|
292
|
+
* const user = { id: '123', name: 'Alice', password: 'secret', email: 'alice@example.com' }
|
|
293
|
+
* const publicUser = omit(user, ['password'])
|
|
294
|
+
* // { id: '123', name: 'Alice', email: 'alice@example.com' }
|
|
295
|
+
* ```
|
|
296
|
+
* @internal
|
|
297
|
+
*/
|
|
154
298
|
export function omit(
|
|
155
299
|
obj: Record<string, unknown>,
|
|
156
300
|
keys: ReadonlyArray<string>
|
|
@@ -162,7 +306,23 @@ export function omit(
|
|
|
162
306
|
return result
|
|
163
307
|
}
|
|
164
308
|
|
|
165
|
-
/**
|
|
309
|
+
/**
|
|
310
|
+
* Compares two objects and returns an array of keys where the values differ.
|
|
311
|
+
* Uses Object.is for comparison, which handles NaN and -0/+0 correctly.
|
|
312
|
+
* Only checks keys present in the first object.
|
|
313
|
+
*
|
|
314
|
+
* @param obj1 - The first object (keys to check come from this object)
|
|
315
|
+
* @param obj2 - The second object to compare against
|
|
316
|
+
* @returns Array of keys where values differ between the objects
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* const before = { name: 'Alice', age: 25, city: 'NYC' }
|
|
320
|
+
* const after = { name: 'Alice', age: 26, city: 'NYC' }
|
|
321
|
+
* const changed = getChangedKeys(before, after)
|
|
322
|
+
* // ['age']
|
|
323
|
+
* ```
|
|
324
|
+
* @internal
|
|
325
|
+
*/
|
|
166
326
|
export function getChangedKeys<T extends object>(obj1: T, obj2: T): (keyof T)[] {
|
|
167
327
|
const result: (keyof T)[] = []
|
|
168
328
|
for (const key in obj1) {
|
|
@@ -173,7 +333,27 @@ export function getChangedKeys<T extends object>(obj1: T, obj2: T): (keyof T)[]
|
|
|
173
333
|
return result
|
|
174
334
|
}
|
|
175
335
|
|
|
176
|
-
/**
|
|
336
|
+
/**
|
|
337
|
+
* Deep equality comparison that allows for floating-point precision errors.
|
|
338
|
+
* Numbers are considered equal if they differ by less than the threshold.
|
|
339
|
+
* Uses lodash.isequalwith internally for the deep comparison logic.
|
|
340
|
+
*
|
|
341
|
+
* @param obj1 - First object to compare
|
|
342
|
+
* @param obj2 - Second object to compare
|
|
343
|
+
* @param threshold - Maximum difference allowed between numbers (default: 0.000001)
|
|
344
|
+
* @returns True if objects are deeply equal with floating-point tolerance
|
|
345
|
+
* @example
|
|
346
|
+
* ```ts
|
|
347
|
+
* const a = { x: 0.1 + 0.2 } // 0.30000000000000004
|
|
348
|
+
* const b = { x: 0.3 }
|
|
349
|
+
* isEqualAllowingForFloatingPointErrors(a, b) // true
|
|
350
|
+
*
|
|
351
|
+
* const c = { coords: [1.0000001, 2.0000001] }
|
|
352
|
+
* const d = { coords: [1.0000002, 2.0000002] }
|
|
353
|
+
* isEqualAllowingForFloatingPointErrors(c, d) // true
|
|
354
|
+
* ```
|
|
355
|
+
* @internal
|
|
356
|
+
*/
|
|
177
357
|
export function isEqualAllowingForFloatingPointErrors(
|
|
178
358
|
obj1: object,
|
|
179
359
|
obj2: object,
|
package/src/lib/perf.ts
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color scheme for performance indicators.
|
|
3
|
+
* Provides consistent colors for performance measurement displays.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
1
7
|
export const PERFORMANCE_COLORS = {
|
|
2
8
|
Good: '#40C057',
|
|
3
9
|
Mid: '#FFC078',
|
|
4
10
|
Poor: '#E03131',
|
|
5
11
|
}
|
|
6
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Default color for performance measurement log prefixes.
|
|
15
|
+
* Uses the 'Good' performance color for console output styling.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
7
19
|
export const PERFORMANCE_PREFIX_COLOR = PERFORMANCE_COLORS.Good
|
|
8
20
|
|
|
9
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Measures and logs the execution time of a callback function.
|
|
23
|
+
* Executes the provided callback and logs the duration to the console with styled output.
|
|
24
|
+
*
|
|
25
|
+
* @param name - Descriptive name for the operation being measured
|
|
26
|
+
* @param cb - Callback function to execute and measure
|
|
27
|
+
* @returns The return value of the callback function
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const result = measureCbDuration('data processing', () => {
|
|
32
|
+
* return processLargeDataSet(data)
|
|
33
|
+
* })
|
|
34
|
+
* // Console output: "Perf data processing took 42.5ms"
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
10
39
|
export function measureCbDuration(name: string, cb: () => any) {
|
|
11
40
|
const start = performance.now()
|
|
12
41
|
const result = cb()
|
|
@@ -19,7 +48,28 @@ export function measureCbDuration(name: string, cb: () => any) {
|
|
|
19
48
|
return result
|
|
20
49
|
}
|
|
21
50
|
|
|
22
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Decorator that measures and logs the execution time of class methods.
|
|
53
|
+
* Wraps the decorated method to automatically log its execution duration.
|
|
54
|
+
*
|
|
55
|
+
* @param _target - The class prototype (unused)
|
|
56
|
+
* @param propertyKey - Name of the method being decorated
|
|
57
|
+
* @param descriptor - Property descriptor of the method
|
|
58
|
+
* @returns Modified property descriptor with timing measurement
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* class DataProcessor {
|
|
63
|
+
* @measureDuration
|
|
64
|
+
* processData(data: unknown[]) {
|
|
65
|
+
* return data.map(item => transform(item))
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
* // When processData is called, logs: "Perf processData took: 15.2ms"
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @internal
|
|
72
|
+
*/
|
|
23
73
|
export function measureDuration(_target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
24
74
|
const originalMethod = descriptor.value
|
|
25
75
|
descriptor.value = function (...args: any[]) {
|
|
@@ -38,7 +88,29 @@ export function measureDuration(_target: any, propertyKey: string, descriptor: P
|
|
|
38
88
|
|
|
39
89
|
const averages = new Map<any, { total: number; count: number }>()
|
|
40
90
|
|
|
41
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Decorator that measures method execution time and tracks running averages.
|
|
93
|
+
* Wraps the decorated method to log both current execution time and running average.
|
|
94
|
+
* Maintains a running total and count for each decorated method to calculate averages.
|
|
95
|
+
*
|
|
96
|
+
* @param _target - The class prototype (unused)
|
|
97
|
+
* @param propertyKey - Name of the method being decorated
|
|
98
|
+
* @param descriptor - Property descriptor of the method
|
|
99
|
+
* @returns Modified property descriptor with timing measurement and averaging
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* class RenderEngine {
|
|
104
|
+
* @measureAverageDuration
|
|
105
|
+
* renderFrame() {
|
|
106
|
+
* // Rendering logic here
|
|
107
|
+
* }
|
|
108
|
+
* }
|
|
109
|
+
* // After multiple calls, logs: "Perf renderFrame took 16.67ms | average 15.83ms"
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
42
114
|
export function measureAverageDuration(
|
|
43
115
|
_target: any,
|
|
44
116
|
propertyKey: string,
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
type IndexKey,
|
|
4
|
+
getIndexAbove,
|
|
5
|
+
getIndexBelow,
|
|
6
|
+
getIndexBetween,
|
|
7
|
+
getIndices,
|
|
8
|
+
getIndicesAbove,
|
|
9
|
+
getIndicesBelow,
|
|
10
|
+
getIndicesBetween,
|
|
11
|
+
sortByIndex,
|
|
12
|
+
validateIndexKey,
|
|
13
|
+
} from './reordering'
|
|
14
|
+
|
|
15
|
+
describe('reordering', () => {
|
|
16
|
+
describe('validateIndexKey', () => {
|
|
17
|
+
it('should accept valid index keys', () => {
|
|
18
|
+
expect(() => validateIndexKey('a0')).not.toThrow()
|
|
19
|
+
expect(() => validateIndexKey('a1')).not.toThrow()
|
|
20
|
+
expect(() => validateIndexKey('a0V')).not.toThrow()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should throw for invalid index keys', () => {
|
|
24
|
+
expect(() => validateIndexKey('')).toThrow('invalid index: ')
|
|
25
|
+
expect(() => validateIndexKey('invalid')).toThrow('invalid index: invalid')
|
|
26
|
+
expect(() => validateIndexKey('123')).toThrow('invalid index: 123')
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('getIndicesBetween', () => {
|
|
31
|
+
it('should generate indices between two indices', () => {
|
|
32
|
+
const indices = getIndicesBetween('a0' as IndexKey, 'a2' as IndexKey, 2)
|
|
33
|
+
expect(indices).toHaveLength(2)
|
|
34
|
+
expect(indices.every((index) => index > 'a0' && index < 'a2')).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should handle null/undefined parameters', () => {
|
|
38
|
+
const indices1 = getIndicesBetween(null, 'a2' as IndexKey, 2)
|
|
39
|
+
expect(indices1).toHaveLength(2)
|
|
40
|
+
expect(indices1.every((index) => index < 'a2')).toBe(true)
|
|
41
|
+
|
|
42
|
+
const indices2 = getIndicesBetween('a0' as IndexKey, null, 2)
|
|
43
|
+
expect(indices2).toHaveLength(2)
|
|
44
|
+
expect(indices2.every((index) => index > 'a0')).toBe(true)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('getIndicesAbove', () => {
|
|
49
|
+
it('should generate indices above a given index', () => {
|
|
50
|
+
const indices = getIndicesAbove('a0' as IndexKey, 3)
|
|
51
|
+
expect(indices).toHaveLength(3)
|
|
52
|
+
expect(indices.every((index) => index > 'a0')).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should handle null/undefined parameters', () => {
|
|
56
|
+
const indices = getIndicesAbove(null, 3)
|
|
57
|
+
expect(indices).toHaveLength(3)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('getIndicesBelow', () => {
|
|
62
|
+
it('should generate indices below a given index', () => {
|
|
63
|
+
const indices = getIndicesBelow('a5' as IndexKey, 3)
|
|
64
|
+
expect(indices).toHaveLength(3)
|
|
65
|
+
expect(indices.every((index) => index < 'a5')).toBe(true)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should handle null/undefined parameters', () => {
|
|
69
|
+
const indices = getIndicesBelow(null, 3)
|
|
70
|
+
expect(indices).toHaveLength(3)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('getIndexBetween', () => {
|
|
75
|
+
it('should generate single index between two indices', () => {
|
|
76
|
+
const index = getIndexBetween('a0' as IndexKey, 'a2' as IndexKey)
|
|
77
|
+
expect(index > 'a0' && index < 'a2').toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should handle null/undefined parameters', () => {
|
|
81
|
+
const index1 = getIndexBetween(null, 'a2' as IndexKey)
|
|
82
|
+
expect(index1 < 'a2').toBe(true)
|
|
83
|
+
|
|
84
|
+
const index2 = getIndexBetween('a0' as IndexKey, null)
|
|
85
|
+
expect(index2 > 'a0').toBe(true)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('getIndexAbove', () => {
|
|
90
|
+
it('should generate index above given index', () => {
|
|
91
|
+
const index = getIndexAbove('a0' as IndexKey)
|
|
92
|
+
expect(index > 'a0').toBe(true)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should handle null/undefined/no parameters', () => {
|
|
96
|
+
const index = getIndexAbove()
|
|
97
|
+
expect(typeof index).toBe('string')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe('getIndexBelow', () => {
|
|
102
|
+
it('should generate index below given index', () => {
|
|
103
|
+
const index = getIndexBelow('a5' as IndexKey)
|
|
104
|
+
expect(index < 'a5').toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should handle null/undefined/no parameters', () => {
|
|
108
|
+
const index = getIndexBelow()
|
|
109
|
+
expect(typeof index).toBe('string')
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('getIndices', () => {
|
|
114
|
+
it('should generate indices starting from given index', () => {
|
|
115
|
+
const indices = getIndices(3, 'a1' as IndexKey)
|
|
116
|
+
expect(indices).toHaveLength(4) // start + n additional
|
|
117
|
+
expect(indices[0]).toBe('a1')
|
|
118
|
+
expect(indices.every((index) => index >= 'a1')).toBe(true)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should use default start index when not provided', () => {
|
|
122
|
+
const indices = getIndices(3)
|
|
123
|
+
expect(indices).toHaveLength(4)
|
|
124
|
+
expect(indices[0]).toBe('a1')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('sortByIndex', () => {
|
|
129
|
+
it('should sort objects by index property in ascending order', () => {
|
|
130
|
+
const objects = [
|
|
131
|
+
{ id: 'c', index: 'a3' as IndexKey },
|
|
132
|
+
{ id: 'a', index: 'a1' as IndexKey },
|
|
133
|
+
{ id: 'b', index: 'a2' as IndexKey },
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
const sorted = objects.sort(sortByIndex)
|
|
137
|
+
expect(sorted.map((obj) => obj.id)).toEqual(['a', 'b', 'c'])
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should handle fractional indices correctly', () => {
|
|
141
|
+
const objects = [
|
|
142
|
+
{ id: 'c', index: 'a1V' as IndexKey }, // Between a1 and a2
|
|
143
|
+
{ id: 'a', index: 'a1' as IndexKey },
|
|
144
|
+
{ id: 'b', index: 'a2' as IndexKey },
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
const sorted = objects.sort(sortByIndex)
|
|
148
|
+
expect(sorted.map((obj) => obj.id)).toEqual(['a', 'c', 'b'])
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('integration tests', () => {
|
|
153
|
+
it('should maintain order when inserting indices between existing ones', () => {
|
|
154
|
+
const start = getIndexAbove()
|
|
155
|
+
const end = getIndexAbove(start)
|
|
156
|
+
const between = getIndexBetween(start, end)
|
|
157
|
+
|
|
158
|
+
const objects = [
|
|
159
|
+
{ id: 'first', index: start },
|
|
160
|
+
{ id: 'last', index: end },
|
|
161
|
+
{ id: 'middle', index: between },
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
const sorted = objects.sort(sortByIndex)
|
|
165
|
+
expect(sorted.map((obj) => obj.id)).toEqual(['first', 'middle', 'last'])
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
})
|
package/src/lib/reordering.ts
CHANGED
|
@@ -22,7 +22,12 @@ export type IndexKey = string & { __brand: 'indexKey' }
|
|
|
22
22
|
*/
|
|
23
23
|
export const ZERO_INDEX_KEY = 'a0' as IndexKey
|
|
24
24
|
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Validates that a string is a valid IndexKey.
|
|
27
|
+
* @param index - The string to validate.
|
|
28
|
+
* @throws Error if the index is invalid.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
26
31
|
export function validateIndexKey(index: string): asserts index is IndexKey {
|
|
27
32
|
try {
|
|
28
33
|
generateKeyBetween(index, null)
|
|
@@ -36,6 +41,12 @@ export function validateIndexKey(index: string): asserts index is IndexKey {
|
|
|
36
41
|
* @param below - The index below.
|
|
37
42
|
* @param above - The index above.
|
|
38
43
|
* @param n - The number of indices to get.
|
|
44
|
+
* @returns An array of n IndexKey values between below and above.
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const indices = getIndicesBetween('a0' as IndexKey, 'a2' as IndexKey, 2)
|
|
48
|
+
* console.log(indices) // ['a0V', 'a1']
|
|
49
|
+
* ```
|
|
39
50
|
* @public
|
|
40
51
|
*/
|
|
41
52
|
export function getIndicesBetween(
|
|
@@ -50,6 +61,12 @@ export function getIndicesBetween(
|
|
|
50
61
|
* Get a number of indices above an index.
|
|
51
62
|
* @param below - The index below.
|
|
52
63
|
* @param n - The number of indices to get.
|
|
64
|
+
* @returns An array of n IndexKey values above the given index.
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* const indices = getIndicesAbove('a0' as IndexKey, 3)
|
|
68
|
+
* console.log(indices) // ['a1', 'a2', 'a3']
|
|
69
|
+
* ```
|
|
53
70
|
* @public
|
|
54
71
|
*/
|
|
55
72
|
export function getIndicesAbove(below: IndexKey | null | undefined, n: number) {
|
|
@@ -60,6 +77,12 @@ export function getIndicesAbove(below: IndexKey | null | undefined, n: number) {
|
|
|
60
77
|
* Get a number of indices below an index.
|
|
61
78
|
* @param above - The index above.
|
|
62
79
|
* @param n - The number of indices to get.
|
|
80
|
+
* @returns An array of n IndexKey values below the given index.
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* const indices = getIndicesBelow('a2' as IndexKey, 2)
|
|
84
|
+
* console.log(indices) // ['a1', 'a0V']
|
|
85
|
+
* ```
|
|
63
86
|
* @public
|
|
64
87
|
*/
|
|
65
88
|
export function getIndicesBelow(above: IndexKey | null | undefined, n: number) {
|
|
@@ -70,6 +93,12 @@ export function getIndicesBelow(above: IndexKey | null | undefined, n: number) {
|
|
|
70
93
|
* Get the index between two indices.
|
|
71
94
|
* @param below - The index below.
|
|
72
95
|
* @param above - The index above.
|
|
96
|
+
* @returns A single IndexKey value between below and above.
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const index = getIndexBetween('a0' as IndexKey, 'a2' as IndexKey)
|
|
100
|
+
* console.log(index) // 'a1'
|
|
101
|
+
* ```
|
|
73
102
|
* @public
|
|
74
103
|
*/
|
|
75
104
|
export function getIndexBetween(
|
|
@@ -82,6 +111,12 @@ export function getIndexBetween(
|
|
|
82
111
|
/**
|
|
83
112
|
* Get the index above a given index.
|
|
84
113
|
* @param below - The index below.
|
|
114
|
+
* @returns An IndexKey value above the given index.
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const index = getIndexAbove('a0' as IndexKey)
|
|
118
|
+
* console.log(index) // 'a1'
|
|
119
|
+
* ```
|
|
85
120
|
* @public
|
|
86
121
|
*/
|
|
87
122
|
export function getIndexAbove(below: IndexKey | null | undefined = null) {
|
|
@@ -91,7 +126,13 @@ export function getIndexAbove(below: IndexKey | null | undefined = null) {
|
|
|
91
126
|
/**
|
|
92
127
|
* Get the index below a given index.
|
|
93
128
|
* @param above - The index above.
|
|
94
|
-
*
|
|
129
|
+
* @returns An IndexKey value below the given index.
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* const index = getIndexBelow('a2' as IndexKey)
|
|
133
|
+
* console.log(index) // 'a1'
|
|
134
|
+
* ```
|
|
135
|
+
* @public
|
|
95
136
|
*/
|
|
96
137
|
export function getIndexBelow(above: IndexKey | null | undefined = null) {
|
|
97
138
|
return generateKeysFn(null, above, 1)[0] as IndexKey
|
|
@@ -100,7 +141,13 @@ export function getIndexBelow(above: IndexKey | null | undefined = null) {
|
|
|
100
141
|
/**
|
|
101
142
|
* Get n number of indices, starting at an index.
|
|
102
143
|
* @param n - The number of indices to get.
|
|
103
|
-
* @param start -
|
|
144
|
+
* @param start - The index to start at.
|
|
145
|
+
* @returns An array containing the start index plus n additional IndexKey values.
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* const indices = getIndices(3, 'a1' as IndexKey)
|
|
149
|
+
* console.log(indices) // ['a1', 'a2', 'a3', 'a4']
|
|
150
|
+
* ```
|
|
104
151
|
* @public
|
|
105
152
|
*/
|
|
106
153
|
export function getIndices(n: number, start = 'a1' as IndexKey) {
|
|
@@ -111,7 +158,18 @@ export function getIndices(n: number, start = 'a1' as IndexKey) {
|
|
|
111
158
|
* Sort by index.
|
|
112
159
|
* @param a - An object with an index property.
|
|
113
160
|
* @param b - An object with an index property.
|
|
114
|
-
* @
|
|
161
|
+
* @returns A number indicating sort order (-1, 0, or 1).
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* const shapes = [
|
|
165
|
+
* { id: 'b', index: 'a2' as IndexKey },
|
|
166
|
+
* { id: 'a', index: 'a1' as IndexKey }
|
|
167
|
+
* ]
|
|
168
|
+
* const sorted = shapes.sort(sortByIndex)
|
|
169
|
+
* console.log(sorted) // [{ id: 'a', index: 'a1' }, { id: 'b', index: 'a2' }]
|
|
170
|
+
* ```
|
|
171
|
+
* @public
|
|
172
|
+
*/
|
|
115
173
|
export function sortByIndex<T extends { index: IndexKey }>(a: T, b: T) {
|
|
116
174
|
if (a.index < b.index) {
|
|
117
175
|
return -1
|