@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
Markdown
# 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 (main_db) {
id Int (autoincrement())
email String
role UserRole // Enum field
}
```
### Optional Enum Fields
Add `?` to make enum fields optional:
```
model Order (main_db) {
id Int
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 (main_db) {
id Int
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 (company_db) {
id Int (autoincrement())
email String
role UserRole // Required enum
status AccountStatus // Required enum
department Department? // Optional enum
created_at DateTime (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.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.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.com' },
create: {
email: 'admin.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 '/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 (db) {
id Int
role UserRole // Error: UserRole not defined
}
// ✅ Fixed - enum defined
enum UserRole {
ADMIN
USER
}
model User (db) {
id Int
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