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
Updated 1 day ago