Menggunakan Database
Pola pemakaian harian — query contract, batas modul, transaksi, wrapper RPC, dan aturan zona browser/server.
Menggunakan Database
Aturan Emas
- Halaman/client tidak pernah query langsung. Alur:
page → oRPC → service → query contract. - Satu modul = pemilik tabelnya. Modul lain membaca data milik modul lain lewat contract-nya, bukan query sendiri.
- Semua operasi di-scope tenant di dalam query, dan otorisasi dicek di service/procedure sebelum query jalan.
Memanggil Query dari Service
Import singleton dari modul pemilik:
import { taskQueries } from "@modules/tasks/server/queries";
export async function listTasks(actor: Actor, tenantId: string, input: TaskListInput) {
await assertPermission(actor, "tasks.read");
return taskQueries.list(tenantId, input);
}Contract-nya netral-ORM — bentuk input/output tidak berubah walau ORM aktif diganti:
export interface TaskQueries {
list(tenantId: string, input: TaskListInput): Promise<TaskListResult>;
findById(tenantId: string, id: string): Promise<Task | null>;
create(input: TaskRecordInput): Promise<Task>;
// ...
}Modul besar bisa punya beberapa contract dalam satu binding — mis. billing punya billingQueries, couponQueries, addonQueries, dan creditQueries dalam satu baris import.
Membaca Data Modul Lain
Lewat contract modul pemilik — mis. modul notifikasi butuh nama profile:
import { profileQueries } from "@modules/users/server/queries";
const names = await profileQueries.findNames(ids);Transaksi
Gunakan runner per-ORM dari infrastructure/db/ (bukan membuat koneksi sendiri):
import { createDrizzleTransactionRunner } from "@packages/database/transaction.drizzle";
import { drizzleDatabase } from "@packages/database/client.drizzle";
const runner = createDrizzleTransactionRunner(drizzleDatabase);
await runner.run(async (tx) => {
// tx adalah client transaksi — operasi di dalamnya atomik
});Untuk Prisma: createPrismaTransactionRunner dari transaction.prisma.ts. Lindungi pemakaian dengan cek capability bila fitur bersifat opsional:
import { databaseCapabilities } from "@packages/database/database";
if (!databaseCapabilities.transactions) {
// fallback tanpa transaksi (mis. idempotensi via RPC)
}Fungsi SQL (RPC)
Operasi dengan invarian database — ledger kredit billing, klaim webhook idempoten, redeem kupon — tetap hidup sebagai fungsi SQL SECURITY DEFINER dan dipanggil lewat method contract yang membungkusnya secara typed. Kamu tidak pernah memanggil .rpc() langsung dari service.
Batas Zona Browser/Server
| Zona | Boleh |
|---|---|
modules/<m>/shared/ | Dipakai siapa saja (browser termasuk) |
modules/<m>/server/ | Hanya server — oRPC, service, server action, job |
modules/<m>/index.ts | Hanya me-re-export shared/ (aturan dijaga architecture check) |
| Komponen client | Data hanya via oRPC client (orpc.tasks.list(...)) |
Migrasi Data (DDL)
DDL kanonik ada di tooling/supabase/migrations/ dan di-apply dengan supabase db push. Jangan menjalankan drizzle-kit push atau prisma migrate terhadap database project ini — alat migrasi ORM hanya untuk project hasil generate (lihat Multi-ORM & Generator).
