@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.
- package/LICENSE +37 -0
- package/README.md +400 -0
- package/dist/constants/index.d.ts +56 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +103 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/data/colors_xiv.json +3130 -0
- package/dist/data/locales/de.json +231 -0
- package/dist/data/locales/en.json +231 -0
- package/dist/data/locales/fr.json +231 -0
- package/dist/data/locales/ja.json +231 -0
- package/dist/data/locales/ko.json +233 -0
- package/dist/data/locales/zh.json +233 -0
- package/dist/data/presets.json +390 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/services/APIService.d.ts +246 -0
- package/dist/services/APIService.d.ts.map +1 -0
- package/dist/services/APIService.js +499 -0
- package/dist/services/APIService.js.map +1 -0
- package/dist/services/ColorService.d.ts +146 -0
- package/dist/services/ColorService.d.ts.map +1 -0
- package/dist/services/ColorService.js +209 -0
- package/dist/services/ColorService.js.map +1 -0
- package/dist/services/DyeService.d.ts +230 -0
- package/dist/services/DyeService.d.ts.map +1 -0
- package/dist/services/DyeService.js +326 -0
- package/dist/services/DyeService.js.map +1 -0
- package/dist/services/LocalizationService.d.ts +338 -0
- package/dist/services/LocalizationService.d.ts.map +1 -0
- package/dist/services/LocalizationService.js +449 -0
- package/dist/services/LocalizationService.js.map +1 -0
- package/dist/services/PaletteService.d.ts +137 -0
- package/dist/services/PaletteService.d.ts.map +1 -0
- package/dist/services/PaletteService.js +349 -0
- package/dist/services/PaletteService.js.map +1 -0
- package/dist/services/PresetService.d.ts +196 -0
- package/dist/services/PresetService.d.ts.map +1 -0
- package/dist/services/PresetService.js +261 -0
- package/dist/services/PresetService.js.map +1 -0
- package/dist/services/color/ColorAccessibility.d.ts +39 -0
- package/dist/services/color/ColorAccessibility.d.ts.map +1 -0
- package/dist/services/color/ColorAccessibility.js +71 -0
- package/dist/services/color/ColorAccessibility.js.map +1 -0
- package/dist/services/color/ColorConverter.d.ts +146 -0
- package/dist/services/color/ColorConverter.d.ts.map +1 -0
- package/dist/services/color/ColorConverter.js +393 -0
- package/dist/services/color/ColorConverter.js.map +1 -0
- package/dist/services/color/ColorManipulator.d.ts +36 -0
- package/dist/services/color/ColorManipulator.d.ts.map +1 -0
- package/dist/services/color/ColorManipulator.js +56 -0
- package/dist/services/color/ColorManipulator.js.map +1 -0
- package/dist/services/color/ColorblindnessSimulator.d.ts +35 -0
- package/dist/services/color/ColorblindnessSimulator.d.ts.map +1 -0
- package/dist/services/color/ColorblindnessSimulator.js +110 -0
- package/dist/services/color/ColorblindnessSimulator.js.map +1 -0
- package/dist/services/dye/DyeDatabase.d.ts +131 -0
- package/dist/services/dye/DyeDatabase.d.ts.map +1 -0
- package/dist/services/dye/DyeDatabase.js +367 -0
- package/dist/services/dye/DyeDatabase.js.map +1 -0
- package/dist/services/dye/DyeSearch.d.ts +55 -0
- package/dist/services/dye/DyeSearch.d.ts.map +1 -0
- package/dist/services/dye/DyeSearch.js +196 -0
- package/dist/services/dye/DyeSearch.js.map +1 -0
- package/dist/services/dye/HarmonyGenerator.d.ts +110 -0
- package/dist/services/dye/HarmonyGenerator.d.ts.map +1 -0
- package/dist/services/dye/HarmonyGenerator.js +221 -0
- package/dist/services/dye/HarmonyGenerator.js.map +1 -0
- package/dist/services/localization/LocaleLoader.d.ts +38 -0
- package/dist/services/localization/LocaleLoader.d.ts.map +1 -0
- package/dist/services/localization/LocaleLoader.js +83 -0
- package/dist/services/localization/LocaleLoader.js.map +1 -0
- package/dist/services/localization/LocaleRegistry.d.ts +73 -0
- package/dist/services/localization/LocaleRegistry.d.ts.map +1 -0
- package/dist/services/localization/LocaleRegistry.js +84 -0
- package/dist/services/localization/LocaleRegistry.js.map +1 -0
- package/dist/services/localization/TranslationProvider.d.ts +157 -0
- package/dist/services/localization/TranslationProvider.d.ts.map +1 -0
- package/dist/services/localization/TranslationProvider.js +289 -0
- package/dist/services/localization/TranslationProvider.js.map +1 -0
- package/dist/types/index.d.ts +409 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +87 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/logger.d.ts +84 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/logger.js +54 -0
- package/dist/types/logger.js.map +1 -0
- package/dist/utils/index.d.ts +441 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +577 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/kd-tree.d.ts +76 -0
- package/dist/utils/kd-tree.d.ts.map +1 -0
- package/dist/utils/kd-tree.js +195 -0
- package/dist/utils/kd-tree.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- 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"}
|