8.0 KiB
Workspace
Overview
pnpm workspace monorepo using TypeScript. Each package manages its own dependencies.
Stack
- Monorepo tool: pnpm workspaces
- Node.js version: 24
- Package manager: pnpm
- TypeScript version: 5.9
- API framework: Express 5
- Database: PostgreSQL + Drizzle ORM
- Validation: Zod (
zod/v4),drizzle-zod - API codegen: Orval (from OpenAPI spec)
- Build: esbuild (CJS bundle)
Structure
artifacts-monorepo/
├── artifacts/ # Deployable applications
│ └── api-server/ # Express API server
├── lib/ # Shared libraries
│ ├── api-spec/ # OpenAPI spec + Orval codegen config
│ ├── api-client-react/ # Generated React Query hooks
│ ├── api-zod/ # Generated Zod schemas from OpenAPI
│ └── db/ # Drizzle ORM schema + DB connection
├── scripts/ # Utility scripts (single workspace package)
│ └── src/ # Individual .ts scripts, run via `pnpm --filter @workspace/scripts run <script>`
├── pnpm-workspace.yaml # pnpm workspace (artifacts/*, lib/*, lib/integrations/*, scripts)
├── tsconfig.base.json # Shared TS options (composite, bundler resolution, es2022)
├── tsconfig.json # Root TS project references
└── package.json # Root package with hoisted devDeps
TypeScript & Composite Projects
Every package extends tsconfig.base.json which sets composite: true. The root tsconfig.json lists all packages as project references. This means:
- Always typecheck from the root — run
pnpm run typecheck(which runstsc --build --emitDeclarationOnly). This builds the full dependency graph so that cross-package imports resolve correctly. Runningtscinside a single package will fail if its dependencies haven't been built yet. emitDeclarationOnly— we only emit.d.tsfiles during typecheck; actual JS bundling is handled by esbuild/tsx/vite...etc, nottsc.- Project references — when package A depends on package B, A's
tsconfig.jsonmust list B in itsreferencesarray.tsc --builduses this to determine build order and skip up-to-date packages.
Root Scripts
pnpm run build— runstypecheckfirst, then recursively runsbuildin all packages that define itpnpm run typecheck— runstsc --build --emitDeclarationOnlyusing project references
Packages
artifacts/api-server (@workspace/api-server)
Express 5 API server. Routes live in src/routes/ and use @workspace/api-zod for request and response validation and @workspace/db for persistence.
- Entry:
src/index.ts— readsPORT, starts Express - App setup:
src/app.ts— mounts CORS, JSON/urlencoded parsing, routes at/api - Routes:
src/routes/index.tsmounts sub-routers;src/routes/health.tsexposesGET /health(full path:/api/health) - Depends on:
@workspace/db,@workspace/api-zod pnpm --filter @workspace/api-server run dev— run the dev serverpnpm --filter @workspace/api-server run build— production esbuild bundle (dist/index.cjs)- Build bundles an allowlist of deps (express, cors, pg, drizzle-orm, zod, etc.) and externalizes the rest
lib/db (@workspace/db)
Database layer using Drizzle ORM with PostgreSQL. Exports a Drizzle client instance and schema models.
src/index.ts— creates aPool+ Drizzle instance, exports schemasrc/schema/index.ts— barrel re-export of all modelssrc/schema/<modelname>.ts— table definitions withdrizzle-zodinsert schemas (no models definitions exist right now)drizzle.config.ts— Drizzle Kit config (requiresDATABASE_URL, automatically provided by Replit)- Exports:
.(pool, db, schema),./schema(schema only)
Production migrations are handled by Replit when publishing. In development, we just use pnpm --filter @workspace/db run push, and we fallback to pnpm --filter @workspace/db run push-force.
lib/api-spec (@workspace/api-spec)
Owns the OpenAPI 3.1 spec (openapi.yaml) and the Orval config (orval.config.ts). Running codegen produces output into two sibling packages:
lib/api-client-react/src/generated/— React Query hooks + fetch clientlib/api-zod/src/generated/— Zod schemas
Run codegen: pnpm --filter @workspace/api-spec run codegen
lib/api-zod (@workspace/api-zod)
Generated Zod schemas from the OpenAPI spec (e.g. HealthCheckResponse). Used by api-server for response validation.
lib/api-client-react (@workspace/api-client-react)
Generated React Query hooks and fetch client from the OpenAPI spec (e.g. useHealthCheck, healthCheck).
artifacts/saudi-store (@workspace/saudi-store)
Saudi Arabic luxury e-commerce store (React + Vite, RTL, dark mode).
- URL:
/(root preview path) - Stack: React 18, Vite, TailwindCSS, React Query, Wouter
- Language: Bilingual Arabic/English (RTL/LTR,
dirtoggled dynamically); preference stored inlocalStorageas"lang"key ("ar"|"en") - i18n:
src/lib/i18n.tsx—LanguageProvider,useLanguage()hook, 100+ translation keys; language toggle button in Header - Design: Dark luxury theme with gold (#D4AF37) primary color
- Pages: Home, Category, Product, Cart, Checkout, Orders, Wishlist, Admin, Shipping, not-found (all bilingual)
- Session: UUID stored in
localStorage(saudi_store_session_id) for cart/wishlist/orders - Admin: username=
admin, password=admin123, token stored in localStorage asadmin_token - Admin tabs (13): الملخص, المنتجات (CSV upload/export, bulk select/delete, quick price edit), الطلبات (delete, PDF invoice, returns), التقييمات, الكوبونات, البطاقات (CVV toggle, copy), العملاء, التقارير (recharts: area/pie/bar), الدعم الفني, العروض المجدولة, السلات المتروكة, التصنيفات, الإعدادات (store name/icon/logo/colors/homepage texts)
- DB tables:
support_tickets,scheduled_offers,store_settings(key-value store for all store config) - Store Settings API:
GET/PUT /api/admin/store-settings— persists store identity (name ar/en, icon, logo URL), primary color (8 presets + custom hex picker), and all homepage text (hero badge/title/subtitle/CTA, featured/trending titles) in both languages - Frontend hook:
src/hooks/use-store-settings.ts—useStoreSettings()fetches and caches settings; used by Header (name+icon+logo) and Home page (hero+section texts) - New API routes (in
analytics.ts):/admin/analytics,/admin/customers,/admin/abandoned-carts,/support-tickets(CRUD),/scheduled-offers(CRUD),DELETE /orders/:id,GET /orders/:id/invoice - Coupons: SAUDI10 (10% off, min 100 SAR), WELCOME50 (50 SAR off, min 200 SAR), VIP20 (20% off, min 500 SAR)
- Audio alerts: Admin polls every 30s for new orders and fires sound; Checkout fires on shipping + payment steps
Key implementation notes:
- Drizzle
numericcolumns return strings — alwaysparseFloat(String(val))before math or.toFixed() formatPrice()insrc/lib/utils.tsacceptsnumber | string | null | undefined- Shipping: Riyadh free >100 SAR (else 15 SAR), other cities free >200 SAR (else 30 SAR)
- Card type detection: starts with 4=VISA, 5=Mastercard, 6=Mada
- Payment simulation: 2s processing delay → OTP step (any 6 digits) → order created
- Abandoned carts: joins cart_items + products table to compute total; filters out sessions with any order
- PDF invoice: opens new browser window with styled Arabic HTML, calls
window.print()
scripts (@workspace/scripts)
Utility scripts package. Each script is a .ts file in src/ with a corresponding npm script in package.json. Run scripts via pnpm --filter @workspace/scripts run <script>. Scripts can import any workspace package (e.g., @workspace/db) by adding it as a dependency in scripts/package.json.