UNPKG

@aradox/multi-orm

Version:

Type-safe ORM with multi-datasource support, row-level security, and Prisma-like API for PostgreSQL, SQL Server, and HTTP APIs

626 lines (495 loc) 14.3 kB
# Enum Support Guide Complete guide to using enums in your ORM schema for type-safe string values. ## Table of Contents - [Overview](#overview) - [Defining Enums](#defining-enums) - [Using Enums in Models](#using-enums-in-models) - [Type Generation](#type-generation) - [Query Examples](#query-examples) - [Best Practices](#best-practices) - [Advanced Usage](#advanced-usage) - [Troubleshooting](#troubleshooting) ## Overview Enums provide type-safe string values in your schema, similar to Prisma's enum support. When you define an enum: 1. The parser validates enum definitions and usage 2. The IR includes enum metadata 3. The type generator emits TypeScript string union types 4. You get full IntelliSense support in queries and mutations **Benefits:** - Type safety - prevent invalid values at compile time - IntelliSense - autocomplete for enum values - Self-documenting - enum values are visible in schema - Refactoring - rename enum values safely across codebase - Validation - catch typos before runtime ## Defining Enums ### Basic Enum Define enums at the top level of your schema (before or after datasources/models): ``` enum UserRole { ADMIN USER GUEST } ``` ### Multiple Values Enums can have multiple values, one per line or comma-separated: ``` // One per line enum OrderStatus { PENDING PROCESSING SHIPPED DELIVERED CANCELLED } // Comma-separated (also valid) enum Priority { LOW, MEDIUM, HIGH, CRITICAL } // Mixed format (also valid) enum NotificationType { EMAIL, SMS PUSH IN_APP } ``` ### Naming Conventions **Recommended conventions:** - Enum names: `PascalCase` (e.g., `UserRole`, `OrderStatus`) - Enum values: `ALL_CAPS` with underscores (e.g., `SUPER_ADMIN`, `IN_PROGRESS`) ``` // ✅ Good - follows conventions enum UserRole { SUPER_ADMIN CONTENT_MODERATOR REGULAR_USER } // ⚠️ Works but not recommended enum userRole { admin user } ``` **Note:** The parser will emit warnings (not errors) for non-standard naming conventions. ## Using Enums in Models ### Basic Usage Use enum types like any other field type: ``` model User @datasource(main_db) { id Int @id @default(autoincrement()) email String @unique role UserRole // Enum field } ``` ### Optional Enum Fields Add `?` to make enum fields optional: ``` model Order @datasource(main_db) { id Int @id status OrderStatus // Required enum field priority Priority? // Optional enum field (can be null) } ``` ### Enum Array Fields Use `[]` for array fields (if supported by your database): ``` model User @datasource(main_db) { id Int @id roles UserRole[] // Array of enum values permissions Permission[] // Array of permission enums } ``` **Note:** Not all databases support array columns. PostgreSQL and MongoDB support arrays natively. For SQL Server and MySQL, you may need to use a junction table or JSON column. ### Complete Example ``` // Define enums enum UserRole { SUPER_ADMIN ADMIN MODERATOR USER } enum AccountStatus { ACTIVE SUSPENDED PENDING_VERIFICATION DELETED } enum Department { ENGINEERING SALES MARKETING HR } // Use in models datasource company_db { provider = "postgres" url = env("DATABASE_URL") } model Employee @datasource(company_db) { id Int @id @default(autoincrement()) email String @unique role UserRole // Required enum status AccountStatus // Required enum department Department? // Optional enum created_at DateTime @default(now()) // ... relations, etc. } ``` ## Type Generation When you run `npm run generate`, enums are emitted as TypeScript string union types: ```typescript // Generated in .qts/types.d.ts export type UserRole = 'SUPER_ADMIN' | 'ADMIN' | 'MODERATOR' | 'USER'; export type AccountStatus = 'ACTIVE' | 'SUSPENDED' | 'PENDING_VERIFICATION' | 'DELETED'; export type Department = 'ENGINEERING' | 'SALES' | 'MARKETING' | 'HR'; // Used in model types export type Employee = { id: number; email: string; role: UserRole; status: AccountStatus; department?: Department | null; created_at: Date | string; }; // Used in input types export type EmployeeWhereInput = { id?: IntFilter | number; email?: StringFilter | string; role?: StringFilter | UserRole; // Enum filter status?: StringFilter | AccountStatus; // Enum filter department?: NullableStringFilter | Department | null; // Optional enum filter // ... }; ``` ## Query Examples ### Filtering by Enum ```typescript // Find all admins const admins = await db.Employee.findMany({ where: { role: { eq: 'ADMIN' } // ✅ IntelliSense shows: SUPER_ADMIN | ADMIN | MODERATOR | USER } }); // Shorthand (also type-safe) const users = await db.Employee.findMany({ where: { role: 'USER' } // ✅ Type-safe, IntelliSense autocomplete }); // Multiple enum values const managers = await db.Employee.findMany({ where: { role: { in: ['ADMIN', 'MODERATOR'] } // ✅ Type-safe array } }); // Not equal const nonAdmins = await db.Employee.findMany({ where: { role: { ne: 'SUPER_ADMIN' } // ✅ Exclude super admins } }); // Combined with other filters const activeSalespeople = await db.Employee.findMany({ where: { status: 'ACTIVE', department: 'SALES', role: { in: ['USER', 'ADMIN'] } } }); ``` ### Creating Records with Enums ```typescript // Create with enum values const employee = await db.Employee.create({ data: { email: 'john@example.com', role: 'ADMIN', // ✅ IntelliSense autocomplete status: 'ACTIVE', // ✅ Type-safe department: 'ENGINEERING' // ✅ Optional enum } }); // TypeScript will catch typos at compile time const invalid = await db.Employee.create({ data: { email: 'jane@example.com', role: 'ADMINS', // ❌ TypeScript error: not a valid UserRole status: 'active' // ❌ TypeScript error: should be 'ACTIVE' } }); ``` ### Updating with Enums ```typescript // Update enum field const updated = await db.Employee.update({ where: { id: 1 }, data: { role: 'MODERATOR', // ✅ Type-safe status: 'SUSPENDED' // ✅ IntelliSense } }); // Update many with enum filter const result = await db.Employee.updateMany({ where: { role: 'USER', status: 'PENDING_VERIFICATION' }, data: { status: 'ACTIVE' // ✅ Bulk activation } }); ``` ### Upsert with Enums ```typescript const employee = await db.Employee.upsert({ where: { email: 'admin@example.com' }, create: { email: 'admin@example.com', role: 'SUPER_ADMIN', status: 'ACTIVE', department: 'ENGINEERING' }, update: { role: 'ADMIN', // Downgrade if exists status: 'ACTIVE' } }); ``` ## Best Practices ### 1. Choose Meaningful Enum Names ``` // ✅ Good - descriptive and clear enum UserRole { SUPER_ADMIN ADMIN MODERATOR USER } enum OrderStatus { PENDING_PAYMENT PAYMENT_CONFIRMED PROCESSING SHIPPED DELIVERED } // ❌ Bad - unclear abbreviations enum UR { SA A U } ``` ### 2. Use ALL_CAPS for Values ``` // ✅ Good - consistent convention enum Priority { VERY_HIGH HIGH MEDIUM LOW } // ⚠️ Works but not recommended enum Priority { VeryHigh High Medium Low } ``` ### 3. Group Related Enums Together ``` // User-related enums enum UserRole { ADMIN, USER, GUEST } enum UserStatus { ACTIVE, INACTIVE, SUSPENDED } enum UserType { INDIVIDUAL, BUSINESS, ENTERPRISE } // Order-related enums enum OrderStatus { PENDING, CONFIRMED, SHIPPED, DELIVERED } enum PaymentStatus { PENDING, PAID, REFUNDED } enum ShippingMethod { STANDARD, EXPRESS, OVERNIGHT } ``` ### 4. Consider Future Values When designing enums, think about future additions: ``` // ✅ Good - room for growth enum SubscriptionTier { FREE BASIC PROFESSIONAL ENTERPRISE // Easy to add: PREMIUM, ULTIMATE, etc. } // ⚠️ May need refactoring later enum Plan { PLAN_A PLAN_B PLAN_C } ``` ### 5. Use Enums for State Machines Enums are perfect for representing state: ``` enum OrderStatus { DRAFT PENDING_PAYMENT PAYMENT_FAILED PAID PROCESSING SHIPPED DELIVERED CANCELLED REFUNDED } // In your business logic, enforce valid transitions: // DRAFT → PENDING_PAYMENT → PAID → PROCESSING → SHIPPED → DELIVERED // ↓ ↓ ↓ ↓ // CANCELLED REFUNDED CANCELLED CANCELLED ``` ## Advanced Usage ### Enum with Database Column Mapping If your database uses different values than your enum: ``` // In your enum (schema-level names) enum UserRole { SUPER_ADMIN ADMIN USER } // If database uses different values, you may need to map in your application code // or use a database view/computed column ``` ### Enum Validation in Middleware Add custom validation for enum transitions: ```typescript import { Middleware } from '@aradox/multi-orm'; const orderStatusMiddleware: Middleware = { name: 'order-status-validator', beforeQuery: async ({ model, operation, args, context }) => { if (model === 'Order' && operation === 'update') { const { data } = args; // Validate status transitions if (data.status) { const currentOrder = await context.client.Order.findUnique({ where: args.where }); if (!isValidTransition(currentOrder.status, data.status)) { throw new Error(`Invalid status transition: ${currentOrder.status} ${data.status}`); } } } return args; } }; db.use(orderStatusMiddleware); ``` ### Enum-Based RBAC Use enums for role-based access control: ```typescript import { Middleware } from '@aradox/multi-orm'; const rbacMiddleware: Middleware = { name: 'rbac', beforeQuery: async ({ model, operation, context }) => { const userRole = context.user?.role; // UserRole enum value // Define permissions per role const permissions = { SUPER_ADMIN: ['*'], ADMIN: ['User', 'Order', 'Product'], MODERATOR: ['Order', 'Product'], USER: ['Order'], // Can only access their own orders GUEST: [] }; if (!permissions[userRole]?.includes(model) && !permissions[userRole]?.includes('*')) { throw new Error(`Access denied: role ${userRole} cannot access ${model}`); } return args; } }; db.use(rbacMiddleware); db.setContext({ user: { role: 'ADMIN' } }); // Set from session ``` ### Enum Statistics Query enum distribution: ```typescript // Get count by status const statusCounts = await db.$queryRaw( 'main_db', `SELECT status, COUNT(*) as count FROM orders GROUP BY status` ); console.log(statusCounts); // [ // { status: 'PENDING', count: 45 }, // { status: 'PROCESSING', count: 23 }, // { status: 'SHIPPED', count: 67 }, // ... // ] ``` ## Troubleshooting ### Error: "Invalid type 'UnknownEnum' for field" **Cause:** Field references an enum that doesn't exist. **Solution:** Ensure the enum is defined in your schema: ``` // ❌ Missing enum definition model User @datasource(db) { id Int @id role UserRole // Error: UserRole not defined } // ✅ Fixed - enum defined enum UserRole { ADMIN USER } model User @datasource(db) { id Int @id role UserRole // Works! } ``` ### Warning: "Enum value 'someValue' should be ALL_CAPS" **Cause:** Enum values don't follow the recommended ALL_CAPS convention. **Solution:** Rename values to ALL_CAPS (warning only, doesn't block compilation): ``` // ⚠️ Warning enum Status { pending active } // ✅ No warning enum Status { PENDING ACTIVE } ``` ### IntelliSense Not Showing Enum Values **Cause:** Types not generated or IDE not picking up generated types. **Solution:** 1. Run `npm run generate` to regenerate types 2. Restart your IDE/TypeScript server 3. Check that `.qts/types.d.ts` contains your enum definitions ### Database Stores Different Values **Cause:** Database column uses different format than schema enum. **Solution:** Options: 1. Migrate database to match enum values 2. Use database views/computed columns to transform values 3. Add mapping logic in your application layer 4. Consider if enum is the right choice (may need string field with validation) ### Enum Array Fields Not Supported **Cause:** Your database doesn't support array columns (e.g., SQL Server, MySQL). **Solution:** Options: 1. Use PostgreSQL or MongoDB which support arrays natively 2. Use a junction table for many-to-many relationships 3. Use JSON column to store array (loses type safety at DB level) 4. Store comma-separated values in string column (requires parsing) ## Summary Enums provide type-safe string values with full IntelliSense support: - Define enums at schema top level - Use in models like any other type - Support optional (`?`) and array (`[]`) modifiers - Get TypeScript string union types - Full IntelliSense in queries and mutations - Validated by schema and IR validators **Next Steps:** - Add enums to your schema - Run `npm run generate` - Enjoy type-safe queries! For more examples, see: - [orm.qts](../orm.qts) - Example schema with enums - [USER_GUIDE.md](./USER_GUIDE.md) - Complete usage guide - [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) - Cheat sheet