@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,499 @@
1
+ /**
2
+ * @xivdyetools/core - API Service
3
+ *
4
+ * Universalis API integration with caching and debouncing
5
+ * Environment-agnostic with pluggable cache backends
6
+ *
7
+ * @module services/APIService
8
+ */
9
+ import { ErrorCode, AppError, NoOpLogger } from '../types/index.js';
10
+ import { UNIVERSALIS_API_BASE, UNIVERSALIS_API_TIMEOUT, UNIVERSALIS_API_RETRY_COUNT, UNIVERSALIS_API_RETRY_DELAY, API_CACHE_TTL, API_RATE_LIMIT_DELAY, API_CACHE_VERSION, API_MAX_RESPONSE_SIZE, } from '../constants/index.js';
11
+ import { retry, sleep, generateChecksum } from '../utils/index.js';
12
+ /**
13
+ * Default fetch client using global fetch API
14
+ */
15
+ export class DefaultFetchClient {
16
+ async fetch(url, options) {
17
+ return fetch(url, options);
18
+ }
19
+ }
20
+ /**
21
+ * Default rate limiter with configurable minimum delay between requests
22
+ */
23
+ export class DefaultRateLimiter {
24
+ /**
25
+ * Constructor with optional minimum delay
26
+ * @param minDelay Minimum milliseconds between requests (default: API_RATE_LIMIT_DELAY)
27
+ */
28
+ constructor(minDelay = API_RATE_LIMIT_DELAY) {
29
+ this.minDelay = minDelay;
30
+ this.lastRequestTime = 0;
31
+ }
32
+ async waitIfNeeded() {
33
+ const timeSinceLastRequest = Date.now() - this.lastRequestTime;
34
+ if (timeSinceLastRequest < this.minDelay) {
35
+ await sleep(this.minDelay - timeSinceLastRequest);
36
+ }
37
+ }
38
+ recordRequest() {
39
+ this.lastRequestTime = Date.now();
40
+ }
41
+ }
42
+ /**
43
+ * In-memory cache backend (default, no persistence)
44
+ */
45
+ export class MemoryCacheBackend {
46
+ constructor() {
47
+ this.cache = new Map();
48
+ }
49
+ get(key) {
50
+ return this.cache.get(key) || null;
51
+ }
52
+ set(key, value) {
53
+ this.cache.set(key, value);
54
+ }
55
+ delete(key) {
56
+ this.cache.delete(key);
57
+ }
58
+ clear() {
59
+ this.cache.clear();
60
+ }
61
+ keys() {
62
+ return Array.from(this.cache.keys());
63
+ }
64
+ }
65
+ /**
66
+ * Service for Universalis API integration
67
+ * Handles price data fetching with caching and debouncing
68
+ *
69
+ * Refactored for testability: Supports dependency injection of cache, fetch client, and rate limiter
70
+ *
71
+ * @example
72
+ * // With default implementations (memory cache, global fetch, standard rate limiting)
73
+ * const apiService = new APIService();
74
+ *
75
+ * // With custom cache backend (e.g., Redis)
76
+ * const redisCache = new RedisCacheBackend(redisClient);
77
+ * const apiService = new APIService(redisCache);
78
+ *
79
+ * // With custom fetch client for testing
80
+ * const mockFetch = new MockFetchClient();
81
+ * const apiService = new APIService(undefined, mockFetch);
82
+ *
83
+ * // With custom rate limiter (e.g., no rate limiting for tests)
84
+ * const noRateLimit = new NoOpRateLimiter();
85
+ * const apiService = new APIService(undefined, undefined, noRateLimit);
86
+ *
87
+ * // Fetch price data
88
+ * const priceData = await apiService.getPriceData(itemID, worldID, dataCenterID);
89
+ *
90
+ * // With custom logger for debugging
91
+ * import { ConsoleLogger } from 'xivdyetools-core';
92
+ * const apiService = new APIService({ logger: ConsoleLogger });
93
+ */
94
+ export class APIService {
95
+ /**
96
+ * Constructor with optional dependency injection
97
+ * @param options Configuration options or legacy cache backend
98
+ */
99
+ constructor(options, fetchClient, rateLimiter) {
100
+ this.pendingRequests = new Map();
101
+ // Support both legacy positional arguments and new options object
102
+ if (options && 'logger' in options) {
103
+ // New options-based API
104
+ this.cache = options.cacheBackend ?? new MemoryCacheBackend();
105
+ this.fetchClient = options.fetchClient ?? new DefaultFetchClient();
106
+ this.rateLimiter = options.rateLimiter ?? new DefaultRateLimiter();
107
+ this.logger = options.logger ?? NoOpLogger;
108
+ }
109
+ else {
110
+ // Legacy positional arguments API
111
+ this.cache = options ?? new MemoryCacheBackend();
112
+ this.fetchClient = fetchClient ?? new DefaultFetchClient();
113
+ this.rateLimiter = rateLimiter ?? new DefaultRateLimiter();
114
+ this.logger = NoOpLogger;
115
+ }
116
+ }
117
+ // ============================================================================
118
+ // Cache Management
119
+ // ============================================================================
120
+ /**
121
+ * Get price from cache if available and not expired
122
+ * Validates cache version and checksum
123
+ */
124
+ async getCachedPrice(cacheKey) {
125
+ const cached = await this.cache.get(cacheKey);
126
+ if (!cached) {
127
+ return null;
128
+ }
129
+ // Check cache version
130
+ if (cached.version && cached.version !== API_CACHE_VERSION) {
131
+ await this.cache.delete(cacheKey);
132
+ return null;
133
+ }
134
+ // Check if cache has expired
135
+ if (Date.now() - cached.timestamp > cached.ttl) {
136
+ await this.cache.delete(cacheKey);
137
+ return null;
138
+ }
139
+ // Validate checksum if present
140
+ if (cached.checksum) {
141
+ const computedChecksum = generateChecksum(cached.data);
142
+ if (computedChecksum !== cached.checksum) {
143
+ this.logger.warn(`Cache corruption detected for key: ${cacheKey}`);
144
+ await this.cache.delete(cacheKey);
145
+ return null;
146
+ }
147
+ }
148
+ return cached.data;
149
+ }
150
+ /**
151
+ * Set price in cache with version and checksum
152
+ */
153
+ async setCachedPrice(cacheKey, data) {
154
+ const checksum = generateChecksum(data);
155
+ await this.cache.set(cacheKey, {
156
+ data,
157
+ timestamp: Date.now(),
158
+ ttl: API_CACHE_TTL,
159
+ version: API_CACHE_VERSION,
160
+ checksum,
161
+ });
162
+ }
163
+ /**
164
+ * Clear cache
165
+ */
166
+ async clearCache() {
167
+ await this.cache.clear();
168
+ this.logger.info('Price cache cleared');
169
+ }
170
+ /**
171
+ * Get cache stats
172
+ */
173
+ async getCacheStats() {
174
+ const keys = await this.cache.keys();
175
+ return { size: keys.length, keys };
176
+ }
177
+ // ============================================================================
178
+ // API Calls
179
+ // ============================================================================
180
+ /**
181
+ * Fetch price data for a dye from Universalis API
182
+ * Implements caching, debouncing, and retry logic
183
+ */
184
+ async getPriceData(itemID, worldID, dataCenterID) {
185
+ try {
186
+ // Build cache key
187
+ const cacheKey = this.buildCacheKey(itemID, worldID, dataCenterID);
188
+ // Check cache first
189
+ const cached = await this.getCachedPrice(cacheKey);
190
+ if (cached) {
191
+ this.logger.info(`Price cache hit for item ${itemID}`);
192
+ return cached;
193
+ }
194
+ // Check if request is already pending (deduplication)
195
+ if (this.pendingRequests.has(cacheKey)) {
196
+ this.logger.info(`Using pending request for item ${itemID}`);
197
+ return await this.pendingRequests.get(cacheKey);
198
+ }
199
+ // Create pending request
200
+ const promise = this.fetchPriceData(itemID, worldID, dataCenterID).then(async (data) => {
201
+ this.pendingRequests.delete(cacheKey);
202
+ if (data) {
203
+ await this.setCachedPrice(cacheKey, data);
204
+ }
205
+ return data;
206
+ }, (error) => {
207
+ this.pendingRequests.delete(cacheKey);
208
+ throw error;
209
+ });
210
+ this.pendingRequests.set(cacheKey, promise);
211
+ return await promise;
212
+ }
213
+ catch (error) {
214
+ this.logger.error('Failed to fetch price data', error);
215
+ return null;
216
+ }
217
+ }
218
+ /**
219
+ * Internal method to fetch price data from API
220
+ */
221
+ async fetchPriceData(itemID, worldID, dataCenterID) {
222
+ // Rate limiting: use injected rate limiter
223
+ await this.rateLimiter.waitIfNeeded();
224
+ this.rateLimiter.recordRequest();
225
+ // Build API URL
226
+ const url = this.buildApiUrl(itemID, worldID, dataCenterID);
227
+ try {
228
+ const data = await retry(() => this.fetchWithTimeout(url, UNIVERSALIS_API_TIMEOUT), UNIVERSALIS_API_RETRY_COUNT, UNIVERSALIS_API_RETRY_DELAY);
229
+ if (!data) {
230
+ return null;
231
+ }
232
+ // Parse and validate response
233
+ return this.parseApiResponse(data, itemID);
234
+ }
235
+ catch (error) {
236
+ throw new AppError(ErrorCode.API_CALL_FAILED, `Failed to fetch price data for item ${itemID}: ${error instanceof Error ? error.message : 'Unknown error'}`, 'warning');
237
+ }
238
+ }
239
+ /**
240
+ * Fetch with timeout and size limits
241
+ * Uses injected fetch client for better testability
242
+ */
243
+ async fetchWithTimeout(url, timeoutMs) {
244
+ const controller = new AbortController();
245
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
246
+ try {
247
+ const response = await this.fetchClient.fetch(url, { signal: controller.signal });
248
+ if (!response.ok) {
249
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
250
+ }
251
+ const contentType = response.headers.get('content-type');
252
+ if (!contentType?.includes('application/json')) {
253
+ throw new Error('Invalid content type: expected application/json');
254
+ }
255
+ // Check content-length header if available
256
+ const contentLength = response.headers.get('content-length');
257
+ if (contentLength) {
258
+ const size = parseInt(contentLength, 10);
259
+ if (!isNaN(size) && size > API_MAX_RESPONSE_SIZE) {
260
+ throw new Error(`Response too large: ${size} bytes (max: ${API_MAX_RESPONSE_SIZE} bytes)`);
261
+ }
262
+ }
263
+ // Read response text and validate size
264
+ const text = await response.text();
265
+ if (text.length > API_MAX_RESPONSE_SIZE) {
266
+ throw new Error(`Response too large: ${text.length} bytes (max: ${API_MAX_RESPONSE_SIZE} bytes)`);
267
+ }
268
+ // Parse JSON with error handling
269
+ try {
270
+ return JSON.parse(text);
271
+ }
272
+ catch (parseError) {
273
+ throw new Error(`Invalid JSON response: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
274
+ }
275
+ }
276
+ finally {
277
+ clearTimeout(timeoutId);
278
+ }
279
+ }
280
+ /**
281
+ * Parse and validate API response
282
+ */
283
+ parseApiResponse(data, itemID) {
284
+ try {
285
+ // Validate response structure
286
+ if (!data || typeof data !== 'object') {
287
+ this.logger.warn(`Invalid API response structure for item ${itemID}`);
288
+ return null;
289
+ }
290
+ // Parse aggregated endpoint response format
291
+ if (!data.results || !Array.isArray(data.results) || data.results.length === 0) {
292
+ this.logger.warn(`No price data available for item ${itemID}`);
293
+ return null;
294
+ }
295
+ const result = data.results[0];
296
+ if (!result || typeof result !== 'object') {
297
+ this.logger.warn(`Invalid result structure for item ${itemID}`);
298
+ return null;
299
+ }
300
+ // Validate itemId type and match
301
+ if (typeof result.itemId !== 'number' || result.itemId !== itemID) {
302
+ this.logger.warn(`Item ID mismatch: expected ${itemID}, got ${result.itemId}`);
303
+ return null;
304
+ }
305
+ // Try to get price from nq.minListing (prefer DC, then world, then region)
306
+ let price = null;
307
+ if (result.nq?.minListing) {
308
+ // Prefer data center price
309
+ if (result.nq.minListing.dc?.price) {
310
+ price = result.nq.minListing.dc.price;
311
+ }
312
+ // Fall back to world price
313
+ else if (result.nq.minListing.world?.price) {
314
+ price = result.nq.minListing.world.price;
315
+ }
316
+ // Fall back to region price
317
+ else if (result.nq.minListing.region?.price) {
318
+ price = result.nq.minListing.region.price;
319
+ }
320
+ }
321
+ // If no NQ price, try HQ
322
+ if (!price && result.hq?.minListing) {
323
+ if (result.hq.minListing.dc?.price) {
324
+ price = result.hq.minListing.dc.price;
325
+ }
326
+ else if (result.hq.minListing.world?.price) {
327
+ price = result.hq.minListing.world.price;
328
+ }
329
+ else if (result.hq.minListing.region?.price) {
330
+ price = result.hq.minListing.region.price;
331
+ }
332
+ }
333
+ if (!price) {
334
+ this.logger.warn(`No price data available for item ${itemID}`);
335
+ return null;
336
+ }
337
+ return {
338
+ itemID,
339
+ currentAverage: Math.round(price),
340
+ currentMinPrice: Math.round(price), // Using same price for all fields
341
+ currentMaxPrice: Math.round(price),
342
+ lastUpdate: Date.now(),
343
+ };
344
+ }
345
+ catch (error) {
346
+ this.logger.error(`Failed to parse price data for item ${itemID}`, error);
347
+ return null;
348
+ }
349
+ }
350
+ /**
351
+ * Build API URL for item price query
352
+ */
353
+ buildApiUrl(itemID, _worldID, dataCenterID) {
354
+ // Universalis API endpoint: /api/v2/aggregated/{dataCenter or worldName}/{itemIDs}
355
+ // Note: worldID parameter reserved for future use (world-specific queries)
356
+ // Sanitize dataCenterID to prevent URL path injection
357
+ const pathSegment = dataCenterID ? this.sanitizeDataCenterId(dataCenterID) : 'universal';
358
+ return `${UNIVERSALIS_API_BASE}/aggregated/${pathSegment}/${itemID}`;
359
+ }
360
+ /**
361
+ * Sanitize datacenter ID to prevent cache key injection.
362
+ * Only allows alphanumeric characters (a-z, A-Z, 0-9).
363
+ */
364
+ sanitizeDataCenterId(dataCenterID) {
365
+ return dataCenterID.replace(/[^a-zA-Z0-9]/g, '');
366
+ }
367
+ /**
368
+ * Build cache key from parameters
369
+ * SECURITY: Uses type prefixes to prevent cache key collisions
370
+ * Format: itemID:type:value where type is 'dc', 'world', or 'global'
371
+ * This prevents crafted dataCenterID values from colliding with other key patterns
372
+ */
373
+ buildCacheKey(itemID, worldID, dataCenterID) {
374
+ const parts = [itemID];
375
+ if (dataCenterID) {
376
+ // Type prefix 'dc' ensures datacenter keys don't collide with world/global keys
377
+ const sanitized = this.sanitizeDataCenterId(dataCenterID);
378
+ parts.push('dc', sanitized);
379
+ }
380
+ else if (worldID) {
381
+ // Type prefix 'world' ensures world keys don't collide with datacenter/global keys
382
+ parts.push('world', worldID);
383
+ }
384
+ else {
385
+ parts.push('global');
386
+ }
387
+ // Use colon delimiter (not in sanitized values) for unambiguous parsing
388
+ return parts.join(':');
389
+ }
390
+ // ============================================================================
391
+ // Batch Operations
392
+ // ============================================================================
393
+ /**
394
+ * Fetch prices for multiple items
395
+ * PERFORMANCE: Uses Promise.allSettled for parallel requests
396
+ * Failed requests are logged but don't block other results
397
+ */
398
+ async getPricesForItems(itemIDs) {
399
+ const results = new Map();
400
+ // Fire all requests in parallel
401
+ const promises = itemIDs.map(async (itemID) => {
402
+ const price = await this.getPriceData(itemID);
403
+ return { itemID, price };
404
+ });
405
+ // Wait for all to settle (don't fail on individual errors)
406
+ const settled = await Promise.allSettled(promises);
407
+ // Collect successful results
408
+ for (const result of settled) {
409
+ if (result.status === 'fulfilled' && result.value.price) {
410
+ results.set(result.value.itemID, result.value.price);
411
+ }
412
+ }
413
+ return results;
414
+ }
415
+ /**
416
+ * Fetch prices for dyes in a specific data center
417
+ * PERFORMANCE: Uses Promise.allSettled for parallel requests
418
+ */
419
+ async getPricesForDataCenter(itemIDs, dataCenterID) {
420
+ const results = new Map();
421
+ // Fire all requests in parallel
422
+ const promises = itemIDs.map(async (itemID) => {
423
+ const price = await this.getPriceData(itemID, undefined, dataCenterID);
424
+ return { itemID, price };
425
+ });
426
+ // Wait for all to settle (don't fail on individual errors)
427
+ const settled = await Promise.allSettled(promises);
428
+ // Collect successful results
429
+ for (const result of settled) {
430
+ if (result.status === 'fulfilled' && result.value.price) {
431
+ results.set(result.value.itemID, result.value.price);
432
+ }
433
+ }
434
+ return results;
435
+ }
436
+ // ============================================================================
437
+ // Health Checks
438
+ // ============================================================================
439
+ /**
440
+ * Check if Universalis API is available
441
+ * Uses injected fetch client
442
+ */
443
+ async isAPIAvailable() {
444
+ try {
445
+ const response = await this.fetchClient.fetch(`${UNIVERSALIS_API_BASE}/data-centers`);
446
+ return response.ok;
447
+ }
448
+ catch {
449
+ return false;
450
+ }
451
+ }
452
+ /**
453
+ * Get API status information
454
+ */
455
+ async getAPIStatus() {
456
+ const start = Date.now();
457
+ try {
458
+ const available = await this.isAPIAvailable();
459
+ const latency = Date.now() - start;
460
+ return { available, latency };
461
+ }
462
+ catch {
463
+ return { available: false, latency: -1 };
464
+ }
465
+ }
466
+ // ============================================================================
467
+ // Utility Methods
468
+ // ============================================================================
469
+ /**
470
+ * Format price for display (FFXIV Gil format: 69,420G)
471
+ */
472
+ static formatPrice(price) {
473
+ const formattedNumber = new Intl.NumberFormat('en-US', {
474
+ minimumFractionDigits: 0,
475
+ maximumFractionDigits: 0,
476
+ }).format(price);
477
+ // Return with small "G" suffix
478
+ return `${formattedNumber}G`;
479
+ }
480
+ /**
481
+ * Calculate price trend (simplified)
482
+ */
483
+ static getPriceTrend(currentPrice, previousPrice) {
484
+ const change = currentPrice - previousPrice;
485
+ const changePercent = previousPrice === 0 ? 0 : (change / previousPrice) * 100;
486
+ let trend;
487
+ if (change > previousPrice * 0.05) {
488
+ trend = 'up';
489
+ }
490
+ else if (change < -previousPrice * 0.05) {
491
+ trend = 'down';
492
+ }
493
+ else {
494
+ trend = 'stable';
495
+ }
496
+ return { trend, change, changePercent: Math.round(changePercent * 100) / 100 };
497
+ }
498
+ }
499
+ //# sourceMappingURL=APIService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"APIService.js","sourceRoot":"","sources":["../../src/services/APIService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @xivdyetools/core - Color Service
3
+ *
4
+ * Color conversion algorithms and colorblindness simulation.
5
+ * Provides utilities for converting between RGB, HSV, and hex color formats,
6
+ * calculating color distances, and simulating color vision deficiencies.
7
+ *
8
+ * Per R-4: Facade class that delegates to focused service classes
9
+ * Maintains backward compatibility while using split services internally
10
+ *
11
+ * @module services/ColorService
12
+ * @example
13
+ * ```typescript
14
+ * import { ColorService } from '@xivdyetools/core';
15
+ *
16
+ * // Convert hex to RGB
17
+ * const rgb = ColorService.hexToRgb('#FF0000');
18
+ * // { r: 255, g: 0, b: 0 }
19
+ *
20
+ * // Convert RGB to HSV
21
+ * const hsv = ColorService.rgbToHsv(rgb);
22
+ * // { h: 0, s: 100, v: 100 }
23
+ *
24
+ * // Calculate color distance
25
+ * const distance = ColorService.getColorDistance('#FF0000', '#00FF00');
26
+ * ```
27
+ */
28
+ import type { RGB, HSV, HexColor, VisionType } from '../types/index.js';
29
+ /**
30
+ * Color conversion and manipulation service (Facade)
31
+ * Per R-4: Delegates to focused service classes for better separation of concerns
32
+ * Maintains backward compatibility with existing API
33
+ */
34
+ export declare class ColorService {
35
+ /**
36
+ * Clear all caches (useful for testing or memory management)
37
+ */
38
+ static clearCaches(): void;
39
+ /**
40
+ * Get cache statistics (for monitoring)
41
+ */
42
+ static getCacheStats(): {
43
+ hexToRgb: number;
44
+ rgbToHex: number;
45
+ rgbToHsv: number;
46
+ hsvToRgb: number;
47
+ hexToHsv: number;
48
+ colorblind: number;
49
+ };
50
+ /**
51
+ * Convert hexadecimal color to RGB
52
+ * @example hexToRgb("#FF0000") -> { r: 255, g: 0, b: 0 }
53
+ */
54
+ static hexToRgb(hex: string): RGB;
55
+ /**
56
+ * Convert RGB to hexadecimal color
57
+ * @example rgbToHex(255, 0, 0) -> "#FF0000"
58
+ */
59
+ static rgbToHex(r: number, g: number, b: number): HexColor;
60
+ /**
61
+ * Convert RGB to HSV
62
+ * @example rgbToHsv(255, 0, 0) -> { h: 0, s: 100, v: 100 }
63
+ */
64
+ static rgbToHsv(r: number, g: number, b: number): HSV;
65
+ /**
66
+ * Convert HSV to RGB
67
+ * @example hsvToRgb(0, 100, 100) -> { r: 255, g: 0, b: 0 }
68
+ */
69
+ static hsvToRgb(h: number, s: number, v: number): RGB;
70
+ /**
71
+ * Convert hex to HSV
72
+ */
73
+ static hexToHsv(hex: string): HSV;
74
+ /**
75
+ * Convert HSV to hex
76
+ */
77
+ static hsvToHex(h: number, s: number, v: number): HexColor;
78
+ /**
79
+ * Normalize a hex color to #RRGGBB format
80
+ */
81
+ static normalizeHex(hex: string): HexColor;
82
+ /**
83
+ * Calculate Euclidean distance between two RGB colors
84
+ * Returns 0 for identical colors, ~441.67 for white vs black
85
+ */
86
+ static getColorDistance(hex1: string, hex2: string): number;
87
+ /**
88
+ * Simulate colorblindness on an RGB color
89
+ * @example simulateColorblindness({ r: 255, g: 0, b: 0 }, 'deuteranopia')
90
+ */
91
+ static simulateColorblindness(rgb: RGB, visionType: VisionType): RGB;
92
+ /**
93
+ * Simulate colorblindness on a hex color
94
+ */
95
+ static simulateColorblindnessHex(hex: string, visionType: VisionType): HexColor;
96
+ /**
97
+ * Calculate perceived luminance of a color (0-1)
98
+ * Uses relative luminance formula from WCAG
99
+ */
100
+ static getPerceivedLuminance(hex: string): number;
101
+ /**
102
+ * Calculate contrast ratio between two colors
103
+ * Returns 1 (no contrast) to 21 (maximum contrast)
104
+ */
105
+ static getContrastRatio(hex1: string, hex2: string): number;
106
+ /**
107
+ * Check if two colors meet WCAG AA contrast ratio
108
+ */
109
+ static meetsWCAGAA(hex1: string, hex2: string, largeText?: boolean): boolean;
110
+ /**
111
+ * Check if two colors meet WCAG AAA contrast ratio
112
+ */
113
+ static meetsWCAGAAA(hex1: string, hex2: string, largeText?: boolean): boolean;
114
+ /**
115
+ * Check if a color is light (for determining text color on background)
116
+ */
117
+ static isLightColor(hex: string): boolean;
118
+ /**
119
+ * Get optimal text color for a background color
120
+ */
121
+ static getOptimalTextColor(backgroundColor: string): HexColor;
122
+ /**
123
+ * Adjust brightness of a color
124
+ * @param amount -100 to 100 (negative = darker, positive = lighter)
125
+ */
126
+ static adjustBrightness(hex: string, amount: number): HexColor;
127
+ /**
128
+ * Adjust saturation of a color
129
+ * @param amount -100 to 100 (negative = less saturated, positive = more saturated)
130
+ */
131
+ static adjustSaturation(hex: string, amount: number): HexColor;
132
+ /**
133
+ * Rotate hue of a color
134
+ * @param degrees 0-360 (amount to rotate hue)
135
+ */
136
+ static rotateHue(hex: string, degrees: number): HexColor;
137
+ /**
138
+ * Invert a color (create complementary color)
139
+ */
140
+ static invert(hex: string): HexColor;
141
+ /**
142
+ * Desaturate a color (convert to grayscale)
143
+ */
144
+ static desaturate(hex: string): HexColor;
145
+ }
146
+ //# sourceMappingURL=ColorService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColorService.d.ts","sourceRoot":"","sources":["../../src/services/ColorService.ts"],"names":[],"mappings":"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"}