# @memberjunction/core-entities
Strongly-typed entity classes, singleton engine caches, and domain logic for every entity in MemberJunction's core metadata schema. This package is the primary data-access layer that all MJ applications -- server and client -- depend on to interact with the MJ data model in a type-safe, validated way.
All entity subclasses in this package are **auto-generated by CodeGen**. Manual edits to files under `src/generated/` will be overwritten. Custom business logic is layered on top via extended classes in `src/custom/`.
## Installation
```bash
npm install @memberjunction/core-entities
```
## Overview
The package is structured into four layers that build on each other:
```mermaid
flowchart TD
subgraph Generated["Generated Layer (CodeGen)"]
ZOD["Zod Schemas
Runtime validation"]
ENT["272 Entity Subclasses
Typed getters/setters"]
end
subgraph Custom["Custom Layer (Hand-Written)"]
EXT["Extended Entities
Business logic overrides"]
RPE["Resource Permissions
Access control"]
end
subgraph Engines["Engine Layer (Singletons)"]
UIE["UserInfoEngine"]
UVE["UserViewEngine"]
DE["DashboardEngine"]
CME["ComponentMetadataEngine"]
AME["ArtifactMetadataEngine"]
FE["FileStorageEngine"]
EE["EncryptionEngineBase"]
CE["ConversationEngine"]
ME["MCPEngine"]
TTC["TypeTablesCache"]
KHE["KnowledgeHubMetadataEngine"]
end
subgraph Extraction["Artifact Extraction"]
AER["Extract Rules
Type definitions"]
AEX["ArtifactExtractor
Rule resolution + execution"]
end
ENT --> EXT
ZOD --> ENT
EXT --> Engines
ENT --> Engines
AER --> AEX
style Generated fill:#2d6a9f,stroke:#1a4971,color:#fff
style Custom fill:#2d8659,stroke:#1a5c3a,color:#fff
style Engines fill:#7c5295,stroke:#563a6b,color:#fff
style Extraction fill:#b8762f,stroke:#8a5722,color:#fff
```
### Generated Entity Classes
CodeGen scans the MemberJunction database schema and produces:
- **Zod schema objects** for every entity, providing runtime validation with field-level descriptions, related-entity annotations, and value-list constraints.
- **Entity subclasses** extending `BaseEntity` with strongly-typed getter/setter properties for every column, typed `Load()` methods, and JSDoc annotations including SQL types, defaults, and foreign key references.
There are currently **272 generated entity classes** covering approximately 78,000 lines of code in a single `entity_subclasses.ts` file.
#### AIAgentNote Consolidation Schema (v5.30.x+)
Migration `V202604260056__v5.30.x__Memory_Consolidation_Schema.sql` adds five columns to `AIAgentNote` to support the Memory Manager consolidation pipeline. The generated `MJAIAgentNoteEntity` getters/setters reflect these in `entity_subclasses.ts`:
| Column | Type | Purpose |
|---|---|---|
| `ConsolidatedIntoNoteID` | uniqueidentifier (FK → AIAgentNote.ID, nullable) | When this note has been folded into a successor consolidation, points at the successor — preserves provenance after revocation |
| `ConsolidationCount` | int | Number of times this note has itself absorbed clusters; capped at `maxConsolidationCount=3` for drift prevention |
| `DerivedFromNoteIDs` | nvarchar(max) JSON array of UUIDs, nullable | For consolidated notes, the list of original sources the LLM merged into this one — enables anchored-mode drilling when a cluster reaches the cap |
| `ProtectionTier` | nvarchar (Immutable / Protected / Standard / Ephemeral) | Modulates decay rate and consolidation eligibility |
| `ImportanceScore` | float | Composite score driving Ebbinghaus decay and outlier auto-promotion |
Server-side persistence and vector-store invariant maintenance for these fields lives in [`@memberjunction/core-entities-server`](../MJCoreEntitiesServer/README.md#mjaiagentnoteentityserver-v530x). The pipeline that produces them is documented in [`@memberjunction/ai-agents`](../AI/Agents/README.md#consolidation-pipeline) and [`specs/001-memory-consolidation/spec.md`](../../specs/001-memory-consolidation/spec.md).
### Extended Entity Classes
Several entities require custom business logic beyond what CodeGen can generate. Extended classes use `@RegisterClass` to override the generated base class in MJ's class factory:
| Extended Class | Entity Name | Purpose |
|---|---|---|
| `UserViewEntityExtended` | User Views | Filter/column JSON parsing, WHERE clause generation, smart filter AI stub, permission checks |
| `DashboardEntityExtended` | Dashboards | Default config initialization, permission-based validation on save/delete |
| `EntityEntityExtended` | Entities | Auto-sets `AutoUpdateDescription` when description is manually changed |
| `EntityFieldEntityExtended` | Entity Fields | Prevents modification of database-reflected properties, auto-sets `AutoUpdateDescription` |
| `ComponentEntityExtended` | MJ: Components | Syncs derived fields from Specification JSON, keeps `spec` property in sync |
| `TemplateEntityExtended` | Templates | Content/param management, template input validation |
| `ScheduledActionEntityExtended` | Scheduled Actions | Auto-generates CronExpression from schedule type settings |
| `ListDetailEntityExtended` | List Details | Composite key handling, duplicate detection on save |
| `EnvironmentEntityExtended` | MJ: Environments | Exposes static `DefaultEnvironmentID` constant |
| `ResourcePermissionEntityExtended` | Resource Permissions | Notification workflow on permission request/approval |
### Singleton Engines
Engine classes extend `BaseEngine` to provide cached, singleton access to groups of related entities. They load data once and keep it in memory, with auto-refresh when underlying data changes.
```mermaid
flowchart LR
subgraph App["Application Code"]
SVC["Services / Components"]
end
subgraph Engines["Singleton Engines"]
UIE["UserInfoEngine"]
DE["DashboardEngine"]
RPE["ResourcePermissionEngine"]
end
subgraph DB["Database"]
MJD["MJ Schema"]
end
SVC -->|".Instance"| Engines
Engines -->|"Config() loads once"| DB
Engines -->|"Cached data"| SVC
style App fill:#2d6a9f,stroke:#1a4971,color:#fff
style Engines fill:#7c5295,stroke:#563a6b,color:#fff
style DB fill:#64748b,stroke:#475569,color:#fff
```
### Artifact Extraction System
A metadata-driven system for extracting structured attributes from artifact content using declarative JavaScript-based rules with hierarchical inheritance through parent artifact types.
## Key Features
- **272 strongly-typed entity classes** covering all MemberJunction core schema entities
- **Zod runtime validation** with field-level constraints and value list enums
- **Extended entity classes** with permission checks, workflow logic, and JSON state parsing
- **10 singleton engine caches** eliminating redundant database queries (including KnowledgeHubMetadataEngine for vector/content metadata)
- **Resource permission engine** with role-based access control (View/Edit/Owner levels)
- **Dashboard permission engine** with owner/direct/category permission hierarchy
- **User view engine** with filter-to-SQL conversion and smart filter AI support
- **Artifact extraction** with hierarchical rule inheritance and sandboxed execution
- **Encryption engine base** for field-level encryption metadata caching
## Usage
### Working with Entity Objects
Always use `Metadata.GetEntityObject()` to create entity instances -- never instantiate entity classes directly. This ensures the MJ class factory resolves the correct (potentially extended) subclass.
```typescript
import { Metadata } from '@memberjunction/core';
import { UserEntity, ApplicationEntity } from '@memberjunction/core-entities';
const md = new Metadata();
// Load an existing record
const user = await md.GetEntityObject('Users');
await user.Load('some-user-id');
console.log(user.Email);
console.log(user.FirstName);
// Create a new record
const app = await md.GetEntityObject('Applications');
app.NewRecord();
app.Name = 'My Application';
app.Description = 'A custom application';
await app.Save();
```
### Loading Collections with RunView
Use `RunView` with generics for loading collections of entity records:
```typescript
import { RunView } from '@memberjunction/core';
import { ActionEntity } from '@memberjunction/core-entities';
const rv = new RunView();
const result = await rv.RunView({
EntityName: 'Actions',
ExtraFilter: `Status='Active'`,
OrderBy: 'Name',
ResultType: 'entity_object'
});
if (result.Success) {
for (const action of result.Results) {
console.log(action.Name, action.Status);
}
}
```
### Using the UserViewEntityExtended
The extended User View entity parses JSON state columns into structured objects and generates SQL WHERE clauses:
```typescript
import { Metadata } from '@memberjunction/core';
import { UserViewEntityExtended, ViewInfo } from '@memberjunction/core-entities';
// Load a view by name using the convenience class
const view = await ViewInfo.GetViewEntityByName('Active Contacts');
// Access parsed column and filter info
const columns = view.Columns; // ViewColumnInfo[]
const filters = view.Filter; // ViewFilterInfo[]
const sortInfo = view.ViewSortInfo; // ViewSortInfo[]
// Check user permissions
if (view.UserCanEdit) {
view.Name = 'Updated View Name';
await view.Save(); // Auto-regenerates WhereClause from FilterState
}
```
### Using Singleton Engines
All engines follow the same pattern: get the singleton instance, call `Config()` to load data, then access cached properties.
```typescript
import {
UserInfoEngine,
DashboardEngine,
ResourcePermissionEngine
} from '@memberjunction/core-entities';
// UserInfoEngine -- notifications, favorites, settings, applications
const userEngine = UserInfoEngine.Instance;
await userEngine.Config(false, contextUser);
const notifications = userEngine.UnreadNotifications;
const favorites = userEngine.UserFavorites;
// Debounced settings (batches rapid updates)
userEngine.SetSettingDebounced('ui-preference/theme', 'dark');
// DashboardEngine -- dashboards, categories, permissions
const dashEngine = DashboardEngine.Instance;
await dashEngine.Config(false, contextUser);
const accessible = dashEngine.GetAccessibleDashboards(userId);
const canEdit = dashEngine.CanUserEditDashboard(dashboardId, userId);
// ResourcePermissionEngine -- role-based resource access
const permEngine = ResourcePermissionEngine.Instance;
await permEngine.Config(false, contextUser);
const level = permEngine.GetUserResourcePermissionLevel(resourceTypeId, recordId, user);
// Returns 'View' | 'Edit' | 'Owner' | null
```
### Artifact Extraction
Extract structured attributes from artifact content using declarative rules:
```typescript
import { ArtifactExtractor } from '@memberjunction/core-entities';
// Resolve extract rules with inheritance from parent types
const rules = ArtifactExtractor.ResolveExtractRules([
childArtifactType, // Most specific (overrides parent rules by name)
parentArtifactType // Least specific
]);
// Extract attributes from content
const result = await ArtifactExtractor.ExtractAttributes({
content: '{"subject": "Q4 Campaign", "body": "..."}',
extractRules: rules,
throwOnError: false,
timeout: 5000
});
// Access extracted values
const name = ArtifactExtractor.GetStandardProperty(result.attributes, 'name');
const description = ArtifactExtractor.GetStandardProperty(result.attributes, 'description');
// Serialize for database storage
const serialized = ArtifactExtractor.SerializeForStorage(result.attributes);
```
### Encryption Metadata
The `EncryptionEngineBase` provides cached access to encryption keys, algorithms, and key sources:
```typescript
import { EncryptionEngineBase } from '@memberjunction/core-entities';
const engine = EncryptionEngineBase.Instance;
await engine.Config(false, contextUser);
// Validate a key is usable
const validation = engine.ValidateKey(keyId);
if (!validation.isValid) {
console.error(validation.error);
}
// Get full key configuration (key + algorithm + source)
const config = engine.GetKeyConfiguration(keyId);
```
## API Reference
### Generated Entity Classes
Every generated entity class provides:
| Member | Description |
|---|---|
| `Load(ID, relationships?)` | Load a record by primary key |
| `Save(options?)` | Persist changes to the database |
| `Delete(options?)` | Delete the record |
| `NewRecord()` | Initialize a blank record for creation |
| `Validate()` | Run Zod schema validation |
| Typed getters/setters | One per database column, with JSDoc annotations |
### UserViewEntityExtended
| Member | Type | Description |
|---|---|---|
| `Filter` | `ViewFilterInfo[]` | Parsed filter state from JSON |
| `Columns` | `ViewColumnInfo[]` | Parsed column configuration from JSON |
| `ViewSortInfo` | `ViewSortInfo[]` | Parsed sort state |
| `OrderByClause` | `string` | SQL ORDER BY clause |
| `ViewEntityInfo` | `EntityInfo` | Metadata for the view's entity |
| `ParsedDisplayState` | `ViewDisplayState \| null` | View mode and display preferences |
| `UserCanEdit` | `boolean` | Whether current user can edit this view |
| `UserCanView` | `boolean` | Whether current user can view this view |
| `UserCanDelete` | `boolean` | Whether current user can delete this view |
| `SetDefaultsFromEntity(e)` | method | Initialize columns from entity metadata |
| `UpdateWhereClause()` | method | Regenerate WHERE clause from filter state |
### ViewInfo (Static Utility)
| Method | Description |
|---|---|
| `GetViewsForUser(entityId?, contextUser?)` | Load all views for a user |
| `GetViewID(viewName)` | Look up a view ID by name |
| `GetViewEntity(viewId, contextUser?)` | Get a cached view by ID |
| `GetViewEntityByName(viewName, contextUser?)` | Get a cached view by name |
### UserInfoEngine
| Member | Description |
|---|---|
| `UserNotifications` | Notifications for current user (newest first) |
| `UnreadNotifications` | Unread notifications only |
| `Workspaces` | User's workspaces |
| `UserApplications` | Installed applications (sorted by sequence) |
| `UserFavorites` | Favorited records (newest first) |
| `UserRecordLogs` | Recent record access history |
| `UserSettings` | User settings key-value pairs |
| `GetSetting(key)` | Get a setting value by key |
| `SetSetting(key, value)` | Create or update a setting |
| `SetSettingDebounced(key, value)` | Debounced setting update for rapid UI changes |
| `FlushPendingSettings()` | Force-save all debounced settings |
| `InstallApplication(appId)` | Install an application for the user |
| `DisableApplication(appId)` | Disable (but not delete) an application |
| `CreateDefaultApplications()` | Create records for default-for-new-user apps |
| `NotificationPreferences` | User's notification preferences |
### DashboardEngine
| Member | Description |
|---|---|
| `Dashboards` | All cached dashboards |
| `DashboardCategories` | All dashboard categories |
| `DashboardPartTypes` | Dashboard widget/part types |
| `GetDashboardPermissions(id, userId)` | Get effective permissions (owner/direct/category) |
| `CanUserReadDashboard(id, userId)` | Check read access |
| `CanUserEditDashboard(id, userId)` | Check edit access |
| `CanUserDeleteDashboard(id, userId)` | Check delete access |
| `GetAccessibleDashboards(userId)` | All dashboards user can read |
| `GetSharedDashboards(userId)` | Dashboards shared with user (not owned) |
| `GetAccessibleCategories(userId)` | Categories user can access |
### ResourcePermissionEngine
| Member | Description |
|---|---|
| `ResourceTypes` | All resource type definitions |
| `Permissions` | All resource permission records |
| `GetResourcePermissions(typeId, recordId)` | Get all permissions for a resource |
| `GetUserResourcePermissionLevel(typeId, recordId, user)` | Get highest permission level (View/Edit/Owner/null) |
| `GetUserAvailableResources(user, typeId?)` | Get all resources a user can access |
### UserViewEngine
| Member | Description |
|---|---|
| `AllViews` | All cached views |
| `GetViewById(id)` | Look up view by ID |
| `GetViewByName(name)` | Look up view by name |
| `GetViewsForEntity(entityId)` | Views for a specific entity |
| `GetViewsForCurrentUser()` | Views owned by current user |
| `GetAccessibleViewsForEntity(entityId)` | Owned + shared views with permission checks |
| `GetDefaultViewForEntity(entityId)` | Default view for an entity |
### ArtifactExtractor
| Method | Description |
|---|---|
| `ResolveExtractRules(typeChain)` | Resolve rules with hierarchical inheritance |
| `ExtractAttributes(config)` | Execute extraction rules against content |
| `SerializeForStorage(attributes)` | Convert attributes to database-storable format |
| `DeserializeFromStorage(stored)` | Convert stored attributes back to runtime objects |
| `GetStandardProperty(attributes, prop)` | Find a standard property value (name/description/etc.) |
### ConversationEngine
Centralized, reactive cache for conversations, conversation details (messages), and peripheral data such as agent runs. `ConversationEngine` is the single source of truth for conversation data across all UI consumers (chat area, sidebar, overlay, etc.), replacing per-component caching that previously lived in multiple scattered locations.
The engine is user-scoped and environment-filtered, so it manages its own caching instead of using `BaseEngine.Load()` bulk-load pattern.
```typescript
import { ConversationEngine } from '@memberjunction/core-entities';
// Initialize once at app startup
await ConversationEngine.Instance.Config(false, contextUser);
// Load conversations for the current user and environment
await ConversationEngine.Instance.LoadConversations(environmentId, contextUser);
// Subscribe to reactive conversation list changes
ConversationEngine.Instance.Conversations$.subscribe(conversations => {
console.log(`${conversations.length} conversations loaded`);
});
// Load and cache details (messages) for a specific conversation
const details = await ConversationEngine.Instance.LoadConversationDetails(conversationId, contextUser);
// Instant cache reads (no DB round-trip)
const cached = ConversationEngine.Instance.GetCachedDetails(conversationId);
// CRUD operations that automatically update the reactive list
await ConversationEngine.Instance.CreateConversation('New Chat', environmentId, contextUser);
await ConversationEngine.Instance.PinConversation(conversationId, true, contextUser);
await ConversationEngine.Instance.ArchiveConversation(conversationId, contextUser);
// Cache management
ConversationEngine.Instance.InvalidateConversation(conversationId);
ConversationEngine.Instance.ClearCache();
```
Source: [`src/engines/conversations.ts`](src/engines/conversations.ts)
### KnowledgeHubMetadataEngine
Caches all Knowledge Hub-related metadata in a single singleton, providing fast lookups for entity documents, vector indexes, vector databases, content sources, content types, content source types, and content file types. Uses `BaseEngine` for automatic caching and entity-event auto-refresh.
```typescript
import { KnowledgeHubMetadataEngine } from '@memberjunction/core-entities';
const khEngine = KnowledgeHubMetadataEngine.Instance;
await khEngine.Config(false, contextUser);
// Cached data access (no DB round-trip)
const activeDocuments = khEngine.GetActiveEntityDocuments();
const entitiesWithDocs = khEngine.GetEntitiesWithDocuments(); // For dropdowns
const doc = khEngine.GetEntityDocumentById(docId);
const docsForEntity = khEngine.GetEntityDocumentsForEntity('Contacts');
const vectorIndex = khEngine.GetVectorIndexById(indexId);
const vectorDB = khEngine.GetVectorDatabaseById(dbId);
// Raw cached arrays
khEngine.EntityDocuments; // All entity documents
khEngine.VectorIndexes; // All vector indexes
khEngine.VectorDatabases; // All vector databases
khEngine.ContentSources; // All content sources
khEngine.ContentTypes; // All content types
khEngine.ContentSourceTypes; // All content source types (Web, RSS, etc.)
khEngine.ContentFileTypes; // All content file types (.pdf, .html, etc.)
```
Source: [`src/engines/knowledgeHubMetadata.ts`](src/engines/knowledgeHubMetadata.ts)
### Other Engines
| Engine | Purpose |
|---|---|
| `KnowledgeHubMetadataEngine` | Caches entity documents, vector indexes/databases, content sources/types/file types |
| `ConversationEngine` | Reactive cache for conversations, messages, and agent runs (user-scoped) |
| `EncryptionEngineBase` | Caches encryption keys, algorithms, and key sources |
| `FileStorageEngine` | Caches file storage accounts and providers |
| `MCPEngine` | Caches MCP servers, connections, and tools |
| `ComponentMetadataEngine` | Caches component definitions, libraries, and registries |
| `ArtifactMetadataEngine` | Caches artifact type definitions |
| `TypeTablesCache` | Caches entity relationship display components |
## Permission Model
The package implements a layered permission model used across views, dashboards, and resources:
```mermaid
flowchart TD
subgraph Check["Permission Resolution Order"]
direction TB
OWN["1. Ownership Check
Record owner gets full access"]
ADM["2. Admin Check
System admins get full access"]
DIR["3. Direct Permission
User-specific grants"]
CAT["4. Category Permission
Inherited from categories"]
ROL["5. Role Permission
Granted through user roles"]
end
OWN -->|"Not owner"| ADM
ADM -->|"Not admin"| DIR
DIR -->|"No direct grant"| CAT
CAT -->|"No category grant"| ROL
ROL -->|"No role grant"| DENY["Access Denied"]
style Check fill:#2d6a9f,stroke:#1a4971,color:#fff
style DENY fill:#64748b,stroke:#475569,color:#fff
```
Permission levels are hierarchical: **Owner** > **Edit** > **View**. The engine returns the highest applicable level across all sources (direct user grants and role-based grants).
## View Filter Architecture
The `UserViewEntityExtended` converts structured filter JSON into SQL WHERE clauses:
```mermaid
flowchart LR
subgraph Input["Filter State (JSON)"]
FS["{ logic: 'and',
filters: [...] }"]
end
subgraph Process["UserViewEntityExtended"]
PARSE["Parse FilterState
JSON column"]
GEN["GenerateWhereClause()
Recursive group processing"]
SQL["SQL WHERE clause"]
end
subgraph Modes["Filter Modes"]
STD["Standard Filter
UI-driven FilterState"]
SMART["Smart Filter
AI-generated from prompt"]
CUSTOM["Custom Filter
Admin-set WhereClause"]
end
FS --> PARSE --> GEN --> SQL
Modes --> GEN
style Input fill:#b8762f,stroke:#8a5722,color:#fff
style Process fill:#2d6a9f,stroke:#1a4971,color:#fff
style Modes fill:#2d8659,stroke:#1a5c3a,color:#fff
```
Supported filter operators: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `startswith`, `endswith`, `contains`, `doesnotcontain`, `isnull`, `isempty`, `isnotnull`, `isnotempty`. Filters support arbitrary nesting with `and`/`or` logic groups.
## Important: CodeGen and This Package
This package's `src/generated/entity_subclasses.ts` file is **entirely auto-generated** by the MemberJunction CodeGen system. Key implications:
- **Never edit generated files** -- changes will be overwritten on the next CodeGen run.
- **Custom logic goes in `src/custom/`** -- create an extended class with `@RegisterClass` to override the generated base.
- **Entity names matter** -- some entities use the `MJ: ` prefix (e.g., `MJ: Components`, `MJ: Environments`). Always verify names in the generated file's `@RegisterClass` decorators.
- **Zod schemas are authoritative** -- they reflect the exact database constraints including value lists, nullable fields, and default values.
## Dependencies
- [@memberjunction/core](../MJCore/readme.md) -- Base entity classes, metadata system, RunView, BaseEngine
- [@memberjunction/global](../MJGlobal/README.md) -- `@RegisterClass` decorator, global utilities
- [@memberjunction/ai](../AI/README.md) -- AI integration types (used by some engine classes)
- [@memberjunction/interactive-component-types](../InteractiveComponents/README.md) -- ComponentSpec type for component entity extensions
- [zod](https://zod.dev/) -- Runtime schema validation
## Related Packages
- [@memberjunction/core](../MJCore/readme.md) -- Framework foundation this package builds on
- [@memberjunction/core-entities-server](../MJCoreEntitiesServer/README.md) -- Server-side extensions of entities defined here (e.g., smart filter AI implementation)
- [@memberjunction/server](../MJServer/README.md) -- Server that uses these entities for API operations
## Contributing
See the [MemberJunction Contributing Guide](../../CONTRIBUTING.md) for development setup and guidelines.