refactor: introduce domain model classes and mapper layer
Replace ad-hoc inline row casting (snake_case → camelCase) spread across services, routes, and the indexing pipeline with explicit model classes (Repository, IndexingJob, RepositoryVersion, Snippet, SearchResult) and dedicated mapper classes that own the DB → domain conversion. - Add src/lib/server/models/ with typed model classes for all domain entities - Add src/lib/server/mappers/ with mapper classes per entity - Remove duplicated RawRow interfaces and inline map functions from job-queue, repository.service, indexing.pipeline, and all API routes - Add dtoJsonResponse helper to standardise JSON responses via SvelteKit json() - Add api-contract.integration.test.ts as a regression baseline Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
71
src/lib/server/mappers/context-response.mapper.ts
Normal file
71
src/lib/server/mappers/context-response.mapper.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
CodeListItemDto,
|
||||
CodeSnippetJsonDto,
|
||||
ContextJsonResponseDto,
|
||||
InfoSnippetJsonDto,
|
||||
LibrarySearchJsonResponseDto,
|
||||
LibrarySearchJsonResultDto,
|
||||
type SnippetJsonDto
|
||||
} from '$lib/server/models/context-response.js';
|
||||
import { LibrarySearchResult, SnippetSearchResult } from '$lib/server/models/search-result.js';
|
||||
|
||||
export class ContextResponseMapper {
|
||||
static toLibrarySearchJson(results: LibrarySearchResult[]): LibrarySearchJsonResponseDto {
|
||||
return new LibrarySearchJsonResponseDto(
|
||||
results.map(
|
||||
({ repository, versions }) =>
|
||||
new LibrarySearchJsonResultDto({
|
||||
id: repository.id,
|
||||
title: repository.title,
|
||||
description: repository.description ?? null,
|
||||
branch: repository.branch ?? null,
|
||||
lastUpdateDate: repository.lastIndexedAt
|
||||
? repository.lastIndexedAt.toISOString()
|
||||
: null,
|
||||
state: repository.state === 'indexed' ? 'finalized' : repository.state === 'error' ? 'error' : 'initial',
|
||||
totalTokens: repository.totalTokens ?? null,
|
||||
totalSnippets: repository.totalSnippets ?? null,
|
||||
stars: repository.stars ?? null,
|
||||
trustScore: repository.trustScore ?? null,
|
||||
benchmarkScore: repository.benchmarkScore ?? null,
|
||||
versions: versions.map((version) => version.tag),
|
||||
source: repository.sourceUrl
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static toContextJson(snippets: SnippetSearchResult[], rules: string[]): ContextJsonResponseDto {
|
||||
const mapped: SnippetJsonDto[] = snippets.map(({ snippet }) => {
|
||||
if (snippet.type === 'code') {
|
||||
return new CodeSnippetJsonDto({
|
||||
title: snippet.title ?? null,
|
||||
description: snippet.breadcrumb ?? null,
|
||||
language: snippet.language ?? null,
|
||||
codeList: [
|
||||
new CodeListItemDto({
|
||||
language: snippet.language ?? '',
|
||||
code: snippet.content
|
||||
})
|
||||
],
|
||||
id: snippet.id,
|
||||
tokenCount: snippet.tokenCount ?? null,
|
||||
pageTitle: snippet.breadcrumb ? snippet.breadcrumb.split('>')[0].trim() || null : null
|
||||
});
|
||||
}
|
||||
|
||||
return new InfoSnippetJsonDto({
|
||||
text: snippet.content,
|
||||
breadcrumb: snippet.breadcrumb ?? null,
|
||||
pageId: snippet.id,
|
||||
tokenCount: snippet.tokenCount ?? null
|
||||
});
|
||||
});
|
||||
|
||||
return new ContextJsonResponseDto({
|
||||
snippets: mapped,
|
||||
rules,
|
||||
totalTokens: snippets.reduce((sum, result) => sum + (result.snippet.tokenCount ?? 0), 0)
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user