BigStarter
Database

Menggunakan Database

Pola pemakaian harian — query contract, batas modul, transaksi, wrapper RPC, dan aturan zona browser/server.

Menggunakan Database

Aturan Emas

  1. Halaman/client tidak pernah query langsung. Alur: page → oRPC → service → query contract.
  2. Satu modul = pemilik tabelnya. Modul lain membaca data milik modul lain lewat contract-nya, bukan query sendiri.
  3. 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

ZonaBoleh
modules/<m>/shared/Dipakai siapa saja (browser termasuk)
modules/<m>/server/Hanya server — oRPC, service, server action, job
modules/<m>/index.tsHanya me-re-export shared/ (aturan dijaga architecture check)
Komponen clientData 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).