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

  1. Use Svelte 5 runes:

    • $state() for mutable state
    • $derived() for computed values
    • $effect() for side effects
  2. Use $props() for props:

    let {(title, (count = 0))} = $props()
  3. Use onclick for 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

  1. System Architecture - Learn about the technical foundations
  2. API Documentation - Complete API reference

Happy coding!