Skip to content

Tech Stack

Dokumen ini nge-list semua teknologi yang dipakai di Soul Map Atlas — dari frontend sampai database. Kalau ada yang mau ganti library atau nambah dependency baru, cek dulu di sini biar konsisten.

Package Manager

  • pnpm dengan workspace protocol (workspace:* untuk internal packages).
  • pnpm workspace catalogs untuk shared dependency versions. Semua dep yang dipakai di 2+ packages harus masuk catalog di pnpm-workspace.yaml.

Frontend (apps/web)

  • React 19 — UI framework utama.
  • Vite 6 — build tool dan dev server.
  • Tailwind CSS 4 — utility-first CSS framework.
  • shadcn/ui — komponen UI yang sudah ada di legacy, kita retain.
  • TanStack Query 5 — server state management (fetching, caching, invalidation).
  • React Router 7 — SPA routing.
  • oRPC Client — (@orpc/client + @orpc/openapi-client/fetch) untuk type-safe API calls ke backend.

Backend (apps/api)

  • oRPC 1.14 — type-safe RPC framework dengan auto OpenAPI generation.
    • @orpc/server — procedures dan middleware.
    • @orpc/contract — contract-first route definitions (zero server deps).
    • @orpc/openapi — OpenAPI spec generation.
    • @orpc/openapi/fetch — fetch adapter untuk Cloudflare Workers.
    • @orpc/client + @orpc/openapi-client — typed client untuk frontend.
  • better-auth 1.6 — authentication dengan Drizzle adapter.
    • Drizzle adapter import path: better-auth/adapters/drizzle (bukan drizzle-adapter atau @better-auth/drizzle-adapter).
    • baseURL wajib di config.
    • additionalFields dipakai untuk nambahin kolom role di tabel user.
  • Zod — schema validation untuk input/output API.
  • Drizzle ORM — database access via @packages/db.
  • Pure Cloudflare Workers fetch handler — tidak pakai Hono atau framework HTTP tambahan. oRPC OpenAPIHandler dari @orpc/openapi/fetch nge-handle routing langsung.

Database

  • Cloudflare D1 — SQLite-compatible edge database.
  • Drizzle ORM + drizzle-kit untuk schema definition dan migrations.
  • Schema auth (user, session, account, verification) ada di packages/db/src/schema/auth.ts.

Auth

  • better-auth plugins:
    • Google OAuth
    • Email / Password dengan custom hasher
  • Password hashing: scrypt via node:crypto.scrypt (dengan flag nodejs_compat).
  • Jangan pakai bcrypt atau argon2 — butuh native bindings yang gagal di Workers runtime.
  • Role system sederhana: member, admin, owner. Check-nya string comparison di middleware, tanpa ACL system.

Email

  • Resend — untuk transactional emails.

Payment

  • Doku — payment gateway untuk market Indonesia.

Deployment

  • Cloudflare Workers — untuk apps/api.
  • Cloudflare Pages — untuk apps/web dan apps/docs.
  • wrangler CLI — untuk local dev dan deploy.
  • worker-configuration.d.ts di-generate via wrangler types (file ini di-gitignore).

Workers vs Pages Config Difference

AppPlatformWrangler Config
apps/apiWorkersSupport env.development dan env.production keys
apps/webPagesTidak support env keys — env vars via dashboard atau --branch CLI
apps/docsPagesStatic site, tidak butuh env keys

Code Quality

  • Biome — satu-satunya formatter dan linter. Tidak ada Prettier, tidak ada ESLint.
  • lefthook — pre-commit hooks yang nge-run biome check --write hanya di staged files.
  • turbo — monorepo task orchestration (build, lint, dev).

Constraints & Best Practices

  • NO native dependencies — tidak ada Rust, tidak ada node-gyp, tidak ada native modules.
  • Semua packages harus bisa jalan di Cloudflare Workers runtime (V8 isolates).
  • Prefer Web APIs daripada Node.js APIs kalau bisa.
  • Semua env vars diakses lewat Env binding atau env di Worker fetch context — bukan process.env.
  • Cloudflare Workers Compat Date harus diatur di wrangler.jsonc.

oRPC 1.14 Specific Notes

Beberapa hal yang perlu diperhatikan karena kita pakai oRPC versi 1.14:

GET Route Input

GET route tidak bisa pakai z.void() — harus pakai z.object({}) (empty object):

ts
// ✅ Benar
publicProcedure
  .input(z.object({}))
  .output(z.object({ message: z.string() }))
  .handler(() => ({ message: "ok" }));

// ❌ Salah — compile error
publicProcedure
  .input(z.void())
  .handler(() => ...);

Error Handling

Selalu pakai ORPCError dari @orpc/server. Plain Error jadi 500. ORPCError("UNAUTHORIZED") → 401, ORPCError("FORBIDDEN") → 403.

Import Paths

  • OpenAPIHandler dari @orpc/openapi/fetch
  • OpenAPIGenerator dari @orpc/openapi
  • OpenAPILink dari @orpc/openapi-client/fetch
  • ZodToJsonSchemaConverter dari @orpc/zod

Context Requirement

Field context di OpenAPIHandler.handle() itu required, bukan optional.

CORS

Tidak ada CORSPlugin di oRPC 1.14. CORS di-implement via manual header map di fetch handler.

Auth Integration Notes

Drizzle Adapter Path (better-auth 1.6)

ts
// ✅ Benar
import { drizzleAdapter } from "better-auth/adapters/drizzle";

// ❌ Salah
import { drizzleAdapter } from "better-auth/adapters/drizzle-adapter";
import { drizzleAdapter } from "@better-auth/drizzle-adapter";

baseURL Requirement

better-auth 1.6 butuh baseURL di config. Kalau nggak ada, bakal muncul warning dan OAuth callback bisa gagal:

ts
export const auth = betterAuth({
  baseURL: env.BETTER_AUTH_URL || "http://localhost:8787",
  // ...
});

Social Provider Config

Client ID dan client secret untuk OAuth harus di-pass dari env (Worker bindings), bukan process.env:

ts
socialProviders: {
  google: {
    clientId: env.GOOGLE_CLIENT_ID as string,
    clientSecret: env.GOOGLE_CLIENT_SECRET as string,
  },
},

Scrypt di Workers

node:crypto.scrypt jalan di Cloudflare Workers dengan flag nodejs_compat. Sudah diverifikasi dengan timingSafeEqual untuk constant-time comparison. Tidak perlu fallback untuk production.