@xivdyetools/core 1.3.7

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 (103) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +400 -0
  3. package/dist/constants/index.d.ts +56 -0
  4. package/dist/constants/index.d.ts.map +1 -0
  5. package/dist/constants/index.js +103 -0
  6. package/dist/constants/index.js.map +1 -0
  7. package/dist/data/colors_xiv.json +3130 -0
  8. package/dist/data/locales/de.json +231 -0
  9. package/dist/data/locales/en.json +231 -0
  10. package/dist/data/locales/fr.json +231 -0
  11. package/dist/data/locales/ja.json +231 -0
  12. package/dist/data/locales/ko.json +233 -0
  13. package/dist/data/locales/zh.json +233 -0
  14. package/dist/data/presets.json +390 -0
  15. package/dist/index.d.ts +16 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +18 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/services/APIService.d.ts +246 -0
  20. package/dist/services/APIService.d.ts.map +1 -0
  21. package/dist/services/APIService.js +499 -0
  22. package/dist/services/APIService.js.map +1 -0
  23. package/dist/services/ColorService.d.ts +146 -0
  24. package/dist/services/ColorService.d.ts.map +1 -0
  25. package/dist/services/ColorService.js +209 -0
  26. package/dist/services/ColorService.js.map +1 -0
  27. package/dist/services/DyeService.d.ts +230 -0
  28. package/dist/services/DyeService.d.ts.map +1 -0
  29. package/dist/services/DyeService.js +326 -0
  30. package/dist/services/DyeService.js.map +1 -0
  31. package/dist/services/LocalizationService.d.ts +338 -0
  32. package/dist/services/LocalizationService.d.ts.map +1 -0
  33. package/dist/services/LocalizationService.js +449 -0
  34. package/dist/services/LocalizationService.js.map +1 -0
  35. package/dist/services/PaletteService.d.ts +137 -0
  36. package/dist/services/PaletteService.d.ts.map +1 -0
  37. package/dist/services/PaletteService.js +349 -0
  38. package/dist/services/PaletteService.js.map +1 -0
  39. package/dist/services/PresetService.d.ts +196 -0
  40. package/dist/services/PresetService.d.ts.map +1 -0
  41. package/dist/services/PresetService.js +261 -0
  42. package/dist/services/PresetService.js.map +1 -0
  43. package/dist/services/color/ColorAccessibility.d.ts +39 -0
  44. package/dist/services/color/ColorAccessibility.d.ts.map +1 -0
  45. package/dist/services/color/ColorAccessibility.js +71 -0
  46. package/dist/services/color/ColorAccessibility.js.map +1 -0
  47. package/dist/services/color/ColorConverter.d.ts +146 -0
  48. package/dist/services/color/ColorConverter.d.ts.map +1 -0
  49. package/dist/services/color/ColorConverter.js +393 -0
  50. package/dist/services/color/ColorConverter.js.map +1 -0
  51. package/dist/services/color/ColorManipulator.d.ts +36 -0
  52. package/dist/services/color/ColorManipulator.d.ts.map +1 -0
  53. package/dist/services/color/ColorManipulator.js +56 -0
  54. package/dist/services/color/ColorManipulator.js.map +1 -0
  55. package/dist/services/color/ColorblindnessSimulator.d.ts +35 -0
  56. package/dist/services/color/ColorblindnessSimulator.d.ts.map +1 -0
  57. package/dist/services/color/ColorblindnessSimulator.js +110 -0
  58. package/dist/services/color/ColorblindnessSimulator.js.map +1 -0
  59. package/dist/services/dye/DyeDatabase.d.ts +131 -0
  60. package/dist/services/dye/DyeDatabase.d.ts.map +1 -0
  61. package/dist/services/dye/DyeDatabase.js +367 -0
  62. package/dist/services/dye/DyeDatabase.js.map +1 -0
  63. package/dist/services/dye/DyeSearch.d.ts +55 -0
  64. package/dist/services/dye/DyeSearch.d.ts.map +1 -0
  65. package/dist/services/dye/DyeSearch.js +196 -0
  66. package/dist/services/dye/DyeSearch.js.map +1 -0
  67. package/dist/services/dye/HarmonyGenerator.d.ts +110 -0
  68. package/dist/services/dye/HarmonyGenerator.d.ts.map +1 -0
  69. package/dist/services/dye/HarmonyGenerator.js +221 -0
  70. package/dist/services/dye/HarmonyGenerator.js.map +1 -0
  71. package/dist/services/localization/LocaleLoader.d.ts +38 -0
  72. package/dist/services/localization/LocaleLoader.d.ts.map +1 -0
  73. package/dist/services/localization/LocaleLoader.js +83 -0
  74. package/dist/services/localization/LocaleLoader.js.map +1 -0
  75. package/dist/services/localization/LocaleRegistry.d.ts +73 -0
  76. package/dist/services/localization/LocaleRegistry.d.ts.map +1 -0
  77. package/dist/services/localization/LocaleRegistry.js +84 -0
  78. package/dist/services/localization/LocaleRegistry.js.map +1 -0
  79. package/dist/services/localization/TranslationProvider.d.ts +157 -0
  80. package/dist/services/localization/TranslationProvider.d.ts.map +1 -0
  81. package/dist/services/localization/TranslationProvider.js +289 -0
  82. package/dist/services/localization/TranslationProvider.js.map +1 -0
  83. package/dist/types/index.d.ts +409 -0
  84. package/dist/types/index.d.ts.map +1 -0
  85. package/dist/types/index.js +87 -0
  86. package/dist/types/index.js.map +1 -0
  87. package/dist/types/logger.d.ts +84 -0
  88. package/dist/types/logger.d.ts.map +1 -0
  89. package/dist/types/logger.js +54 -0
  90. package/dist/types/logger.js.map +1 -0
  91. package/dist/utils/index.d.ts +441 -0
  92. package/dist/utils/index.d.ts.map +1 -0
  93. package/dist/utils/index.js +577 -0
  94. package/dist/utils/index.js.map +1 -0
  95. package/dist/utils/kd-tree.d.ts +76 -0
  96. package/dist/utils/kd-tree.d.ts.map +1 -0
  97. package/dist/utils/kd-tree.js +195 -0
  98. package/dist/utils/kd-tree.js.map +1 -0
  99. package/dist/version.d.ts +11 -0
  100. package/dist/version.d.ts.map +1 -0
  101. package/dist/version.js +11 -0
  102. package/dist/version.js.map +1 -0
  103. package/package.json +84 -0
@@ -0,0 +1,577 @@
1
+ /**
2
+ * @xivdyetools/core - Shared Utilities
3
+ *
4
+ * Reusable utility functions (environment-agnostic)
5
+ *
6
+ * @module utils
7
+ */
8
+ import { RGB_MIN, RGB_MAX, HUE_MIN, HUE_MAX, SATURATION_MIN, SATURATION_MAX, VALUE_MIN, VALUE_MAX, PATTERNS, } from '../constants/index.js';
9
+ // ============================================================================
10
+ // Math Utilities
11
+ // ============================================================================
12
+ /**
13
+ * Clamp a number between min and max values
14
+ *
15
+ * @param value - The value to clamp
16
+ * @param min - Minimum allowed value
17
+ * @param max - Maximum allowed value
18
+ * @returns Clamped value between min and max
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * clamp(150, 0, 100) // Returns 100
23
+ * clamp(-10, 0, 100) // Returns 0
24
+ * clamp(50, 0, 100) // Returns 50
25
+ * ```
26
+ *
27
+ * Edge cases:
28
+ * - NaN values return NaN
29
+ * - Infinity is clamped to max
30
+ * - -Infinity is clamped to min
31
+ */
32
+ export function clamp(value, min, max) {
33
+ if (isNaN(value) || isNaN(min) || isNaN(max)) {
34
+ return NaN;
35
+ }
36
+ return Math.min(Math.max(value, min), max);
37
+ }
38
+ /**
39
+ * Linear interpolation between two values
40
+ *
41
+ * @param a - Start value
42
+ * @param b - End value
43
+ * @param t - Interpolation factor (0 = a, 1 = b, 0.5 = midpoint)
44
+ * @returns Interpolated value
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * lerp(0, 100, 0.5) // Returns 50
49
+ * lerp(0, 100, 0) // Returns 0
50
+ * lerp(0, 100, 1) // Returns 100
51
+ * lerp(10, 20, 0.25) // Returns 12.5
52
+ * ```
53
+ *
54
+ * Edge cases:
55
+ * - t can be outside [0, 1] for extrapolation
56
+ * - NaN values return NaN
57
+ * - Handles Infinity correctly
58
+ */
59
+ export function lerp(a, b, t) {
60
+ if (isNaN(a) || isNaN(b) || isNaN(t)) {
61
+ return NaN;
62
+ }
63
+ return a + (b - a) * t;
64
+ }
65
+ /**
66
+ * Round a number to a specific decimal place
67
+ *
68
+ * @param value - The number to round
69
+ * @param decimals - Number of decimal places (default: 0)
70
+ * @returns Rounded number
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * round(3.14159, 2) // Returns 3.14
75
+ * round(2.5) // Returns 3 (rounds to nearest integer)
76
+ * round(123.456, 1) // Returns 123.5
77
+ * round(-2.5) // Returns -2
78
+ * ```
79
+ *
80
+ * Edge cases:
81
+ * - NaN returns NaN
82
+ * - Infinity returns Infinity/-Infinity
83
+ * - Negative decimals round to left of decimal point
84
+ */
85
+ export function round(value, decimals = 0) {
86
+ if (isNaN(value)) {
87
+ return NaN;
88
+ }
89
+ if (!isFinite(value)) {
90
+ return value; // Preserve Infinity/-Infinity
91
+ }
92
+ const factor = Math.pow(10, decimals);
93
+ return Math.round(value * factor) / factor;
94
+ }
95
+ /**
96
+ * Calculate Euclidean distance between two points in 2D space
97
+ *
98
+ * @param x1 - X coordinate of first point
99
+ * @param y1 - Y coordinate of first point
100
+ * @param x2 - X coordinate of second point
101
+ * @param y2 - Y coordinate of second point
102
+ * @returns Distance between the two points (always >= 0)
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * distance(0, 0, 3, 4) // Returns 5 (Pythagorean theorem)
107
+ * distance(0, 0, 0, 0) // Returns 0 (same point)
108
+ * distance(1, 1, 4, 5) // Returns 5
109
+ * ```
110
+ *
111
+ * Edge cases:
112
+ * - NaN values return NaN
113
+ * - Infinity values may return Infinity
114
+ * - Always returns non-negative value
115
+ */
116
+ export function distance(x1, y1, x2, y2) {
117
+ if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) {
118
+ return NaN;
119
+ }
120
+ const dx = x2 - x1;
121
+ const dy = y2 - y1;
122
+ return Math.sqrt(dx * dx + dy * dy);
123
+ }
124
+ // ============================================================================
125
+ // Array Utilities
126
+ // ============================================================================
127
+ /**
128
+ * Get unique values from an array
129
+ *
130
+ * @param array - Input array (may contain duplicates)
131
+ * @returns New array with only unique values (preserves first occurrence order)
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * unique([1, 2, 2, 3, 1]) // Returns [1, 2, 3]
136
+ * unique(['a', 'b', 'a']) // Returns ['a', 'b']
137
+ * unique([]) // Returns []
138
+ * ```
139
+ *
140
+ * Edge cases:
141
+ * - Empty array returns empty array
142
+ * - Uses Set equality (NaN === NaN, +0 === -0)
143
+ * - Preserves object/array references
144
+ */
145
+ export function unique(array) {
146
+ return Array.from(new Set(array));
147
+ }
148
+ /**
149
+ * Group array items by a key function
150
+ *
151
+ * @param array - Input array to group
152
+ * @param keyFn - Function that extracts grouping key from each item
153
+ * @returns Object with keys as groups and values as arrays of items
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * const items = [
158
+ * { type: 'fruit', name: 'apple' },
159
+ * { type: 'fruit', name: 'banana' },
160
+ * { type: 'vegetable', name: 'carrot' }
161
+ * ];
162
+ * groupBy(items, item => item.type)
163
+ * // Returns { fruit: [...], vegetable: [...] }
164
+ * ```
165
+ *
166
+ * Edge cases:
167
+ * - Empty array returns empty object
168
+ * - Handles undefined/null keys as string keys
169
+ */
170
+ export function groupBy(array, keyFn) {
171
+ return array.reduce((acc, item) => {
172
+ const key = keyFn(item);
173
+ if (!acc[key]) {
174
+ acc[key] = [];
175
+ }
176
+ acc[key].push(item);
177
+ return acc;
178
+ }, {});
179
+ }
180
+ /**
181
+ * Sort array by property value
182
+ *
183
+ * @param array - Input array to sort
184
+ * @param property - Property key to sort by
185
+ * @param order - Sort order ('asc' or 'desc', default: 'asc')
186
+ * @returns New sorted array (does not mutate original)
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * const items = [{ age: 30 }, { age: 20 }, { age: 25 }];
191
+ * sortByProperty(items, 'age') // Sorted by age ascending
192
+ * sortByProperty(items, 'age', 'desc') // Sorted by age descending
193
+ * ```
194
+ *
195
+ * Edge cases:
196
+ * - Returns shallow copy of array
197
+ * - Handles undefined properties (sorted to end)
198
+ * - Stable sort (preserves relative order of equal elements)
199
+ */
200
+ export function sortByProperty(array, property, order = 'asc') {
201
+ return [...array].sort((a, b) => {
202
+ const aVal = a[property];
203
+ const bVal = b[property];
204
+ const comparison = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
205
+ return order === 'asc' ? comparison : -comparison;
206
+ });
207
+ }
208
+ /**
209
+ * Filter array items, removing null and undefined values
210
+ *
211
+ * @param array - Input array with possibly null/undefined items
212
+ * @returns New array with null and undefined values removed
213
+ *
214
+ * @example
215
+ * ```typescript
216
+ * filterNulls([1, null, 2, undefined, 3]) // Returns [1, 2, 3]
217
+ * filterNulls([null, undefined]) // Returns []
218
+ * filterNulls([0, false, '']) // Returns [0, false, ''] (keeps falsy values)
219
+ * ```
220
+ *
221
+ * Edge cases:
222
+ * - Keeps falsy values (0, false, empty string)
223
+ * - Type guard ensures return type doesn't include null|undefined
224
+ */
225
+ export function filterNulls(array) {
226
+ return array.filter((item) => item !== null && item !== undefined);
227
+ }
228
+ // ============================================================================
229
+ // Validation Utilities
230
+ // ============================================================================
231
+ /**
232
+ * Validate a hexadecimal color string
233
+ *
234
+ * @param hex - Hex color string to validate
235
+ * @returns true if valid hex color, false otherwise
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * isValidHexColor('#FF0000') // Returns true
240
+ * isValidHexColor('#F00') // Returns true (shorthand)
241
+ * isValidHexColor('FF0000') // Returns false (missing #)
242
+ * isValidHexColor('#GGGGGG') // Returns false (invalid characters)
243
+ * isValidHexColor('') // Returns false
244
+ * ```
245
+ *
246
+ * Accepts:
247
+ * - Full format: #RRGGBB (e.g., #FF0000)
248
+ * - Shorthand format: #RGB (e.g., #F00)
249
+ * - Case insensitive (A-F or a-f)
250
+ */
251
+ export function isValidHexColor(hex) {
252
+ if (typeof hex !== 'string') {
253
+ return false;
254
+ }
255
+ return PATTERNS.HEX_COLOR.test(hex);
256
+ }
257
+ /**
258
+ * Validate RGB color values
259
+ *
260
+ * @param r - Red value
261
+ * @param g - Green value
262
+ * @param b - Blue value
263
+ * @returns true if all values are valid (0-255, finite, not NaN), false otherwise
264
+ *
265
+ * @example
266
+ * ```typescript
267
+ * isValidRGB(255, 0, 0) // Returns true
268
+ * isValidRGB(0, 128, 255) // Returns true
269
+ * isValidRGB(256, 0, 0) // Returns false (r > 255)
270
+ * isValidRGB(-1, 0, 0) // Returns false (r < 0)
271
+ * isValidRGB(NaN, 0, 0) // Returns false
272
+ * isValidRGB(Infinity, 0, 0) // Returns false
273
+ * ```
274
+ *
275
+ * Valid range: 0-255 (inclusive) for all channels
276
+ */
277
+ export function isValidRGB(r, g, b) {
278
+ return (Number.isFinite(r) &&
279
+ Number.isFinite(g) &&
280
+ Number.isFinite(b) &&
281
+ r >= RGB_MIN &&
282
+ r <= RGB_MAX &&
283
+ g >= RGB_MIN &&
284
+ g <= RGB_MAX &&
285
+ b >= RGB_MIN &&
286
+ b <= RGB_MAX);
287
+ }
288
+ /**
289
+ * Validate HSV color values
290
+ *
291
+ * @param h - Hue value (0-360)
292
+ * @param s - Saturation value (0-100)
293
+ * @param v - Value/brightness (0-100)
294
+ * @returns true if all values are valid (finite, not NaN, within ranges), false otherwise
295
+ *
296
+ * @example
297
+ * ```typescript
298
+ * isValidHSV(180, 50, 100) // Returns true
299
+ * isValidHSV(0, 0, 0) // Returns true
300
+ * isValidHSV(360, 100, 100) // Returns true (edge of range)
301
+ * isValidHSV(361, 50, 50) // Returns false (h > 360)
302
+ * isValidHSV(180, -1, 50) // Returns false (s < 0)
303
+ * isValidHSV(NaN, 50, 50) // Returns false
304
+ * ```
305
+ *
306
+ * Valid ranges:
307
+ * - Hue: 0-360 (degrees)
308
+ * - Saturation: 0-100 (percent)
309
+ * - Value: 0-100 (percent)
310
+ */
311
+ export function isValidHSV(h, s, v) {
312
+ return (Number.isFinite(h) &&
313
+ Number.isFinite(s) &&
314
+ Number.isFinite(v) &&
315
+ h >= HUE_MIN &&
316
+ h <= HUE_MAX &&
317
+ s >= SATURATION_MIN &&
318
+ s <= SATURATION_MAX &&
319
+ v >= VALUE_MIN &&
320
+ v <= VALUE_MAX);
321
+ }
322
+ // ============================================================================
323
+ // Type Guards
324
+ // ============================================================================
325
+ /**
326
+ * Type guard: Check if a value is a string
327
+ *
328
+ * @param value - Value to check
329
+ * @returns true if value is a string, false otherwise
330
+ *
331
+ * @example
332
+ * ```typescript
333
+ * isString('hello') // Returns true
334
+ * isString('') // Returns true (empty string)
335
+ * isString(123) // Returns false
336
+ * isString(new String('')) // Returns false (String object, not primitive)
337
+ * ```
338
+ */
339
+ export function isString(value) {
340
+ return typeof value === 'string';
341
+ }
342
+ /**
343
+ * Type guard: Check if a value is a finite number (excludes NaN and Infinity)
344
+ *
345
+ * @param value - Value to check
346
+ * @returns true if value is a number and finite (not NaN, not Infinity), false otherwise
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * isNumber(42) // Returns true
351
+ * isNumber(0) // Returns true
352
+ * isNumber(-3.14) // Returns true
353
+ * isNumber(NaN) // Returns false
354
+ * isNumber(Infinity) // Returns false
355
+ * isNumber('123') // Returns false (string)
356
+ * isNumber(new Number(5)) // Returns false (Number object)
357
+ * ```
358
+ *
359
+ * Note: This excludes NaN and Infinity for safer numeric operations
360
+ */
361
+ export function isNumber(value) {
362
+ return typeof value === 'number' && Number.isFinite(value);
363
+ }
364
+ /**
365
+ * Type guard: Check if a value is an array
366
+ *
367
+ * @param value - Value to check
368
+ * @returns true if value is an array, false otherwise
369
+ *
370
+ * @example
371
+ * ```typescript
372
+ * isArray([1, 2, 3]) // Returns true
373
+ * isArray([]) // Returns true
374
+ * isArray('not array') // Returns false
375
+ * isArray({ length: 0 }) // Returns false (array-like object)
376
+ * ```
377
+ */
378
+ export function isArray(value) {
379
+ return Array.isArray(value);
380
+ }
381
+ /**
382
+ * Type guard: Check if a value is a plain object (not array, not null)
383
+ *
384
+ * @param value - Value to check
385
+ * @returns true if value is a plain object, false otherwise
386
+ *
387
+ * @example
388
+ * ```typescript
389
+ * isObject({ a: 1 }) // Returns true
390
+ * isObject({}) // Returns true
391
+ * isObject([]) // Returns false (array)
392
+ * isObject(null) // Returns false
393
+ * isObject(new Date()) // Returns true (object instance)
394
+ * ```
395
+ */
396
+ export function isObject(value) {
397
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
398
+ }
399
+ /**
400
+ * Type guard: Check if a value is null or undefined
401
+ *
402
+ * @param value - Value to check
403
+ * @returns true if value is null or undefined, false otherwise
404
+ *
405
+ * @example
406
+ * ```typescript
407
+ * isNullish(null) // Returns true
408
+ * isNullish(undefined) // Returns true
409
+ * isNullish(0) // Returns false
410
+ * isNullish('') // Returns false
411
+ * isNullish(false) // Returns false
412
+ * ```
413
+ *
414
+ * Note: More precise than falsy check - only null/undefined, not 0/false/''
415
+ */
416
+ export function isNullish(value) {
417
+ return value === null || value === undefined;
418
+ }
419
+ // ============================================================================
420
+ // Async Utilities
421
+ // ============================================================================
422
+ /**
423
+ * Sleep for a specified duration (async delay)
424
+ *
425
+ * @param ms - Milliseconds to sleep (must be non-negative)
426
+ * @returns Promise that resolves after the specified delay
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * await sleep(1000); // Wait 1 second
431
+ * await sleep(0); // Immediate next tick
432
+ *
433
+ * // Use with async/await
434
+ * async function delayed() {
435
+ * console.log('Start');
436
+ * await sleep(2000);
437
+ * console.log('After 2 seconds');
438
+ * }
439
+ * ```
440
+ *
441
+ * Note: Negative values are clamped to 0 (immediate resolution)
442
+ */
443
+ export function sleep(ms) {
444
+ const delay = Math.max(0, ms); // Clamp to non-negative
445
+ return new Promise((resolve) => setTimeout(resolve, delay));
446
+ }
447
+ /**
448
+ * Check if an error is an AbortError (from AbortController timeout)
449
+ *
450
+ * @param error - Error to check
451
+ * @returns true if error is an AbortError, false otherwise
452
+ *
453
+ * @example
454
+ * ```typescript
455
+ * try {
456
+ * await fetch(url, { signal: controller.signal });
457
+ * } catch (error) {
458
+ * if (isAbortError(error)) {
459
+ * console.log('Request timed out or was aborted');
460
+ * }
461
+ * }
462
+ * ```
463
+ */
464
+ export function isAbortError(error) {
465
+ return (error instanceof Error &&
466
+ (error.name === 'AbortError' ||
467
+ error.name === 'TimeoutError' ||
468
+ (error instanceof DOMException && error.code === DOMException.ABORT_ERR)));
469
+ }
470
+ /**
471
+ * Retry a function multiple times with exponential backoff
472
+ *
473
+ * @param fn - Async function to retry
474
+ * @param maxAttempts - Maximum number of attempts (default: 3, min: 1)
475
+ * @param delayMs - Initial delay in milliseconds (default: 1000, doubles each retry)
476
+ * @returns Promise resolving to function result
477
+ * @throws Last error if all attempts fail
478
+ *
479
+ * @example
480
+ * ```typescript
481
+ * // Retry API call up to 3 times
482
+ * const data = await retry(
483
+ * () => fetch('https://api.example.com/data').then(r => r.json()),
484
+ * 3,
485
+ * 1000
486
+ * );
487
+ * // Delays: 0ms (try 1), 1000ms (try 2), 2000ms (try 3)
488
+ *
489
+ * // Custom retry logic
490
+ * const result = await retry(
491
+ * async () => {
492
+ * const response = await riskyOperation();
493
+ * if (!response.ok) throw new Error('Not OK');
494
+ * return response;
495
+ * },
496
+ * 5,
497
+ * 500
498
+ * );
499
+ * ```
500
+ *
501
+ * Backoff schedule:
502
+ * - Attempt 1: Immediate (no delay)
503
+ * - Attempt 2: delayMs * 2^0 = delayMs
504
+ * - Attempt 3: delayMs * 2^1 = delayMs * 2
505
+ * - Attempt 4: delayMs * 2^2 = delayMs * 4
506
+ * - etc.
507
+ *
508
+ * @remarks
509
+ * Retries on all errors including AbortError (timeout), allowing
510
+ * transient network issues to be recovered from.
511
+ */
512
+ export async function retry(fn, maxAttempts = 3, delayMs = 1000) {
513
+ const attempts = Math.max(1, Math.floor(maxAttempts)); // Ensure at least 1 attempt
514
+ let lastError = null;
515
+ for (let i = 0; i < attempts; i++) {
516
+ try {
517
+ return await fn();
518
+ }
519
+ catch (error) {
520
+ lastError = error instanceof Error ? error : new Error(String(error));
521
+ // Log timeout errors specifically for debugging
522
+ if (isAbortError(error)) {
523
+ console.warn(`Request timed out (attempt ${i + 1}/${attempts})`);
524
+ }
525
+ if (i < attempts - 1) {
526
+ await sleep(delayMs * Math.pow(2, i)); // Exponential backoff
527
+ }
528
+ }
529
+ }
530
+ throw lastError;
531
+ }
532
+ // ============================================================================
533
+ // Data Integrity Utilities
534
+ // ============================================================================
535
+ /**
536
+ * Generate a simple checksum for data integrity checking
537
+ *
538
+ * Uses a non-cryptographic hash function (djb2-like algorithm)
539
+ * Suitable for cache validation and detecting data corruption
540
+ *
541
+ * @param data - Any JSON-serializable data
542
+ * @returns Base-36 encoded hash string
543
+ * @throws Error if data contains circular references or cannot be stringified
544
+ *
545
+ * @example
546
+ * ```typescript
547
+ * const checksum1 = generateChecksum({ a: 1, b: 2 }); // "abc123"
548
+ * const checksum2 = generateChecksum({ a: 1, b: 2 }); // "abc123" (same)
549
+ * const checksum3 = generateChecksum({ a: 1, b: 3 }); // "xyz789" (different)
550
+ *
551
+ * // Use for cache validation
552
+ * const cachedData = { checksum: "abc123", data: {...} };
553
+ * const computedChecksum = generateChecksum(cachedData.data);
554
+ * if (computedChecksum !== cachedData.checksum) {
555
+ * console.warn('Cache corruption detected!');
556
+ * }
557
+ * ```
558
+ *
559
+ * Important notes:
560
+ * - NOT cryptographically secure (do not use for security)
561
+ * - Deterministic: same input always produces same output
562
+ * - Fast and lightweight
563
+ * - Collision-resistant for typical cache validation use cases
564
+ * - Throws on circular references (by JSON.stringify)
565
+ * - Per Issue #7: Uses |0 to properly convert to 32-bit integer
566
+ */
567
+ export function generateChecksum(data) {
568
+ const str = JSON.stringify(data); // Throws on circular references
569
+ let hash = 0;
570
+ for (let i = 0; i < str.length; i++) {
571
+ const char = str.charCodeAt(i);
572
+ hash = (hash << 5) - hash + char;
573
+ hash = hash | 0; // Per Issue #7: Convert to 32-bit signed integer (|0 is idiomatic)
574
+ }
575
+ return Math.abs(hash).toString(36);
576
+ }
577
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,OAAO,EACP,OAAO,EACP,OAAO,EACP,OAAO,EACP,cAAc,EACd,cAAc,EACd,SAAS,EACT,SAAS,EACT,QAAQ,GACT,MAAM,uBAAuB,CAAC;AAE/B,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,KAAK,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IAC3D,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7C,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,IAAI,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IAClD,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,KAAK,CAAC,KAAa,EAAE,WAAmB,CAAC;IACvD,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACjB,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC,CAAC,8BAA8B;IAC9C,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IACtC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,EAAU;IACrE,IAAI,KAAK,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACrD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACnB,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACnB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,+EAA+E;AAC/E,kBAAkB;AAClB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,MAAM,CAAI,KAAU;IAClC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,OAAO,CACrB,KAAU,EACV,KAAqB;IAErB,OAAO,KAAK,CAAC,MAAM,CACjB,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QACZ,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACd,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;QAChB,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,GAAG,CAAC;IACb,CAAC,EACD,EAAoB,CACrB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAU,EACV,QAAiB,EACjB,QAAwB,KAAK;IAE7B,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1D,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,WAAW,CAAI,KAA+B;IAC5D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAa,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,CAAC,CAAC;AAChF,CAAC;AAED,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,UAAU,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IACxD,OAAO,CACL,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClB,CAAC,IAAI,OAAO;QACZ,CAAC,IAAI,OAAO;QACZ,CAAC,IAAI,OAAO;QACZ,CAAC,IAAI,OAAO;QACZ,CAAC,IAAI,OAAO;QACZ,CAAC,IAAI,OAAO,CACb,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,UAAU,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IACxD,OAAO,CACL,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClB,CAAC,IAAI,OAAO;QACZ,CAAC,IAAI,OAAO;QACZ,CAAC,IAAI,cAAc;QACnB,CAAC,IAAI,cAAc;QACnB,CAAC,IAAI,SAAS;QACd,CAAC,IAAI,SAAS,CACf,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,OAAO,CAAc,KAAc;IACjD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC;AAC/C,CAAC;AAED,+EAA+E;AAC/E,kBAAkB;AAClB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,wBAAwB;IACvD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,CACL,KAAK,YAAY,KAAK;QACtB,CAAC,KAAK,CAAC,IAAI,KAAK,YAAY;YAC1B,KAAK,CAAC,IAAI,KAAK,cAAc;YAC7B,CAAC,KAAK,YAAY,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC,SAAS,CAAC,CAAC,CAC5E,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,EAAoB,EACpB,cAAsB,CAAC,EACvB,UAAkB,IAAI;IAEtB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,4BAA4B;IACnF,IAAI,SAAS,GAAiB,IAAI,CAAC;IAEnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAEtE,gDAAgD;YAChD,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxB,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,GAAG,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC;YACnE,CAAC;YAED,IAAI,CAAC,GAAG,QAAQ,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;YAC/D,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,SAAS,CAAC;AAClB,CAAC;AAED,+EAA+E;AAC/E,2BAA2B;AAC3B,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAa;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,gCAAgC;IAClE,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;QACjC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,mEAAmE;IACtF,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * k-d Tree implementation for 3D color space (RGB)
3
+ * Per P-7: Spatial indexing for fast nearest neighbor search
4
+ *
5
+ * k-d tree provides O(log n) average case for nearest neighbor queries
6
+ * vs O(n) for linear search, significant improvement for color matching
7
+ */
8
+ /**
9
+ * Point in 3D space (RGB color)
10
+ */
11
+ export interface Point3D {
12
+ x: number;
13
+ y: number;
14
+ z: number;
15
+ data?: unknown;
16
+ }
17
+ /**
18
+ * k-d Tree for 3D color space (RGB)
19
+ * Optimized for nearest neighbor search in color matching
20
+ */
21
+ export declare class KDTree {
22
+ private root;
23
+ private size;
24
+ /**
25
+ * Build k-d tree from array of points
26
+ * @param points - Array of 3D points (RGB colors)
27
+ */
28
+ constructor(points: Point3D[]);
29
+ /**
30
+ * Recursively build k-d tree using index arrays
31
+ * CORE-PERF-002: Optimized to reduce temporary array allocations
32
+ * @param points - Full points array (never copied)
33
+ * @param indices - Indices into points array for current subtree
34
+ * @param depth - Current depth (determines splitting dimension)
35
+ */
36
+ private buildTreeOptimized;
37
+ /**
38
+ * Calculate Euclidean distance between two points
39
+ */
40
+ private distance;
41
+ /**
42
+ * Find nearest neighbor to target point
43
+ * @param target - Target point to search for
44
+ * @param excludeData - Optional function to exclude certain data points
45
+ * @returns Nearest point or null if tree is empty
46
+ */
47
+ nearestNeighbor(target: Point3D, excludeData?: (data: unknown) => boolean): Point3D | null;
48
+ /**
49
+ * Recursive nearest neighbor search
50
+ */
51
+ private searchNearest;
52
+ /**
53
+ * Find all points within a distance threshold
54
+ * @param target - Target point
55
+ * @param maxDistance - Maximum distance
56
+ * @param excludeData - Optional function to exclude certain data points
57
+ * @returns Array of points within distance, sorted by distance
58
+ */
59
+ pointsWithinDistance(target: Point3D, maxDistance: number, excludeData?: (data: unknown) => boolean): Array<{
60
+ point: Point3D;
61
+ distance: number;
62
+ }>;
63
+ /**
64
+ * Recursive search for points within distance
65
+ */
66
+ private searchWithinDistance;
67
+ /**
68
+ * Get tree size
69
+ */
70
+ getSize(): number;
71
+ /**
72
+ * Check if tree is empty
73
+ */
74
+ isEmpty(): boolean;
75
+ }
76
+ //# sourceMappingURL=kd-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kd-tree.d.ts","sourceRoot":"","sources":["../../src/utils/kd-tree.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAiBD;;;GAGG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,IAAI,CAAa;IAEzB;;;OAGG;gBACS,MAAM,EAAE,OAAO,EAAE;IAW7B;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAoC1B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAOhB;;;;;OAKG;IACH,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,GAAG,IAAI;IAS1F;;OAEG;IACH,OAAO,CAAC,aAAa;IA8CrB;;;;;;OAMG;IACH,oBAAoB,CAClB,MAAM,EAAE,OAAO,EACf,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,GACvC,KAAK,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAa9C;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA4C5B;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,OAAO,IAAI,OAAO;CAGnB"}