@memberjunction/global
Core global utilities and coordination library for MemberJunction applications. This package provides essential singleton management, class factory patterns, event coordination, and utility functions that are used throughout the MemberJunction ecosystem.
Installation
npm install @memberjunction/global
Overview
The @memberjunction/global library serves as the foundation for cross-component communication and coordination in MemberJunction applications. It provides:
- Global Singleton Management - Ensures true singleton instances across module boundaries
- Class Factory System - Dynamic class registration and instantiation with automatic root class detection
- Event System - RxJS-based event bus for component communication
- Object Caching - In-memory object cache for application lifetime
- Class Reflection Utilities - Runtime class hierarchy inspection and analysis
- Deep Diff Engine - Comprehensive object comparison and change tracking
- JSON Validator - Lightweight JSON validation with flexible rules and special syntax
- Utility Functions - Common string manipulation, JSON parsing (including recursive nested JSON parsing), pattern matching, and formatting utilities
Core Components
MJGlobal Class
The central singleton class that coordinates events and manages components across your application.
import { MJGlobal } from '@memberjunction/global';
const mjGlobal = MJGlobal.Instance;
mjGlobal.RegisterComponent(myComponent);
mjGlobal.RaiseEvent({
component: myComponent,
event: MJEventType.ComponentEvent,
eventCode: 'CUSTOM_EVENT',
args: { data: 'example' }
});
const subscription = mjGlobal.GetEventListener().subscribe(event => {
console.log('Event received:', event);
});
const replaySubscription = mjGlobal.GetEventListener(true).subscribe(event => {
console.log('Event with replay:', event);
});
Class Factory System
Register and instantiate classes dynamically with automatic root class detection and inheritance chain support.
import { RegisterClass, MJGlobal } from '@memberjunction/global';
class BaseProcessor {
process(data: any): void {
console.log('Base processing');
}
}
class SpecialProcessor extends BaseProcessor {
process(data: any): void {
console.log('Special processing');
}
}
@RegisterClass(SpecialProcessor, 'custom')
class CustomProcessor extends SpecialProcessor {
process(data: any): void {
console.log('Custom processing');
}
}
const factory = MJGlobal.Instance.ClassFactory;
const processor = factory.CreateInstance<BaseProcessor>(BaseProcessor, 'custom');
processor.process(data);
@RegisterClass(SpecialProcessor, 'special', 0, false, false)
class DirectRegistration extends SpecialProcessor {
}
Object Cache
In-memory caching system for application-lifetime object storage.
const cache = MJGlobal.Instance.ObjectCache;
cache.Add('user:123', { id: 123, name: 'John Doe' });
const user = cache.Find<User>('user:123');
cache.Replace('user:123', { id: 123, name: 'Jane Doe' });
cache.Remove('user:123');
cache.Clear();
BaseSingleton Class
Abstract base class for creating global singleton instances that persist across module boundaries.
import { BaseSingleton } from '@memberjunction/global';
export class MyService extends BaseSingleton<MyService> {
private data: string[] = [];
public static get Instance(): MyService {
return super.getInstance<MyService>();
}
public addData(item: string): void {
this.data.push(item);
}
}
const service = MyService.Instance;
service.addData('example');
Class Reflection Utilities
Runtime utilities for inspecting and analyzing class hierarchies.
import {
GetSuperclass,
GetRootClass,
IsSubclassOf,
IsRootClass,
IsDescendantClassOf,
GetClassInheritance,
GetFullClassHierarchy,
IsClassConstructor,
GetClassName
} from '@memberjunction/global';
class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}
class GoldenRetriever extends Dog {}
const parent = GetSuperclass(GoldenRetriever);
const root = GetRootClass(GoldenRetriever);
IsSubclassOf(GoldenRetriever, Animal);
IsDescendantClassOf(GoldenRetriever, Animal);
IsRootClass(Animal);
IsRootClass(Dog);
const chain = GetClassInheritance(GoldenRetriever);
const fullChain = GetFullClassHierarchy(GoldenRetriever);
IsClassConstructor(Dog);
IsClassConstructor(() => {});
GetClassName(GoldenRetriever);
Deep Diff Engine
Comprehensive object comparison and change tracking with hierarchical diff visualization.
import { DeepDiffer, DiffChangeType } from '@memberjunction/global';
const differ = new DeepDiffer({
includeUnchanged: false,
maxDepth: 10,
maxStringLength: 100,
treatNullAsUndefined: false
});
const oldData = {
user: { name: 'John', age: 30, role: 'admin' },
settings: { theme: 'dark', notifications: true },
tags: ['important', 'active']
};
const newData = {
user: { name: 'John', age: 31, role: 'superadmin' },
settings: { theme: 'light', notifications: true, language: 'en' },
tags: ['important', 'active', 'premium']
};
const result = differ.diff(oldData, newData);
console.log(result.summary);
result.changes.forEach(change => {
console.log(`${change.path}: ${change.type} - ${change.description}`);
});
const additions = result.changes.filter(c => c.type === DiffChangeType.Added);
const modifications = result.changes.filter(c => c.type === DiffChangeType.Modified);
differ.updateConfig({ includeUnchanged: true });
Treating null as undefined
When working with APIs or databases where null and undefined are used interchangeably, you can enable the treatNullAsUndefined option:
const differ = new DeepDiffer({ treatNullAsUndefined: true });
const oldData = {
name: null,
status: 'active',
oldProp: 'value'
};
const newData = {
name: 'John',
status: null,
newProp: 'value'
};
const result = differ.diff(oldData, newData);
JSON Validator
Lightweight JSON validation with flexible validation rules and special field suffixes.
import { JSONValidator } from '@memberjunction/global';
const validator = new JSONValidator();
const template = {
name: "John Doe",
email?: "user@example.com",
settings*: {},
tags:[1+]: ["tag1"],
age:number: 25,
username:string:!empty: "johndoe",
items:array:[2-5]?: ["A", "B"]
};
const data = {
name: "Jane Smith",
tags: ["work", "urgent"],
age: 30,
username: "jsmith"
};
const result = validator.validate(data, template);
if (result.Success) {
console.log('Validation passed!');
} else {
result.Errors.forEach(error => {
console.log(`${error.Source}: ${error.Message}`);
});
}
Validation Syntax
Field Suffixes:
? - Optional field (e.g., email?)
* - Required field with any content/structure (e.g., payload*)
Validation Rules (using : delimiter):
-
Array Length:
[N+] - At least N elements (e.g., tags:[1+])
[N-M] - Between N and M elements (e.g., items:[2-5])
[=N] - Exactly N elements (e.g., coordinates:[=2])
-
Type Checking:
string - Must be a string
number - Must be a number (NaN fails validation)
boolean - Must be a boolean
object - Must be an object (not array or null)
array - Must be an array
-
Value Constraints:
!empty - Non-empty string, array, or object
Combining Rules:
Multiple validation rules can be combined with : delimiter:
{
"tags:array:[2+]": ["important", "urgent"],
"username:string:!empty": "johndoe",
"score:number?": 85,
"options:array:[1-3]?": ["A", "B"]
}
Nested Object Validation
The validator recursively validates nested objects:
const template = {
user: {
id:number: 123,
name:string:!empty: "John",
roles:array:[1+]: ["admin"]
},
settings?: {
theme: "dark",
notifications:boolean: true
}
};
Cleaning Validation Syntax
The validator can clean validation syntax from JSON objects that may have been returned by AI systems:
const aiResponse = {
"name?": "John Doe",
"email:string": "john@example.com",
"tags:[2+]": ["work", "urgent"],
"settings*": { theme: "dark" },
"score:number:!empty": 85
};
const cleaned = validator.cleanValidationSyntax<any>(aiResponse);
The cleanValidationSyntax method:
- Recursively processes all object keys
- Removes validation suffixes (
?, *)
- Removes validation rules (
:type, :[N+], :!empty, etc.)
- Preserves the original values unchanged
- Handles nested objects and arrays
- Returns a new object with cleaned keys
Convenience Methods
const schemaJson = '{"name": "string", "age:number": 0}';
const result = validator.validateAgainstSchema(data, schemaJson);
Use Cases
const apiResponseTemplate = {
status:string: "success",
data*: {},
errors:array?: []
};
- Configuration Validation:
const configTemplate = {
apiUrl:string:!empty: "https://api.example.com",
timeout:number: 5000,
retries:number: 3,
features:array:[1+]: ["logging"]
};
const formTemplate = {
username:string:!empty: "user",
email:string: "user@example.com",
age:number?: 25,
preferences:object?: {
notifications:boolean: true
}
};
Event Types
The library provides predefined event types for common scenarios:
export const MJEventType = {
ComponentRegistered: 'ComponentRegistered',
ComponentUnregistered: 'ComponentUnregistered',
ComponentEvent: 'ComponentEvent',
LoggedIn: 'LoggedIn',
LoggedOut: 'LoggedOut',
LoginFailed: 'LoginFailed',
LogoutFailed: 'LogoutFailed',
ManualResizeRequest: 'ManualResizeRequest',
DisplaySimpleNotificationRequest: 'DisplaySimpleNotificationRequest',
} as const;
Utility Functions
String Manipulation
import {
convertCamelCaseToHaveSpaces,
stripWhitespace,
generatePluralName,
adjustCasing,
stripTrailingChars,
replaceAllSpaces
} from '@memberjunction/global';
convertCamelCaseToHaveSpaces('AIAgentLearningCycle');
stripWhitespace(' Hello World ');
generatePluralName('child');
generatePluralName('box');
generatePluralName('party');
adjustCasing('hello', { capitalizeFirstLetterOnly: true });
adjustCasing('world', { capitalizeEntireWord: true });
stripTrailingChars('example.txt', '.txt', false);
replaceAllSpaces('Hello World');
JSON Utilities
import { CleanJSON, SafeJSONParse, ParseJSONRecursive } from '@memberjunction/global';
const parsed = SafeJSONParse<MyType>('{"key": "value"}', true);
const input = {
data: '{"nested": "{\\"deeply\\": \\"nested\\"}"}',
messages: '[{"content": "{\\"type\\": \\"greeting\\", \\"text\\": \\"Hello\\"}"}]'
};
const output = ParseJSONRecursive(input);
const textWithJson = {
result: 'Action completed: {"status": "success", "count": 42}'
};
const extracted = ParseJSONRecursive(textWithJson, { extractInlineJson: true });
const deeplyNested = ParseJSONRecursive(complexData, {
maxDepth: 50,
extractInlineJson: true,
debug: true
});
CleanJSON Function
The CleanJSON function intelligently extracts and cleans JSON from various input formats, including double-escaped strings, strings with embedded JSON, and markdown code blocks. It's particularly useful when dealing with AI-generated responses or data from external systems that may have inconsistent JSON formatting.
Processing Order:
- First attempts to parse the input as valid JSON (preserving embedded content)
- If that fails, handles double-escaped characters (
\\n, \\", etc.)
- Only extracts from markdown blocks or inline JSON as a last resort
import { CleanJSON } from '@memberjunction/global';
const valid = CleanJSON('{"name": "test", "value": 123}');
const escaped = CleanJSON('{\\"name\\": \\"test\\", \\"value\\": 123}');
const withNewlines = CleanJSON('\\n{\\"mode\\": \\"test\\",\\n\\"data\\": [1, 2, 3]}\\n');
const complexJson = CleanJSON(`{
"taskComplete": false,
"message": "Processing complete",
"nextAction": {
"type": "design",
"payload": {
"outputFormat": "\`\`\`json\\n{\\"componentName\\": \\"Example\\"}\\n\`\`\`"
}
}
}`);
const markdown = CleanJSON('Some text ```json\n{"extracted": true}\n``` more text');
const mixed = CleanJSON('Response: {"status": "success", "code": 200} - Done');
const aiResponse = CleanJSON(`{
"analysis": "Complete",
"data": "{\\"users\\": [{\\"name\\": \\"John\\", \\"active\\": true}]}",
"metadata": {
"template": "\`\`\`json\\n{\\"format\\": \\"standard\\"}\\n\`\`\`"
}
}`);
Key Features:
- Preserves embedded content: When the input is valid JSON, markdown blocks and escaped strings within values are preserved
- Smart unescaping: Handles
\\n → \n, \\" → ", \\\\ → \ and other common escape sequences
- Markdown extraction: Extracts JSON from
```json code blocks when needed
- Inline extraction: Finds JSON objects/arrays within surrounding text
- Null safety: Returns
null for invalid inputs instead of throwing errors
Common Use Cases:
- Processing AI model responses that may contain escaped JSON
- Cleaning data from external APIs with inconsistent formatting
- Extracting JSON from log files or debug output
- Handling JSON strings stored in databases that may be double-escaped
- Processing user input that may contain JSON in various formats
HTML Conversion
import { ConvertMarkdownStringToHtmlList } from '@memberjunction/global';
const html = ConvertMarkdownStringToHtmlList('Unordered', '- Item 1\n- Item 2\n- Item 3');
Safe Expression Evaluator
Secure boolean expression evaluation for conditional logic without allowing arbitrary code execution:
import { SafeExpressionEvaluator } from '@memberjunction/global';
const evaluator = new SafeExpressionEvaluator();
const result1 = evaluator.evaluate(
"status == 'active' && score > 80",
{ status: 'active', score: 95 }
);
console.log(result1.success);
console.log(result1.value);
const result2 = evaluator.evaluate(
"user.role == 'admin' && user.permissions.includes('write')",
{
user: {
role: 'admin',
permissions: ['read', 'write', 'delete']
}
}
);
console.log(result2.value);
const result3 = evaluator.evaluate(
"items.some(item => item.price > 100) && items.length >= 2",
{
items: [
{ name: 'Item 1', price: 50 },
{ name: 'Item 2', price: 150 }
]
}
);
console.log(result3.value);
const result4 = evaluator.evaluate(
"eval('malicious code')",
{ data: 'test' }
);
console.log(result4.success);
console.log(result4.error);
const result5 = evaluator.evaluate(
"payload.status == 'complete'",
{ payload: { status: 'complete' } },
true
);
console.log(result5.diagnostics);
Supported Operations
Comparison Operators:
==, ===, !=, !==
<, >, <=, >=
Logical Operators:
Property Access:
- Dot notation:
object.property.nested
- Array access:
array[0], array[index]
Safe Methods:
- String:
.length, .includes(), .startsWith(), .endsWith(), .toLowerCase(), .toUpperCase(), .trim()
- Array:
.length, .includes(), .some(), .every(), .find(), .filter(), .map()
- Type checking:
typeof, limited instanceof
Type Coercion:
Boolean(value)
- String concatenation with
+
Security Features
The evaluator blocks dangerous patterns including:
eval(), Function(), new Function()
require(), import statements
- Access to
global, window, document, process
- Template literals and string interpolation
- Code blocks with
{} and ;
this keyword usage
constructor, prototype, __proto__ access
Use Cases
const canProceed = evaluator.evaluate(
"order.status == 'approved' && order.total < budget",
{ order: { status: 'approved', total: 500 }, budget: 1000 }
).value;
const featureEnabled = evaluator.evaluate(
"user.tier == 'premium' || user.roles.includes('beta')",
{ user: { tier: 'standard', roles: ['beta', 'tester'] } }
).value;
const isValid = evaluator.evaluate(
"form.password.length >= 8 && form.password != form.username",
{ form: { username: 'john', password: 'secretpass123' } }
).value;
const shouldDelegate = evaluator.evaluate(
"confidence < 0.7 || taskComplexity > 8",
{ confidence: 0.6, taskComplexity: 5 }
).value;
Batch Evaluation
Evaluate multiple expressions at once:
const results = evaluator.evaluateMultiple([
{ expression: "status == 'active'", name: 'isActive' },
{ expression: "score > threshold", name: 'passedThreshold' },
{ expression: "tags.includes('priority')", name: 'isPriority' }
], {
status: 'active',
score: 85,
threshold: 80,
tags: ['urgent', 'priority']
});
Pattern Matching Utilities
Convert string patterns to RegExp objects with support for simple wildcards and full regex syntax:
import {
parsePattern,
parsePatterns,
ensureRegExp,
ensureRegExps,
matchesAnyPattern,
matchesAllPatterns
} from '@memberjunction/global';
parsePattern('*AIPrompt*');
parsePattern('spCreate*');
parsePattern('*Run');
parsePattern('exact');
parsePattern('/spCreate.*Run/i');
parsePattern('/^SELECT.*FROM.*vw/');
parsePattern('/INSERT INTO (Users|Roles)/i');
const patterns = parsePatterns([
'*User*',
'/^EXEC sp_/i',
'*EntityFieldValue*'
]);
const mixed = ['*User*', /^Admin/i, '/DELETE.*WHERE/i'];
const regexps = ensureRegExps(mixed);
const sql = 'SELECT * FROM Users WHERE Active = 1';
matchesAnyPattern(sql, ['*User*', '*Role*', '/^UPDATE/i']);
const filename = 'UserRoleManager.ts';
matchesAllPatterns(filename, ['*User*', '*Role*', '*.ts']);
Pattern Syntax
Simple Wildcard Patterns (Recommended for most users):
* acts as a wildcard matching any characters
- Case-insensitive by default
- Examples:
*pattern* - Contains "pattern" anywhere
pattern* - Starts with "pattern"
*pattern - Ends with "pattern"
pattern - Exact match only
Regex String Patterns (For advanced users):
- Must start with
/ to be recognized as regex
- Optionally end with flags like
/pattern/i
- Full JavaScript regex syntax supported
- Examples:
/^start/i - Case-insensitive start match
/end$/ - Case-sensitive end match
/(option1|option2)/ - Match alternatives
Common Use Cases
const sqlFilters = [
'*AIPrompt*',
'/^EXEC sp_/i',
'*EntityFieldValue*'
];
const shouldLog = !matchesAnyPattern(sqlStatement, sqlFilters);
const includePatterns = ['*.ts', '*.js', '/^(?!test)/'];
const shouldProcess = matchesAnyPattern(filename, includePatterns);
const allowedFormats = ['*@*.com', '*@*.org', '*@company.net'];
const isValidEmail = matchesAnyPattern(email, allowedFormats);
Global Object Store
Access the global object store for cross-module state sharing:
import { GetGlobalObjectStore } from '@memberjunction/global';
const globalStore = GetGlobalObjectStore();
Manual Resize Request
Trigger a manual resize event across components:
import { InvokeManualResize } from '@memberjunction/global';
InvokeManualResize(50, myComponent);
Advanced Usage
Class Factory with Root Class Detection
The Class Factory automatically detects and uses root classes for registration, ensuring proper priority ordering in inheritance hierarchies:
class BaseEntity {}
class UserEntity extends BaseEntity {}
@RegisterClass(UserEntity, 'Admin')
class AdminUserEntity extends UserEntity {}
@RegisterClass(AdminUserEntity, 'SuperAdmin')
class SuperAdminEntity extends AdminUserEntity {}
factory.CreateInstance(BaseEntity, 'SuperAdmin');
factory.CreateInstance(UserEntity, 'SuperAdmin');
factory.CreateInstance(AdminUserEntity, 'SuperAdmin');
Disabling Auto-Root Registration
Sometimes you want to register at a specific level in the hierarchy:
@RegisterClass(UserEntity, 'Special', 0, false, false)
class SpecialUserEntity extends UserEntity {
}
factory.CreateInstance(UserEntity, 'Special');
factory.CreateInstance(BaseEntity, 'Special');
Class Factory with Parameters
@RegisterClass(BaseService, 'api')
class ApiService extends BaseService {
constructor(private apiUrl: string) {
super();
}
}
const service = factory.CreateInstance<BaseService>(
BaseService,
'api',
'https://api.example.com'
);
Priority-based Registration
@RegisterClass(BaseHandler, 'data', 10)
class BasicDataHandler extends BaseHandler {}
@RegisterClass(BaseHandler, 'data', 20)
class AdvancedDataHandler extends BaseHandler {}
const handler = factory.CreateInstance<BaseHandler>(BaseHandler, 'data');
Inspecting Registrations
const registrations = factory.GetAllRegistrations(BaseEntity, 'Users');
const rootRegistrations = factory.GetRegistrationsByRootClass(BaseEntity);
Global Properties
Store and retrieve global properties:
const properties = MJGlobal.Instance.Properties;
properties.push({
key: 'apiEndpoint',
value: 'https://api.example.com'
});
Integration with MemberJunction
This package is a core dependency for most MemberJunction packages. It provides the foundation for:
- Entity registration and instantiation
- Cross-component event communication
- Singleton service management
- Global state coordination
When building MemberJunction applications or extensions, use this package to ensure proper integration with the framework's architecture.
TypeScript Support
This package is written in TypeScript and includes full type definitions. All exports are properly typed for excellent IDE support and compile-time type checking.
Dependencies
- rxjs (^7.8.1) - For reactive event handling
Development
npm run build
npm run start
npm test
License
ISC
Author
MemberJunction.com