6.0 KiB
TRUEREF-0002 — Repository Management Service & REST API
Priority: P0 Status: Pending Depends On: TRUEREF-0001 Blocks: TRUEREF-0009, TRUEREF-0010, TRUEREF-0015
Overview
Implement the core RepositoryService that handles CRUD operations for repositories, and expose those operations via SvelteKit API routes. This feature establishes the management plane for all library sources.
Acceptance Criteria
RepositoryServiceclass with full CRUD operationsGET /api/v1/libs— list all repositories with metadataPOST /api/v1/libs— add a new repository (GitHub URL or local path)GET /api/v1/libs/:id— get single repository detailsPATCH /api/v1/libs/:id— update repository metadataDELETE /api/v1/libs/:id— delete repository and all associated dataPOST /api/v1/libs/:id/index— trigger indexing job (queues job, returns job ID)- Input validation with descriptive error messages
- All endpoints return JSON with consistent error shape
- Unit tests for
RepositoryServicecovering all operations
Repository ID Generation
GitHub repositories:
- Input URL:
https://github.com/facebook/reactorgithub.com/facebook/react - Generated ID:
/facebook/react
Local repositories:
- Input path:
/home/user/projects/my-sdk - Generated ID:
/local/my-sdk(basename of path, slugified) - Collision resolution: append
-2,-3, etc.
Version-specific IDs: /facebook/react/v18.3.0
Service Interface
// src/lib/server/services/repository.service.ts
export interface AddRepositoryInput {
source: 'github' | 'local';
sourceUrl: string; // GitHub URL or absolute local path
title?: string; // override auto-detected title
description?: string;
branch?: string; // GitHub: default branch; Local: n/a
githubToken?: string; // for private GitHub repos
}
export interface UpdateRepositoryInput {
title?: string;
description?: string;
branch?: string;
githubToken?: string;
}
export class RepositoryService {
constructor(private db: BetterSQLite3.Database) {}
async list(options?: {
state?: Repository['state'];
limit?: number;
offset?: number;
}): Promise<Repository[]>
async get(id: string): Promise<Repository | null>
async add(input: AddRepositoryInput): Promise<Repository>
async update(id: string, input: UpdateRepositoryInput): Promise<Repository>
async remove(id: string): Promise<void>
async getStats(id: string): Promise<{
totalSnippets: number;
totalTokens: number;
totalDocuments: number;
lastIndexedAt: Date | null;
}>
}
API Route Specifications
GET /api/v1/libs
Query parameters:
state(optional): filter by state (pending,indexed,error, etc.)limit(optional, default 50): max resultsoffset(optional, default 0): pagination offset
Response 200:
{
"libraries": [
{
"id": "/facebook/react",
"title": "React",
"description": "...",
"source": "github",
"state": "indexed",
"totalSnippets": 1234,
"totalTokens": 98000,
"trustScore": 8.5,
"stars": 228000,
"lastIndexedAt": "2026-03-22T10:00:00Z",
"versions": ["v18.3.0", "v17.0.2"]
}
],
"total": 12,
"limit": 50,
"offset": 0
}
POST /api/v1/libs
Request body:
{
"source": "github",
"sourceUrl": "https://github.com/facebook/react",
"branch": "main",
"githubToken": "ghp_...",
"autoIndex": true
}
Response 201:
{
"library": { ...Repository },
"job": { "id": "uuid", "status": "queued" }
}
autoIndex: true (default) immediately queues an indexing job.
Response 409 if repository already exists:
{ "error": "Repository /facebook/react already exists" }
GET /api/v1/libs/:id
:id must be URL-encoded (e.g., %2Ffacebook%2Freact for /facebook/react).
Response 200: single Repository object with versions array.
Response 404: { "error": "Repository not found" }
PATCH /api/v1/libs/:id
Request body: partial UpdateRepositoryInput.
Response 200: updated Repository.
DELETE /api/v1/libs/:id
Cascades: deletes all documents, snippets, embeddings, jobs for this repository.
Response 204: no body.
Response 404: not found.
POST /api/v1/libs/:id/index
Triggers a new indexing job. If a job is already running for this repo, returns the existing job.
Request body (optional):
{ "version": "v18.3.0" }
Response 202:
{
"job": {
"id": "uuid",
"repositoryId": "/facebook/react",
"status": "queued",
"progress": 0,
"createdAt": "2026-03-22T10:00:00Z"
}
}
Error Response Shape
All error responses follow:
{
"error": "Human-readable message",
"code": "MACHINE_READABLE_CODE",
"details": {}
}
Error codes:
NOT_FOUNDALREADY_EXISTSINVALID_INPUTINVALID_URLINDEXING_IN_PROGRESS
ID Resolution Logic
function resolveGitHubId(url: string): string {
// Parse owner/repo from URL variants:
// https://github.com/facebook/react
// https://github.com/facebook/react.git
// github.com/facebook/react
const match = url.match(/github\.com\/([^/]+)\/([^/\s.]+)/);
if (!match) throw new Error('Invalid GitHub URL');
return `/${match[1]}/${match[2]}`;
}
function resolveLocalId(path: string, existingIds: string[]): string {
const base = slugify(path.split('/').at(-1)!);
let id = `/local/${base}`;
let counter = 2;
while (existingIds.includes(id)) {
id = `/local/${base}-${counter++}`;
}
return id;
}
Files to Create
src/lib/server/services/repository.service.tssrc/routes/api/v1/libs/+server.ts— GET (list), POST (add)src/routes/api/v1/libs/[id]/+server.ts— GET, PATCH, DELETEsrc/routes/api/v1/libs/[id]/index/+server.ts— POST (trigger indexing)src/lib/server/utils/id-resolver.ts— ID generation helperssrc/lib/server/utils/validation.ts— input validatorssrc/lib/server/services/repository.service.test.ts