Phase 01: Foundation - 2 plans in 2 waves - Wave 1: Project setup with SvelteKit + Drizzle schema - Wave 2: Repository layer + verification page - Ready for execution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
11 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01-foundation | 02 | execute | 2 |
|
|
true |
|
Purpose: Provides the data access layer that all future features will use, and verifies the foundation works end-to-end. Output: Working repository with CRUD operations, auto-initializing database, and a verification page proving everything connects.
<execution_context> @/home/tho/.claude/get-shit-done/workflows/execute-plan.md @/home/tho/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/01-foundation/01-01-SUMMARY.md @src/lib/server/db/schema.ts @src/lib/server/db/index.ts Task 1: Create EntryRepository with typed CRUD operations src/lib/server/db/repository.ts Create `src/lib/server/db/repository.ts` implementing the repository pattern:import { eq, desc } from 'drizzle-orm';
import { db } from './index';
import { entries, type Entry, type NewEntry } from './schema';
import { nanoid } from 'nanoid';
export interface EntryRepository {
create(entry: Omit<NewEntry, 'id' | 'createdAt' | 'updatedAt'>): Entry;
getById(id: string): Entry | undefined;
getAll(options?: { limit?: number; offset?: number }): Entry[];
update(id: string, updates: Partial<Omit<Entry, 'id' | 'createdAt'>>): Entry | undefined;
delete(id: string): boolean;
count(): number;
}
class SQLiteEntryRepository implements EntryRepository {
create(entry: Omit<NewEntry, 'id' | 'createdAt' | 'updatedAt'>): Entry {
const now = new Date().toISOString();
const newEntry: NewEntry = {
...entry,
id: nanoid(),
createdAt: now,
updatedAt: now,
};
db.insert(entries).values(newEntry).run();
return this.getById(newEntry.id)!;
}
getById(id: string): Entry | undefined {
return db.select().from(entries).where(eq(entries.id, id)).get();
}
getAll(options: { limit?: number; offset?: number } = {}): Entry[] {
const { limit = 100, offset = 0 } = options;
return db
.select()
.from(entries)
.orderBy(desc(entries.createdAt))
.limit(limit)
.offset(offset)
.all();
}
update(id: string, updates: Partial<Omit<Entry, 'id' | 'createdAt'>>): Entry | undefined {
const existing = this.getById(id);
if (!existing) return undefined;
db.update(entries)
.set({
...updates,
updatedAt: new Date().toISOString(),
})
.where(eq(entries.id, id))
.run();
return this.getById(id);
}
delete(id: string): boolean {
const existing = this.getById(id);
if (!existing) return false;
db.delete(entries).where(eq(entries.id, id)).run();
return true;
}
count(): number {
const result = db.select().from(entries).all();
return result.length;
}
}
// Singleton instance
export const entryRepository: EntryRepository = new SQLiteEntryRepository();
This provides:
- Type-safe CRUD operations using Drizzle ORM
- Automatic ID generation with nanoid
- Automatic timestamp management (createdAt, updatedAt)
- Pagination support via limit/offset
- Singleton pattern for consistent access Create a quick test by importing the repository:
import { entryRepository } from './repository';
const entry = entryRepository.create({ content: 'Test', type: 'thought' });
console.log(entry); // Should have id, createdAt, updatedAt
const retrieved = entryRepository.getById(entry.id);
console.log(retrieved); // Should match entry
entryRepository.delete(entry.id);
TypeScript compilation should pass with npm run check.
EntryRepository interface and implementation exist with create, getById, getAll, update, delete, count methods.
All methods are type-safe with Drizzle schema types.
mkdir -p data/attachments
touch data/.gitkeep
touch data/attachments/.gitkeep
Add to .gitignore:
# Data directory (persistent data)
data/*.db
data/*.db-wal
data/*.db-shm
data/attachments/*
!data/.gitkeep
!data/attachments/.gitkeep
- Create
src/hooks.server.tsfor database initialization:
import type { Handle } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { entries } from '$lib/server/db/schema';
import { sql } from 'drizzle-orm';
// Ensure database tables exist on first request
let initialized = false;
export const handle: Handle = async ({ event, resolve }) => {
if (!initialized) {
// Run a simple query to ensure connection works
// Drizzle with push handles schema creation, but this verifies connectivity
try {
db.select().from(entries).limit(1).all();
initialized = true;
console.log('Database initialized successfully');
} catch (error) {
console.error('Database initialization failed:', error);
// Don't block - let the error propagate to the request
}
}
return resolve(event);
};
- Create
src/routes/+page.server.tsfor verification data:
import type { PageServerLoad } from './$types';
import { entryRepository } from '$lib/server/db/repository';
export const load: PageServerLoad = async () => {
const count = entryRepository.count();
// Create a test entry if none exist (for verification)
let testEntry = null;
if (count === 0) {
testEntry = entryRepository.create({
title: 'Foundation Test',
content: 'This entry was created to verify the foundation is working.',
type: 'thought',
});
}
const entries = entryRepository.getAll({ limit: 5 });
return {
dbStatus: 'connected',
entryCount: testEntry ? count + 1 : count,
recentEntries: entries,
};
};
- Update
src/routes/+page.svelteto show verification status:
<script lang="ts">
let { data } = $props();
</script>
<main class="min-h-screen bg-gray-50 p-8">
<div class="max-w-2xl mx-auto">
<h1 class="text-3xl font-bold text-gray-900 mb-2">TaskPlanner</h1>
<p class="text-gray-600 mb-8">Foundation Phase Complete</p>
<div class="bg-white rounded-lg shadow p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">System Status</h2>
<div class="space-y-3">
<div class="flex items-center gap-2">
<span class="w-3 h-3 rounded-full bg-green-500"></span>
<span class="text-gray-700">Database: {data.dbStatus}</span>
</div>
<div class="flex items-center gap-2">
<span class="w-3 h-3 rounded-full bg-green-500"></span>
<span class="text-gray-700">Entries in database: {data.entryCount}</span>
</div>
<div class="flex items-center gap-2">
<span class="w-3 h-3 rounded-full bg-green-500"></span>
<span class="text-gray-700">Repository layer: operational</span>
</div>
</div>
</div>
{#if data.recentEntries.length > 0}
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Recent Entries</h2>
<ul class="space-y-2">
{#each data.recentEntries as entry}
<li class="p-3 bg-gray-50 rounded">
<div class="font-medium text-gray-900">{entry.title || '(no title)'}</div>
<div class="text-sm text-gray-600">{entry.content}</div>
<div class="text-xs text-gray-400 mt-1">
Type: {entry.type} | Created: {new Date(entry.createdAt).toLocaleString()}
</div>
</li>
{/each}
</ul>
</div>
{/if}
</div>
</main>
<success_criteria>
- EntryRepository provides typed create, getById, getAll, update, delete, count operations
- Database initializes automatically via server hooks
- Data directory structure exists (data/, data/attachments/)
- Verification page confirms database connected, entry count, repository operational
- Test entry created and displayed proving end-to-end data flow </success_criteria>