UNPKG

emr-types

Version:

Comprehensive TypeScript Types Library for Electronic Medical Record (EMR) Applications - Domain-Driven Design with Zod Validation

554 lines 21.6 kB
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