Key Components

Key Components

Entity Framework

The entity framework provides a TypeScript-based object model that represents data entities with strong typing, validation, and relationship support.

EntityBase

EntityBase is the base class for all entities in MemberJunction. It provides:

  • Property management
  • Change tracking
  • Validation
  • Event handling
  • Serialization/deserialization
  • Data access methods
abstract class EntityBase {
  // Core properties
  private _properties: Map<string, any>;
  private _originalValues: Map<string, any>;
  private _isDirty: boolean = false;
  private _isNew: boolean = true;
  private _validationErrors: ValidationError[] = [];
  
  // Core methods
  public getValue(propertyName: string): any;
  public setValue(propertyName: string, value: any): void;
  public isDirty(): boolean;
  public isNew(): boolean;
  public resetDirty(): void;
  public validate(): boolean;
  public addValidationError(propertyName: string, errorMessage: string): void;
  public getValidationErrors(): ValidationError[];
  public save(): Promise<boolean>;
  public delete(): Promise<boolean>;
  
  // Lifecycle hooks
  protected beforeSave(): Promise<boolean>;
  protected afterSave(): Promise<void>;
  protected beforeDelete(): Promise<boolean>;
  protected afterDelete(): Promise<void>;
}

EntityManager

The EntityManager provides centralized access to entities and handles the creation, retrieval, and manipulation of entity instances:

class EntityManager {
  // Factory methods
  public createEntity(entityName: string): EntityBase;
  
  // Retrieval methods
  public getEntityById(entityName: string, id: any): Promise<EntityBase>;
  public getEntitiesWithFilter(entityName: string, filters: Filter[]): Promise<EntityBase[]>;
  
  // Metadata access
  public getEntityMetadata(entityName: string): EntityMetadata;
  
  // Transaction management
  public createUnitOfWork(): UnitOfWork;
}

Metadata System

The metadata system stores and manages metadata about entities, relationships, and business logic, powering the metadata-driven architecture of MemberJunction.

EntityMetadata

EntityMetadata contains descriptive information about an entity:

class EntityMetadata {
  // Core properties
  public name: string;
  public displayName: string;
  public schema: string;
  public tableName: string;
  public primaryKey: string;
  public properties: PropertyMetadata[];
  public relationships: RelationshipMetadata[];
  
  // Behavior flags
  public isMutable: boolean;
  public isAuditable: boolean;
  public isVersioned: boolean;
  
  // Methods
  public getProperty(propertyName: string): PropertyMetadata;
  public getRelationship(relationshipName: string): RelationshipMetadata;
}

PropertyMetadata

PropertyMetadata describes an entity property:

class PropertyMetadata {
  public name: string;
  public displayName: string;
  public description: string;
  public dataType: DataType;
  public maxLength: number;
  public isPrimaryKey: boolean;
  public isRequired: boolean;
  public isUnique: boolean;
  public defaultValue: any;
  public validationRules: ValidationRule[];
}

RelationshipMetadata

RelationshipMetadata describes a relationship between entities:

class RelationshipMetadata {
  public name: string;
  public entityName: string;
  public relatedEntityName: string;
  public relationType: RelationType; // OneToOne, OneToMany, ManyToMany
  public propertyName: string;
  public foreignKeyName: string;
  public isRequired: boolean;
  public cascade: CascadeType;
}

Provider Architecture

The provider architecture enables MemberJunction to work with various database systems, authentication providers, and storage solutions.

DatabaseProvider

DatabaseProvider is an abstraction over database access:

interface DatabaseProvider {
  connect(): Promise<void>;
  disconnect(): Promise<void>;
  executeQuery(query: string, parameters?: any[]): Promise<any[]>;
  executeNonQuery(command: string, parameters?: any[]): Promise<number>;
  beginTransaction(): Promise<Transaction>;
  getMetadata(): Promise<EntityMetadata[]>;
}

AuthenticationProvider

AuthenticationProvider handles user authentication:

interface AuthenticationProvider {
  authenticate(credentials: any): Promise<UserContext>;
  validateToken(token: string): Promise<UserContext>;
  refreshToken(token: string): Promise<string>;
  logout(token: string): Promise<void>;
}

StorageProvider

StorageProvider manages file and binary data storage:

interface StorageProvider {
  storeFile(fileName: string, content: Buffer): Promise<string>;
  retrieveFile(fileId: string): Promise<Buffer>;
  deleteFile(fileId: string): Promise<boolean>;
  getFileUrl(fileId: string): string;
}

Configuration System

The configuration system manages application settings and environment-specific configurations.

ConfigManager

ConfigManager provides access to configuration settings:

class ConfigManager {
  // Access methods
  public static getValue(key: string): any;
  public static getSection(sectionName: string): any;
  
  // Modification methods
  public static setValue(key: string, value: any): void;
  
  // Environment handling
  public static getEnvironment(): string;
  public static isProduction(): boolean;
}

Validation Framework

The validation framework provides mechanisms for validating entity data according to business rules.

ValidationRule

ValidationRule defines a validation constraint:

interface ValidationRule {
  validate(value: any, context: ValidationContext): boolean;
  getMessage(): string;
}

ValidationContext

ValidationContext provides context for validation:

class ValidationContext {
  public entity: EntityBase;
  public property: string;
  public value: any;
  public relatedValues: Map<string, any>;
}

Event System

The event system enables communication between components through an event-driven architecture.

EventEmitter

EventEmitter provides event handling capabilities:

class EventEmitter {
  // Event registration
  public on(eventName: string, handler: EventHandler): void;
  public off(eventName: string, handler: EventHandler): void;
  
  // Event triggering
  public emit(eventName: string, data?: any): void;
  
  // Utility methods
  public hasListeners(eventName: string): boolean;
}

EntityEvents

EntityEvents defines entity-specific events:

enum EntityEvents {
  BeforeCreate = 'beforeCreate',
  AfterCreate = 'afterCreate',
  BeforeUpdate = 'beforeUpdate',
  AfterUpdate = 'afterUpdate',
  BeforeDelete = 'beforeDelete',
  AfterDelete = 'afterDelete',
  ValidationError = 'validationError'
}

Utility Classes

The core package includes various utility classes for common tasks.

QueryBuilder

QueryBuilder assists in constructing SQL queries:

class QueryBuilder {
  // Base query methods
  public select(entityName: string): SelectQueryBuilder;
  public insert(entityName: string): InsertQueryBuilder;
  public update(entityName: string): UpdateQueryBuilder;
  public delete(entityName: string): DeleteQueryBuilder;
}

TypeConverter

TypeConverter handles data type conversions:

class TypeConverter {
  // Conversion methods
  public static toBoolean(value: any): boolean;
  public static toNumber(value: any): number;
  public static toDate(value: any): Date;
  public static toString(value: any): string;
  
  // Type checking methods
  public static isBoolean(value: any): boolean;
  public static isNumber(value: any): boolean;
  public static isDate(value: any): boolean;
  public static isString(value: any): boolean;
}

Logger

Logger provides logging capabilities:

class Logger {
  // Log levels
  public static debug(message: string, ...args: any[]): void;
  public static info(message: string, ...args: any[]): void;
  public static warn(message: string, ...args: any[]): void;
  public static error(message: string, ...args: any[]): void;
  
  // Configuration
  public static setLogLevel(level: LogLevel): void;
  public static addLogHandler(handler: LogHandler): void;
}

Usage Examples

Creating and Using Entities

// Get the entity manager
const entityManager = MJ.getEntityManager();

// Create a new member
const member = entityManager.createEntity('Member') as Member;
member.FirstName = 'John';
member.LastName = 'Doe';
member.Email = '[email protected]';
member.MembershipTypeID = 1;

// Save the entity
const saved = await member.save();
if (saved) {
  console.log(`Member saved with ID: ${member.MemberID}`);
} else {
  console.error('Failed to save member:', member.getValidationErrors());
}

// Load an entity by ID
const loadedMember = await entityManager.getEntityById('Member', 1) as Member;
console.log(`Loaded member: ${loadedMember.FirstName} ${loadedMember.LastName}`);

// Query entities with a filter
const filter = [
  { fieldName: 'MembershipTypeID', operator: 'eq', value: 1 }
];
const members = await entityManager.getEntitiesWithFilter('Member', filter) as Member[];
console.log(`Found ${members.length} members with membership type 1`);

Extending Entity Classes

// Import the generated base class
import { Member as MemberBase } from './generated/member.entity';
import { EntityRegister } from '@memberjunction/core';

// Create a subclass with custom logic
export class Member extends MemberBase {
  // Add custom properties
  get FullName(): string {
    return `${this.FirstName} ${this.LastName}`;
  }

  // Add custom methods
  async sendWelcomeEmail(): Promise<boolean> {
    // Implementation
    return true;
  }

  // Override lifecycle hooks
  override async beforeSave(): Promise<boolean> {
    // Custom validation
    if (!this.Email.includes('@')) {
      this.addValidationError('Email', 'Invalid email format');
      return false;
    }
    return await super.beforeSave();
  }
}

// Register the subclass
EntityRegister.registerEntity(Member);

Integration with Other Packages

The core package integrates with other MemberJunction packages:

  • @memberjunction/codegen: The core metadata classes drive code generation
  • @memberjunction/data-access: Core entities and repositories provide data access
  • @memberjunction/graphql-server: GraphQL schemas are built from core metadata
  • @memberjunction/explorer: UI components use core entities and metadata

Dependencies

The core package has minimal external dependencies:

  • TypeScript
  • Node.js
  • Reflect Metadata (for decorators)

Configuration

Configure the core package through environment variables or a configuration file:

{
  "database": {
    "provider": "mssql",
    "connectionString": "Server=localhost;Database=MemberJunction;User Id=sa;Password=yourpassword;"
  },
  "authentication": {
    "provider": "msal",
    "options": {
      "clientId": "your-client-id",
      "tenantId": "your-tenant-id"
    }
  },
  "logging": {
    "level": "info",
    "outputs": ["console", "file"]
  }
}

Best Practices

  • Use the subclass registration pattern to extend entity classes
  • Leverage metadata for dynamic behavior rather than hard-coding logic
  • Implement validation in entity classes for business rule enforcement
  • Use transactions for operations that modify multiple entities
  • Follow the provider interfaces for custom provider implementations