/** * SDK Catalog Manager - Central Registry * * Maps all SDK catalogs to a unified interface for browsing, searching, and CRUD. */ import type { CatalogId, CatalogMeta, CatalogModule, CatalogEntry, CatalogStats, CatalogOverviewStats, CustomCatalogEntry, CustomCatalogs, } from './types' // ============================================================================= // CATALOG DATA IMPORTS // ============================================================================= import { RISK_CATALOG } from '../dsfa/risk-catalog' import { MITIGATION_LIBRARY } from '../dsfa/mitigation-library' import { AI_RISK_CATALOG } from '../dsfa/ai-risk-catalog' import { AI_MITIGATION_LIBRARY } from '../dsfa/ai-mitigation-library' import { PROHIBITED_AI_PRACTICES } from '../dsfa/prohibited-ai-practices' import { EU_BASE_FRAMEWORKS, NATIONAL_FRAMEWORKS } from '../dsfa/eu-legal-frameworks' import { GDPR_ENFORCEMENT_CASES } from '../dsfa/gdpr-enforcement-cases' import { WP248_CRITERIA, SDM_GOALS, DSFA_AUTHORITY_RESOURCES } from '../dsfa/types' import { VVT_BASELINE_CATALOG } from '../vvt-baseline-catalog' import { BASELINE_TEMPLATES } from '../loeschfristen-baseline-catalog' import { VENDOR_TEMPLATES, COUNTRY_RISK_PROFILES } from '../vendor-compliance/catalog/vendor-templates' import { LEGAL_BASIS_INFO, STANDARD_RETENTION_PERIODS } from '../vendor-compliance/catalog/legal-basis' // ============================================================================= // HELPER: Resolve localized text fields // ============================================================================= function resolveField(value: unknown): string { if (value === null || value === undefined) return '' if (typeof value === 'string') return value if (typeof value === 'number') return String(value) if (typeof value === 'object' && 'de' in (value as Record)) { return String((value as Record).de || '') } return String(value) } // ============================================================================= // SDM_GOALS as entries array (it's a Record, not an array) // ============================================================================= const SDM_GOALS_ENTRIES = Object.entries(SDM_GOALS).map(([key, val]) => ({ id: key, name: val.name, description: val.description, article: val.article, })) // ============================================================================= // CATALOG REGISTRY // ============================================================================= export const CATALOG_REGISTRY: Record = { 'dsfa-risks': { id: 'dsfa-risks', name: 'DSFA Risikokatalog', description: 'Standardrisiken fuer Datenschutz-Folgenabschaetzungen', module: 'dsfa', icon: 'ShieldAlert', systemCount: RISK_CATALOG.length, allowCustom: true, idField: 'id', nameField: 'title', descriptionField: 'description', categoryField: 'category', fields: [ { key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-XXX-01' }, { key: 'title', label: 'Titel', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'category', label: 'Kategorie', type: 'select', required: true, options: [ { value: 'confidentiality', label: 'Vertraulichkeit' }, { value: 'integrity', label: 'Integritaet' }, { value: 'availability', label: 'Verfuegbarkeit' }, { value: 'rights_freedoms', label: 'Rechte & Freiheiten' }, ]}, { key: 'typicalLikelihood', label: 'Typische Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [ { value: 'low', label: 'Niedrig' }, { value: 'medium', label: 'Mittel' }, { value: 'high', label: 'Hoch' }, ]}, { key: 'typicalImpact', label: 'Typische Auswirkung', type: 'select', required: false, options: [ { value: 'low', label: 'Niedrig' }, { value: 'medium', label: 'Mittel' }, { value: 'high', label: 'Hoch' }, ]}, ], searchableFields: ['id', 'title', 'description', 'category'], }, 'dsfa-mitigations': { id: 'dsfa-mitigations', name: 'DSFA Massnahmenbibliothek', description: 'Technische und organisatorische Massnahmen fuer DSFAs', module: 'dsfa', icon: 'Shield', systemCount: MITIGATION_LIBRARY.length, allowCustom: true, idField: 'id', nameField: 'title', descriptionField: 'description', categoryField: 'type', fields: [ { key: 'id', label: 'Massnahmen-ID', type: 'text', required: true, placeholder: 'M-XXX-01' }, { key: 'title', label: 'Titel', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'type', label: 'Typ', type: 'select', required: true, options: [ { value: 'technical', label: 'Technisch' }, { value: 'organizational', label: 'Organisatorisch' }, { value: 'legal', label: 'Rechtlich' }, ]}, { key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [ { value: 'low', label: 'Niedrig' }, { value: 'medium', label: 'Mittel' }, { value: 'high', label: 'Hoch' }, ]}, { key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false }, ], searchableFields: ['id', 'title', 'description', 'type', 'legalBasis'], }, 'ai-risks': { id: 'ai-risks', name: 'KI-Risikokatalog', description: 'Spezifische Risiken fuer KI-Systeme', module: 'ai_act', icon: 'Bot', systemCount: AI_RISK_CATALOG.length, allowCustom: true, idField: 'id', nameField: 'title', descriptionField: 'description', categoryField: 'category', fields: [ { key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-AI-XXX-01' }, { key: 'title', label: 'Titel', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'category', label: 'Kategorie', type: 'select', required: true, options: [ { value: 'confidentiality', label: 'Vertraulichkeit' }, { value: 'integrity', label: 'Integritaet' }, { value: 'availability', label: 'Verfuegbarkeit' }, { value: 'rights_freedoms', label: 'Rechte & Freiheiten' }, ]}, { key: 'typicalLikelihood', label: 'Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [ { value: 'low', label: 'Niedrig' }, { value: 'medium', label: 'Mittel' }, { value: 'high', label: 'Hoch' }, ]}, { key: 'typicalImpact', label: 'Auswirkung', type: 'select', required: false, options: [ { value: 'low', label: 'Niedrig' }, { value: 'medium', label: 'Mittel' }, { value: 'high', label: 'Hoch' }, ]}, ], searchableFields: ['id', 'title', 'description', 'category'], }, 'ai-mitigations': { id: 'ai-mitigations', name: 'KI-Massnahmenbibliothek', description: 'Massnahmen fuer KI-spezifische Risiken', module: 'ai_act', icon: 'ShieldCheck', systemCount: AI_MITIGATION_LIBRARY.length, allowCustom: true, idField: 'id', nameField: 'title', descriptionField: 'description', categoryField: 'type', fields: [ { key: 'id', label: 'Massnahmen-ID', type: 'text', required: true }, { key: 'title', label: 'Titel', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'type', label: 'Typ', type: 'select', required: true, options: [ { value: 'technical', label: 'Technisch' }, { value: 'organizational', label: 'Organisatorisch' }, { value: 'legal', label: 'Rechtlich' }, ]}, { key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [ { value: 'low', label: 'Niedrig' }, { value: 'medium', label: 'Mittel' }, { value: 'high', label: 'Hoch' }, ]}, ], searchableFields: ['id', 'title', 'description', 'type'], }, 'prohibited-ai-practices': { id: 'prohibited-ai-practices', name: 'Verbotene KI-Praktiken', description: 'Absolut und bedingt verbotene KI-Anwendungen nach AI Act', module: 'ai_act', icon: 'Ban', systemCount: PROHIBITED_AI_PRACTICES.length, allowCustom: false, idField: 'id', nameField: 'title', descriptionField: 'description', categoryField: 'severity', fields: [ { key: 'id', label: 'ID', type: 'text', required: true }, { key: 'title', label: 'Titel', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'severity', label: 'Schwere', type: 'select', required: true, options: [ { value: 'absolute', label: 'Absolutes Verbot' }, { value: 'conditional', label: 'Bedingtes Verbot' }, ]}, { key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false }, ], searchableFields: ['id', 'title', 'description', 'severity', 'legalBasis'], }, 'vvt-templates': { id: 'vvt-templates', name: 'VVT Baseline-Vorlagen', description: 'Vorlagen fuer Verarbeitungstaetigkeiten', module: 'vvt', icon: 'FileText', systemCount: VVT_BASELINE_CATALOG.length, allowCustom: true, idField: 'templateId', nameField: 'name', descriptionField: 'description', categoryField: 'businessFunction', fields: [ { key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true }, { key: 'name', label: 'Name', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'businessFunction', label: 'Geschaeftsbereich', type: 'select', required: true, options: [ { value: 'hr', label: 'Personal' }, { value: 'finance', label: 'Finanzen' }, { value: 'sales', label: 'Vertrieb' }, { value: 'marketing', label: 'Marketing' }, { value: 'support', label: 'Support' }, { value: 'it', label: 'IT' }, { value: 'other', label: 'Sonstige' }, ]}, { key: 'protectionLevel', label: 'Schutzniveau', type: 'select', required: false, options: [ { value: 'LOW', label: 'Niedrig' }, { value: 'MEDIUM', label: 'Mittel' }, { value: 'HIGH', label: 'Hoch' }, ]}, ], searchableFields: ['templateId', 'name', 'description', 'businessFunction'], }, 'loeschfristen-templates': { id: 'loeschfristen-templates', name: 'Loeschfristen-Vorlagen', description: 'Baseline-Vorlagen fuer Aufbewahrungsfristen', module: 'vvt', icon: 'Clock', systemCount: BASELINE_TEMPLATES.length, allowCustom: true, idField: 'templateId', nameField: 'dataObjectName', descriptionField: 'description', categoryField: 'retentionDriver', fields: [ { key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true }, { key: 'dataObjectName', label: 'Datenobjekt', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'retentionDuration', label: 'Aufbewahrungsdauer', type: 'number', required: true, min: 0 }, { key: 'retentionUnit', label: 'Einheit', type: 'select', required: true, options: [ { value: 'days', label: 'Tage' }, { value: 'months', label: 'Monate' }, { value: 'years', label: 'Jahre' }, ]}, { key: 'deletionMethod', label: 'Loeschmethode', type: 'text', required: false }, { key: 'responsibleRole', label: 'Verantwortlich', type: 'text', required: false }, ], searchableFields: ['templateId', 'dataObjectName', 'description', 'retentionDriver'], }, 'vendor-templates': { id: 'vendor-templates', name: 'AV-Vorlagen', description: 'Vorlagen fuer Auftragsverarbeitungsvertraege', module: 'vendor', icon: 'Building2', systemCount: VENDOR_TEMPLATES.length, allowCustom: true, idField: 'id', nameField: 'name', descriptionField: 'description', categoryField: 'serviceCategory', fields: [ { key: 'id', label: 'ID', type: 'text', required: true }, { key: 'name', label: 'Name', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'serviceCategory', label: 'Kategorie', type: 'text', required: true }, ], searchableFields: ['id', 'name', 'description', 'serviceCategory'], }, 'country-risk-profiles': { id: 'country-risk-profiles', name: 'Laenderrisikoprofile', description: 'Datenschutz-Risikobewertung nach Laendern', module: 'vendor', icon: 'Globe', systemCount: COUNTRY_RISK_PROFILES.length, allowCustom: false, idField: 'code', nameField: 'name', categoryField: 'riskLevel', fields: [ { key: 'code', label: 'Laendercode', type: 'text', required: true }, { key: 'name', label: 'Land', type: 'text', required: true }, { key: 'riskLevel', label: 'Risikostufe', type: 'select', required: true, options: [ { value: 'LOW', label: 'Niedrig' }, { value: 'MEDIUM', label: 'Mittel' }, { value: 'HIGH', label: 'Hoch' }, { value: 'VERY_HIGH', label: 'Sehr hoch' }, ]}, { key: 'isEU', label: 'EU-Mitglied', type: 'boolean', required: false }, { key: 'isEEA', label: 'EWR-Mitglied', type: 'boolean', required: false }, { key: 'hasAdequacyDecision', label: 'Angemessenheitsbeschluss', type: 'boolean', required: false }, ], searchableFields: ['code', 'name', 'riskLevel'], }, 'legal-bases': { id: 'legal-bases', name: 'Rechtsgrundlagen', description: 'DSGVO Art. 6 und Art. 9 Rechtsgrundlagen', module: 'reference', icon: 'Scale', systemCount: LEGAL_BASIS_INFO.length, allowCustom: false, idField: 'type', nameField: 'name', descriptionField: 'description', categoryField: 'article', fields: [ { key: 'type', label: 'Typ', type: 'text', required: true }, { key: 'article', label: 'Artikel', type: 'text', required: true }, { key: 'name', label: 'Name', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'isSpecialCategory', label: 'Besondere Kategorie (Art. 9)', type: 'boolean', required: false }, ], searchableFields: ['type', 'article', 'name', 'description'], }, 'retention-periods': { id: 'retention-periods', name: 'Aufbewahrungsfristen', description: 'Gesetzliche Standard-Aufbewahrungsfristen', module: 'reference', icon: 'Timer', systemCount: STANDARD_RETENTION_PERIODS.length, allowCustom: false, idField: 'id', nameField: 'name', descriptionField: 'description', fields: [ { key: 'id', label: 'ID', type: 'text', required: true }, { key: 'name', label: 'Name', type: 'text', required: true }, { key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, ], searchableFields: ['id', 'name', 'legalBasis', 'description'], }, 'eu-legal-frameworks': { id: 'eu-legal-frameworks', name: 'EU-Rechtsrahmen', description: 'EU-weite Datenschutzgesetze und -verordnungen', module: 'reference', icon: 'Landmark', systemCount: EU_BASE_FRAMEWORKS.length, allowCustom: false, idField: 'id', nameField: 'name', descriptionField: 'description', categoryField: 'type', fields: [ { key: 'id', label: 'ID', type: 'text', required: true }, { key: 'name', label: 'Name', type: 'text', required: true }, { key: 'fullName', label: 'Vollstaendiger Name', type: 'text', required: true }, { key: 'type', label: 'Typ', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, ], searchableFields: ['id', 'name', 'fullName', 'description', 'type'], }, 'national-legal-frameworks': { id: 'national-legal-frameworks', name: 'Nationale Rechtsrahmen', description: 'Nationale Datenschutzgesetze der EU/EWR-Staaten', module: 'reference', icon: 'Flag', systemCount: NATIONAL_FRAMEWORKS.length, allowCustom: false, idField: 'id', nameField: 'name', descriptionField: 'description', categoryField: 'countryCode', fields: [ { key: 'id', label: 'ID', type: 'text', required: true }, { key: 'name', label: 'Name', type: 'text', required: true }, { key: 'countryCode', label: 'Land', type: 'text', required: true }, { key: 'type', label: 'Typ', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, ], searchableFields: ['id', 'name', 'countryCode', 'description', 'type'], }, 'gdpr-enforcement-cases': { id: 'gdpr-enforcement-cases', name: 'DSGVO-Bussgeldentscheidungen', description: 'Relevante Bussgeldentscheidungen als Referenz', module: 'reference', icon: 'Gavel', systemCount: GDPR_ENFORCEMENT_CASES.length, allowCustom: false, idField: 'id', nameField: 'company', descriptionField: 'description', categoryField: 'country', fields: [ { key: 'id', label: 'ID', type: 'text', required: true }, { key: 'company', label: 'Unternehmen', type: 'text', required: true }, { key: 'country', label: 'Land', type: 'text', required: true }, { key: 'year', label: 'Jahr', type: 'number', required: true }, { key: 'fineOriginal', label: 'Bussgeld (EUR)', type: 'number', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, ], searchableFields: ['id', 'company', 'country', 'description'], }, 'wp248-criteria': { id: 'wp248-criteria', name: 'WP248 Kriterien', description: 'Kriterien zur DSFA-Pflichtpruefung nach WP248', module: 'dsfa', icon: 'ClipboardCheck', systemCount: WP248_CRITERIA.length, allowCustom: false, idField: 'code', nameField: 'title', descriptionField: 'description', fields: [ { key: 'code', label: 'Code', type: 'text', required: true }, { key: 'title', label: 'Titel', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'gdprRef', label: 'DSGVO-Referenz', type: 'text', required: false }, ], searchableFields: ['code', 'title', 'description', 'gdprRef'], }, 'sdm-goals': { id: 'sdm-goals', name: 'SDM Gewaehrleistungsziele', description: 'Standard-Datenschutzmodell Gewaehrleistungsziele', module: 'dsfa', icon: 'Target', systemCount: SDM_GOALS_ENTRIES.length, allowCustom: false, idField: 'id', nameField: 'name', descriptionField: 'description', fields: [ { key: 'id', label: 'ID', type: 'text', required: true }, { key: 'name', label: 'Name', type: 'text', required: true }, { key: 'description', label: 'Beschreibung', type: 'textarea', required: true }, { key: 'article', label: 'DSGVO-Artikel', type: 'text', required: false }, ], searchableFields: ['id', 'name', 'description', 'article'], }, 'dsfa-authority-resources': { id: 'dsfa-authority-resources', name: 'Aufsichtsbehoerden-Ressourcen', description: 'DSFA-Ressourcen der deutschen Aufsichtsbehoerden', module: 'dsfa', icon: 'Building', systemCount: DSFA_AUTHORITY_RESOURCES.length, allowCustom: false, idField: 'id', nameField: 'shortName', descriptionField: 'name', categoryField: 'state', fields: [ { key: 'id', label: 'ID', type: 'text', required: true }, { key: 'shortName', label: 'Kurzname', type: 'text', required: true }, { key: 'name', label: 'Voller Name', type: 'text', required: true }, { key: 'state', label: 'Bundesland', type: 'text', required: true }, ], searchableFields: ['id', 'shortName', 'name', 'state'], }, } // ============================================================================= // SYSTEM ENTRIES MAP // ============================================================================= const SYSTEM_ENTRIES_MAP: Record[]> = { 'dsfa-risks': RISK_CATALOG as unknown as Record[], 'dsfa-mitigations': MITIGATION_LIBRARY as unknown as Record[], 'ai-risks': AI_RISK_CATALOG as unknown as Record[], 'ai-mitigations': AI_MITIGATION_LIBRARY as unknown as Record[], 'prohibited-ai-practices': PROHIBITED_AI_PRACTICES as unknown as Record[], 'vvt-templates': VVT_BASELINE_CATALOG as unknown as Record[], 'loeschfristen-templates': BASELINE_TEMPLATES as unknown as Record[], 'vendor-templates': VENDOR_TEMPLATES as unknown as Record[], 'country-risk-profiles': COUNTRY_RISK_PROFILES as unknown as Record[], 'legal-bases': LEGAL_BASIS_INFO as unknown as Record[], 'retention-periods': STANDARD_RETENTION_PERIODS as unknown as Record[], 'eu-legal-frameworks': EU_BASE_FRAMEWORKS as unknown as Record[], 'national-legal-frameworks': NATIONAL_FRAMEWORKS as unknown as Record[], 'gdpr-enforcement-cases': GDPR_ENFORCEMENT_CASES as unknown as Record[], 'wp248-criteria': WP248_CRITERIA as unknown as Record[], 'sdm-goals': SDM_GOALS_ENTRIES as unknown as Record[], 'dsfa-authority-resources': DSFA_AUTHORITY_RESOURCES as unknown as Record[], } // ============================================================================= // ENTRY CONVERTERS // ============================================================================= function systemEntryToCatalogEntry( catalogId: CatalogId, data: Record, ): CatalogEntry { const meta = CATALOG_REGISTRY[catalogId] const idValue = resolveField(data[meta.idField]) const nameValue = resolveField(data[meta.nameField]) const descValue = meta.descriptionField ? resolveField(data[meta.descriptionField]) : undefined const catValue = meta.categoryField ? resolveField(data[meta.categoryField]) : undefined return { id: idValue || crypto.randomUUID(), catalogId, source: 'system', data, displayName: nameValue || idValue || '(Unbenannt)', displayDescription: descValue, category: catValue, } } function customEntryToCatalogEntry( catalogId: CatalogId, entry: CustomCatalogEntry, ): CatalogEntry { const meta = CATALOG_REGISTRY[catalogId] const nameValue = resolveField(entry.data[meta.nameField]) const descValue = meta.descriptionField ? resolveField(entry.data[meta.descriptionField]) : undefined const catValue = meta.categoryField ? resolveField(entry.data[meta.categoryField]) : undefined return { id: entry.id, catalogId, source: 'custom', data: entry.data, displayName: nameValue || '(Unbenannt)', displayDescription: descValue, category: catValue, } } // ============================================================================= // PUBLIC API // ============================================================================= export function getSystemEntries(catalogId: CatalogId): CatalogEntry[] { const raw = SYSTEM_ENTRIES_MAP[catalogId] || [] return raw.map(data => systemEntryToCatalogEntry(catalogId, data)) } export function getAllEntries( catalogId: CatalogId, customEntries: CustomCatalogEntry[] = [], ): CatalogEntry[] { const system = getSystemEntries(catalogId) const custom = customEntries.map(e => customEntryToCatalogEntry(catalogId, e)) return [...system, ...custom] } export function getCatalogsByModule(module: CatalogModule): CatalogMeta[] { return Object.values(CATALOG_REGISTRY).filter(c => c.module === module) } export function getCatalogStats( catalogId: CatalogId, customEntries: CustomCatalogEntry[] = [], ): CatalogStats { const meta = CATALOG_REGISTRY[catalogId] return { catalogId, systemCount: meta.systemCount, customCount: customEntries.length, totalCount: meta.systemCount + customEntries.length, } } export function getOverviewStats(customCatalogs: CustomCatalogs): CatalogOverviewStats { const modules: CatalogModule[] = ['dsfa', 'vvt', 'vendor', 'ai_act', 'reference'] const byModule = {} as Record let totalSystemEntries = 0 let totalCustomEntries = 0 for (const mod of modules) { const cats = getCatalogsByModule(mod) let entries = 0 for (const cat of cats) { const customCount = customCatalogs[cat.id]?.length ?? 0 entries += cat.systemCount + customCount totalSystemEntries += cat.systemCount totalCustomEntries += customCount } byModule[mod] = { catalogs: cats.length, entries } } return { totalCatalogs: Object.keys(CATALOG_REGISTRY).length, totalSystemEntries, totalCustomEntries, totalEntries: totalSystemEntries + totalCustomEntries, byModule, } } export function searchCatalog( catalogId: CatalogId, query: string, customEntries: CustomCatalogEntry[] = [], ): CatalogEntry[] { const allEntries = getAllEntries(catalogId, customEntries) const meta = CATALOG_REGISTRY[catalogId] const q = query.toLowerCase().trim() if (!q) return allEntries return allEntries .map(entry => { let score = 0 for (const field of meta.searchableFields) { const value = resolveField(entry.data[field]).toLowerCase() if (value.includes(q)) { score += value.startsWith(q) ? 10 : 5 } } // Also search display name if (entry.displayName.toLowerCase().includes(q)) { score += 15 } return { entry, score } }) .filter(r => r.score > 0) .sort((a, b) => b.score - a.score) .map(r => r.entry) }