DreamBees Art is a layered TypeScript monolith: one Next.js deployable containing storefront, admin, API routes, and commerce orchestration. The design mirrors Shopify’s familiar surfaces while keeping explicit protocol boundaries for money and stock — because self-hosted checkout must be recoverable, auditable, and testable in source.
New here? Read onboarding.md first, then flows.md for end-to-end stories.
flowchart TB
subgraph clients [Clients]
SF[Storefront browser]
AD[Admin browser]
ST[Stripe webhooks]
end
subgraph app [Next.js App Router]
API[src/app/api routes]
PAGES[src/app pages]
end
subgraph core [Core protocols]
CH[services.checkout]
RF[services.refunds]
INV[services.inventory]
ADM[services.admin]
end
subgraph infra [Infrastructure]
FS[(Firestore)]
STR[Stripe API]
AUTH[Firebase Auth]
end
SF --> PAGES
SF --> API
AD --> PAGES
AD --> API
ST --> API
API --> CH
API --> RF
API --> INV
API --> ADM
CH --> INV
ADM --> RF
ADM --> INV
CH --> STR
RF --> STR
CH --> FS
RF --> FS
INV --> FS
ADM --> FS
API --> AUTH
| Layer | Path | Responsibility | Constraints |
|---|---|---|---|
| Domain | src/domain/ |
Models, repository interfaces, pure rules, validation, calculations, typed errors | No I/O. No Next.js, Firestore, fetch, or cookies. |
| Core | src/core/ |
Application services, workflow orchestration, commerce protocols | No HTTP. Delegates to injected adapters. |
| Infrastructure | src/infrastructure/ |
Firestore repos, Firebase/Stripe/Brevo adapters, session, guards | Implements Domain contracts; maps transport ↔ Core. |
| App Router | src/app/ |
Pages and route.ts handlers |
Thin: parse, guard, delegate to services.*, map results. |
| UI | src/ui/ |
React pages and components | Calls APIs via apiClientServices.ts; no direct Infra. |
| Utils | src/utils/ |
Formatters, logging, SEO helpers | Stateless. |
Dependency rule: UI → API → Core → Domain ← Infrastructure. Domain never imports outward.
These four boundaries are frozen. Full policy: commerce-protocol-frozen.md.
checkout = money capture → CheckoutApplicationService
refunds = money reversal → RefundApplicationService
inventory = stock movement → InventoryApplicationService
admin = human authority → AdminApplicationServiceNo route, tool, admin action, or automation touches raw money mutation services directly.| Protocol | Container key | Internal engine (do not import from routes) |
|---|---|---|
| Checkout | services.checkout |
CheckoutMutationService, StripeService |
| Refunds | services.refunds |
RefundService |
| Inventory | services.inventory |
InventoryMutationService → batchUpdateStock |
| Admin | services.admin |
Delegates to above + OrderService (authorized) |
Each protocol returns *Result<T> — expected failures are data, not thrown exceptions.
All server wiring flows through src/core/container.ts:
getInitialServices() / getServerServices()
├── checkout, refunds, inventory, admin ← mutation boundaries
├── orderService, productService, cartService ← orchestration / reads
├── fulfillmentService, orderQueryService ← fulfillment & queries
└── refundService ← INTERNAL (RefundFlowService only)
| Protocol | Factory | Implementation |
|---|---|---|
| Checkout | createCheckoutStack() / wireOrderCheckoutStack() |
CheckoutFlowService |
| Refunds | createRefundStack() |
RefundFlowService |
| Inventory | createInventoryStack() |
InventoryFlowService |
| Admin | createAdminStack() |
AdminFlowService |
Not every API call goes through the four protocols. Use this table when adding routes:
| Operation | Correct entry | Example |
|---|---|---|
| Start checkout | services.checkout |
createCheckoutSession |
| Stripe webhook | services.checkout |
handleCheckoutWebhook |
| Refund | services.admin.requestRefund → services.refunds |
Admin UI |
| Concierge refund | services.refunds.createRefund |
Chat tool |
| Adjust stock | services.admin → services.inventory |
Batch adjust |
| PO receive | services.admin → PO service → receiveStockAtLocation |
Receiving UI |
| List orders (customer) | orderQueryService / read repos |
GET /api/orders |
| Product detail (storefront) | productService / repos |
GET /api/products/[id] |
| Fulfill order | services.admin.fulfillOrder |
Admin order page |
Rule of thumb: If it moves money or catalog stock, it goes through a protocol. If it only reads or sends email, it uses the appropriate read/orchestration service.
The most important integration in the codebase:
sequenceDiagram
participant R as Route
participant C as checkout
participant I as inventory
participant S as Stripe
participant F as Firestore
R->>C: createCheckoutSession
C->>I: reserveInventory
I->>F: stock -= qty, ledger
C->>S: PaymentIntent
S-->>R: clientSecret
Note over S,R: customer pays
S->>C: webhook succeeded
C->>I: confirmReservation
C->>F: order = paid
Full narrative: flows.md § Purchase flow
Every mutation route should follow this shape:
1. HTTP request → src/app/api/.../route.ts
2. Guards → session, role, rate limit, same-origin (apiGuards.ts)
3. Parse + validate → domain-aligned parsers
4. Delegate → services.{checkout|refunds|inventory|admin}.*
5. Adapt result → *RouteAdapter → JSON + HTTP status
6. Audit (optional) → AuditService / operator event log
Forensic correlation fields logged at protocol boundaries: orderId, caseId, stripeEventId, idempotencyKey.
Runtime commerce data lives in Firestore (src/infrastructure/repositories/firestore/).
| Concern | Representative data | Idempotency stores |
|---|---|---|
| Catalog | products, collections, taxonomy | — |
| Cart & orders | carts, orders, checkout attempts | checkout attempt keys |
| Payments | Stripe metadata on orders | stripe_webhook_events |
| Inventory | levels, reservations, ledger | ledger markers per operation |
| Refunds | order refund metadata | refund_execution_claims |
| Admin ops | audit, operator events | operator_action_events |
| Support | tickets, knowledgebase | — |
Checkout and refund protocols use separate claim collections so duplicate webhooks and retries never double-apply.
How major records relate — not an exhaustive schema:
erDiagram
Product ||--o{ OrderLine : sold_as
Order ||--|{ OrderLine : contains
Order ||--o| InventoryReservation : holds
Order }o--|| User : placed_by
PurchaseOrder ||--|{ POLine : contains
POLine }o--|| Product : receives
InventoryReservation }o--|| Product : reserves
Ticket }o--|| User : opened_by
Product {
string id
number stock
string handle
}
Order {
string id
string status
object metadata
}
InventoryReservation {
string state
string orderId
}
| Entity | Primary store | Owned by |
|---|---|---|
| Product | Firestore products |
Catalog / ProductService reads; stock via inventory protocol |
| Order | Firestore orders |
Checkout creates; admin fulfills |
| Reservation | Firestore reservations | Inventory protocol |
| Ledger entry | Firestore ledger | Inventory protocol (append-only) |
| Refund event | refund_execution_events |
Refund protocol |
| Checkout claim | stripe_webhook_events |
Checkout protocol |
Schema detail: .wiki/architecture/schemas.md
Full checklist: contributing-commerce.md
Money? → checkout or refunds
Stock? → inventory (admin wraps with authorization)
Operator? → admin
Read-only? → query services / repos
| Mechanism | Location | Purpose |
|---|---|---|
| Signed session cookie | session.ts |
Customer and admin identity |
| Admin guards | apiGuards.ts, admin layouts |
Role + route protection |
| Elevation | AdminFlowService |
Refunds, sensitive batch ops |
| Same-origin mutations | assertTrustedMutationOrigin |
CSRF mitigation |
| Rate limits | Route guards | Abuse protection |
| Checkout lock | Checkout protocol | One active checkout per user |
| Idempotency keys | All mutation protocols | Safe retries |
| I want to… | Open |
|---|---|
| Understand entity shapes | src/domain/models.ts |
| Wire a new service | src/core/container.ts |
| Change checkout behavior | src/core/order/CheckoutFlowService.ts |
| Change stock behavior | src/core/inventory/InventoryFlowService.ts |
| Change refund behavior | src/core/refund/RefundFlowService.ts |
| Change admin authorization | src/core/admin/AdminFlowService.ts |
| Add an API route | src/app/api/… (thin delegate only) |
| Change storefront UI | src/ui/pages/ |
| Change admin UI | src/ui/pages/admin/ |
| Map HTTP errors | src/infrastructure/server/*RouteAdapter.ts |
| Firestore adapter | src/infrastructure/repositories/firestore/ |
| Layer | Tooling | When to run |
|---|---|---|
| Storefront frozen chain | npm run test:storefront-release |
Cart, checkout, catalog, PDP changes |
| Protocol seals | *-verification-ladder.test.ts |
After any protocol change |
| Flow modules | Unit tests + in-memory repos | During development |
| API routes | Route tests on critical paths | Webhook, checkout verify |
| Checkout browser smoke | npm run test:e2e:checkout-smoke |
Checkout UI releases |
| Storefront journeys | Playwright e2e/ |
Broader UI regression |
| Throughput | npm run benchmark:order-flow |
Performance regression |
Protocol changes require ladder test updates — that is the architecture enforcement mechanism.
npm run test:storefront-release
npm test -- --run \
src/tests/checkout-verification-ladder.test.ts \
src/tests/refund-verification-ladder.test.ts \
src/tests/inventory-verification-ladder.test.ts \
src/tests/admin-verification-ladder.test.ts| Topic | Document |
|---|---|
| End-to-end stories | flows.md |
| Money capture | checkout.md |
| Stock movement | inventory.md |
| Money reversal | refunds.md |
| Merchant console | admin.md |
| Frozen rules | commerce-protocol-frozen.md |
| Storefront release proofs | storefront-release.md |
| Local setup | onboarding.md |