@memberjunction/core-entities-server
Server-side entity subclasses for MemberJunction that provide extended functionality and business logic for core entities when running in a Node.js environment.
Overview
Section titled “Overview”This package contains server-side implementations of MemberJunction entity subclasses that require server-specific functionality such as direct database access, file system operations, server-side API integrations, complex business logic, and automatic creation/management of related entities.
graph TD
A["Core Entities Server"] --> B["AIPromptEntityExtended"]
A --> C["QueryEntityExtended"]
A --> D["ComponentEntityExtended"]
A --> E["MJActionEntityExtended"]
A --> F["ApplicationEntityExtended"]
A --> G["Other Server Entities"]
B -->|manages| H["Templates<br/>Template Contents"]
C -->|manages| H
D -->|generates| I["Vector Embeddings"]
J["MJ Class Factory"] -->|returns server subclass| A
K["Core Entities<br/>(base classes)"] -->|extended by| A
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
style B fill:#7c5295,stroke:#563a6b,color:#fff
style C fill:#7c5295,stroke:#563a6b,color:#fff
style D fill:#7c5295,stroke:#563a6b,color:#fff
style E fill:#7c5295,stroke:#563a6b,color:#fff
style F fill:#7c5295,stroke:#563a6b,color:#fff
style H fill:#2d8659,stroke:#1a5c3a,color:#fff
style I fill:#b8762f,stroke:#8a5722,color:#fff
style J fill:#2d6a9f,stroke:#1a4971,color:#fff
style K fill:#b8762f,stroke:#8a5722,color:#fff
Key Differences from Core Entities
Section titled “Key Differences from Core Entities”- Core Entities: Work everywhere, basic CRUD operations, suitable for client-side usage
- Core Entities Server: Server-only, extended functionality, can directly interact with system resources
Installation
Section titled “Installation”npm install @memberjunction/core-entities-serverBasic Setup
Section titled “Basic Setup”import { LoadCoreEntitiesServerSubClasses } from '@memberjunction/core-entities-server';
// Load all server-side entity subclasses at application startupLoadCoreEntitiesServerSubClasses();Important: Call LoadCoreEntitiesServerSubClasses() early in your application initialization to ensure the server-side subclasses are registered with the MemberJunction metadata system before any entities are instantiated.
Example: AI Prompt Entity
Section titled “Example: AI Prompt Entity”The package includes an extended AI Prompt entity that automatically manages Template and Template Contents records:
import { Metadata } from '@memberjunction/core';
// The metadata system will automatically use the server-side subclassconst md = new Metadata();const aiPrompt = await md.GetEntityObject<AIPromptEntity>('AI Prompts');
// Setting TemplateText will automatically create/update Template recordsaiPrompt.TemplateText = `You are a helpful assistant...`;
// Save will handle all the Template management automaticallyawait aiPrompt.Save();Available Server-Side Entities
Section titled “Available Server-Side Entities”AIPromptEntityExtendedServer
Section titled “AIPromptEntityExtendedServer”Extends the base AI Prompt entity with automatic Template management:
- Virtual Property:
TemplateText- A convenient way to get/set the prompt template content - Automatic Template Creation: Creates Template and Template Contents records when saving
- Automatic Template Updates: Updates existing templates when content changes
- Proper Relationship Management: Maintains proper foreign key relationships
ComponentEntityExtended_Server (v2.90.0+)
Section titled “ComponentEntityExtended_Server (v2.90.0+)”Extends the Component entity with automatic vector embedding generation:
- Automatic Embeddings: Generates vector embeddings for FunctionalRequirements and TechnicalDesign fields
- Model Tracking: Stores the AI model ID used for each embedding
- Smart Updates: Only regenerates embeddings when source text changes
- Parallel Processing: Generates multiple embeddings concurrently for performance
QueryEntityExtendedServer
Section titled “QueryEntityExtendedServer”Extends the Query entity with automatic Template management similar to AIPromptEntity:
- Virtual Property:
TemplateText- Manages the SQL query template content - Automatic Template Management: Creates and updates Template records when saving
- Dynamic SQL Support: Supports parameterized queries with Nunjucks templates
MJAIAgentNoteEntityServer (v5.30.x+)
Section titled “MJAIAgentNoteEntityServer (v5.30.x+)”Extends the agent note entity to keep the in-process vector store in sync with persisted state:
- Vector Store Invariant: Overrides
Save()andDelete()so thatAIEngine.Instance._noteVectorServicecontains an entry for a note iff its persistedStatus='Active'and itsEmbeddingVectoris non-null - Inline Maintenance: When a note’s Status flips between
Activeand any other value, or when a note is deleted, the corresponding vector store entry is added/removed in the same operation — eliminating the need for an MJAPI restart after note revocation (e.g., during MemoryManagerAgent consolidation or contradiction resolution) - Consolidation Field Persistence: New
AIAgentNotecolumns introduced byV202604260056__v5.30.x__Memory_Consolidation_Schema.sql(ConsolidatedIntoNoteID,ConsolidationCount,DerivedFromNoteIDs,ProtectionTier,ImportanceScore) persist via the standard generated setters — no custom logic needed in this override
See @memberjunction/aiengine README for the read-side and the consuming code in @memberjunction/ai-agents.
MJAIAgentExampleEntityServer
Section titled “MJAIAgentExampleEntityServer”Companion to MJAIAgentNoteEntityServer — maintains the parallel invariant for the agent example vector store on Save/Delete.
MJRemoteOperationEntityServer (v5.43.x+)
Section titled “MJRemoteOperationEntityServer (v5.43.x+)”The AI-from-Description half of the Remote Operations primitive (RO-4). For a MJ: Remote Operations row with GenerationType='AI', the Save hook calls the Generate Remote Operation Code prompt to author the body of the operation’s InternalExecute — against the ambient input / provider / user / context contract — stores it in Code, records the libraries it used in the JSONType LibrariesObject, and resets CodeApprovalStatus='Pending' for human review (skipped when CodeLocked). CodeGen then emits a complete registered class from the approved code. Mirrors MJActionEntityServer; transactional; no junction CRUD (the library list is a JSONType field).
Architecture
Section titled “Architecture”Entity Registration
Section titled “Entity Registration”The package uses MemberJunction’s entity subclassing system:
- At startup,
LoadCoreEntitiesServerSubClasses()is called - This registers all server-side entity subclasses with the metadata system
- When code requests an entity (e.g., “AI Prompts”), the metadata system returns the server-side subclass
- The server-side subclass extends the base entity with additional functionality
Virtual Properties
Section titled “Virtual Properties”Server-side entities can expose virtual properties that don’t exist in the database but provide convenient access to complex data:
export class AIPromptEntityExtendedServer extends AIPromptEntity { private _TemplateText?: string;
get TemplateText(): string | undefined { return this._TemplateText; }
set TemplateText(value: string | undefined) { this._TemplateText = value; }}Development Guidelines
Section titled “Development Guidelines”Creating New Server-Side Entities
Section titled “Creating New Server-Side Entities”- Create a new file in
src/custom/ - Import and extend the base entity class
- Add server-specific functionality
- Register the subclass in the loader
Example:
import { BaseEntity, RegisterClass } from '@memberjunction/core';import { UserEntity } from '@memberjunction/core-entities';
@RegisterClass(BaseEntity, 'Users')export class UserEntityExtendedServer extends UserEntity { async BeforeSave(): Promise<boolean> { // Server-side validation or processing if (this.Email) { // Verify email domain against company whitelist const validDomain = await this.checkEmailDomain(this.Email); if (!validDomain) { throw new Error('Invalid email domain'); } } return super.BeforeSave(); }
private async checkEmailDomain(email: string): Promise<boolean> { // Server-side logic to validate email domain return true; }}Best Practices
Section titled “Best Practices”- Keep It Server-Side: Only include code that must run on the server
- Extend, Don’t Replace: Always extend the base entity class, don’t replace core functionality
- Handle Errors Gracefully: Server-side operations can fail - handle errors appropriately
- Document Virtual Properties: Clearly document any virtual properties and their behavior
- Test Thoroughly: Server-side logic can be complex - ensure comprehensive testing
Integration with Other MJ Packages
Section titled “Integration with Other MJ Packages”This package works seamlessly with:
@memberjunction/core: Provides the base entity system@memberjunction/core-entities: Provides the base entity classes@memberjunction/server: Can be used in MJ server applications@memberjunction/cli: Used by CLI tools like MetadataSync
Vector Embeddings Support (v2.90.0+)
Section titled “Vector Embeddings Support (v2.90.0+)”This package provides a utility helper for server-side entities to easily implement vector embedding generation using the MemberJunction AI Engine.
EmbedTextLocalHelper
Section titled “EmbedTextLocalHelper”A shared utility function that simplifies embedding generation for any server-side entity:
import { EmbedTextLocalHelper } from '@memberjunction/core-entities-server';import { BaseEntity, SimpleEmbeddingResult } from '@memberjunction/core';
@RegisterClass(BaseEntity, 'MyEntity')export class MyEntityServer extends MyEntity { protected async EmbedTextLocal(textToEmbed: string): Promise<SimpleEmbeddingResult> { return EmbedTextLocalHelper(this, textToEmbed); }}The helper:
- Configures the AI Engine with the current user context
- Calls the embedding generation
- Validates the response
- Returns a properly typed
SimpleEmbeddingResult
Using Vector Embeddings in Your Entity
Section titled “Using Vector Embeddings in Your Entity”- Add database fields for storing vectors and model IDs
- Override EmbedTextLocal using the helper function
- Call GenerateEmbeddings in your Save method:
public async Save(): Promise<boolean> { await this.GenerateEmbeddingsByFieldName([ { fieldName: "Content", vectorFieldName: "ContentVector", modelFieldName: "ContentVectorModelID" } ]); return await super.Save();}Common Use Cases
Section titled “Common Use Cases”1. Automatic Related Entity Management
Section titled “1. Automatic Related Entity Management”// AI Prompts automatically manage their Template recordsconst prompt = await md.GetEntityObject<AIPromptEntity>('AI Prompts');prompt.TemplateText = "New prompt content";await prompt.Save(); // Template and Template Contents created/updated automatically2. Vector Embedding Generation
Section titled “2. Vector Embedding Generation”// Components automatically generate embeddings for text fieldsconst component = await md.GetEntityObject<ComponentEntity>('MJ: Components');component.FunctionalRequirements = "The system shall...";component.TechnicalDesign = "Architecture overview...";await component.Save(); // Embeddings generated automatically3. Server-Side Validation
Section titled “3. Server-Side Validation”@RegisterClass(BaseEntity, 'Documents')export class DocumentEntityServer extends DocumentEntity { async BeforeSave(): Promise<boolean> { // Scan document for viruses using server-side scanner if (this.Content) { const isSafe = await virusScanner.scan(this.Content); if (!isSafe) { throw new Error('Document failed security scan'); } } return super.BeforeSave(); }}3. Complex Business Logic
Section titled “3. Complex Business Logic”@RegisterClass(BaseEntity, 'Orders')export class OrderEntityServer extends OrderEntity { async AfterSave(): Promise<boolean> { // Send order to fulfillment system await fulfillmentAPI.submitOrder(this);
// Update inventory await inventorySystem.decrementStock(this.OrderItems);
return super.AfterSave(); }}SQL Parser
Section titled “SQL Parser”The SQLParser utility class extracts table/view references and column references from SQL statements. It uses node-sql-parser for AST-based parsing with automatic regex fallback when the AST parser fails. This class has no dependency on MJ Metadata — it returns raw parse results that callers cross-reference against entities themselves.
When to Use
Section titled “When to Use”- Virtual entity decoration: Parse a view’s SQL to identify which source entities it pulls from
- Query entity metadata: Parse SQL queries to find referenced tables and columns
- Any server-side SQL analysis: Extract structural information from SQL without executing it
Basic Usage — Plain SQL
Section titled “Basic Usage — Plain SQL”import { SQLParser } from '@memberjunction/core-entities-server';
const sql = ` SELECT c.Name, c.Email, o.OrderDate, o.Total FROM [dbo].[Customer] c INNER JOIN [dbo].[Order] o ON o.CustomerID = c.ID WHERE o.Status = 'Active'`;
const result = SQLParser.Parse(sql);
// Tables found in the queryfor (const table of result.Tables) { console.log(`${table.SchemaName}.${table.TableName} (alias: ${table.Alias})`);}// Output:// dbo.Customer (alias: c)// dbo.Order (alias: o)
// Columns referenced in the queryfor (const col of result.Columns) { console.log(`${col.TableQualifier ? col.TableQualifier + '.' : ''}${col.ColumnName}`);}// Output:// c.Name, c.Email, o.OrderDate, o.Total, o.CustomerID, c.ID, o.Status
// Whether AST parsing succeeded (true) or regex fallback was used (false)console.log(`Used AST: ${result.UsedASTParsing}`);Nunjucks Template SQL
Section titled “Nunjucks Template SQL”For SQL that contains Nunjucks template syntax (e.g., MemberJunction query templates), use ParseWithTemplatePreprocessing. This replaces template expressions with safe placeholder values before parsing:
import { SQLParser } from '@memberjunction/core-entities-server';
const templateSQL = ` SELECT * FROM [dbo].[Customer] WHERE Status = {{ status | sqlString }} {% if regionId %} AND RegionID = {{ regionId | sqlNumber }} {% endif %}`;
// Nunjucks syntax is replaced with placeholders, then parsed normallyconst result = SQLParser.ParseWithTemplatePreprocessing(templateSQL);console.log(result.Tables); // [{ TableName: 'Customer', SchemaName: 'dbo', Alias: 'Customer' }]Cross-Referencing with Entity Metadata
Section titled “Cross-Referencing with Entity Metadata”The parser returns raw table/column references. To resolve them to MemberJunction entities:
import { Metadata } from '@memberjunction/core';import { SQLParser } from '@memberjunction/core-entities-server';
const parseResult = SQLParser.Parse(viewDefinitionSQL);const md = new Metadata();
for (const tableRef of parseResult.Tables) { const entity = md.Entities.find(e => (e.BaseTable.toLowerCase() === tableRef.TableName.toLowerCase() || e.BaseView.toLowerCase() === tableRef.TableName.toLowerCase()) && e.SchemaName.toLowerCase() === tableRef.SchemaName.toLowerCase() );
if (entity) { console.log(`Table ${tableRef.TableName} → Entity "${entity.Name}"`); // Access entity.Fields for full field metadata }}Return Types
Section titled “Return Types”interface SQLParseResult { Tables: SQLTableReference[]; // All table/view references (FROM, JOIN, subqueries, CTEs) Columns: SQLColumnReference[]; // All column references (SELECT, WHERE, JOIN ON, etc.) UsedASTParsing: boolean; // true = AST parsed, false = regex fallback}
interface SQLTableReference { TableName: string; // Table or view name SchemaName: string; // Schema name (defaults to 'dbo') Alias: string; // Query alias, or table name if no alias}
interface SQLColumnReference { ColumnName: string; // Column name TableQualifier: string | null; // Table alias prefix (e.g., "c" in "c.Name")}How It Works
Section titled “How It Works”-
AST Parsing (preferred): Uses
node-sql-parserwithTransactSQLdialect to build an AST, then recursively walks it to extract table references fromFROM/JOINclauses, subqueries, and CTEs, plus column references fromSELECT,WHERE,GROUP BY,ORDER BY, andONconditions. -
Regex Fallback: If AST parsing fails (complex SQL, dialect edge cases), a regex-based extractor finds
FROM/JOINtable references. Column references are not extracted in regex mode (theColumnsarray will be empty). -
Nunjucks Preprocessing:
ParseWithTemplatePreprocessingfirst renders the SQL through a Nunjucks environment with placeholder filters (sqlString→'placeholder',sqlNumber→0, etc.), stripping template blocks while preserving the SQL structure. If Nunjucks rendering fails, it falls back to regex-based template removal.
Troubleshooting
Section titled “Troubleshooting”Entity subclass not being used
Section titled “Entity subclass not being used”Ensure LoadCoreEntitiesServerSubClasses() is called before any entity instantiation.
Module loading errors
Section titled “Module loading errors”This package uses CommonJS. Ensure your Node.js application is configured to handle CommonJS modules.
Virtual properties not persisting
Section titled “Virtual properties not persisting”Virtual properties are not saved to the database. Use them as convenient accessors that manage real database fields behind the scenes.
Future Enhancements
Section titled “Future Enhancements”- Additional server-side entity implementations
- Integration with file storage providers
- Advanced caching mechanisms
- Batch processing utilities
- Background job integration
Contributing
Section titled “Contributing”When contributing new server-side entities:
- Follow the existing patterns
- Document all virtual properties
- Include comprehensive tests
- Ensure no client-side dependencies
- Update this README with new entities
License
Section titled “License”This package is part of the MemberJunction open-source project.