@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,131 @@
1
+ /**
2
+ * Dye Database
3
+ * Per R-4: Focused class for dye database management
4
+ * Handles loading, indexing, and data access
5
+ */
6
+ import type { Dye } from '../../types/index.js';
7
+ import type { Logger } from '../../types/index.js';
8
+ import { KDTree } from '../../utils/kd-tree.js';
9
+ /**
10
+ * Configuration for DyeDatabase
11
+ */
12
+ export interface DyeDatabaseConfig {
13
+ /**
14
+ * Logger for database operations (default: NoOpLogger)
15
+ */
16
+ logger?: Logger;
17
+ }
18
+ /**
19
+ * Dye database manager
20
+ * Per R-4: Single Responsibility - database management only
21
+ */
22
+ export declare class DyeDatabase {
23
+ private dyes;
24
+ private dyesByIdMap;
25
+ private dyesByHueBucket;
26
+ private kdTree;
27
+ private isLoaded;
28
+ private lastLoaded;
29
+ private readonly logger;
30
+ private static readonly HUE_BUCKET_SIZE;
31
+ private static readonly HUE_BUCKET_COUNT;
32
+ /**
33
+ * Dangerous prototype pollution keys to filter out
34
+ * Per Security: Prevents prototype pollution attacks from untrusted data
35
+ */
36
+ private static readonly DANGEROUS_KEYS;
37
+ /**
38
+ * Constructor with optional configuration
39
+ * @param config Configuration options including logger
40
+ */
41
+ constructor(config?: DyeDatabaseConfig);
42
+ /**
43
+ * Create a safe copy of an object, filtering out prototype pollution keys
44
+ * Per Security: Deep clones object while preventing prototype pollution
45
+ */
46
+ private safeClone;
47
+ /**
48
+ * Validate dye data structure
49
+ * Per Issue #14: Runtime validation prevents malformed data from causing runtime errors
50
+ * @param dye - Object to validate as a Dye
51
+ * @returns true if valid, false otherwise
52
+ */
53
+ private isValidDye;
54
+ /**
55
+ * Initialize dye database from data
56
+ * Per Security: Includes prototype pollution protection for untrusted data sources
57
+ */
58
+ initialize(dyeData: unknown): void;
59
+ /**
60
+ * Ensure database is loaded, throw error if not
61
+ */
62
+ ensureLoaded(): void;
63
+ /**
64
+ * Get all dyes (defensive copy)
65
+ */
66
+ getAllDyes(): Dye[];
67
+ /**
68
+ * Get dye by ID
69
+ */
70
+ getDyeById(id: number): Dye | null;
71
+ /**
72
+ * Get multiple dyes by IDs
73
+ */
74
+ getDyesByIds(ids: number[]): Dye[];
75
+ /**
76
+ * Check if database is loaded
77
+ */
78
+ isLoadedStatus(): boolean;
79
+ /**
80
+ * Get timestamp of last load
81
+ */
82
+ getLastLoadedTime(): number;
83
+ /**
84
+ * Get total dye count
85
+ */
86
+ getDyeCount(): number;
87
+ /**
88
+ * Get all unique categories
89
+ */
90
+ getCategories(): string[];
91
+ /**
92
+ * Get k-d tree (for search operations)
93
+ */
94
+ getKdTree(): KDTree | null;
95
+ /**
96
+ * Get hue bucket index for a given hue (0-35 for 10° buckets)
97
+ * Per P-2: Maps hue to bucket for indexed lookups
98
+ */
99
+ getHueBucket(hue: number): number;
100
+ /**
101
+ * Get hue buckets to search for a target hue with tolerance
102
+ * Per P-2: Returns bucket indices that could contain matching dyes
103
+ */
104
+ getHueBucketsToSearch(targetHue: number, tolerance: number): number[];
105
+ /**
106
+ * Get dyes by hue bucket
107
+ */
108
+ getDyesByHueBucket(bucket: number): Dye[];
109
+ /**
110
+ * Get all dyes (internal access, no defensive copy)
111
+ *
112
+ * **Internal Use Only** - Returns a direct reference to the internal dyes array.
113
+ * Modifications to the returned array will affect the database state.
114
+ *
115
+ * For public API access, use {@link getAllDyes} which returns a defensive copy.
116
+ *
117
+ * @internal
118
+ * @returns Direct reference to internal dyes array - DO NOT MODIFY
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * // Internal usage in DyeSearch (optimized for read-only iteration)
123
+ * const dyes = this.database.getDyesInternal();
124
+ * for (const dye of dyes) {
125
+ * // Read-only operations
126
+ * }
127
+ * ```
128
+ */
129
+ getDyesInternal(): Dye[];
130
+ }
131
+ //# sourceMappingURL=DyeDatabase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DyeDatabase.d.ts","sourceRoot":"","sources":["../../../src/services/dye/DyeDatabase.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAgB,MAAM,wBAAwB,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,WAAW,CAA+B;IAGlD,OAAO,CAAC,eAAe,CAAiC;IAExD,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAGhC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAM;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAE9C;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAsD;IAE5F;;;OAGG;gBACS,MAAM,GAAE,iBAAsB;IAI1C;;;OAGG;IACH,OAAO,CAAC,SAAS;IAgBjB;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAsGlB;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IA+GlC;;OAEG;IACH,YAAY,IAAI,IAAI;IAMpB;;OAEG;IACH,UAAU,IAAI,GAAG,EAAE;IAKnB;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAKlC;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,EAAE;IAKlC;;OAEG;IACH,cAAc,IAAI,OAAO;IAIzB;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,WAAW,IAAI,MAAM;IAKrB;;OAEG;IACH,aAAa,IAAI,MAAM,EAAE;IAWzB;;OAEG;IACH,SAAS,IAAI,MAAM,GAAG,IAAI;IAI1B;;;OAGG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAMjC;;;OAGG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE;IAmBrE;;OAEG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,EAAE;IAKzC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,IAAI,GAAG,EAAE;CAIzB"}
@@ -0,0 +1,367 @@
1
+ /**
2
+ * Dye Database
3
+ * Per R-4: Focused class for dye database management
4
+ * Handles loading, indexing, and data access
5
+ */
6
+ import { ErrorCode, AppError } from '../../types/index.js';
7
+ import { NoOpLogger } from '../../types/index.js';
8
+ import { KDTree } from '../../utils/kd-tree.js';
9
+ /**
10
+ * Dye database manager
11
+ * Per R-4: Single Responsibility - database management only
12
+ */
13
+ export class DyeDatabase {
14
+ /**
15
+ * Constructor with optional configuration
16
+ * @param config Configuration options including logger
17
+ */
18
+ constructor(config = {}) {
19
+ this.dyes = [];
20
+ this.dyesByIdMap = new Map();
21
+ // Per P-2: Hue-indexed map for fast harmony lookups (70-90% speedup)
22
+ // Maps hue bucket (0-35 for 10° buckets) to array of dyes in that range
23
+ this.dyesByHueBucket = new Map();
24
+ // Per P-7: k-d tree for fast nearest neighbor search in RGB space
25
+ this.kdTree = null;
26
+ this.isLoaded = false;
27
+ this.lastLoaded = 0;
28
+ this.logger = config.logger ?? NoOpLogger;
29
+ }
30
+ /**
31
+ * Create a safe copy of an object, filtering out prototype pollution keys
32
+ * Per Security: Deep clones object while preventing prototype pollution
33
+ */
34
+ safeClone(obj) {
35
+ const result = Object.create(null);
36
+ for (const key of Object.keys(obj)) {
37
+ if (DyeDatabase.DANGEROUS_KEYS.has(key)) {
38
+ continue; // Skip dangerous keys
39
+ }
40
+ const value = obj[key];
41
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
42
+ result[key] = this.safeClone(value);
43
+ }
44
+ else {
45
+ result[key] = value;
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+ /**
51
+ * Validate dye data structure
52
+ * Per Issue #14: Runtime validation prevents malformed data from causing runtime errors
53
+ * @param dye - Object to validate as a Dye
54
+ * @returns true if valid, false otherwise
55
+ */
56
+ isValidDye(dye) {
57
+ // Required: id or itemID (can be null for Facewear dyes, which will get generated id)
58
+ const hasValidId = typeof dye.id === 'number' || typeof dye.itemID === 'number';
59
+ const hasFacewearNullId = dye.itemID === null && dye.category === 'Facewear';
60
+ if (!hasValidId && !hasFacewearNullId) {
61
+ this.logger.warn('Dye missing required id or itemID field');
62
+ return false;
63
+ }
64
+ // Required: name must be a non-empty string
65
+ if (typeof dye.name !== 'string' || dye.name.length === 0) {
66
+ const idForLog = typeof dye.id === 'number'
67
+ ? String(dye.id)
68
+ : typeof dye.itemID === 'number'
69
+ ? String(dye.itemID)
70
+ : 'unknown';
71
+ this.logger.warn(`Dye ${idForLog} has invalid name`);
72
+ return false;
73
+ }
74
+ // Validate hex format if present
75
+ if (dye.hex !== undefined && dye.hex !== null) {
76
+ if (typeof dye.hex !== 'string' || !/^#[A-Fa-f0-9]{6}$/.test(dye.hex)) {
77
+ const idForLog = typeof dye.id === 'number'
78
+ ? String(dye.id)
79
+ : typeof dye.itemID === 'number'
80
+ ? String(dye.itemID)
81
+ : String(dye.name ?? 'unknown');
82
+ const hexForLog = typeof dye.hex === 'string' ? dye.hex : String(dye.hex);
83
+ this.logger.warn(`Dye ${idForLog} has invalid hex format: ${hexForLog}`);
84
+ return false;
85
+ }
86
+ }
87
+ // Validate RGB if present
88
+ if (dye.rgb !== undefined && dye.rgb !== null) {
89
+ const rgb = dye.rgb;
90
+ if (typeof rgb.r !== 'number' ||
91
+ rgb.r < 0 ||
92
+ rgb.r > 255 ||
93
+ typeof rgb.g !== 'number' ||
94
+ rgb.g < 0 ||
95
+ rgb.g > 255 ||
96
+ typeof rgb.b !== 'number' ||
97
+ rgb.b < 0 ||
98
+ rgb.b > 255) {
99
+ const idForLog = typeof dye.id === 'number'
100
+ ? String(dye.id)
101
+ : typeof dye.itemID === 'number'
102
+ ? String(dye.itemID)
103
+ : String(dye.name ?? 'unknown');
104
+ this.logger.warn(`Dye ${idForLog} has invalid RGB values`);
105
+ return false;
106
+ }
107
+ }
108
+ // Validate HSV if present
109
+ if (dye.hsv !== undefined && dye.hsv !== null) {
110
+ const hsv = dye.hsv;
111
+ if (typeof hsv.h !== 'number' ||
112
+ hsv.h < 0 ||
113
+ hsv.h > 360 ||
114
+ typeof hsv.s !== 'number' ||
115
+ hsv.s < 0 ||
116
+ hsv.s > 100 ||
117
+ typeof hsv.v !== 'number' ||
118
+ hsv.v < 0 ||
119
+ hsv.v > 100) {
120
+ const idForLog = typeof dye.id === 'number'
121
+ ? String(dye.id)
122
+ : typeof dye.itemID === 'number'
123
+ ? String(dye.itemID)
124
+ : String(dye.name ?? 'unknown');
125
+ this.logger.warn(`Dye ${idForLog} has invalid HSV values`);
126
+ return false;
127
+ }
128
+ }
129
+ // Validate category if present
130
+ if (dye.category !== undefined && dye.category !== null && typeof dye.category !== 'string') {
131
+ const idForLog = typeof dye.id === 'number'
132
+ ? String(dye.id)
133
+ : typeof dye.itemID === 'number'
134
+ ? String(dye.itemID)
135
+ : String(dye.name ?? 'unknown');
136
+ this.logger.warn(`Dye ${idForLog} has invalid category type`);
137
+ return false;
138
+ }
139
+ return true;
140
+ }
141
+ /**
142
+ * Initialize dye database from data
143
+ * Per Security: Includes prototype pollution protection for untrusted data sources
144
+ */
145
+ initialize(dyeData) {
146
+ try {
147
+ // Validate input type
148
+ if (dyeData === null || dyeData === undefined) {
149
+ throw new Error('Invalid dye database format: null or undefined');
150
+ }
151
+ if (typeof dyeData === 'string' ||
152
+ typeof dyeData === 'number' ||
153
+ typeof dyeData === 'boolean') {
154
+ throw new Error('Invalid dye database format: expected array or object');
155
+ }
156
+ // Support both array and object formats
157
+ const loadedDyes = Array.isArray(dyeData) ? dyeData : Object.values(dyeData);
158
+ if (!Array.isArray(loadedDyes) || loadedDyes.length === 0) {
159
+ throw new Error('Invalid dye database format: empty or not an array');
160
+ }
161
+ // Normalize dyes: map itemID to id, price to cost, with prototype pollution protection
162
+ // Per Issue #14: Filter out invalid dyes during loading
163
+ this.dyes = loadedDyes
164
+ .map((dye) => {
165
+ // Per Security: Create a safe clone to prevent prototype pollution
166
+ const normalizedDye = this.safeClone(dye);
167
+ // If the dye has itemID but no id, use itemID as the id
168
+ if (normalizedDye.itemID && !normalizedDye.id) {
169
+ normalizedDye.id = normalizedDye.itemID;
170
+ }
171
+ // Generate synthetic ID for Facewear dyes with null itemID
172
+ // Uses negative numbers to avoid collision with real itemIDs
173
+ if (normalizedDye.itemID === null && normalizedDye.category === 'Facewear') {
174
+ // Use hash of name as synthetic ID (negative to distinguish from real IDs)
175
+ const nameHash = String(normalizedDye.name)
176
+ .split('')
177
+ .reduce((acc, char) => acc + char.charCodeAt(0), 0);
178
+ normalizedDye.id = -(1000 + nameHash);
179
+ normalizedDye.itemID = normalizedDye.id;
180
+ }
181
+ // Per Bug Fix: Map 'price' field to 'cost' for Dye interface compatibility
182
+ // JSON data uses 'price' but Dye interface expects 'cost'
183
+ if (normalizedDye.price !== undefined && normalizedDye.cost === undefined) {
184
+ normalizedDye.cost = normalizedDye.price ?? 0;
185
+ }
186
+ // Ensure cost is always a number (handle null values in JSON)
187
+ if (normalizedDye.cost === null || normalizedDye.cost === undefined) {
188
+ normalizedDye.cost = 0;
189
+ }
190
+ return normalizedDye;
191
+ })
192
+ .filter((dye) => this.isValidDye(dye))
193
+ .map((dye) => dye);
194
+ // Build ID map for fast lookups
195
+ this.dyesByIdMap.clear();
196
+ // Per P-2: Build hue-indexed map for harmony lookups
197
+ this.dyesByHueBucket.clear();
198
+ // Per P-7: Build k-d tree for RGB color space
199
+ const kdTreePoints = [];
200
+ for (const dye of this.dyes) {
201
+ // Map by id (which equals itemID after normalization)
202
+ this.dyesByIdMap.set(dye.id, dye);
203
+ // Per Issue #5: Only map itemID separately if it differs from id
204
+ // This avoids storing the same dye twice with the same key
205
+ if (dye.itemID && dye.itemID !== dye.id) {
206
+ this.dyesByIdMap.set(dye.itemID, dye);
207
+ }
208
+ // Per P-2: Index by hue bucket (10° buckets)
209
+ const hueBucket = this.getHueBucket(dye.hsv.h);
210
+ if (!this.dyesByHueBucket.has(hueBucket)) {
211
+ this.dyesByHueBucket.set(hueBucket, []);
212
+ }
213
+ this.dyesByHueBucket.get(hueBucket).push(dye);
214
+ // Per P-7: Add to k-d tree (exclude Facewear dyes from tree)
215
+ if (dye.category !== 'Facewear') {
216
+ kdTreePoints.push({
217
+ x: dye.rgb.r,
218
+ y: dye.rgb.g,
219
+ z: dye.rgb.b,
220
+ data: dye,
221
+ });
222
+ }
223
+ }
224
+ // Per P-7: Build k-d tree
225
+ this.kdTree = new KDTree(kdTreePoints);
226
+ this.isLoaded = true;
227
+ this.lastLoaded = Date.now();
228
+ this.logger.info(`Dye database loaded: ${this.dyes.length} dyes`);
229
+ }
230
+ catch (error) {
231
+ this.isLoaded = false;
232
+ throw new AppError(ErrorCode.DATABASE_LOAD_FAILED, `Failed to load dye database: ${error instanceof Error ? error.message : 'Unknown error'}`, 'critical');
233
+ }
234
+ }
235
+ /**
236
+ * Ensure database is loaded, throw error if not
237
+ */
238
+ ensureLoaded() {
239
+ if (!this.isLoaded) {
240
+ throw new AppError(ErrorCode.DATABASE_LOAD_FAILED, 'Dye database is not loaded', 'critical');
241
+ }
242
+ }
243
+ /**
244
+ * Get all dyes (defensive copy)
245
+ */
246
+ getAllDyes() {
247
+ this.ensureLoaded();
248
+ return [...this.dyes];
249
+ }
250
+ /**
251
+ * Get dye by ID
252
+ */
253
+ getDyeById(id) {
254
+ this.ensureLoaded();
255
+ return this.dyesByIdMap.get(id) || null;
256
+ }
257
+ /**
258
+ * Get multiple dyes by IDs
259
+ */
260
+ getDyesByIds(ids) {
261
+ this.ensureLoaded();
262
+ return ids.map((id) => this.dyesByIdMap.get(id)).filter((dye) => dye !== undefined);
263
+ }
264
+ /**
265
+ * Check if database is loaded
266
+ */
267
+ isLoadedStatus() {
268
+ return this.isLoaded;
269
+ }
270
+ /**
271
+ * Get timestamp of last load
272
+ */
273
+ getLastLoadedTime() {
274
+ return this.lastLoaded;
275
+ }
276
+ /**
277
+ * Get total dye count
278
+ */
279
+ getDyeCount() {
280
+ this.ensureLoaded();
281
+ return this.dyes.length;
282
+ }
283
+ /**
284
+ * Get all unique categories
285
+ */
286
+ getCategories() {
287
+ this.ensureLoaded();
288
+ const categories = new Set();
289
+ for (const dye of this.dyes) {
290
+ categories.add(dye.category);
291
+ }
292
+ return Array.from(categories).sort();
293
+ }
294
+ /**
295
+ * Get k-d tree (for search operations)
296
+ */
297
+ getKdTree() {
298
+ return this.kdTree;
299
+ }
300
+ /**
301
+ * Get hue bucket index for a given hue (0-35 for 10° buckets)
302
+ * Per P-2: Maps hue to bucket for indexed lookups
303
+ */
304
+ getHueBucket(hue) {
305
+ // Normalize hue to 0-360 range
306
+ const normalizedHue = ((hue % 360) + 360) % 360;
307
+ return Math.floor(normalizedHue / DyeDatabase.HUE_BUCKET_SIZE);
308
+ }
309
+ /**
310
+ * Get hue buckets to search for a target hue with tolerance
311
+ * Per P-2: Returns bucket indices that could contain matching dyes
312
+ */
313
+ getHueBucketsToSearch(targetHue, tolerance) {
314
+ const targetBucket = this.getHueBucket(targetHue);
315
+ const bucketsToSearch = new Set();
316
+ // Add target bucket
317
+ bucketsToSearch.add(targetBucket);
318
+ // Add adjacent buckets based on tolerance
319
+ // Each bucket is 10°, so tolerance/10 buckets on each side
320
+ const bucketRange = Math.ceil(tolerance / DyeDatabase.HUE_BUCKET_SIZE);
321
+ for (let i = -bucketRange; i <= bucketRange; i++) {
322
+ const bucket = (targetBucket + i + DyeDatabase.HUE_BUCKET_COUNT) % DyeDatabase.HUE_BUCKET_COUNT;
323
+ bucketsToSearch.add(bucket);
324
+ }
325
+ return Array.from(bucketsToSearch);
326
+ }
327
+ /**
328
+ * Get dyes by hue bucket
329
+ */
330
+ getDyesByHueBucket(bucket) {
331
+ this.ensureLoaded();
332
+ return this.dyesByHueBucket.get(bucket) || [];
333
+ }
334
+ /**
335
+ * Get all dyes (internal access, no defensive copy)
336
+ *
337
+ * **Internal Use Only** - Returns a direct reference to the internal dyes array.
338
+ * Modifications to the returned array will affect the database state.
339
+ *
340
+ * For public API access, use {@link getAllDyes} which returns a defensive copy.
341
+ *
342
+ * @internal
343
+ * @returns Direct reference to internal dyes array - DO NOT MODIFY
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * // Internal usage in DyeSearch (optimized for read-only iteration)
348
+ * const dyes = this.database.getDyesInternal();
349
+ * for (const dye of dyes) {
350
+ * // Read-only operations
351
+ * }
352
+ * ```
353
+ */
354
+ getDyesInternal() {
355
+ this.ensureLoaded();
356
+ return this.dyes;
357
+ }
358
+ }
359
+ // Per P-2: Hue bucket size (10 degrees per bucket for 36 buckets total)
360
+ DyeDatabase.HUE_BUCKET_SIZE = 10;
361
+ DyeDatabase.HUE_BUCKET_COUNT = 36; // 360 / 10
362
+ /**
363
+ * Dangerous prototype pollution keys to filter out
364
+ * Per Security: Prevents prototype pollution attacks from untrusted data
365
+ */
366
+ DyeDatabase.DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
367
+ //# sourceMappingURL=DyeDatabase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DyeDatabase.js","sourceRoot":"","sources":["../../../src/services/dye/DyeDatabase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Dye Search
3
+ * Per R-4: Focused class for dye search and matching operations
4
+ * Handles finding dyes by name, category, color distance, etc.
5
+ */
6
+ import type { Dye } from '../../types/index.js';
7
+ import type { DyeDatabase } from './DyeDatabase.js';
8
+ /**
9
+ * Dye search and matching utilities
10
+ * Per R-4: Single Responsibility - search operations only
11
+ */
12
+ export declare class DyeSearch {
13
+ private database;
14
+ constructor(database: DyeDatabase);
15
+ /**
16
+ * Search dyes by name (case-insensitive, partial match)
17
+ */
18
+ searchByName(query: string): Dye[];
19
+ /**
20
+ * Search dyes by category
21
+ */
22
+ searchByCategory(category: string): Dye[];
23
+ /**
24
+ * Filter dyes with optional exclusion list
25
+ */
26
+ filterDyes(filter?: {
27
+ category?: string;
28
+ excludeIds?: number[];
29
+ minPrice?: number;
30
+ maxPrice?: number;
31
+ }): Dye[];
32
+ /**
33
+ * Find closest dye to a given hex color
34
+ * Per P-7: Uses k-d tree for O(log n) average case vs O(n) linear search
35
+ */
36
+ findClosestDye(hex: string, excludeIds?: number[]): Dye | null;
37
+ /**
38
+ * Find dyes within a color distance threshold
39
+ * Per P-7: Uses k-d tree for efficient range queries
40
+ */
41
+ findDyesWithinDistance(hex: string, maxDistance: number, limit?: number): Dye[];
42
+ /**
43
+ * Get dyes sorted by brightness
44
+ */
45
+ getDyesSortedByBrightness(ascending?: boolean): Dye[];
46
+ /**
47
+ * Get dyes sorted by saturation
48
+ */
49
+ getDyesSortedBySaturation(ascending?: boolean): Dye[];
50
+ /**
51
+ * Get dyes sorted by hue
52
+ */
53
+ getDyesSortedByHue(ascending?: boolean): Dye[];
54
+ }
55
+ //# sourceMappingURL=DyeSearch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DyeSearch.d.ts","sourceRoot":"","sources":["../../../src/services/dye/DyeSearch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAGhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,SAAS;IACR,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,WAAW;IAEzC;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;IAYlC;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE;IAQzC;;OAEG;IACH,UAAU,CACR,MAAM,GAAE;QACN,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACd,GACL,GAAG,EAAE;IA0BR;;;OAGG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,EAAO,GAAG,GAAG,GAAG,IAAI;IAsDlE;;;OAGG;IACH,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE;IAyD/E;;OAEG;IACH,yBAAyB,CAAC,SAAS,GAAE,OAAc,GAAG,GAAG,EAAE;IAW3D;;OAEG;IACH,yBAAyB,CAAC,SAAS,GAAE,OAAc,GAAG,GAAG,EAAE;IAW3D;;OAEG;IACH,kBAAkB,CAAC,SAAS,GAAE,OAAc,GAAG,GAAG,EAAE;CAUrD"}