Project Structure
Murai is a monorepo with five packages. You can install the meta-package for convenience or pick individual packages.
Package overview
Architecture diagram
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐│ Your App │────▶│ Wallet │────▶│ Ledger (core) ││ │ │ (core) │ │ append-only log │└──────────────┘ └──────────────┘ └──────────────────┘ │ │ ┌──────┴──────┐ ┌──────┴──────┐ │ Checkout │ │ Storage │ │ Manager │ │ Adapter │ └──────┬──────┘ └──────┬──────┘ │ │ ┌──────┴──────┐ ┌──────┴──────┐ │ Gateway │ │ Drizzle │ │ Adapter │ │ PostgreSQL │ └─────────────┘ └─────────────┘ Midtrans │ XenditCore module breakdown
wallet.ts — Public API
The createWallet factory returns the main interface: getBalance, canSpend, spend, topUp, getTransactions, getCheckouts. This is what most application code interacts with.
ledger.ts — Append-only transaction log
The ledger enforces the core invariants: positive amounts only, idempotency keys on every mutation, and delegation to the storage adapter for atomic balance updates. Ledger entries are never updated or deleted.
checkout.ts — Payment gateway abstraction
The createCheckoutManager factory bridges the gateway adapter with the ledger. It creates checkout sessions and handles webhooks with a 7-step verification flow including dual idempotency guards.
types.ts — Public interfaces
All exported TypeScript interfaces: Wallet, StorageAdapter, PaymentGatewayAdapter, LedgerEntry, CheckoutSession, WebhookResult, and query types.
errors.ts — Typed error hierarchy
Domain errors that extend MuraiError: InsufficientBalanceError, IdempotencyConflictError, WebhookVerificationError, InvalidAmountError, GatewayError. Each has a typed code string for programmatic error handling.
Dependency graph
@murai-wallet/murai (meta-package)├── @murai-wallet/core├── @murai-wallet/gateway-midtrans → depends on core├── @murai-wallet/gateway-xendit → depends on core└── @murai-wallet/storage-drizzle → depends on coreGateway and storage adapters depend only on @murai-wallet/core for its types and errors. They have no dependency on each other, so you can use Midtrans without Xendit and vice versa.