-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Problem
The product.service.ts file is currently 1,662 lines long, making it one of the largest files in the codebase. A significant portion (approximately 150+ lines) consists of Zod validation schemas that are tightly coupled with the service logic. Additionally, similar validation patterns for common fields (email, phone, URLs) are duplicated across 10+ files in the codebase.
Current Code Location
- Primary file:
src/lib/services/product.service.ts(lines 66-165) - Complexity: High (1,662 total lines, 150+ lines of validation schemas)
- Related duplication: Email validation in 10+ files, phone validation in 10+ files
Proposed Refactoring
Extract Zod validation schemas into a centralized, reusable validation module. This will:
- Reduce the product service file size by ~150 lines
- Enable schema reuse across the codebase
- Standardize validation patterns for common fields (email, phone, URLs, addresses)
- Improve testability of validation logic in isolation
Benefits
- Maintainability: Changes to validation rules happen in one place
- Consistency: Standardized validation across all forms and services
- Testability: Validation schemas can be tested independently
- Reusability: Common schemas (email, phone, address) can be composed into larger schemas
- Discoverability: Developers can find all validation rules in one location
- Type Safety: Inferred types from schemas remain consistent across usage
Suggested Approach
-
Create new module structure:
src/lib/validation/ ├── index.ts # Barrel export ├── common.ts # Common field validators (email, phone, url, etc.) ├── address.ts # Address validation schemas ├── product.ts # Product-specific schemas (from product.service.ts) ├── order.ts # Order validation schemas └── store.ts # Store validation schemas
-
Extract common validators (
src/lib/validation/common.ts):import { z } from 'zod'; // Email validation - standardize across all usages export const emailSchema = z.string().email('Invalid email address'); // Phone validation - consistent pattern across checkout, orders, customers export const phoneSchema = z.string() .regex(/^\+?[\d\s\-()]{10,}$/, 'Please enter a valid phone number') .min(10, 'Phone number must be at least 10 digits'); // URL validation with relative path support export const urlSchema = z.string().refine((v) => { if (!v) return true; try { new URL(v); return true; } catch { return typeof v === 'string' && v.startsWith('/'); } }, { message: 'Invalid URL' }); // Optional string that allows empty string to be treated as undefined export const optionalString = z.string().optional().or(z.literal(""));
-
Extract product schemas (
src/lib/validation/product.ts):import { z } from 'zod'; import { DiscountType, ProductStatus, InventoryStatus } from '`@prisma/client`'; import { urlSchema, optionalString } from './common'; export const variantSchema = z.object({ id: z.string().cuid().optional(), name: z.string().min(1, "Variant name is required").max(255), sku: z.string().min(1, "Variant SKU is required").max(100), barcode: z.string().max(100).optional().nullable(), price: z.coerce.number().min(0).optional().nullable(), compareAtPrice: z.coerce.number().min(0).optional().nullable(), discountType: z.nativeEnum(DiscountType).optional().nullable().default(DiscountType.NONE), discountValue: z.coerce.number().min(0).optional().nullable(), discountStartDate: z.coerce.date().optional().nullable(), discountEndDate: z.coerce.date().optional().nullable(), inventoryQty: z.coerce.number().int().min(0).default(0), lowStockThreshold: z.coerce.number().int().min(0).default(5), weight: z.coerce.number().min(0).optional().nullable(), image: urlSchema.optional().nullable(), options: z.union([z.string(), z.record(z.string(), z.string())]).default("{}"), isDefault: z.boolean().default(false), }); export const createProductSchema = z.object({ // ... (extracted from product.service.ts lines 99-164) }); export const updateProductSchema = createProductSchema.partial().extend({ id: z.string().cuid(), }); export type VariantData = z.infer(typeof variantSchema); export type CreateProductData = z.infer(typeof createProductSchema); export type UpdateProductData = z.infer(typeof updateProductSchema);
-
Update product.service.ts to import schemas:
import { variantSchema, createProductSchema, updateProductSchema, type VariantData, type CreateProductData, type UpdateProductData } from '@/lib/validation/product';
-
Migrate existing usages:
- Update
src/app/store/[slug]/checkout/page.tsxto usephoneSchemafrom common validators - Update
src/components/checkout/shipping-details-step.tsxto use standardized validators - Update all email validation in 10+ files to use
emailSchema - Update phone validation in 10+ files to use
phoneSchema
- Update
Code Example
Before (scattered across multiple files):
// src/app/store/[slug]/checkout/page.tsx
const checkoutSchema = z.object({
email: z.string().email("Please enter a valid email address"),
phone: z.string().regex(/^\+?[\d\s\-()]{10,}$/, "Please enter a valid phone number"),
// ...
});
// src/components/stores/store-form-dialog.tsx
const storeSchema = z.object({
email: z.string().email('Invalid email address'),
phone: z.string().optional(),
// ...
});
// src/lib/services/product.service.ts (lines 66-165)
export const variantSchema = z.object({
// 30+ lines of validation logic
});
export const createProductSchema = z.object({
// 65+ lines of validation logic
});After (centralized and reusable):
// src/lib/validation/common.ts
export const emailSchema = z.string().email('Invalid email address');
export const phoneSchema = z.string().regex(/^\+?[\d\s\-()]{10,}$/, 'Please enter a valid phone number');
// src/lib/validation/product.ts
export const variantSchema = z.object({ /* ... */ });
export const createProductSchema = z.object({ /* ... */ });
// Usage in checkout
import { emailSchema, phoneSchema } from '@/lib/validation/common';
const checkoutSchema = z.object({
email: emailSchema,
phone: phoneSchema,
// ...
});
// Usage in product service
import { variantSchema, createProductSchema } from '@/lib/validation/product';Impact Assessment
-
Effort: Medium - Estimated 4-6 hours
- 1-2 hours: Create validation module structure
- 2-3 hours: Extract and organize schemas
- 1-2 hours: Update all imports and test
-
Risk: Low
- No behavioral changes, only code organization
- Validation logic remains identical
- Type safety preserved through Zod inference
- Can be done incrementally (start with common validators)
-
Benefit: High
- Reduces product.service.ts by ~150 lines (9% reduction)
- Eliminates 20+ instances of duplicated validation code
- Standardizes validation patterns across entire codebase
- Makes validation logic discoverable and reusable
-
Priority: High - This is a foundational improvement that will benefit many future features
Related Files
Files to create:
src/lib/validation/index.tssrc/lib/validation/common.tssrc/lib/validation/address.tssrc/lib/validation/product.ts
Files to update (import changes only):
src/lib/services/product.service.tssrc/app/store/[slug]/checkout/page.tsxsrc/components/checkout/shipping-details-step.tsxsrc/components/stores/store-form-dialog.tsxsrc/components/admin/create-store-form.tsxsrc/components/customers/customer-dialog.tsxsrc/lib/services/store.service.tssrc/lib/services/order.service.ts- ...and 10+ other files with email/phone validation
Testing Strategy
-
Unit tests for validators:
// src/lib/validation/__tests__/common.test.ts describe('emailSchema', () => { it('accepts valid emails', () => { expect(emailSchema.parse('test@example.com')).toBe('test@example.com'); }); it('rejects invalid emails', () => { expect(() => emailSchema.parse('not-an-email')).toThrow(); }); });
-
Existing integration tests should pass without modification (validation behavior unchanged)
-
Type checking: Run
npm run type-checkto ensure all imports resolve correctly -
Manual testing: Test forms that use migrated validators (checkout, store creation, customer management)
Success Criteria
- All validation schemas extracted to
src/lib/validation/ - Common validators (email, phone, URL) standardized across codebase
- product.service.ts reduced by ~150 lines
- All existing tests pass
- No TypeScript errors
- Documentation added to
src/lib/validation/README.mdexplaining the module structure
AI generated by Daily Codebase Analyzer - Semantic Function Extraction & Refactoring
- expires on Mar 8, 2026, 1:41 PM UTC
Metadata
Metadata
Assignees
Type
Projects
Status