Development Workflow
This guide covers the development patterns, best practices, and workflows used in the Abrianto project.
Development Cycle
┌──────────────┐
│ 1. Plan │ Define requirements, analyze features
└──────┬───────┘
│
┌──────▼───────┐
│ 2. Design │ Architect solution, define schema
└──────┬───────┘
│
┌──────▼───────┐
│ 3. Code │ Implement solution with patterns
└──────┬───────┘
│
┌──────▼───────┐
│ 4. Test │ Unit tests, code review, validation
└──────┬───────┘
│
┌──────▼───────┐
│ 5. Deploy │ Production deployment, monitoring
└──────────────┘ Component Development
Svelte 5 Component Structure
<script>
let { title, items = [] } = $props();
let isOpen = $state(false);
let searchQuery = $state('');
let filteredItems = $derived(
items.filter((item) => item.name.toLowerCase().includes(searchQuery.toLowerCase()))
);
$effect(() => {
console.log('Search query changed:', searchQuery);
});
function toggleOpen() {
isOpen = !isOpen;
}
function handleSearch(event) {
searchQuery = event.target.value;
}
</script>
<div class="component-container">
<header class="component-header">
<h2>{title}</h2>
<button onclick={toggleOpen}>
{isOpen ? 'Close' : 'Open'}
</button>
</header>
{#if isOpen}
<div class="component-content">
<input type="text" placeholder="Search..." value={searchQuery} oninput={handleSearch} />
<div class="items-list">
{#each filteredItems as item (item.id)}
<div class="item">{item.name}</div>
{/each}
</div>
</div>
{/if}
</div> Best Practices
Use Svelte 5 runes:
$state()for mutable state$derived()for computed values$effect()for side effects
Use
$props()for props:let {(title, (count = 0))} = $props()Use
onclickfor events:<button onclick={handleClick}>Click me</button>
Database Development
Query Pattern
// ✅ CORRECT: Proper multi-tenant queries
async function getAccounts(tenantId) {
return await sql`
SELECT * FROM accounts
WHERE tenant_id = ${tenantId}
ORDER BY created_at DESC
`;
}
// ✅ CORRECT: Dynamic WHERE clause
async function getAccountsFiltered(tenantId, filters) {
let query = sql`SELECT * FROM accounts WHERE tenant_id = ${tenantId}`;
if (filters.status) {
query = sql`${query} AND status = ${filters.status}`;
}
if (filters.searchTerm) {
query = sql`${query} AND name ILIKE %${filters.searchTerm}%`;
}
return await sql`${query} ORDER BY created_at DESC`;
}
// ❌ WRONG: Missing tenant scoping
async function getAccounts() {
return await sql`SELECT * FROM accounts`;
} Transaction Pattern
import { sql } from '$lib/server/db.js';
async function createAccountAndTransaction(accountData, transactionData) {
return await sql.begin(async (sql) => {
const [account] = await sql`
INSERT INTO accounts (name, balance, status, tenant_id)
VALUES (${accountData.name}, ${accountData.balance}, ${accountData.status}, ${tenantId})
RETURNING *
`;
const [transaction] = await sql`
INSERT INTO transactions
(account_id, type, amount, status, tenant_id)
VALUES
(${account.id}, ${transactionData.type}, ${transactionData.amount}, ${transactionData.status}, ${tenantId})
RETURNING *
`;
await sql`
UPDATE accounts
SET outstanding_balance = outstanding_balance - ${transaction.amount}
WHERE id = ${account.id}
`;
return { account, transaction };
});
} API Development
REST API Pattern
// src/routes/api/v1/accounts/+server.js
import { json } from '@sveltejs/kit';
import { sql } from '$lib/server/db.js';
/** @openapi
* /api/v1/accounts:
* get:
* summary: List all accounts
* tags:
* - Accounts
* security:
* - ApiKeyAuth: []
* parameters:
* - name: page
* in: query
* schema:
* type: integer
* default: 1
* - name: pageSize
* in: query
* schema:
* type: integer
* default: 20
* responses:
* '200':
* description: Successfully retrieved accounts
* '401':
* description: Unauthorized
*/
export async function GET({ url, locals }) {
const tenantId = locals.tenant.id;
const page = parseInt(url.searchParams.get('page') || '1');
const pageSize = Math.min(parseInt(url.searchParams.get('pageSize') || '20'), 100);
let query = sql`
SELECT * FROM accounts
WHERE tenant_id = ${tenantId}
`;
query = sql`${query} ORDER BY created_at DESC LIMIT ${pageSize} OFFSET ${(page - 1) * pageSize}`;
const accounts = await query;
const countQuery = sql`
SELECT COUNT(*) FROM accounts
WHERE tenant_id = ${tenantId}
`;
const [{ count }] = await countQuery;
return json({
data: accounts,
total: count,
page,
pageSize,
totalPages: Math.ceil(count / pageSize)
});
} Testing Strategy
Unit Testing Pattern
// src/lib/server/example.test.js
import { describe, it, expect } from 'vitest';
import { sql } from '$lib/server/db.js';
describe('Account Service', () => {
it('should create an account', async () => {
const account = await createAccount({
name: 'Test Account',
balance: 1000
});
expect(account.name).toBe('Test Account');
expect(account.balance).toBe(1000);
});
it('should return correct account for tenant', async () => {
const account1 = await createAccount({ name: 'Account 1', balance: 1000 });
const account2 = await createAccount({ name: 'Account 2', balance: 2000 });
const results = await getAccountsForTenant(account1.tenantId);
expect(results).toHaveLength(2);
});
}); E2E Testing Pattern
// e2e/account.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Account Management', () => {
test('should create a new account', async ({ page }) => {
await page.goto('/app/accounts');
await page.locator('[data-testid="create-account"]').click();
await page.fill('[data-testid="account-name"]', 'Test Account');
await page.fill('[data-testid="account-balance"]', '1500');
await page.locator('[data-testid="submit"]').click();
await expect(page.locator('[data-testid="account-created"]')).toBeVisible();
await expect(page.locator('text=Test Account')).toBeVisible();
});
}); Code Review Checklist
Before Submitting
- Code follows Svelte 5 patterns (runes,
$props(),onclick) - All database queries include tenant scoping
- Proper error handling is implemented
- API endpoints have OpenAPI documentation
- Component includes appropriate accessibility attributes
- Tests are written for new functionality
- No unused imports or variables
- Code is formatted with Prettier
Database Changes
- Migration files are properly numbered
- Migrations include proper indexing
- Data integrity constraints are added
- Migration is reversible (if needed)
Security Considerations
- All API endpoints validate tenant_id
- No direct SQL injection vulnerabilities
- Proper authentication/authorization checks
- Input validation on all user inputs
Next Steps
- System Architecture - Learn about the technical foundations
- API Documentation - Complete API reference
Happy coding!