import { getDatabase } from '$lib/server/database'; import { getRegistrySearch } from '$lib/server/pagination'; import { databaseCategories } from '$lib/database/categories'; import { loadMechanicPublicProjectionsForOwners, type MechanicPublicProjectionOwnerRef, type MechanicPublicProjectionsByOwner, } from '$lib/server/mechanic-public-projection'; import { databaseMechanicGroup, databaseMechanicOwnersMatchSearch, databaseMechanicSearchSummary, } from './public-mechanics'; import type { PageServerLoad } from './$types'; const tables = { gear: 'gear', weapons: 'weapons', 'weapon-mods': 'weapon_mods', 'gear-mods': 'gear_mods', 'os-protocols': 'os_protocols', talents: 'talents', brands: 'brand_sets', 'skill-chips': 'skill_chip_sets', specializations: 'specializations', } as const; export const load: PageServerLoad = async ({ url }) => { const database = await getDatabase(); const { search, pattern } = getRegistrySearch(url); const counts = await Promise.all( databaseCategories.map(async (category) => { const row = await ( await database.prepare( `SELECT COUNT(*) AS count FROM ${tables[category.slug]} WHERE active=1`, ) ).get(); return { ...category, count: Number(row?.count ?? 0) }; }), ); const rawSearchResults = search ? await ( await database.prepare(` SELECT * FROM ( SELECT 'gear' AS slug,'Gear' AS category,item.id AS parent_id,item.name, item.slot || ' · T' || item.tier AS detail FROM gear item WHERE item.active=1 AND ( item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.slot LIKE ? ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM gear_talents link JOIN talents linked_talent ON linked_talent.id=link.talent_id AND linked_talent.active=1 JOIN mechanic_authored_sources source ON source.owner_type='talent' AND source.owner_id=linked_talent.id JOIN mechanic_public_variants variant ON variant.source_id=source.id WHERE link.gear_id=item.id AND variant.behavior_text LIKE ? ESCAPE '\\' COLLATE NOCASE ) ) UNION ALL SELECT 'weapons','Weapons',item.id,item.name, item.category || ' · ' || item.damage_type || ' damage' FROM weapons item WHERE item.active=1 AND ( item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.category LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.damage_type LIKE ? ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM talents linked_talent JOIN mechanic_authored_sources source ON source.owner_type='talent' AND source.owner_id=linked_talent.id JOIN mechanic_public_variants variant ON variant.source_id=source.id WHERE linked_talent.active=1 AND ( linked_talent.id=item.exotic_talent_id OR EXISTS ( SELECT 1 FROM weapon_talent_options link WHERE link.weapon_id=item.id AND link.talent_id=linked_talent.id ) ) AND variant.behavior_text LIKE ? ESCAPE '\\' COLLATE NOCASE ) ) UNION ALL SELECT 'weapon-mods','Weapon Mods',item.id,item.name,item.slot_type FROM weapon_mods item WHERE item.active=1 AND ( item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.slot_type LIKE ? ESCAPE '\\' COLLATE NOCASE ) UNION ALL SELECT 'gear-mods','Gear Mods',item.id,item.name,'Gear modification' FROM gear_mods item WHERE item.active=1 AND item.name LIKE ? ESCAPE '\\' COLLATE NOCASE UNION ALL SELECT 'os-protocols','OS Protocols',item.id,item.name, item.quality || ' · ' || item.primary_attribute_type FROM os_protocols item WHERE item.active=1 AND ( item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.quality LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.primary_attribute_type LIKE ? ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM os_protocol_talents talent WHERE talent.protocol_id=item.id AND ( talent.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR talent.description_template LIKE ? ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM mechanic_authored_sources source JOIN mechanic_public_variants variant ON variant.source_id=source.id WHERE source.owner_type='os_protocol_talent' AND source.owner_id=talent.id AND variant.behavior_text LIKE ? ESCAPE '\\' COLLATE NOCASE ) ) ) ) UNION ALL SELECT 'talents','Talents',item.id,item.name,item.category FROM talents item WHERE item.active=1 AND ( item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.category LIKE ? ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM talent_tiers tier WHERE tier.talent_id=item.id AND ( tier.description LIKE ? ESCAPE '\\' COLLATE NOCASE OR tier.trigger_text LIKE ? ESCAPE '\\' COLLATE NOCASE OR tier.effect_text LIKE ? ESCAPE '\\' COLLATE NOCASE ) ) OR EXISTS ( SELECT 1 FROM mechanic_authored_sources source JOIN mechanic_public_variants variant ON variant.source_id=source.id WHERE source.owner_type='talent' AND source.owner_id=item.id AND variant.behavior_text LIKE ? ESCAPE '\\' COLLATE NOCASE ) ) UNION ALL SELECT 'brands','Brands',item.id,item.name, COALESCE(( SELECT GROUP_CONCAT(attribute.name, ', ') FROM brand_set_effects effect JOIN stat_attributes attribute ON attribute.id=effect.stat_attribute_id WHERE effect.brand_set_id=item.id ),'Brand Set bonuses') FROM brand_sets item WHERE item.active=1 AND ( item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM brand_set_effects effect JOIN stat_attributes attribute ON attribute.id=effect.stat_attribute_id WHERE effect.brand_set_id=item.id AND attribute.name LIKE ? ESCAPE '\\' COLLATE NOCASE ) ) UNION ALL SELECT 'skill-chips','Skill Chips',item.id,item.name, COALESCE((SELECT talent.name FROM skill_chip_talents talent WHERE talent.skill_chip_set_id=item.id),'Skill Chip set') FROM skill_chip_sets item WHERE item.active=1 AND ( item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM skill_chip_talents talent WHERE talent.skill_chip_set_id=item.id AND ( talent.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR talent.description_template LIKE ? ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM mechanic_authored_sources source JOIN mechanic_public_variants variant ON variant.source_id=source.id WHERE source.owner_type='skill_chip_talent' AND source.owner_id=talent.id AND variant.behavior_text LIKE ? ESCAPE '\\' COLLATE NOCASE ) ) ) ) UNION ALL SELECT 'specializations','Specializations',item.id,item.name,'Specialization' FROM specializations item WHERE item.active=1 AND ( item.name LIKE ? ESCAPE '\\' COLLATE NOCASE OR item.description LIKE ? ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM specialization_focuses focus WHERE focus.specialization_id=item.id AND focus.active=1 AND focus.name LIKE ? ESCAPE '\\' COLLATE NOCASE ) OR EXISTS ( SELECT 1 FROM specialization_abilities ability WHERE ability.specialization_id=item.id AND ability.active=1 AND ability.name LIKE ? ESCAPE '\\' COLLATE NOCASE ) OR EXISTS ( SELECT 1 FROM specialization_tree_rank_combat_effects attachment JOIN specialization_tree_nodes node ON node.id=attachment.node_id AND node.active=1 JOIN specialization_focuses focus ON focus.id=node.focus_id AND focus.active=1 JOIN specialization_combat_effects combat_effect ON combat_effect.id=attachment.combat_effect_id AND combat_effect.specialization_id=focus.specialization_id AND combat_effect.active=1 JOIN mechanic_authored_sources source ON source.owner_type='specialization_tree_rank' AND source.owner_id=attachment.id JOIN mechanic_public_variants variant ON variant.source_id=source.id WHERE focus.specialization_id=item.id AND variant.behavior_text LIKE ? ESCAPE '\\' COLLATE NOCASE ) ) ) matches ORDER BY name COLLATE NOCASE `) ).all(...Array(33).fill(pattern)) : []; // Raw projection text produces a candidate set only. Canonical projections // below decide behavior matches; this query preserves independent metadata matches. const metadataSearchResultKeys = search ? new Set( ( await ( await database.prepare(`WITH search(pattern) AS (VALUES (?)) SELECT 'gear' AS slug,item.id AS parent_id FROM gear item,search WHERE item.active=1 AND ( item.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR item.slot LIKE search.pattern ESCAPE '\\' COLLATE NOCASE ) UNION ALL SELECT 'weapons',item.id FROM weapons item,search WHERE item.active=1 AND ( item.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR item.category LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR item.damage_type LIKE search.pattern ESCAPE '\\' COLLATE NOCASE ) UNION ALL SELECT 'weapon-mods',item.id FROM weapon_mods item,search WHERE item.active=1 AND ( item.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR item.slot_type LIKE search.pattern ESCAPE '\\' COLLATE NOCASE ) UNION ALL SELECT 'gear-mods',item.id FROM gear_mods item,search WHERE item.active=1 AND item.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE UNION ALL SELECT 'os-protocols',item.id FROM os_protocols item,search WHERE item.active=1 AND ( item.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR item.quality LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR item.primary_attribute_type LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM os_protocol_talents talent WHERE talent.protocol_id=item.id AND ( talent.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR talent.description_template LIKE search.pattern ESCAPE '\\' COLLATE NOCASE ) ) ) UNION ALL SELECT 'talents',item.id FROM talents item,search WHERE item.active=1 AND ( item.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR item.category LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM talent_tiers tier WHERE tier.talent_id=item.id AND ( tier.description LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR tier.trigger_text LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR tier.effect_text LIKE search.pattern ESCAPE '\\' COLLATE NOCASE ) ) ) UNION ALL SELECT 'brands',item.id FROM brand_sets item,search WHERE item.active=1 AND ( item.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM brand_set_effects effect JOIN stat_attributes attribute ON attribute.id=effect.stat_attribute_id WHERE effect.brand_set_id=item.id AND attribute.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE ) ) UNION ALL SELECT 'skill-chips',item.id FROM skill_chip_sets item,search WHERE item.active=1 AND ( item.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM skill_chip_talents talent WHERE talent.skill_chip_set_id=item.id AND ( talent.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR talent.description_template LIKE search.pattern ESCAPE '\\' COLLATE NOCASE ) ) ) UNION ALL SELECT 'specializations',item.id FROM specializations item,search WHERE item.active=1 AND ( item.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR item.description LIKE search.pattern ESCAPE '\\' COLLATE NOCASE OR EXISTS ( SELECT 1 FROM specialization_focuses focus WHERE focus.specialization_id=item.id AND focus.active=1 AND focus.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE ) OR EXISTS ( SELECT 1 FROM specialization_abilities ability WHERE ability.specialization_id=item.id AND ability.active=1 AND ability.name LIKE search.pattern ESCAPE '\\' COLLATE NOCASE ) )`) ).all(pattern) ).map((result) => `${result.slug}\u0000${result.parent_id}`), ) : new Set(); const resultIds = (slug: string) => rawSearchResults .filter((result) => String(result.slug) === slug) .map((result) => String(result.parent_id)); const [ gearTalentRows, weaponTalentRows, protocolTalentRows, skillChipTalentRows, specializationAttachmentRows, ] = await Promise.all([ resultIds('gear').length ? ( await database.prepare(`SELECT link.gear_id,link.talent_id FROM gear_talents link JOIN talents ON talents.id=link.talent_id AND talents.active=1 WHERE link.gear_id IN (${resultIds('gear') .map(() => '?') .join(',')}) ORDER BY link.gear_id,link.talent_id`) ).all(...resultIds('gear')) : [], resultIds('weapons').length ? ( await database.prepare(`SELECT relation.weapon_id,relation.talent_id FROM ( SELECT weapon_id,talent_slot,talent_id FROM weapon_talent_options UNION ALL SELECT id AS weapon_id,NULL AS talent_slot,exotic_talent_id AS talent_id FROM weapons WHERE exotic_talent_id IS NOT NULL ) relation JOIN talents ON talents.id=relation.talent_id AND talents.active=1 WHERE relation.weapon_id IN (${resultIds('weapons') .map(() => '?') .join(',')}) ORDER BY relation.weapon_id,relation.talent_slot,relation.talent_id`) ).all(...resultIds('weapons')) : [], resultIds('os-protocols').length ? ( await database.prepare(`SELECT protocol_id,id FROM os_protocol_talents WHERE protocol_id IN (${resultIds('os-protocols') .map(() => '?') .join(',')}) ORDER BY protocol_id,id`) ).all(...resultIds('os-protocols')) : [], resultIds('skill-chips').length ? ( await database.prepare(`SELECT skill_chip_set_id,id FROM skill_chip_talents WHERE skill_chip_set_id IN (${resultIds('skill-chips') .map(() => '?') .join(',')}) ORDER BY skill_chip_set_id,id`) ).all(...resultIds('skill-chips')) : [], resultIds('specializations').length ? ( await database.prepare(`SELECT attachment.id,focus.specialization_id FROM specialization_tree_rank_combat_effects attachment JOIN specialization_tree_nodes node ON node.id=attachment.node_id AND node.active=1 JOIN specialization_focuses focus ON focus.id=node.focus_id AND focus.active=1 JOIN specialization_combat_effects combat_effect ON combat_effect.id=attachment.combat_effect_id AND combat_effect.specialization_id=focus.specialization_id AND combat_effect.active=1 WHERE focus.specialization_id IN (${resultIds('specializations') .map(() => '?') .join(',')}) ORDER BY focus.specialization_id,focus.display_order,focus.name COLLATE NOCASE, node.row_number,node.column_number,attachment.rank,attachment.effect_order`) ).all(...resultIds('specializations')) : [], ]); const resultKey = (slug: string, id: string) => `${slug}\u0000${id}`; const ownersByResult = new Map(); const addOwner = ( slug: string, parentId: string, owner: MechanicPublicProjectionOwnerRef, ) => { const key = resultKey(slug, parentId); ownersByResult.set(key, [...(ownersByResult.get(key) ?? []), owner]); }; for (const result of rawSearchResults) { if (String(result.slug) === 'talents') { addOwner('talents', String(result.parent_id), { type: 'talent', id: String(result.parent_id), }); } } for (const row of gearTalentRows) { addOwner('gear', String(row.gear_id), { type: 'talent', id: String(row.talent_id), }); } for (const row of weaponTalentRows) { addOwner('weapons', String(row.weapon_id), { type: 'talent', id: String(row.talent_id), }); } for (const row of protocolTalentRows) { addOwner('os-protocols', String(row.protocol_id), { type: 'os_protocol_talent', id: String(row.id), }); } for (const row of skillChipTalentRows) { addOwner('skill-chips', String(row.skill_chip_set_id), { type: 'skill_chip_talent', id: String(row.id), }); } for (const row of specializationAttachmentRows) { addOwner('specializations', String(row.specialization_id), { type: 'specialization_tree_rank', id: String(row.id), }); } const owners = [ ...new Map( [...ownersByResult.values()] .flat() .map((owner) => [`${owner.type}\u0000${owner.id}`, owner]), ).values(), ]; const publicMechanics: MechanicPublicProjectionsByOwner = owners.length ? await loadMechanicPublicProjectionsForOwners(database, owners) : ({} as MechanicPublicProjectionsByOwner); const searchResults = rawSearchResults .filter( (result) => metadataSearchResultKeys.has( resultKey(String(result.slug), String(result.parent_id)), ) || databaseMechanicOwnersMatchSearch( ownersByResult.get( resultKey(String(result.slug), String(result.parent_id)), ) ?? [], publicMechanics, search, ), ) .slice(0, 40); const normalizedSearch = search.toLocaleLowerCase(); return { registries: counts, total: counts.reduce((total, registry) => total + registry.count, 0), search, searchResults: searchResults.map((result) => { const groups = ( ownersByResult.get( resultKey(String(result.slug), String(result.parent_id)), ) ?? [] ) .map((owner) => databaseMechanicGroup(owner, publicMechanics, { label: 'BEHAVIOR' }), ) .filter((group) => group !== null); const group = groups.find((candidate) => candidate.variants.some((variant) => variant.behaviorText.toLocaleLowerCase().includes(normalizedSearch), ), ) ?? groups[0] ?? null; const behavior = databaseMechanicSearchSummary(group); return { slug: String(result.slug), category: String(result.category), name: String(result.name), detail: String(result.detail ?? ''), ...(behavior ?? {}), }; }), }; };