Skip to content

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

LayerFormatContoh
File store<aksi>-<modul>.stores.tsfetch-bulletin.stores.ts
File service<modul>.service.tsbulletin.service.ts
File API<modul>.api.tsbulletin.api.ts
File type<modul>.types.tsbulletin.types.ts
ComponentPascalCaseBulletinCard.svelte
Subdirektori store<aksi>-<modul>/super-user-entry/
Barrel exportindex.tsre-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 Vitest

Step Control Store

form-setup-step-control.stores.ts mengelola navigasi wizard:

MethodDeskripsi
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.json dan lang/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: pakai import type
  • import/order: import wajib diurutkan alfabet
  • curly: all: selalu pakai curly braces
  • Padding lines sekitar control statements (if, return, for, dll)