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

  • Neon Postgres — serverless PostgreSQL dengan Cloudflare Hyperdrive untuk connection pooling di edge.
  • 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.
  • @packages/email — shared email client package yang wrap Resend SDK.
  • Email templates: HTML + text untuk setiap jenis email.
  • Transactional emails yang aktif:
    • Welcome email — dikirim otomatis setelah user signup via better-auth databaseHooks.
    • Password reset — dikirim via better-auth sendResetPassword callback.
    • Payment receipt — dikirim setelah pembayaran Doku sukses (via webhook handler).
  • Email sending tidak di-await (fire-and-forget) untuk mencegah timing attacks dan tidak memperlambat response.
  • Graceful degradation: kalau RESEND_API_KEY tidak di-set, email tidak dikirim tapi aplikasi tetap jalan normal.

Observability

  • @microlabs/otel-cf-workers — OpenTelemetry instrumentation untuk Cloudflare Workers.
    • Worker di-wrap dengan instrument() dari @microlabs/otel-cf-workers.
    • Span aktif bisa diakses via @opentelemetry/api trace.getActiveSpan().
    • Response header traceparent (W3C format) dan X-Trace-ID ditambahkan ke setiap response jika trace valid.
    • Frontend mengekstrak X-Trace-ID dari response dan menampilkannya di error UI via ApiError class.
    • Requires nodejs_compat compatibility flag.

Payment

  • Doku — payment gateway untuk market Indonesia.
    • Integration via Doku Checkout API (/checkout/v1/payment) menggunakan direct fetch calls (bukan SDK) untuk compatibility dengan Cloudflare Workers.
    • Signature menggunakan HMAC-SHA256 dengan Web Crypto API.
    • Webhook notifications diverifikasi signature-nya sebelum diproses.
    • Transaction records disimpan di tabel transactions (PostgreSQL) dengan status tracking (pending, paid, failed, cancelled, expired).
    • Environment variables: DOKU_CLIENT_ID, DOKU_SECRET_KEY (secret), DOKU_IS_PRODUCTION.
    • API endpoints: payment.createTransaction, payment.handleWebhook, payment.checkStatus.

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

Testing

  • Vitest — ESM-native test runner untuk integration tests di apps/api.
    • Integration tests menjalankan Worker nyata via wrangler unstable_dev.
    • Setiap test run membuat fresh Neon branch (PostgreSQL) via Neon API, menjalankan Drizzle migrations, lalu menghapus branch di teardown (kecuali KEEP_NEON_BRANCH=1).
    • HTTP client dengan cookie jar untuk menjaga session state antar request.
    • Base44 InvokeLLM di-mock dengan local Node HTTP server — Worker diarahkan ke mock via BASE44_SERVER_URL env var.
    • Test script: pnpm --filter @apps/api test

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.