import { init, parse_color, lighten_color, darken_color, saturate_color, desaturate_color, rotate_hue, complement_color, mix_colors, get_text_color, calculate_contrast, simulate_protanopia, simulate_deuteranopia, simulate_tritanopia, color_distance, generate_random_colors, generate_gradient, generate_palette, get_all_named_colors, search_named_colors, version, } from '@valknarthing/pastel-wasm'; import type { ApiResponse, ColorInfoRequest, ColorInfoData, ConvertFormatRequest, ConvertFormatData, ColorManipulationRequest, ColorManipulationData, ColorMixRequest, ColorMixData, RandomColorsRequest, RandomColorsData, DistinctColorsRequest, DistinctColorsData, GradientRequest, GradientData, ColorDistanceRequest, ColorDistanceData, ColorSortRequest, ColorSortData, ColorBlindnessRequest, ColorBlindnessData, TextColorRequest, TextColorData, NamedColorsData, NamedColorSearchRequest, NamedColorSearchData, HealthData, CapabilitiesData, PaletteGenerateRequest, PaletteGenerateData, } from './types'; // Initialize WASM module let wasmInitialized = false; async function ensureWasmInit() { if (!wasmInitialized) { init(); // Initialize panic hook wasmInitialized = true; } } /** * WASM-based Pastel client * Provides the same interface as PastelAPIClient but uses WebAssembly * Zero network latency, works offline! */ export class PastelWASMClient { constructor() { // Initialize WASM eagerly ensureWasmInit().catch(console.error); } private async request(fn: () => T): Promise> { try { await ensureWasmInit(); const data = fn(); return { success: true, data, }; } catch (error) { return { success: false, error: { code: 'WASM_ERROR', message: error instanceof Error ? error.message : 'Unknown error', }, }; } } // Color Information async getColorInfo(request: ColorInfoRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => { const info = parse_color(colorStr); return { input: info.input, hex: info.hex, rgb: { r: info.rgb[0], g: info.rgb[1], b: info.rgb[2], }, hsl: { h: info.hsl[0], s: info.hsl[1], l: info.hsl[2], }, hsv: { h: info.hsv[0], s: info.hsv[1], v: info.hsv[2], }, lab: { l: info.lab[0], a: info.lab[1], b: info.lab[2], }, oklab: { l: info.lab[0] / 100.0, a: info.lab[1] / 100.0, b: info.lab[2] / 100.0, }, lch: { l: info.lch[0], c: info.lch[1], h: info.lch[2], }, oklch: { l: info.lch[0] / 100.0, c: info.lch[1] / 100.0, h: info.lch[2], }, cmyk: { c: 0, m: 0, y: 0, k: 0, }, brightness: info.brightness, luminance: info.luminance, is_light: info.is_light, }; }); return { colors }; }); } // Format Conversion async convertFormat(request: ConvertFormatRequest): Promise> { return this.request(() => { const conversions = request.colors.map((colorStr) => { const parsed = parse_color(colorStr); let output: string; switch (request.format) { case 'hex': output = parsed.hex; break; case 'rgb': output = `rgb(${parsed.rgb[0]}, ${parsed.rgb[1]}, ${parsed.rgb[2]})`; break; case 'hsl': output = `hsl(${parsed.hsl[0].toFixed(1)}, ${(parsed.hsl[1] * 100).toFixed(1)}%, ${(parsed.hsl[2] * 100).toFixed(1)}%)`; break; case 'hsv': output = `hsv(${parsed.hsv[0].toFixed(1)}, ${(parsed.hsv[1] * 100).toFixed(1)}%, ${(parsed.hsv[2] * 100).toFixed(1)}%)`; break; case 'lab': output = `lab(${parsed.lab[0].toFixed(2)}, ${parsed.lab[1].toFixed(2)}, ${parsed.lab[2].toFixed(2)})`; break; case 'lch': output = `lch(${parsed.lch[0].toFixed(2)}, ${parsed.lch[1].toFixed(2)}, ${parsed.lch[2].toFixed(2)})`; break; default: output = parsed.hex; } return { input: colorStr, output, }; }); return { conversions }; }); } // Color Manipulation async lighten(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: lighten_color(colorStr, request.amount), })); return { operation: 'lighten', amount: request.amount, colors }; }); } async darken(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: darken_color(colorStr, request.amount), })); return { operation: 'darken', amount: request.amount, colors }; }); } async saturate(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: saturate_color(colorStr, request.amount), })); return { operation: 'saturate', amount: request.amount, colors }; }); } async desaturate(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: desaturate_color(colorStr, request.amount), })); return { operation: 'desaturate', amount: request.amount, colors }; }); } async rotate(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: rotate_hue(colorStr, request.amount), })); return { operation: 'rotate', amount: request.amount, colors }; }); } async complement(colors: string[]): Promise> { return this.request(() => { const results = colors.map((colorStr) => ({ input: colorStr, output: complement_color(colorStr), })); return { operation: 'complement', colors: results }; }); } async grayscale(colors: string[]): Promise> { return this.request(() => { const results = colors.map((colorStr) => ({ input: colorStr, output: desaturate_color(colorStr, 1.0), })); return { operation: 'grayscale', colors: results }; }); } async mix(request: ColorMixRequest): Promise> { return this.request(() => { // Mix pairs of colors const results = []; for (let i = 0; i < request.colors.length - 1; i += 2) { const color1 = request.colors[i]; const color2 = request.colors[i + 1]; const mixed = mix_colors(color1, color2, request.fraction); results.push({ color1, color2, mixed }); } return { results }; }); } // Color Generation async generateRandom(request: RandomColorsRequest): Promise> { return this.request(() => { const vivid = request.strategy === 'vivid' || request.strategy === 'lch'; const colors = generate_random_colors(request.count, vivid); return { colors }; }); } async generateDistinct(request: DistinctColorsRequest): Promise> { return this.request(() => { // Note: WASM version doesn't support distinct colors with simulated annealing yet // Fall back to vivid random colors const colors = generate_random_colors(request.count, true); return { colors, stats: { min_distance: 0, avg_distance: 0, generation_time_ms: 0, }, }; }); } async generateGradient(request: GradientRequest): Promise> { return this.request(() => { if (request.stops.length < 2) { throw new Error('At least 2 color stops are required'); } // For 2 stops, use the WASM gradient function if (request.stops.length === 2) { const gradient = generate_gradient(request.stops[0], request.stops[1], request.count); return { stops: request.stops, count: request.count, colorspace: request.colorspace || 'rgb', gradient, }; } // For multiple stops, interpolate segments const segments = request.stops.length - 1; const colorsPerSegment = Math.floor(request.count / segments); const gradient: string[] = []; for (let i = 0; i < segments; i++) { const segmentColors = generate_gradient( request.stops[i], request.stops[i + 1], i === segments - 1 ? request.count - gradient.length : colorsPerSegment ); gradient.push(...segmentColors.slice(0, -1)); // Avoid duplicates } gradient.push(request.stops[request.stops.length - 1]); return { stops: request.stops, count: request.count, colorspace: request.colorspace || 'rgb', gradient, }; }); } // Color Analysis async calculateDistance(request: ColorDistanceRequest): Promise> { return this.request(() => { const useCiede2000 = request.metric === 'ciede2000'; const distance = color_distance(request.color1, request.color2, useCiede2000); return { color1: request.color1, color2: request.color2, distance, metric: request.metric, }; }); } async sortColors(request: ColorSortRequest): Promise> { return this.request(() => { // Note: WASM version doesn't support sorting yet // Return colors as-is for now return { sorted: request.colors }; }); } // Accessibility async simulateColorBlindness(request: ColorBlindnessRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => { let output: string; switch (request.type) { case 'protanopia': output = simulate_protanopia(colorStr); break; case 'deuteranopia': output = simulate_deuteranopia(colorStr); break; case 'tritanopia': output = simulate_tritanopia(colorStr); break; default: output = colorStr; } const distance = color_distance(colorStr, output, true); return { input: colorStr, output, difference_percentage: (distance / 100.0) * 100.0, }; }); return { type: request.type, colors }; }); } async getTextColor(request: TextColorRequest): Promise> { return this.request(() => { const colors = request.backgrounds.map((bg) => { const textColor = get_text_color(bg); const contrastRatio = calculate_contrast(bg, textColor); return { background: bg, textcolor: textColor, contrast_ratio: contrastRatio, wcag_aa: contrastRatio >= 4.5, wcag_aaa: contrastRatio >= 7.0, }; }); return { colors }; }); } // Named Colors async getNamedColors(): Promise> { return this.request(() => { const colors = get_all_named_colors(); return { colors }; }); } async searchNamedColors(request: NamedColorSearchRequest): Promise> { return this.request(() => { const results = search_named_colors(request.query); return { results }; }); } // System async getHealth(): Promise> { return this.request(() => ({ status: 'healthy', version: version(), })); } async getCapabilities(): Promise> { return this.request(() => ({ endpoints: [ 'colors/info', 'colors/convert', 'colors/lighten', 'colors/darken', 'colors/saturate', 'colors/desaturate', 'colors/rotate', 'colors/complement', 'colors/grayscale', 'colors/mix', 'colors/random', 'colors/gradient', 'colors/colorblind', 'colors/textcolor', 'colors/distance', 'colors/names', ], formats: ['hex', 'rgb', 'hsl', 'hsv', 'lab', 'lch'], color_spaces: ['rgb', 'hsl', 'hsv', 'lab', 'lch'], distance_metrics: ['cie76', 'ciede2000'], colorblindness_types: ['protanopia', 'deuteranopia', 'tritanopia'], })); } // Palette Generation async generatePalette(request: PaletteGenerateRequest): Promise> { return this.request(() => { const colors = generate_palette(request.base, request.scheme); return { base: request.base, scheme: request.scheme, palette: { primary: colors[0], secondary: colors.slice(1), }, }; }); } } // Export singleton instance export const pastelWASM = new PastelWASMClient();