Server-side entity subclasses for MemberJunction that provide extended functionality and business logic for core entities when running in a Node.js environment.
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
Template Contents"]
C -->|manages| H
D -->|generates| I["Vector Embeddings"]
J["MJ Class Factory"] -->|returns server subclass| A
K["Core Entities
(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
npm install @memberjunction/core-entities-server
import { LoadCoreEntitiesServerSubClasses } from '@memberjunction/core-entities-server';
// Load all server-side entity subclasses at application startup
LoadCoreEntitiesServerSubClasses();
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.
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 subclass
const md = new Metadata();
const aiPrompt = await md.GetEntityObject<AIPromptEntity>('AI Prompts');
// Setting TemplateText will automatically create/update Template records
aiPrompt.TemplateText = `You are a helpful assistant...`;
// Save will handle all the Template management automatically
await aiPrompt.Save();
Extends the base AI Prompt entity with automatic Template management:
TemplateText - A convenient way to get/set the prompt template contentExtends the Component entity with automatic vector embedding generation:
Extends the Query entity with automatic Template management similar to AIPromptEntity:
TemplateText - Manages the SQL query template contentExtends the agent note entity to keep the in-process vector store in sync with persisted state:
Save() and Delete() so that AIEngine.Instance._noteVectorService contains an entry for a note iff its persisted Status='Active' and its EmbeddingVector is non-nullActive and 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)AIAgentNote columns introduced by V202604260056__v5.30.x__Memory_Consolidation_Schema.sql (ConsolidatedIntoNoteID, ConsolidationCount, DerivedFromNoteIDs, ProtectionTier, ImportanceScore) persist via the standard generated setters — no custom logic needed in this overrideSee @memberjunction/aiengine README for the read-side and the consuming code in @memberjunction/ai-agents.
Companion to MJAIAgentNoteEntityServer — maintains the parallel invariant for the agent example vector store on Save/Delete.
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).
The package uses MemberJunction's entity subclassing system:
LoadCoreEntitiesServerSubClasses() is calledServer-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;
}
}
src/custom/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;
}
}
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 MetadataSyncThis package provides a utility helper for server-side entities to easily implement vector embedding generation using the MemberJunction AI Engine.
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:
SimpleEmbeddingResultpublic async Save(): Promise<boolean> {
await this.GenerateEmbeddingsByFieldName([
{
fieldName: "Content",
vectorFieldName: "ContentVector",
modelFieldName: "ContentVectorModelID"
}
]);
return await super.Save();
}
// AI Prompts automatically manage their Template records
const prompt = await md.GetEntityObject<AIPromptEntity>('AI Prompts');
prompt.TemplateText = "New prompt content";
await prompt.Save(); // Template and Template Contents created/updated automatically
// Components automatically generate embeddings for text fields
const component = await md.GetEntityObject<ComponentEntity>('MJ: Components');
component.FunctionalRequirements = "The system shall...";
component.TechnicalDesign = "Architecture overview...";
await component.Save(); // Embeddings generated automatically
@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();
}
}
@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();
}
}
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.
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 query
for (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 query
for (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}`);
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 normally
const result = SQLParser.ParseWithTemplatePreprocessing(templateSQL);
console.log(result.Tables); // [{ TableName: 'Customer', SchemaName: 'dbo', Alias: 'Customer' }]
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
}
}
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")
}
AST Parsing (preferred): Uses node-sql-parser with TransactSQL dialect to build an AST, then recursively walks it to extract table references from FROM/JOIN clauses, subqueries, and CTEs, plus column references from SELECT, WHERE, GROUP BY, ORDER BY, and ON conditions.
Regex Fallback: If AST parsing fails (complex SQL, dialect edge cases), a regex-based extractor finds FROM/JOIN table references. Column references are not extracted in regex mode (the Columns array will be empty).
Nunjucks Preprocessing: ParseWithTemplatePreprocessing first 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.
Ensure LoadCoreEntitiesServerSubClasses() is called before any entity instantiation.
This package uses CommonJS. Ensure your Node.js application is configured to handle CommonJS modules.
Virtual properties are not saved to the database. Use them as convenient accessors that manage real database fields behind the scenes.
When contributing new server-side entities:
This package is part of the MemberJunction open-source project.