Konvensi Pengembangan
Arsitektur Store (stores → services → api)
Semua alur data mengikuti layer ketat ini:
Component (Svelte)
▼ subscribe() + panggil action
Store (state management)
▼ delegasi logika
Service (orchestrasi)
▼ panggil HTTP
API (fetch)
▼ network
Backend (Laravel)Aturan: Jangan Lewati Layer
- Component panggil store action, BUKAN service/API langsung
- Store panggil service, BUKAN API langsung
- Service panggil API, BUKAN fetch langsung (kecuali SSE stream)
Aturan Component
Component hanya untuk presentasi — berlaku untuk atoms, molecules, DAN organisms:
svelte
<!-- ✅ BENAR: import store, subscribe, panggil action -->
<script lang="ts">
import { onMount } from 'svelte';
import { myStore } from './stores/my.stores';
let data = $state(myStore.initialData);
onMount(() => {
return myStore.subscribe((s) => { data = s.data; });
});
function handleClick() {
myStore.doAction();
}
</script>svelte
<!-- ❌ SALAH: fetch, parse SSE, logika bisnis di component -->
<script lang="ts">
async function handleClick() {
const res = await fetch('/api/foo'); // JANGAN!
}
</script>File Store
- Format:
<aksi>-<modul>.stores.ts(contoh:fetch-bulletin.stores.ts) - Group store terkait dalam subdirektori:
stores/super-user-entry/,stores/otp-entry/ - Setiap subdirektori punya barrel
index.ts - Semua store pakai
StateContainer<TData>dari@/types/state
typescript
// ✅ Pattern store yang benar
export interface MyData { items: string[]; sending: boolean; }
export type MyContainer = StateContainer<MyData>;
class MyStore {
#state: MyContainer = createInitialState();
#listeners = new Set<Listener>();
get state() { return this.#state; }
subscribe(l) { ... }
doAction() { this.#emit(); }
#emit() { ... }
}File Service
- Format:
<modul>.service.ts - Fungsi murni, tanpa state
- Jembatan antara store dan API
File API
- Format:
<modul>.api.ts - Hanya panggilan HTTP fetch
- Return data mentah, di-typed
Aturan Penamaan
| Layer | Format | Contoh |
|---|---|---|
| File store | <aksi>-<modul>.stores.ts | fetch-bulletin.stores.ts |
| File service | <modul>.service.ts | bulletin.service.ts |
| File API | <modul>.api.ts | bulletin.api.ts |
| File type | <modul>.types.ts | bulletin.types.ts |
| Component | PascalCase | BulletinCard.svelte |
| Subdirektori store | <aksi>-<modul>/ | super-user-entry/ |
| Barrel export | index.ts | re-export semua |
- Frontend: camelCase (
firstName,setField) - Backend: snake_case (
first_name,set_field) - Event handler: pakai referensi fungsi (
onclick={handleClick}) bukan inline arrow
Struktur Modul
module/
├── Index.svelte # Entry point — minimal, komposisi saja
├── api/ # Fungsi API
├── parts/ # Sub-komponen (atomic design)
│ ├── ComponentA.svelte
│ └── ComponentB.svelte
├── services/ # Logika bisnis
├── stores/ # State management
│ ├── aksi-modul.stores.ts
│ └── subdir/
│ ├── store-a.stores.ts
│ └── index.ts
├── types/ # TypeScript interfaces
└── __tests__/ # Tes VitestStep Control Store
form-setup-step-control.stores.ts mengelola navigasi wizard:
| Method | Deskripsi |
|---|---|
next() | Maju ke step berikutnya |
prev() | Mundur satu step |
goToStep(n) | Langsung ke step tertentu |
configure(total, initial) | Set total + step awal |
configureFromChecks(checks) | Hitung step awal dari backend |
Penting: Callback registration (registerAboutSave) BUKAN tempatnya di store. Simpan callback sebagai variabel lokal di component parent.
Auto-Navigasi
Saat akses /setup, wizard auto-advance ke step pertama yang belum selesai:
typescript
// ✅ BENAR: hitung langsung, bukan di $effect
let currentStep = $state(computeInitialStep(initialChecks));Jangan set let currentStep = $state(1) lalu update di effect — ini menyebabkan flash render.
Migration Runner
File: database/migrations/0000_runner_migration.php
- Baca env
MIGRATION_RUNNER_FOLDERS(default:v1_0_0,v1_1_0,...) - Jalankan migrasi per subdirektori versi
- Support custom sort via
sortMigrations()overrides - Output:
echo "--- Migrating: v1_0_0 ---"
Multi-Bahasa (i18n)
- File:
lang/en.jsondanlang/id.json - Import:
import { t } from '@/lib/i18n.svelte' - Pattern key:
<modul>.<bagian>.<key>(e.g.,setup.seed.title) - DILARANG hardcode string display di component
ESLint Rules
consistent-type-imports: pakaiimport typeimport/order: import wajib diurutkan alfabetcurly: all: selalu pakai curly braces- Padding lines sekitar control statements (
if,return,for, dll)