import type { Kysely, Transaction } from 'kysely'; import type { IdentityDatabaseSchema } from '../types/database'; import type { TopicAliasRecord, TopicRecord } from '../types/domain'; export type DatabaseExecutor = Kysely | Transaction; export interface ConnectedTopicRow extends TopicRecord { shared_fact_count: number; } export async function findTopicRowByNormalizedName( executor: DatabaseExecutor, normalizedName: string, ): Promise { return executor .selectFrom('topics') .selectAll() .where('normalized_name', '=', normalizedName) .executeTakeFirst(); } export async function findTopicRowByNormalizedAlias( executor: DatabaseExecutor, normalizedAlias: string, ): Promise { return executor .selectFrom('topic_aliases') .innerJoin('topics', 'topics.id', 'topic_aliases.topic_id') .selectAll('topics') .where('topic_aliases.normalized_alias', '=', normalizedAlias) .executeTakeFirst(); } export async function findTopicRowByNameOrAlias( executor: DatabaseExecutor, normalizedName: string, ): Promise { const directMatch = await findTopicRowByNormalizedName(executor, normalizedName); if (directMatch) { return directMatch; } return findTopicRowByNormalizedAlias(executor, normalizedName); } export async function listTopicAliasRowsForTopicId( executor: DatabaseExecutor, topicId: string, ): Promise { return executor .selectFrom('topic_aliases') .selectAll() .where('topic_id', '=', topicId) .orderBy('is_primary', 'desc') .orderBy('normalized_alias', 'asc') .execute(); } export async function listTopicRows( executor: DatabaseExecutor, limit?: number, ): Promise { let query = executor.selectFrom('topics').selectAll().orderBy('normalized_name', 'asc'); if (limit !== undefined) { query = query.limit(limit); } return query.execute(); } export async function findConnectedTopicRows( executor: DatabaseExecutor, topicId: string, ): Promise { return executor .selectFrom('fact_topics as source_link') .innerJoin('fact_topics as related_link', 'related_link.fact_id', 'source_link.fact_id') .innerJoin('topics', 'topics.id', 'related_link.topic_id') .selectAll('topics') .select((eb) => eb.fn.count('related_link.fact_id').as('shared_fact_count')) .where('source_link.topic_id', '=', topicId) .whereRef('related_link.topic_id', '!=', 'source_link.topic_id') .groupBy('topics.id') .orderBy('shared_fact_count', 'desc') .orderBy('topics.normalized_name', 'asc') .execute() as Promise; } export async function findChildTopicRows( executor: DatabaseExecutor, parentTopicId: string, ): Promise { return executor .selectFrom('topic_relations') .innerJoin('topics', 'topics.id', 'topic_relations.child_topic_id') .selectAll('topics') .where('topic_relations.parent_topic_id', '=', parentTopicId) .where('topic_relations.relation', '=', 'parent_of') .orderBy('topics.normalized_name', 'asc') .execute(); } export async function findParentTopicRows( executor: DatabaseExecutor, childTopicId: string, ): Promise { return executor .selectFrom('topic_relations') .innerJoin('topics', 'topics.id', 'topic_relations.parent_topic_id') .selectAll('topics') .where('topic_relations.child_topic_id', '=', childTopicId) .where('topic_relations.relation', '=', 'parent_of') .orderBy('topics.normalized_name', 'asc') .execute(); }