Domain Model
PrintStudio’s domain model is defined in packages/domain as 408 Zod schemas. The database schema mirrors these types via Drizzle ORM in packages/core/src/db/schema/.
Core Entities
Section titled “Core Entities”Customer └── Order (1:N) └── LineItem (1:N) └── Job (1:1) ├── SKU (N:1) ├── Printer (N:1) └── StateTransition (1:N)
Printer └── PrinterStatus (polling)
Material └── InventoryRecord (1:N)
SKU ├── Material (N:1) └── SlicerProfile (N:1)Entity Descriptions
Section titled “Entity Descriptions”Customer
Section titled “Customer”type Customer = { id: string; email: string; name: string; role: "customer" | "operator" | "admin"; stripeCustomerId?: string; createdAt: Date;};type Order = { id: string; // e.g., "ord-abc123" customerId: string; status: OrderStatus; // pending_payment | paid | processing | ... lineItems: LineItem[]; subtotal: number; shipping: number; tax: number; total: number; stripeSessionId?: string; stripePaymentIntentId?: string; shippingAddress: Address; notes?: string; createdAt: Date; updatedAt: Date;};type Job = { id: string; // e.g., "job-uuid" orderId: string; lineItemId: string; skuId: string; status: JobStatus; // CREATED | VALIDATED | SLICED | QUEUED | ... printerId?: string; // assigned printer gcodeFile?: string; // path in file storage estimatedPrintTime: number; // minutes actualPrintTime?: number; estimatedMaterialGrams: number; actualMaterialGrams?: number; retryCount: number; lastError?: string; preferredPrinterId?: string; // operator override priority: number; // higher = earlier in queue createdAt: Date; updatedAt: Date;};type SKU = { id: string; name: string; description: string; machineType: MachineType; material: string; estimatedPrintTime: number; estimatedMaterialGrams: number; basePrice: number; qualityTiers: Record<QualityTier, { multiplier: number; slicerProfile?: string }>; options: SKUOption[]; slicerProfile: string; active: boolean;};Printer
Section titled “Printer”type Printer = { id: string; name: string; type: "moonraker" | "octoprint" | "bambu"; machineType: MachineType; baseUrl: string; apiKey?: string; buildVolume: { x: number; y: number; z: number }; status: "online" | "offline" | "printing" | "paused" | "error" | "maintenance"; currentJobId?: string; amsSlots?: AMSSlot[]; createdAt: Date;};State Types
Section titled “State Types”JobStatus
Section titled “JobStatus”type JobStatus = | "CREATED" | "VALIDATED" | "SLICED" | "QUEUED" | "PRINTING" | "POST_PROCESS" | "READY" | "SHIPPED" | "FAILED" | "CANCELLED";MachineType
Section titled “MachineType”type MachineType = | "fdm_large" // Bambu X1C, Voron 2.4, Kobra Max | "fdm_standard" // Prusa MK4, Ender 3 | "resin" // Elegoo Saturn, Phrozen | "laser"; // Glowforge, xToolQualityTier
Section titled “QualityTier”type QualityTier = "draft" | "standard" | "fine";Database Schema
Section titled “Database Schema”Tables are defined with Drizzle ORM in packages/core/src/db/schema/:
| Table | Key relationships |
|---|---|
customers | root entity |
orders | → customers |
line_items | → orders, → skus |
jobs | → line_items, → printers |
job_transitions | → jobs (audit log) |
printers | root entity |
printer_status | → printers (latest poll) |
skus | root entity |
materials | root entity |
inventory_records | → materials |
api_keys | → customers |
sessions | → customers |
Validation
Section titled “Validation”All input validation uses Zod schemas from packages/domain. The API layer validates every request body against these schemas before passing to business logic. Invalid input returns 400 with Zod’s error details:
{ "error": "Validation failed", "issues": [ { "path": ["lineItems", 0, "quantity"], "message": "Expected number, received string" } ]}