Appearance
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(bukandrizzle-adapteratau@better-auth/drizzle-adapter). baseURLwajib di config.additionalFieldsdipakai untuk nambahin kolomroledi tabel user.
- Drizzle adapter import path:
- Zod — schema validation untuk input/output API.
- Drizzle ORM — database access via
@packages/db. - Pure Cloudflare Workers
fetchhandler — tidak pakai Hono atau framework HTTP tambahan. oRPCOpenAPIHandlerdari@orpc/openapi/fetchnge-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 flagnodejs_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/webdanapps/docs. - wrangler CLI — untuk local dev dan deploy.
worker-configuration.d.tsdi-generate viawrangler types(file ini di-gitignore).
Workers vs Pages Config Difference
| App | Platform | Wrangler Config |
|---|---|---|
apps/api | Workers | Support env.development dan env.production keys |
apps/web | Pages | Tidak support env keys — env vars via dashboard atau --branch CLI |
apps/docs | Pages | Static 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 --writehanya 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
Envbinding atauenvdi Worker fetch context — bukanprocess.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
OpenAPIHandlerdari@orpc/openapi/fetchOpenAPIGeneratordari@orpc/openapiOpenAPILinkdari@orpc/openapi-client/fetchZodToJsonSchemaConverterdari@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.