Files
trueref/docs/features/TRUEREF-0002.md
2026-03-27 02:23:01 +01:00

5.9 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

  • RepositoryService class with full CRUD operations
  • GET /api/v1/libs — list all repositories with metadata
  • POST /api/v1/libs — add a new repository (GitHub URL or local path)
  • GET /api/v1/libs/:id — get single repository details
  • PATCH /api/v1/libs/:id — update repository metadata
  • DELETE /api/v1/libs/:id — delete repository and all associated data
  • POST /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 RepositoryService covering all operations

Repository ID Generation

GitHub repositories:

  • Input URL: https://github.com/facebook/react or github.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 results
  • offset (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_FOUND
  • ALREADY_EXISTS
  • INVALID_INPUT
  • INVALID_URL
  • INDEXING_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.ts
  • src/routes/api/v1/libs/+server.ts — GET (list), POST (add)
  • src/routes/api/v1/libs/[id]/+server.ts — GET, PATCH, DELETE
  • src/routes/api/v1/libs/[id]/index/+server.ts — POST (trigger indexing)
  • src/lib/server/utils/id-resolver.ts — ID generation helpers
  • src/lib/server/utils/validation.ts — input validators
  • src/lib/server/services/repository.service.test.ts