emr-types
Version:
Comprehensive TypeScript Types Library for Electronic Medical Record (EMR) Applications - Domain-Driven Design with Zod Validation
554 lines • 21.6 kB
JavaScript
import { z } from 'zod';
import { TenantStatus, TenantType } from '../domains/tenant';
// ============================================================================
// BASE SCHEMAS
// ============================================================================
export const TenantIdSchema = z.string().uuid().brand();
export const DomainNameSchema = z.string().min(1).max(253).regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/, {
message: 'Invalid domain name format'
}).brand();
// ============================================================================
// ENUM SCHEMAS
// ============================================================================
export const TenantStatusSchema = z.nativeEnum(TenantStatus);
export const TenantTypeSchema = z.nativeEnum(TenantType);
// ============================================================================
// VALUE OBJECT SCHEMAS
// ============================================================================
export const TenantIdValueObjectSchema = z.object({
value: TenantIdSchema,
format: z.enum(['uuid', 'auto-increment', 'custom']),
createdAt: z.date(),
metadata: z.record(z.unknown()).optional()
});
export const DomainNameValueObjectSchema = z.object({
value: DomainNameSchema,
isVerified: z.boolean(),
verifiedAt: z.date().optional(),
sslCertificate: z.object({
issuer: z.string(),
validFrom: z.date(),
validTo: z.date(),
serialNumber: z.string()
}).optional(),
createdAt: z.date()
});
// ============================================================================
// ENTITY SCHEMAS
// ============================================================================
export const TenantConfigSchema = z.object({
id: TenantIdSchema,
features: z.object({
multiTenancy: z.boolean(),
sso: z.boolean(),
auditLogging: z.boolean(),
backup: z.boolean(),
customBranding: z.boolean(),
apiAccess: z.boolean(),
webhookSupport: z.boolean(),
advancedAnalytics: z.boolean()
}),
limits: z.object({
maxUsers: z.number().positive(),
maxStorage: z.number().positive(),
maxApiCalls: z.number().positive(),
maxBackups: z.number().nonnegative(),
maxCustomDomains: z.number().nonnegative()
}),
settings: z.object({
timezone: z.string(),
locale: z.string(),
dateFormat: z.string(),
timeFormat: z.string(),
currency: z.string(),
theme: z.object({
primaryColor: z.string(),
secondaryColor: z.string(),
logo: z.string().url().optional(),
favicon: z.string().url().optional()
}).optional(),
notifications: z.object({
email: z.boolean(),
sms: z.boolean(),
push: z.boolean(),
webhook: z.boolean()
}),
security: z.object({
passwordPolicy: z.object({
minLength: z.number().min(8),
requireUppercase: z.boolean(),
requireLowercase: z.boolean(),
requireNumbers: z.boolean(),
requireSpecialChars: z.boolean(),
maxAge: z.number().positive()
}),
sessionTimeout: z.number().positive(),
maxLoginAttempts: z.number().positive(),
twoFactorRequired: z.boolean(),
ipWhitelist: z.array(z.string().ip()).optional()
}),
integrations: z.record(z.object({
enabled: z.boolean(),
apiKey: z.string().optional(),
webhookUrl: z.string().url().optional(),
settings: z.record(z.unknown()).optional()
})).optional()
}),
createdAt: z.date(),
updatedAt: z.date(),
createdBy: z.string().uuid().optional(),
updatedBy: z.string().uuid().optional()
});
export const TenantSchema = z.object({
id: TenantIdSchema,
name: z.string().min(1).max(100),
slug: z.string().min(1).max(50).regex(/^[a-z0-9-]+$/, {
message: 'Slug must contain only lowercase letters, numbers, and hyphens'
}),
domain: DomainNameValueObjectSchema,
type: TenantTypeSchema,
status: TenantStatusSchema,
config: TenantConfigSchema,
contact: z.object({
email: z.string().email(),
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(),
address: z.object({
street: z.string().max(200),
city: z.string().max(100),
state: z.string().max(100),
postalCode: z.string().max(20),
country: z.string().max(100)
}).optional()
}),
billing: z.object({
plan: z.enum(['free', 'basic', 'professional', 'enterprise']),
billingCycle: z.enum(['monthly', 'quarterly', 'annually']),
nextBillingDate: z.date(),
lastBillingDate: z.date().optional(),
totalAmount: z.number().nonnegative(),
currency: z.string(),
paymentMethod: z.object({
type: z.enum(['credit_card', 'bank_transfer', 'paypal']),
last4: z.string().length(4).optional(),
expiryDate: z.string().optional()
}).optional(),
invoices: z.array(z.object({
id: z.string(),
amount: z.number().positive(),
currency: z.string(),
status: z.enum(['pending', 'paid', 'overdue', 'cancelled']),
dueDate: z.date(),
paidAt: z.date().optional()
})).optional()
}),
subscription: z.object({
startDate: z.date(),
endDate: z.date().optional(),
isActive: z.boolean(),
autoRenew: z.boolean(),
trialEndsAt: z.date().optional(),
cancelledAt: z.date().optional()
}),
usage: z.object({
activeUsers: z.number().nonnegative(),
storageUsed: z.number().nonnegative(),
apiCallsThisMonth: z.number().nonnegative(),
lastActivityAt: z.date().optional()
}),
createdAt: z.date(),
updatedAt: z.date(),
createdBy: z.string().uuid().optional(),
updatedBy: z.string().uuid().optional(),
deletedAt: z.date().optional()
});
// ============================================================================
// REQUEST/INPUT SCHEMAS
// ============================================================================
export const CreateTenantRequestSchema = z.object({
name: z.string().min(1).max(100),
slug: z.string().min(1).max(50).regex(/^[a-z0-9-]+$/),
domain: z.string().min(1).max(253),
type: TenantTypeSchema,
contact: z.object({
email: z.string().email(),
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(),
address: z.object({
street: z.string().max(200),
city: z.string().max(100),
state: z.string().max(100),
postalCode: z.string().max(20),
country: z.string().max(100)
}).optional()
}),
billing: z.object({
plan: z.enum(['free', 'basic', 'professional', 'enterprise']),
billingCycle: z.enum(['monthly', 'quarterly', 'annually']),
paymentMethod: z.object({
type: z.enum(['credit_card', 'bank_transfer', 'paypal']),
token: z.string().optional()
}).optional()
}),
config: z.object({
features: z.object({
multiTenancy: z.boolean().optional(),
sso: z.boolean().optional(),
auditLogging: z.boolean().optional(),
backup: z.boolean().optional(),
customBranding: z.boolean().optional(),
apiAccess: z.boolean().optional(),
webhookSupport: z.boolean().optional(),
advancedAnalytics: z.boolean().optional()
}).optional(),
limits: z.object({
maxUsers: z.number().positive().optional(),
maxStorage: z.number().positive().optional(),
maxApiCalls: z.number().positive().optional(),
maxBackups: z.number().nonnegative().optional(),
maxCustomDomains: z.number().nonnegative().optional()
}).optional(),
settings: z.object({
timezone: z.string().optional(),
locale: z.string().optional(),
dateFormat: z.string().optional(),
timeFormat: z.string().optional(),
currency: z.string().optional(),
theme: z.object({
primaryColor: z.string().optional(),
secondaryColor: z.string().optional(),
logo: z.string().url().optional(),
favicon: z.string().url().optional()
}).optional(),
notifications: z.object({
email: z.boolean().optional(),
sms: z.boolean().optional(),
push: z.boolean().optional(),
webhook: z.boolean().optional()
}).optional(),
security: z.object({
passwordPolicy: z.object({
minLength: z.number().min(8).optional(),
requireUppercase: z.boolean().optional(),
requireLowercase: z.boolean().optional(),
requireNumbers: z.boolean().optional(),
requireSpecialChars: z.boolean().optional(),
maxAge: z.number().positive().optional()
}).optional(),
sessionTimeout: z.number().positive().optional(),
maxLoginAttempts: z.number().positive().optional(),
twoFactorRequired: z.boolean().optional(),
ipWhitelist: z.array(z.string().ip()).optional()
}).optional()
}).optional()
}).optional()
});
export const UpdateTenantRequestSchema = z.object({
name: z.string().min(1).max(100).optional(),
slug: z.string().min(1).max(50).regex(/^[a-z0-9-]+$/).optional(),
domain: z.string().min(1).max(253).optional(),
type: TenantTypeSchema.optional(),
status: TenantStatusSchema.optional(),
contact: z.object({
email: z.string().email(),
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(),
address: z.object({
street: z.string().max(200),
city: z.string().max(100),
state: z.string().max(100),
postalCode: z.string().max(20),
country: z.string().max(100)
}).optional()
}).optional(),
billing: z.object({
plan: z.enum(['free', 'basic', 'professional', 'enterprise']).optional(),
billingCycle: z.enum(['monthly', 'quarterly', 'annually']).optional(),
paymentMethod: z.object({
type: z.enum(['credit_card', 'bank_transfer', 'paypal']),
token: z.string().optional()
}).optional()
}).optional(),
config: z.object({
features: z.object({
multiTenancy: z.boolean().optional(),
sso: z.boolean().optional(),
auditLogging: z.boolean().optional(),
backup: z.boolean().optional(),
customBranding: z.boolean().optional(),
apiAccess: z.boolean().optional(),
webhookSupport: z.boolean().optional(),
advancedAnalytics: z.boolean().optional()
}).optional(),
limits: z.object({
maxUsers: z.number().positive().optional(),
maxStorage: z.number().positive().optional(),
maxApiCalls: z.number().positive().optional(),
maxBackups: z.number().nonnegative().optional(),
maxCustomDomains: z.number().nonnegative().optional()
}).optional(),
settings: z.object({
timezone: z.string().optional(),
locale: z.string().optional(),
dateFormat: z.string().optional(),
timeFormat: z.string().optional(),
currency: z.string().optional(),
theme: z.object({
primaryColor: z.string().optional(),
secondaryColor: z.string().optional(),
logo: z.string().url().optional(),
favicon: z.string().url().optional()
}).optional(),
notifications: z.object({
email: z.boolean().optional(),
sms: z.boolean().optional(),
push: z.boolean().optional(),
webhook: z.boolean().optional()
}).optional(),
security: z.object({
passwordPolicy: z.object({
minLength: z.number().min(8).optional(),
requireUppercase: z.boolean().optional(),
requireLowercase: z.boolean().optional(),
requireNumbers: z.boolean().optional(),
requireSpecialChars: z.boolean().optional(),
maxAge: z.number().positive().optional()
}).optional(),
sessionTimeout: z.number().positive().optional(),
maxLoginAttempts: z.number().positive().optional(),
twoFactorRequired: z.boolean().optional(),
ipWhitelist: z.array(z.string().ip()).optional()
}).optional()
}).optional()
}).optional()
});
export const ActivateTenantRequestSchema = z.object({
activationCode: z.string().length(6),
adminUser: z.object({
email: z.string().email(),
password: z.string().min(8),
firstName: z.string().min(1).max(50),
lastName: z.string().min(1).max(50)
})
});
export const SuspendTenantRequestSchema = z.object({
reason: z.string().min(1).max(500),
suspendUntil: z.date().optional()
});
export const UpgradeTenantRequestSchema = z.object({
plan: z.enum(['basic', 'professional', 'enterprise']),
billingCycle: z.enum(['monthly', 'quarterly', 'annually']),
paymentMethod: z.object({
type: z.enum(['credit_card', 'bank_transfer', 'paypal']),
token: z.string().optional()
}).optional()
});
// ============================================================================
// RESPONSE SCHEMAS
// ============================================================================
export const TenantResponseSchema = z.object({
id: TenantIdSchema,
name: z.string(),
slug: z.string(),
domain: z.string(),
type: TenantTypeSchema,
status: TenantStatusSchema,
contact: z.object({
email: z.string().email(),
phone: z.string().optional()
}),
billing: z.object({
plan: z.enum(['free', 'basic', 'professional', 'enterprise']),
billingCycle: z.enum(['monthly', 'quarterly', 'annually']),
nextBillingDate: z.date(),
totalAmount: z.number().nonnegative(),
currency: z.string()
}),
subscription: z.object({
isActive: z.boolean(),
autoRenew: z.boolean(),
trialEndsAt: z.date().optional()
}),
usage: z.object({
activeUsers: z.number().nonnegative(),
storageUsed: z.number().nonnegative(),
apiCallsThisMonth: z.number().nonnegative()
}),
createdAt: z.date(),
updatedAt: z.date()
});
export const TenantListResponseSchema = z.object({
tenants: z.array(TenantResponseSchema),
pagination: z.object({
page: z.number().positive(),
limit: z.number().positive(),
total: z.number().nonnegative(),
totalPages: z.number().nonnegative()
})
});
export const TenantConfigResponseSchema = z.object({
features: z.object({
multiTenancy: z.boolean(),
sso: z.boolean(),
auditLogging: z.boolean(),
backup: z.boolean(),
customBranding: z.boolean(),
apiAccess: z.boolean(),
webhookSupport: z.boolean(),
advancedAnalytics: z.boolean()
}),
limits: z.object({
maxUsers: z.number().positive(),
maxStorage: z.number().positive(),
maxApiCalls: z.number().positive(),
maxBackups: z.number().nonnegative(),
maxCustomDomains: z.number().nonnegative()
}),
settings: z.object({
timezone: z.string(),
locale: z.string(),
dateFormat: z.string(),
timeFormat: z.string(),
currency: z.string(),
theme: z.object({
primaryColor: z.string(),
secondaryColor: z.string(),
logo: z.string().url().optional(),
favicon: z.string().url().optional()
}).optional(),
notifications: z.object({
email: z.boolean(),
sms: z.boolean(),
push: z.boolean(),
webhook: z.boolean()
}),
security: z.object({
passwordPolicy: z.object({
minLength: z.number().min(8),
requireUppercase: z.boolean(),
requireLowercase: z.boolean(),
requireNumbers: z.boolean(),
requireSpecialChars: z.boolean(),
maxAge: z.number().positive()
}),
sessionTimeout: z.number().positive(),
maxLoginAttempts: z.number().positive(),
twoFactorRequired: z.boolean(),
ipWhitelist: z.array(z.string().ip()).optional()
})
})
});
export const TenantUsageResponseSchema = z.object({
activeUsers: z.number().nonnegative(),
storageUsed: z.number().nonnegative(),
apiCallsThisMonth: z.number().nonnegative(),
apiCallsLimit: z.number().positive(),
storageLimit: z.number().positive(),
userLimit: z.number().positive(),
usagePercentages: z.object({
users: z.number().min(0).max(100),
storage: z.number().min(0).max(100),
apiCalls: z.number().min(0).max(100)
})
});
// ============================================================================
// FILTER/SEARCH SCHEMAS
// ============================================================================
export const TenantSearchFiltersSchema = z.object({
query: z.string().optional(),
type: TenantTypeSchema.optional(),
status: TenantStatusSchema.optional(),
plan: z.enum(['free', 'basic', 'professional', 'enterprise']).optional(),
billingCycle: z.enum(['monthly', 'quarterly', 'annually']).optional(),
createdAfter: z.date().optional(),
createdBefore: z.date().optional(),
hasActiveSubscription: z.boolean().optional(),
isOverLimit: z.boolean().optional()
});
export const TenantSortOptionsSchema = z.enum([
'createdAt',
'updatedAt',
'name',
'slug',
'type',
'status',
'plan',
'activeUsers',
'storageUsed',
'apiCallsThisMonth'
]);
// ============================================================================
// VALIDATION UTILITIES
// ============================================================================
export const TenantValidationUtils = {
isValidDomainName: (domain) => {
return /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(domain);
},
isValidSlug: (slug) => {
return /^[a-z0-9-]+$/.test(slug);
},
isOverUsageLimit: (usage, limits) => {
return usage.activeUsers > limits.maxUsers ||
usage.storageUsed > limits.maxStorage ||
usage.apiCallsThisMonth > limits.maxApiCalls;
},
calculateUsagePercentage: (current, limit) => {
return Math.round((current / limit) * 100);
},
validateTenantConfig: (config) => {
const errors = [];
if (config.limits.maxUsers < 1) {
errors.push('Max users must be at least 1');
}
if (config.limits.maxStorage < 1) {
errors.push('Max storage must be at least 1');
}
if (config.limits.maxApiCalls < 1) {
errors.push('Max API calls must be at least 1');
}
if (config.settings.security.passwordPolicy.minLength < 8) {
errors.push('Password minimum length must be at least 8');
}
if (config.settings.security.sessionTimeout < 1) {
errors.push('Session timeout must be at least 1 minute');
}
if (config.settings.security.maxLoginAttempts < 1) {
errors.push('Max login attempts must be at least 1');
}
return {
isValid: errors.length === 0,
errors
};
}
};
// ============================================================================
// SCHEMA EXPORTS
// ============================================================================
export const TenantSchemas = {
// Base schemas
TenantId: TenantIdSchema,
DomainName: DomainNameSchema,
// Enum schemas
TenantStatus: TenantStatusSchema,
TenantType: TenantTypeSchema,
// Value object schemas
TenantIdValueObject: TenantIdValueObjectSchema,
DomainNameValueObject: DomainNameValueObjectSchema,
// Entity schemas
Tenant: TenantSchema,
TenantConfig: TenantConfigSchema,
// Request schemas
CreateTenantRequest: CreateTenantRequestSchema,
UpdateTenantRequest: UpdateTenantRequestSchema,
ActivateTenantRequest: ActivateTenantRequestSchema,
SuspendTenantRequest: SuspendTenantRequestSchema,
UpgradeTenantRequest: UpgradeTenantRequestSchema,
// Response schemas
TenantResponse: TenantResponseSchema,
TenantListResponse: TenantListResponseSchema,
TenantConfigResponse: TenantConfigResponseSchema,
TenantUsageResponse: TenantUsageResponseSchema,
// Filter schemas
TenantSearchFilters: TenantSearchFiltersSchema,
TenantSortOptions: TenantSortOptionsSchema,
// Utilities
ValidationUtils: TenantValidationUtils
};
//# sourceMappingURL=tenant-schemas.js.map