@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.
Files changed (160) hide show
  1. package/dist-cjs/index.d.ts +1350 -80
  2. package/dist-cjs/index.js +5 -5
  3. package/dist-cjs/lib/ExecutionQueue.js +79 -0
  4. package/dist-cjs/lib/ExecutionQueue.js.map +2 -2
  5. package/dist-cjs/lib/PerformanceTracker.js +43 -0
  6. package/dist-cjs/lib/PerformanceTracker.js.map +2 -2
  7. package/dist-cjs/lib/array.js +3 -1
  8. package/dist-cjs/lib/array.js.map +2 -2
  9. package/dist-cjs/lib/bind.js.map +2 -2
  10. package/dist-cjs/lib/cache.js +27 -5
  11. package/dist-cjs/lib/cache.js.map +2 -2
  12. package/dist-cjs/lib/control.js +12 -0
  13. package/dist-cjs/lib/control.js.map +2 -2
  14. package/dist-cjs/lib/debounce.js.map +2 -2
  15. package/dist-cjs/lib/error.js.map +2 -2
  16. package/dist-cjs/lib/file.js +76 -11
  17. package/dist-cjs/lib/file.js.map +2 -2
  18. package/dist-cjs/lib/function.js.map +2 -2
  19. package/dist-cjs/lib/hash.js.map +2 -2
  20. package/dist-cjs/lib/id.js.map +2 -2
  21. package/dist-cjs/lib/iterable.js.map +2 -2
  22. package/dist-cjs/lib/json-value.js.map +1 -1
  23. package/dist-cjs/lib/media/apng.js.map +2 -2
  24. package/dist-cjs/lib/media/avif.js.map +2 -2
  25. package/dist-cjs/lib/media/gif.js.map +2 -2
  26. package/dist-cjs/lib/media/media.js +130 -4
  27. package/dist-cjs/lib/media/media.js.map +2 -2
  28. package/dist-cjs/lib/media/png.js +141 -0
  29. package/dist-cjs/lib/media/png.js.map +2 -2
  30. package/dist-cjs/lib/media/webp.js +1 -0
  31. package/dist-cjs/lib/media/webp.js.map +2 -2
  32. package/dist-cjs/lib/network.js.map +2 -2
  33. package/dist-cjs/lib/number.js.map +2 -2
  34. package/dist-cjs/lib/object.js +1 -1
  35. package/dist-cjs/lib/object.js.map +2 -2
  36. package/dist-cjs/lib/perf.js.map +2 -2
  37. package/dist-cjs/lib/reordering.js.map +2 -2
  38. package/dist-cjs/lib/retry.js.map +2 -2
  39. package/dist-cjs/lib/sort.js.map +2 -2
  40. package/dist-cjs/lib/storage.js.map +2 -2
  41. package/dist-cjs/lib/stringEnum.js.map +2 -2
  42. package/dist-cjs/lib/throttle.js.map +2 -2
  43. package/dist-cjs/lib/timers.js +103 -4
  44. package/dist-cjs/lib/timers.js.map +2 -2
  45. package/dist-cjs/lib/types.js.map +1 -1
  46. package/dist-cjs/lib/url.js.map +2 -2
  47. package/dist-cjs/lib/value.js.map +2 -2
  48. package/dist-cjs/lib/version.js.map +2 -2
  49. package/dist-cjs/lib/warn.js.map +2 -2
  50. package/dist-esm/index.d.mts +1350 -80
  51. package/dist-esm/index.mjs +1 -1
  52. package/dist-esm/lib/ExecutionQueue.mjs +79 -0
  53. package/dist-esm/lib/ExecutionQueue.mjs.map +2 -2
  54. package/dist-esm/lib/PerformanceTracker.mjs +43 -0
  55. package/dist-esm/lib/PerformanceTracker.mjs.map +2 -2
  56. package/dist-esm/lib/array.mjs +3 -1
  57. package/dist-esm/lib/array.mjs.map +2 -2
  58. package/dist-esm/lib/bind.mjs.map +2 -2
  59. package/dist-esm/lib/cache.mjs +27 -5
  60. package/dist-esm/lib/cache.mjs.map +2 -2
  61. package/dist-esm/lib/control.mjs +12 -0
  62. package/dist-esm/lib/control.mjs.map +2 -2
  63. package/dist-esm/lib/debounce.mjs.map +2 -2
  64. package/dist-esm/lib/error.mjs.map +2 -2
  65. package/dist-esm/lib/file.mjs +76 -11
  66. package/dist-esm/lib/file.mjs.map +2 -2
  67. package/dist-esm/lib/function.mjs.map +2 -2
  68. package/dist-esm/lib/hash.mjs.map +2 -2
  69. package/dist-esm/lib/id.mjs.map +2 -2
  70. package/dist-esm/lib/iterable.mjs.map +2 -2
  71. package/dist-esm/lib/media/apng.mjs.map +2 -2
  72. package/dist-esm/lib/media/avif.mjs.map +2 -2
  73. package/dist-esm/lib/media/gif.mjs.map +2 -2
  74. package/dist-esm/lib/media/media.mjs +130 -4
  75. package/dist-esm/lib/media/media.mjs.map +2 -2
  76. package/dist-esm/lib/media/png.mjs +141 -0
  77. package/dist-esm/lib/media/png.mjs.map +2 -2
  78. package/dist-esm/lib/media/webp.mjs +1 -0
  79. package/dist-esm/lib/media/webp.mjs.map +2 -2
  80. package/dist-esm/lib/network.mjs.map +2 -2
  81. package/dist-esm/lib/number.mjs.map +2 -2
  82. package/dist-esm/lib/object.mjs.map +2 -2
  83. package/dist-esm/lib/perf.mjs.map +2 -2
  84. package/dist-esm/lib/reordering.mjs.map +2 -2
  85. package/dist-esm/lib/retry.mjs.map +2 -2
  86. package/dist-esm/lib/sort.mjs.map +2 -2
  87. package/dist-esm/lib/storage.mjs.map +2 -2
  88. package/dist-esm/lib/stringEnum.mjs.map +2 -2
  89. package/dist-esm/lib/throttle.mjs.map +2 -2
  90. package/dist-esm/lib/timers.mjs +103 -4
  91. package/dist-esm/lib/timers.mjs.map +2 -2
  92. package/dist-esm/lib/url.mjs.map +2 -2
  93. package/dist-esm/lib/value.mjs.map +2 -2
  94. package/dist-esm/lib/version.mjs.map +2 -2
  95. package/dist-esm/lib/warn.mjs.map +2 -2
  96. package/package.json +1 -1
  97. package/src/lib/ExecutionQueue.test.ts +162 -20
  98. package/src/lib/ExecutionQueue.ts +110 -1
  99. package/src/lib/PerformanceTracker.test.ts +124 -0
  100. package/src/lib/PerformanceTracker.ts +63 -1
  101. package/src/lib/array.test.ts +263 -1
  102. package/src/lib/array.ts +183 -14
  103. package/src/lib/bind.test.ts +47 -0
  104. package/src/lib/bind.ts +69 -4
  105. package/src/lib/cache.test.ts +73 -0
  106. package/src/lib/cache.ts +47 -6
  107. package/src/lib/control.test.ts +50 -0
  108. package/src/lib/control.ts +198 -9
  109. package/src/lib/debounce.ts +28 -3
  110. package/src/lib/error.test.ts +60 -0
  111. package/src/lib/error.ts +27 -1
  112. package/src/lib/file.test.ts +49 -0
  113. package/src/lib/file.ts +117 -12
  114. package/src/lib/function.ts +11 -0
  115. package/src/lib/hash.test.ts +99 -0
  116. package/src/lib/hash.ts +69 -2
  117. package/src/lib/id.test.ts +32 -0
  118. package/src/lib/id.ts +53 -5
  119. package/src/lib/iterable.test.ts +25 -0
  120. package/src/lib/iterable.ts +4 -5
  121. package/src/lib/json-value.ts +71 -4
  122. package/src/lib/media/apng.test.ts +67 -0
  123. package/src/lib/media/apng.ts +38 -21
  124. package/src/lib/media/avif.test.ts +26 -0
  125. package/src/lib/media/avif.ts +34 -0
  126. package/src/lib/media/gif.test.ts +52 -0
  127. package/src/lib/media/gif.ts +25 -2
  128. package/src/lib/media/media.test.ts +58 -0
  129. package/src/lib/media/media.ts +220 -11
  130. package/src/lib/media/png.ts +162 -1
  131. package/src/lib/media/webp.test.ts +81 -0
  132. package/src/lib/media/webp.ts +33 -1
  133. package/src/lib/network.test.ts +38 -0
  134. package/src/lib/network.ts +6 -0
  135. package/src/lib/number.test.ts +74 -0
  136. package/src/lib/number.ts +29 -5
  137. package/src/lib/object.test.ts +236 -0
  138. package/src/lib/object.ts +194 -14
  139. package/src/lib/perf.ts +75 -3
  140. package/src/lib/reordering.test.ts +168 -0
  141. package/src/lib/reordering.ts +62 -4
  142. package/src/lib/retry.test.ts +77 -0
  143. package/src/lib/retry.ts +47 -1
  144. package/src/lib/sort.test.ts +36 -0
  145. package/src/lib/sort.ts +22 -1
  146. package/src/lib/storage.test.ts +130 -0
  147. package/src/lib/storage.tsx +54 -8
  148. package/src/lib/stringEnum.ts +20 -1
  149. package/src/lib/throttle.ts +46 -8
  150. package/src/lib/timers.test.ts +75 -0
  151. package/src/lib/timers.ts +124 -5
  152. package/src/lib/types.ts +126 -4
  153. package/src/lib/url.test.ts +44 -0
  154. package/src/lib/url.ts +40 -1
  155. package/src/lib/value.test.ts +102 -0
  156. package/src/lib/value.ts +67 -3
  157. package/src/lib/version.test.ts +494 -56
  158. package/src/lib/version.ts +36 -1
  159. package/src/lib/warn.test.ts +64 -0
  160. 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
- /** @internal */
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
- /** @internal */
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
- * keys.
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
- * @returns a new object with only the entries that pass the predicate
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 one object map to another.
111
- * @returns a new object with the entries mapped
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
- /** @internal */
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
- /** @internal */
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
- /** @internal */
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
- /** @internal */
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
- /** @internal */
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
- /** @internal */
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
- /** @internal */
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
- /** @internal */
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
+ })
@@ -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
- /** @internal */
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
- * @public
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 - The index to start at.
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
- * @public */
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