The code generation engine for the MemberJunction platform. This library transforms database schema metadata into a complete, type-safe, full-stack application: TypeScript entity classes with Zod validation, Angular form components, SQL stored procedures and views, GraphQL resolvers, Action subclasses, and Remote Operation typed bases (RemoteOperationGeneratorBase → remote_operations.ts, one BaseRemotableOperation subclass per MJ: Remote Operations row — Manual shells or complete AI/Default classes; see the Remote Operations Guide) -- all from a single mj codegen invocation.
npm install @memberjunction/codegen-lib
CodeGenLib sits at the center of MemberJunction's development workflow. When you change a database schema (add a table, alter a column, define a CHECK constraint), CodeGenLib detects those changes, updates internal metadata, and regenerates synchronized code across every layer of the stack. The result is guaranteed type safety from database to UI with zero manual boilerplate.
The library is designed for extensibility: every major generator is a base class that can be subclassed and registered via @RegisterClass to override or extend default behavior.
flowchart TD
subgraph Input["Input Sources"]
DB["SQL Server\nDatabase Schema"]
CFG["mj.config.cjs\nConfiguration"]
AI["AI Prompts\n(Advanced Generation)"]
end
subgraph Pipeline["CodeGen Pipeline"]
META["Metadata\nManagement"]
SQLGEN["SQL\nGeneration"]
ENTITY["Entity Class\nGeneration"]
ANGULAR["Angular\nGeneration"]
GQL["GraphQL\nGeneration"]
ACTION["Action\nGeneration"]
end
subgraph Output["Generated Outputs"]
VIEWS["Views &\nStored Procedures"]
TS["TypeScript Entity\nClasses + Zod"]
NG["Angular Form\nComponents"]
GQLR["GraphQL\nResolvers"]
ACTS["Action\nSubclasses"]
JSON["DB Schema\nJSON"]
end
DB --> META
CFG --> META
AI --> META
META --> SQLGEN
META --> ENTITY
META --> ANGULAR
META --> GQL
META --> ACTION
SQLGEN --> VIEWS
ENTITY --> TS
ANGULAR --> NG
GQL --> GQLR
ACTION --> ACTS
META --> JSON
style Input fill:#2d6a9f,stroke:#1a4971,color:#fff
style Pipeline fill:#7c5295,stroke:#563a6b,color:#fff
style Output fill:#2d8659,stroke:#1a5c3a,color:#fff
Authoring constraint: every SQL artifact CodeGen emits ships in a Flyway migration that customer databases will replay forever. The Publish-Then-No-Breaking-Changes Policy governs what schema changes are safe to feed into CodeGen: within a published OpenApp major version, only additive changes are allowed (new tables, new optional columns, widened types, new optional SP parameters). Dropping or renaming columns, narrowing types, and adding required parameters break historical migrations and require a major version bump.
SQLCodeGenBase, EntitySubClassGeneratorBase, AngularClientGeneratorBase, etc.) can be subclassed and overridden via MJ's class factoryRoot{FieldName} columns in views@RegisterClass-decorated classes by generating static import manifestsThe code generation process follows a well-defined pipeline orchestrated by the RunCodeGenBase class:
flowchart LR
S0["BEFORE\nCommands"]
S1["Metadata\nManagement"]
S2["SQL Object\nGeneration"]
S3["GraphQL\nResolvers"]
S4["Entity\nSubclasses"]
S5["Angular\nComponents"]
S6["DB Schema\nJSON"]
S7["Action\nSubclasses"]
S8["Integrity\nChecks"]
S9["AFTER\nCommands"]
S0 --> S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7 --> S8 --> S9
style S0 fill:#64748b,stroke:#475569,color:#fff
style S1 fill:#2d6a9f,stroke:#1a4971,color:#fff
style S2 fill:#2d6a9f,stroke:#1a4971,color:#fff
style S3 fill:#7c5295,stroke:#563a6b,color:#fff
style S4 fill:#7c5295,stroke:#563a6b,color:#fff
style S5 fill:#7c5295,stroke:#563a6b,color:#fff
style S6 fill:#7c5295,stroke:#563a6b,color:#fff
style S7 fill:#7c5295,stroke:#563a6b,color:#fff
style S8 fill:#b8762f,stroke:#8a5722,color:#fff
style S9 fill:#64748b,stroke:#475569,color:#fff
| Stage | Class | Description |
|---|---|---|
| BEFORE Commands | RunCommandsBase |
Execute pre-generation shell commands and SQL scripts |
| Metadata Management | ManageMetadataBase |
Analyze schema changes, create/update entity metadata, run AI-powered field analysis |
| SQL Generation | SQLCodeGenBase |
Generate base views (with recursive CTEs), stored procedures (create/update/delete), foreign key indexes, permissions |
| GraphQL Resolvers | GraphQLServerGeneratorBase |
Generate TypeGraphQL resolver and type definitions for all API-enabled entities |
| Entity Subclasses | EntitySubClassGeneratorBase |
Generate TypeScript entity classes with Zod validation schemas, typed getters/setters, and value list types |
| Angular Components | AngularClientGeneratorBase |
Generate Angular form components with smart field types, category-based layouts, and related entity tabs |
| DB Schema JSON | DBSchemaGeneratorBase |
Export database schema as JSON for documentation and AI consumption |
| Action Subclasses | ActionSubClassGeneratorBase |
Generate Action implementation classes from metadata-defined business logic |
| Integrity Checks | SystemIntegrityBase |
Validate entity field sequences and other system consistency rules |
| AFTER Commands | RunCommandsBase |
Execute post-generation commands (typically package builds) |
CodeGen distinguishes between core MemberJunction entities (in the __mj schema) and application-specific entities. Each generator runs twice: once for core entities with output directed to @memberjunction/core-entities, and once for non-core entities with output directed to the application's generated packages. This separation ensures MJ framework code and application code stay independent.
Every generator base class can be subclassed and registered with a higher priority to customize behavior:
classDiagram
class RunCodeGenBase {
+setupDataSource() SQLServerDataProvider
+Run(skipDatabaseGeneration) void
}
class ManageMetadataBase {
+manageMetadata(pool, user) boolean
+loadGeneratedCode(pool, user) boolean
}
class SQLCodeGenBase {
+manageSQLScriptsAndExecution(pool, entities, dir, user) boolean
+runCustomSQLScripts(pool, when) boolean
}
class EntitySubClassGeneratorBase {
+generateAllEntitySubClasses(pool, entities, dir, skipDB) boolean
}
class AngularClientGeneratorBase {
+generateAngularCode(entities, dir, prefix, user) boolean
}
class GraphQLServerGeneratorBase {
+generateGraphQLServerCode(entities, dir, lib, exclude) boolean
}
class ActionSubClassGeneratorBase {
+generateActions(actions, dir) boolean
}
RunCodeGenBase --> ManageMetadataBase : creates via ClassFactory
RunCodeGenBase --> SQLCodeGenBase : creates via ClassFactory
RunCodeGenBase --> EntitySubClassGeneratorBase : creates via ClassFactory
RunCodeGenBase --> AngularClientGeneratorBase : creates via ClassFactory
RunCodeGenBase --> GraphQLServerGeneratorBase : creates via ClassFactory
RunCodeGenBase --> ActionSubClassGeneratorBase : creates via ClassFactory
style RunCodeGenBase fill:#2d6a9f,stroke:#1a4971,color:#fff
style ManageMetadataBase fill:#7c5295,stroke:#563a6b,color:#fff
style SQLCodeGenBase fill:#7c5295,stroke:#563a6b,color:#fff
style EntitySubClassGeneratorBase fill:#7c5295,stroke:#563a6b,color:#fff
style AngularClientGeneratorBase fill:#7c5295,stroke:#563a6b,color:#fff
style GraphQLServerGeneratorBase fill:#7c5295,stroke:#563a6b,color:#fff
style ActionSubClassGeneratorBase fill:#7c5295,stroke:#563a6b,color:#fff
To override any generator, subclass the base and register it:
import { RegisterClass } from '@memberjunction/global';
import { EntitySubClassGeneratorBase } from '@memberjunction/codegen-lib';
@RegisterClass(EntitySubClassGeneratorBase, undefined, 1) // priority 1 overrides default (0)
export class CustomEntityGenerator extends EntitySubClassGeneratorBase {
// Override methods to customize generation
}
import { RunCodeGenBase, initializeConfig } from '@memberjunction/codegen-lib';
// Initialize configuration from working directory
const config = initializeConfig(process.cwd());
// Run the complete code generation pipeline
const codeGen = new RunCodeGenBase();
await codeGen.Run();
// Or skip database operations for faster UI-only regeneration
await codeGen.Run(true);
The convenience function provides a simpler entry point:
import { runMemberJunctionCodeGeneration } from '@memberjunction/codegen-lib';
await runMemberJunctionCodeGeneration();
Each generator can be used independently:
import { EntitySubClassGeneratorBase } from '@memberjunction/codegen-lib';
import { MJGlobal } from '@memberjunction/global';
const generator = MJGlobal.Instance.ClassFactory.CreateInstance<EntitySubClassGeneratorBase>(
EntitySubClassGeneratorBase
);
await generator.generateAllEntitySubClasses(pool, entities, outputDir, false);
The manifest generator prevents tree-shaking of @RegisterClass-decorated classes:
import { generateClassRegistrationsManifest } from '@memberjunction/codegen-lib';
const result = await generateClassRegistrationsManifest({
outputPath: './src/generated/class-registrations-manifest.ts',
appDir: './packages/MJAPI',
excludePackages: ['@memberjunction'], // Use pre-built manifest for MJ packages
});
if (result.success) {
console.log(`${result.packages.length} packages, ${result.classes.length} classes`);
}
See the Class Manifest Guide for comprehensive documentation on the manifest system.
CodeGenLib uses cosmiconfig to locate configuration. The recommended approach is a mj.config.cjs file at the repository root:
module.exports = {
// Database connection
dbHost: 'localhost',
dbPort: 1433,
dbDatabase: 'YourDatabase',
codeGenLogin: 'codegen_user',
codeGenPassword: 'your_password',
mjCoreSchema: '__mj',
// Output directories for each generator
output: [
{ type: 'SQL', directory: '../../SQL Scripts/generated' },
{ type: 'Angular', directory: '../MJExplorer/src/app/generated' },
{ type: 'GraphQLServer', directory: '../MJAPI/src/generated' },
{ type: 'CoreEntitySubclasses', directory: '../MJCoreEntities/src/generated' },
{ type: 'EntitySubclasses', directory: '../GeneratedEntities/src/generated' },
],
// AI-powered features
advancedGeneration: {
enableAdvancedGeneration: true,
features: [
{ name: 'SmartFieldIdentification', enabled: true },
{ name: 'FormLayoutGeneration', enabled: true },
{ name: 'ParseCheckConstraints', enabled: true },
{ name: 'TransitiveJoinIntelligence', enabled: true },
],
},
// SQL output for Flyway migrations
SQLOutput: {
enabled: true,
folderPath: './migrations/v3/',
convertCoreSchemaToFlywayMigrationFile: true,
},
// Force regeneration of specific objects
forceRegeneration: {
enabled: false,
entityWhereClause: "SchemaName = 'dbo'",
baseViews: true,
spUpdate: true,
},
};
All configuration is validated at startup using Zod schemas, with clear error messages for invalid settings. Environment variables (DB_HOST, DB_DATABASE, CODEGEN_DB_USERNAME, CODEGEN_DB_PASSWORD) provide fallback values for database connection settings.
| Section | Purpose |
|---|---|
output |
Maps each generator type to its output directory |
advancedGeneration |
Controls which AI-powered features are enabled |
newEntityDefaults |
Default settings for newly discovered entities (permissions, tracking, API access) |
forceRegeneration |
Surgically regenerate specific SQL object types for filtered entities |
SQLOutput |
Controls Flyway migration file generation from SQL logging |
commands |
Shell commands to run before/after generation (typically package builds) |
excludeSchemas / excludeTables |
Filter schemas and tables from metadata discovery |
entityNaming |
Controls ALL CAPS normalization and compound word splitting for entity/field names |
additionalSchemaInfo |
Path to JSON file with soft PK/FK definitions and schema prefix rules |
dbPlatform |
Database backend selector. See Database Platform Selection below. |
dbPlatform)CodeGenLib accepts a single canonical dbPlatform field with two values:
// mj.config.cjs
module.exports = {
dbPlatform: 'sqlserver', // or 'postgresql'
// …
};
| Value | Backend |
|---|---|
'sqlserver' (default) |
Microsoft SQL Server |
'postgresql' |
PostgreSQL 14+ |
The same vocabulary is used by @memberjunction/cli, @memberjunction/server, and every other MJ package that needs to branch on platform. There is one name (dbPlatform) and one pair of values ('sqlserver', 'postgresql') — no aliases ('mssql', 'postgres', 'pg') are recognized in config or env vars.
If dbPlatform is not set in mj.config.cjs, CodeGen reads DB_PLATFORM from the environment (restricted to the canonical pair) and falls back to 'sqlserver'. An unrecognized non-empty DB_PLATFORM value throws — silent fallback is the bug we don't want, because it routes the wrong provider at the wrong dialect against a real database.
Migration note (was
dbType/DB_TYPE): Earlier dev builds of the PG support exposed bothdbType(config key) andDB_TYPE(env var). Both have been replaced bydbPlatform/DB_PLATFORMwith strict canonical values. RenamedbType: 'mssql'todbPlatform: 'sqlserver'(anddbType: 'postgresql'todbPlatform: 'postgresql') in yourmj.config.cjs. Same forDB_TYPE=...in.env→DB_PLATFORM=.... Legacy aliases (mssql,postgres,pg) are no longer accepted in either.
When CodeGen discovers new tables from legacy databases that use ALL CAPS naming conventions (e.g., PAYMENT, INDIVIDUALDESIGNATION), the entityNaming config section controls how those names are converted to human-readable entity and field names.
// In mj.config.cjs
module.exports = {
entityNaming: {
// Normalize ALL CAPS to Title Case: PAYMENT -> Payment
normalizeAllCaps: true, // default: true
// Split compound words: INDIVIDUALDESIGNATION -> Individual Designation
// Uses dictionary-based matching with backtracking
splitCompoundWords: true, // default: true
// Apply same normalization to field/column names
normalizeFieldNames: true, // default: true
// Additional domain-specific words for the compound word splitter
additionalDomainWords: ['AutoCare', 'SKU', 'UPC'],
},
};
Examples of normalization results:
| Database Identifier | Normalized Display Name |
|---|---|
PAYMENT |
Payment |
INDIVIDUALDESIGNATION |
Individual Designation |
CUSTOMFIELDVALUE |
Custom Field Value |
CUSTOMERID |
Customer ID |
LINEITEMEDUCATIONCREDIT |
Line Item Education Credit |
INVOICEORDERSCHEDULE |
Invoice Order Schedule |
TERMINATE_REASON |
Terminate Reason |
AI_COMMERCE_CONTEXT |
AI Commerce Context |
The compound word splitter uses a built-in dictionary of ~500 common business/technical words, supplemented by known acronyms (ID, API, CRM, SQL, etc.) that stay uppercase. The additionalDomainWords config lets you add industry-specific terms for your database.
When using additionalSchemaInfo (typically generated by DBAutoDoc), the JSON file can include a Schemas array with entity name prefix/suffix recommendations:
{
"Schemas": [
{ "name": "ACCOUNTING", "entityNamePrefix": "Accounting: ", "entityNameSuffix": "" },
{ "name": "CRM", "entityNamePrefix": "CRM: ", "entityNameSuffix": "" }
],
"ACCOUNTING": [
{ "TableName": "PAYMENT", "PrimaryKey": [...], "ForeignKeys": [...] }
]
}
These prefixes are applied to SchemaInfo records during CodeGen, preventing entity name collisions across schemas (e.g., CRM: Categories vs Award: Categories instead of CATEGORYs__CRM). Config-file rules in newEntityDefaults.NameRulesBySchema still take priority.
From a SQL table with CHECK constraints, CodeGen produces a complete entity class:
// Auto-generated from database schema
export class AIPromptEntity extends BaseEntity {
// Typed getter/setter for CHECK-constrained field
get PromptRole(): 'System' | 'User' | 'Assistant' | 'SystemOrUser' {
return this.Get('PromptRole');
}
set PromptRole(value: 'System' | 'User' | 'Assistant' | 'SystemOrUser') {
this.Set('PromptRole', value);
}
// Zod validation from CHECK constraint
validate(): ValidationResult {
return this.validateWithZod(AIPromptSchema);
}
}
// Zod schema with union types from CHECK constraint
export const AIPromptSchema = z.object({
PromptRole: z.union([
z.literal('System'),
z.literal('User'),
z.literal('Assistant'),
z.literal('SystemOrUser'),
]),
// ... all other fields
});
For tables with self-referential foreign keys, CodeGen automatically generates recursive CTEs:
-- Auto-detected: Task.ParentTaskID references Task.ID
CREATE VIEW [vwTasks] AS
WITH CTE_RootParentTaskID AS (
SELECT [ID], [ID] AS [RootParentTaskID]
FROM [__mj].[Task]
WHERE [ParentTaskID] IS NULL
UNION ALL
SELECT child.[ID], parent.[RootParentTaskID]
FROM [__mj].[Task] child
INNER JOIN CTE_RootParentTaskID parent
ON child.[ParentTaskID] = parent.[ID]
)
SELECT t.*, cte.[RootParentTaskID]
FROM [__mj].[Task] AS t
LEFT OUTER JOIN CTE_RootParentTaskID cte ON t.[ID] = cte.[ID]
The CTE is zero-overhead: the SQL optimizer eliminates it entirely when the root column is not selected.
CodeGen creates production-ready Angular forms with AI-determined field categories and smart field types:
@Component({
selector: 'mj-ai-prompt-form',
template: `
<mj-form-field [record]="record"
FieldName="PromptRole"
Type="dropdownlist"
[EditMode]="EditMode">
</mj-form-field>
`
})
export class AIPromptFormComponent extends BaseFormComponent { }
Delete procedures use cursor-based stored procedure calls to respect business logic at every level of the hierarchy:
CREATE PROCEDURE [spDeleteOrder] @ID UNIQUEIDENTIFIER AS
BEGIN
-- Cascade through child stored procedures
DECLARE @ItemID UNIQUEIDENTIFIER
DECLARE cascade_cursor CURSOR FOR
SELECT [ID] FROM [OrderItems] WHERE [OrderID] = @ID
OPEN cascade_cursor
FETCH NEXT FROM cascade_cursor INTO @ItemID
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC [spDeleteOrderItem] @ItemID -- Respects OrderItem's own cascade logic
FETCH NEXT FROM cascade_cursor INTO @ItemID
END
CLOSE cascade_cursor
DEALLOCATE cascade_cursor
DELETE FROM [Orders] WHERE [ID] = @ID
END
When advancedGeneration.enableAdvancedGeneration is enabled, CodeGen uses AI prompts (stored in the database as AI Prompt entities) to enhance the generation process:
flowchart TD
subgraph Features["AI-Powered Features"]
SF["Smart Field\nIdentification"]
FL["Form Layout\nGeneration"]
CC["CHECK Constraint\nParsing"]
ED["Entity\nDescriptions"]
TJ["Transitive Join\nIntelligence"]
EN["Entity Name\nGeneration"]
end
subgraph Results["What AI Determines"]
SF --> R1["Name fields, Default-in-View\nfields, Searchable fields"]
FL --> R2["Field categories, Icons\nDisplay names, Extended types"]
CC --> R3["Zod schemas, Validation\nfunctions, Descriptions"]
ED --> R4["Entity descriptions\nfor new entities"]
TJ --> R5["Junction table detection\nMany-to-many relationships"]
EN --> R6["Human-friendly entity\nnames from table names"]
end
style Features fill:#7c5295,stroke:#563a6b,color:#fff
style Results fill:#2d8659,stroke:#1a5c3a,color:#fff
| Feature | Purpose | When It Runs |
|---|---|---|
SmartFieldIdentification |
Determines which field is the "name" field, which fields show in default views, and which are searchable | Entity/field creation, or when AutoUpdate flags allow |
FormLayoutGeneration |
Groups fields into semantic categories with icons and display names | Every run (forms are always regenerated) |
ParseCheckConstraints |
Translates SQL CHECK constraints into Zod validation schemas and TypeScript union types | When CHECK constraints are detected |
EntityDescriptions |
Generates human-readable descriptions for entities | Entity creation only |
TransitiveJoinIntelligence |
Detects junction tables and many-to-many relationships | Entity/relationship creation |
EntityNames |
Converts technical table names to user-friendly entity names | Entity creation only |
VirtualEntityFieldDecoration |
Analyzes SQL view definitions to identify PKs, FKs, descriptions, and extended types for virtual entities | Virtual entity creation (idempotent unless forceRegenerate option is set) |
SmartFieldIdentification may propose several ranked name-field candidates (e.g. FirstName, LastName), but MemberJunction metadata supports exactly one IsNameField per entity — EntityInfo.NameField, the base-view FK-name virtual columns, and RelatedEntityNameFieldMap resolution all assume a single winner. CodeGen enforces this:
MAX text, and virtual (view-only) fields are rejected.IsNameField keeps it; AI proposals never move an established winner.Name wins, else the first eligible flagged field in sequence order. All other auto-updatable flags are cleared.AutoUpdateIsNameField = 0 are never set or cleared by CodeGen; EntityInfo.NameField's literal-Name preference arbitrates at runtime if a pinned conflict exists.Why this matters: with multiple flags, the FK-name pick silently drifted between CodeGen runs as flags accumulated (observed: a related entity's name column flipping from one field to another, reshaping every view that joins to it).
The form layout system enforces stability to prevent unnecessary churn:
AutoUpdateCategory and AutoUpdateDisplayName flags provide granular controlRegenerate specific SQL objects without schema changes using surgical filtering:
// In mj.config.cjs
forceRegeneration: {
enabled: true,
// Filter to specific entities
entityWhereClause: "SchemaName = 'CRM' AND __mj_UpdatedAt >= '2025-06-24'",
// Control which object types regenerate
baseViews: true,
spCreate: false,
spUpdate: true,
spDelete: false,
indexes: true,
}
Only the intersection of matched entities and enabled object types gets regenerated.
All SQL generated during metadata management and object generation is logged to a Flyway-compatible migration file. The SQLOutput configuration controls this behavior:
SQLOutput: {
enabled: true,
folderPath: './migrations/v3/',
appendToFile: true,
convertCoreSchemaToFlywayMigrationFile: true,
schemaPlaceholders: [
{ schema: '__mj', placeholder: '${flyway:defaultSchema}' },
],
}
The SQLLogging class accumulates all SQL statements during a run and writes them as a single migration file with schema names replaced by Flyway placeholders.
src/
index.ts # Public API exports
runCodeGen.ts # RunCodeGenBase - main pipeline orchestrator
Config/
config.ts # Zod-validated configuration schemas and loaders
db-connection.ts # SQL Server connection pool management
Database/
manage-metadata.ts # ManageMetadataBase - schema analysis and metadata sync
sql_codegen.ts # SQLCodeGenBase - views, procedures, indexes, permissions
sql.ts # SQLUtilityBase - SQL file management and execution
dbSchema.ts # DBSchemaGeneratorBase - JSON schema export
reorder-columns.ts # Table column reordering utilities
Angular/
angular-codegen.ts # AngularClientGeneratorBase - form and module generation
related-entity-components.ts # Base classes for related entity display components
entity-data-grid-related-entity-component.ts # Data grid component generator
join-grid-related-entity-component.ts # Join grid component generator
timeline-related-entity-component.ts # Timeline component generator
Misc/
entity_subclasses_codegen.ts # EntitySubClassGeneratorBase - TypeScript entity generation
action_subclasses_codegen.ts # ActionSubClassGeneratorBase - Action class generation
graphql_server_codegen.ts # GraphQLServerGeneratorBase - resolver generation
advanced_generation.ts # AdvancedGeneration - AI-powered enhancement features
status_logging.ts # Spinner and log utilities (ora-based)
sql_logging.ts # SQLLogging - migration file accumulator
system_integrity.ts # SystemIntegrityBase - post-generation validation
createNewUser.ts # CreateNewUserBase - initial user setup
runCommand.ts # RunCommandsBase - shell command execution
util.ts # File system and sorting utilities
Manifest/
GenerateClassRegistrationsManifest.ts # Tree-shaking prevention manifest generator
The main orchestrator class. Creates instances of all generator classes via MJGlobal.ClassFactory and runs the pipeline.
| Method | Description |
|---|---|
Run(skipDatabaseGeneration?) |
Execute the full code generation pipeline. Pass true to skip database operations. |
setupDataSource() |
Initialize the SQL Server connection pool and data provider. |
Analyzes database schema changes and updates MJ metadata tables.
| Method | Description |
|---|---|
manageMetadata(pool, user) |
Full metadata management: detect schema changes, create entities/fields, run AI features. |
loadGeneratedCode(pool, user) |
Load previously generated AI code from database (used when skipping DB generation). |
Generates database objects: views, stored procedures, indexes, and permissions. Delegates platform-specific SQL generation to CodeGenDatabaseProvider implementations (see below).
| Method | Description |
|---|---|
manageSQLScriptsAndExecution(pool, entities, dir, user) |
Generate and execute all SQL objects for the given entities. |
runCustomSQLScripts(pool, when) |
Execute custom SQL scripts configured for the specified timing. |
The CodeGenDatabaseProvider is the abstract base class that encapsulates all database-specific SQL generation for CodeGen. It has 55 abstract methods organized into categories that each platform provider must implement:
| Category | Methods | Purpose |
|---|---|---|
| DROP Guards | generateDropGuard |
Conditional drop statements (IF EXISTS, IF OBJECT_ID) |
| Base Views | generateBaseView |
Entity views with joins and soft-delete filtering |
| CRUD Routines | generateCRUDCreate, generateCRUDUpdate, generateCRUDDelete |
Create/Update/Delete stored procedures or functions |
| Triggers | generateTimestampTrigger |
Timestamp auto-update triggers |
| Indexes | generateForeignKeyIndexes |
Foreign key index generation |
| Full-Text Search | generateFullTextSearch |
Platform-specific FTS infrastructure |
| Root ID Functions | generateRootIDFunction, generateRootFieldSelect, generateRootFieldJoin |
Recursive hierarchy root ID calculation |
| Permissions | generateViewPermissions, generateCRUDPermissions, generateFullTextSearchPermissions |
GRANT statements per entity role |
| Cascade Deletes | generateSingleCascadeOperation |
Cascade delete/update-to-NULL operations |
| Timestamp Columns | generateTimestampColumns |
Adding __mj_CreatedAt/__mj_UpdatedAt columns |
| Parameter Helpers | generateCRUDParamString, generateInsertFieldString, generateUpdateFieldString |
SQL generation utilities for routines |
| DDL Operations | addColumnSQL, alterColumnTypeAndNullabilitySQL, dropObjectSQL, etc. |
Schema modification statements |
| Introspection | getViewDefinitionSQL, getPrimaryKeyIndexNameSQL, getViewColumnsSQL |
Catalog queries for schema discovery |
| Type System | compareDataTypes, get TimestampType |
Data type comparison and platform constants |
| Platform Config | getSystemSchemasToExclude, get NeedsViewRefresh, get PlatformKey |
Platform-specific behavior flags |
| SQL Execution | executeSQLFileViaShell |
Shell-based SQL file execution (sqlcmd, psql) |
| Default Parsing | parseColumnDefaultValue |
Extracting defaults from catalog metadata |
| Provider | Package | Platform |
|---|---|---|
SQLServerCodeGenProvider |
@memberjunction/codegen-lib |
SQL Server (T-SQL stored procedures, OBJECT_ID checks, sqlcmd) |
PostgreSQLCodeGenProvider |
@memberjunction/postgresql-dataprovider |
PostgreSQL (PL/pgSQL functions, DROP IF EXISTS, psql) |
Providers are registered via @RegisterClass(CodeGenDatabaseProvider, 'ProviderName') and selected at runtime based on the configured database platform.
To add support for a new database (e.g., MySQL, Oracle):
CodeGenDatabaseProviderSQLDialect subclass in @memberjunction/sql-dialect for identifier quoting, type mapping, etc.@RegisterClass so CodeGen discovers it at runtimeDatabaseProviderBase from @memberjunction/coreimport { RegisterClass } from '@memberjunction/global';
import { CodeGenDatabaseProvider } from '@memberjunction/codegen-lib';
@RegisterClass(CodeGenDatabaseProvider, 'MySQLCodeGenProvider')
export class MySQLCodeGenProvider extends CodeGenDatabaseProvider {
get PlatformKey(): string { return 'mysql'; }
get Dialect(): SQLDialect { return new MySQLDialect(); }
generateDropGuard(objectType, schema, name): string {
return `DROP ${objectType} IF EXISTS ${schema}.\`${name}\`;`;
}
// ... implement remaining 53 abstract methods
}
The PostgreSQL provider in @memberjunction/postgresql-dataprovider serves as the reference implementation for adding new backends.
Generates TypeScript entity classes with Zod validation.
| Method | Description |
|---|---|
generateAllEntitySubClasses(pool, entities, dir, skipDB) |
Generate all entity subclass files including Zod schemas. |
generateEntitySubClass(pool, entity, includeHeader, skipDB) |
Generate a single entity subclass. |
GenerateSchemaAndType(entity) |
Generate Zod schema and TypeScript type for an entity. |
Generates Angular form components, section components, and Angular modules.
| Method | Description |
|---|---|
generateAngularCode(entities, dir, prefix, user) |
Generate all Angular components and modules. |
Generates TypeGraphQL resolver and type definitions.
| Method | Description |
|---|---|
generateGraphQLServerCode(entities, dir, lib, exclude) |
Generate GraphQL resolvers for all entities. |
Generates an import manifest that prevents tree-shaking of @RegisterClass decorated classes. See the Class Manifest Guide for full documentation.
| Option | Description |
|---|---|
outputPath |
Path for the generated manifest file |
appDir |
Directory containing the app's package.json (default: process.cwd()) |
filterBaseClasses |
Only include classes extending specific base classes |
excludePackages |
Skip packages matching name prefixes (e.g., ['@memberjunction']) |
| Function | Description |
|---|---|
initializeConfig(cwd) |
Load and validate configuration from the given directory |
outputDir(type, fallback) |
Get the configured output directory for a generator type |
getSettingValue(name, default) |
Get a named setting value from configuration |
mj_core_schema() |
Get the MJ core schema name (typically __mj) |
This package depends on:
@RegisterClass decorator and MJGlobal.ClassFactorymj codegen commands)entity_subclasses.ts output@RegisterClass classesMemberJunction supports IS-A (inheritance) relationships between entities, where one entity extends another by adding additional fields while inheriting the parent's schema. CodeGen automatically handles IS-A relationships with specialized generation logic.
For comprehensive conceptual documentation, see the IS-A Relationships Guide in MJCore.
When an entity has a ParentEntity relationship (IS-A child):
Child views automatically JOIN to parent views to provide a complete record with all inherited fields:
-- CodeGen automatically generates:
CREATE VIEW [vwEmployee]
AS
SELECT
e.*, -- All Employee fields
p.FirstName, -- Inherited from Person
p.LastName, -- Inherited from Person
p.DateOfBirth -- Inherited from Person
FROM
[__mj].[Employee] AS e
INNER JOIN
[__mj].[vwPerson] AS p ON e.[ID] = p.[ID]
This ensures querying the child view returns a complete record including all parent fields.
Create and Update procedures only include the child's own fields, not parent fields:
-- spCreateEmployee only has Employee-specific parameters
CREATE PROCEDURE [spCreateEmployee]
@ID uniqueidentifier,
@EmployeeNumber nvarchar(50),
@HireDate date,
@Salary decimal(18,2)
-- No FirstName, LastName (those are Person fields)
AS BEGIN
-- Only inserts into Employee table
INSERT INTO [__mj].[Employee] (ID, EmployeeNumber, HireDate, Salary)
VALUES (@ID, @EmployeeNumber, @HireDate, @Salary)
END
Why this design? When creating an Employee, you first create the Person record (which gets an ID), then use that same ID to create the Employee record. The stored procedures reflect this two-step creation pattern.
GraphQL input types include ALL fields (parent + child) for seamless API usage:
input CreateEmployeeInput {
# Parent fields (from Person)
firstName: String!
lastName: String!
dateOfBirth: Date
# Child fields (from Employee)
employeeNumber: String!
hireDate: Date!
salary: Decimal!
}
This provides a convenient single-operation API while the resolver handles the underlying two-step creation.
Generated entity classes include JSDoc annotations on getter/setter methods to indicate IS-A relationships:
export class EmployeeEntity extends BaseEntity {
/**
* Inherited from Person entity
*/
get FirstName(): string {
return this.Get('FirstName');
}
set FirstName(value: string) {
this.Set('FirstName', value);
}
// Own fields have no annotation
get EmployeeNumber(): string {
return this.Get('EmployeeNumber');
}
}
manageEntityFields() respects IS-A hierarchy when syncing field metadata:
EntityField for the child entityRelatedEntityID and field relationships are preserved across the hierarchyIS-A relationships are defined in the Entity table:
-- Person is the base entity
INSERT INTO Entity (ID, ParentEntity, Name)
VALUES (NEWID(), NULL, 'Person')
-- Employee IS-A Person
INSERT INTO Entity (ID, ParentEntity, Name)
VALUES (NEWID(), 'Person', 'Employee')
CodeGen detects the ParentEntity relationship and applies the specialized generation logic automatically.
Common scenarios where IS-A relationships improve your schema:
MemberJunction supports virtual entities - entities backed by database views instead of tables. Virtual entities enable read-only access to complex queries, external data sources, or denormalized views while maintaining the full MemberJunction metadata and API experience.
For comprehensive conceptual documentation, see the Virtual Entities Guide in MJCore.
Virtual entities are defined in database-metadata-config.json under the VirtualEntities array:
{
"VirtualEntities": [
{
"ViewName": "vwSalesSummary",
"EntityName": "Sales Summary",
"SchemaName": "__mj",
"Description": "Aggregated sales data by region and period",
"PrimaryKey": ["SummaryID"],
"ForeignKeys": [
{
"FieldName": "RegionID",
"SchemaName": "__mj",
"RelatedTable": "Region",
"RelatedField": "ID",
"Description": "FK to Region table"
}
]
}
]
}
ViewName: The SQL view name (must already exist in the database)EntityName: The MemberJunction entity name (appears in metadata, UI, APIs)SchemaName: Database schema (typically __mj for core entities)Description: Entity description for metadata and documentationPrimaryKey: Array of column names forming the primary key (supports composite keys)ForeignKeys: Optional array of foreign key relationships to other entities (if omitted, LLM decoration discovers them)CodeGen processes virtual entities through several specialized steps:
processVirtualEntityConfig() - Entity CreationReads the VirtualEntities configuration and calls spCreateVirtualEntity for each entry:
// CodeGen calls this stored procedure for each virtual entity
EXEC spCreateVirtualEntity
@Name = 'Sales Summary',
@SchemaName = '__mj',
@BaseView = 'vwSalesSummary',
@Description = 'Aggregated sales data...',
@PrimaryKeyColumnName = 'SummaryID'
This creates the Entity metadata record with VirtualEntity = 1.
manageVirtualEntities() - Field SynchronizationScans sys.columns on the virtual entity's view and creates EntityField metadata for each column:
EntityField records for all view columnsIsVirtual = 1 in metadata// CodeGen inspects the view schema
SELECT
c.name,
t.name AS TypeName,
c.max_length,
c.is_nullable
FROM
sys.columns c
INNER JOIN
sys.types t ON c.user_type_id = t.user_type_id
WHERE
object_id = OBJECT_ID('__mj.vwSalesSummary')
applySoftPKFKConfig() - Explicit Relationship OverridesApplies the primaryKeyColumnName and foreignKeyDefinitions from the config:
// Sets the primary key field
UPDATE EntityField
SET IsPrimaryKey = 1
WHERE EntityID = @VirtualEntityID
AND Name = 'SummaryID'
// Creates foreign key relationships
INSERT INTO EntityRelationship (...)
SELECT ... FROM foreignKeyDefinitions
Why explicit FK definitions? Views don't have database-level foreign keys, so CodeGen can't detect relationships automatically. The config provides this metadata.
The decorateVirtualEntitiesWithLLM() pipeline step uses AI to enhance virtual entity field metadata:
import { AIPromptRunner } from '@memberjunction/ai-prompts';
// CodeGen calls a database-driven prompt to decorate fields
const promptParams = new AIPromptParams();
promptParams.prompt = 'Decorate Virtual Entity Fields';
promptParams.data = {
entityName: 'Sales Summary',
viewDefinition: viewSQL,
existingFields: fieldsFromMetadata
};
const runner = new AIPromptRunner();
const result = await runner.ExecutePrompt(promptParams);
The LLM analyzes the view definition and provides:
TotalRevenue → "Total Revenue")This is controlled by the VirtualEntityFieldDecoration feature in the Advanced Generation Features configuration.
After CodeGen processing, virtual entities have complete metadata:
-- Entity record
SELECT * FROM Entity WHERE Name = 'Sales Summary'
-- VirtualEntity = 1, BaseView = 'vwSalesSummary'
-- EntityField records (auto-detected from view)
SELECT * FROM EntityField WHERE EntityID = @SalesEntityID
-- Name, Type, Description, IsVirtual = 1
-- EntityRelationship records (from config)
SELECT * FROM EntityRelationship WHERE EntityID = @SalesEntityID
-- Foreign keys defined in foreignKeyDefinitions
Virtual entities generate the same TypeScript, GraphQL, and Angular code as table-based entities:
TypeScript Entity Class:
export class SalesSummaryEntity extends BaseEntity {
get SummaryID(): string {
return this.Get('SummaryID');
}
get RegionID(): string {
return this.Get('RegionID');
}
get TotalRevenue(): number {
return this.Get('TotalRevenue');
}
// Save/Delete methods throw errors (read-only entity)
}
GraphQL Schema:
type SalesSummary {
summaryID: ID!
regionID: ID!
region: Region # Auto-resolved from FK definition
totalRevenue: Float!
}
type Query {
SalesSummaries(filter: String): [SalesSummary!]!
}
Angular Form:
<mj-form-field
[record]="record"
FieldName="TotalRevenue"
Type="textbox"
[ReadOnly]="true" <!-- Virtual entities are read-only -->
></mj-form-field>
Virtual entities are read-only by design:
spCreate, spUpdate, or spDelete procedures generatedAllowCreateAPI, AllowUpdateAPI, AllowDeleteAPI set to 0 in metadataSave() and Delete() methods throw errorsVirtual entity LLM decoration is controlled in the advancedGeneration.features array in mj.config.cjs:
advancedGeneration: {
enableAdvancedGeneration: true,
features: [
{
name: 'VirtualEntityFieldDecoration',
enabled: true,
// Optional: force re-decoration even if entities already have soft PK/FK annotations
options: [{ name: 'forceRegenerate', value: true }],
},
],
},
By default, VirtualEntityFieldDecoration is enabled and uses an idempotency check — entities that already have IsSoftPrimaryKey or IsSoftForeignKey annotations are skipped. Set the forceRegenerate option to true to override this check and re-run LLM decoration for all virtual entities (useful after prompt improvements or when you want to refresh metadata).
When active, CodeGen calls decorateVirtualEntitiesWithLLM() after field synchronization.
Virtual entities excel at:
entityName appears throughout UI and APIsSee the MemberJunction Contributing Guide for development setup and guidelines.
When contributing to CodeGenLib:
CodeGen is invoked through the MemberJunction CLI (mj command). Two subcommands are available:
mj codegen — Full Code Generation PipelineRuns the complete pipeline: database schema analysis, metadata sync, and code generation across all layers.
# Run the full pipeline (most common usage)
mj codegen
# Skip database operations, only regenerate code files from existing metadata
mj codegen --skipdb
| Flag | Description |
|---|---|
--skipdb |
Skip all database operations (metadata sync, SQL object generation). Only regenerates TypeScript entities, Angular components, and GraphQL resolvers from existing metadata. |
Verbose output is controlled via mj.config.cjs (not a CLI flag):
module.exports = {
verboseOutput: true, // Enable detailed logging during code generation
};
When enabled, you see additional detail about each pipeline stage including per-entity processing, AI prompt calls, and SQL statement execution.
mj codegen manifest — Class Registration ManifestGenerates a TypeScript manifest file that prevents modern bundlers (ESBuild, Vite) from tree-shaking @RegisterClass-decorated classes.
# Generate manifest with defaults
mj codegen manifest
# Generate for a specific app, excluding MJ packages
mj codegen manifest --appDir ./packages/MJAPI \
--output ./packages/MJAPI/src/generated/class-registrations-manifest.ts \
--exclude-packages @memberjunction
# Only include classes extending specific base classes
mj codegen manifest --filter BaseEngine --filter BaseAction --verbose
| Flag | Short | Description |
|---|---|---|
--output <path> |
-o |
Output file path. Default: ./src/generated/class-registrations-manifest.ts |
--appDir <path> |
-a |
Root directory whose package.json dependency tree is scanned. Default: cwd |
--filter <class> |
-f |
Only include classes extending this base class. Repeatable. |
--exclude-packages <prefix> |
-e |
Skip packages whose name starts with this prefix. Repeatable. |
--quiet |
-q |
Suppress all output except errors. |
--verbose |
-v |
Show detailed per-package scanning progress. |
# 1. Configure database connection in mj.config.cjs
# 2. Run the full pipeline
mj codegen
# 3. Generated output lands in directories specified in mj.config.cjs:
# - TypeScript entities → packages/GeneratedEntities/src/generated/
# - Angular forms → packages/MJExplorer/src/app/generated/
# - GraphQL resolvers → packages/MJAPI/src/generated/
# - SQL migration file → migrations/v3/
# CodeGen detects schema changes automatically
mj codegen
# Review the generated Flyway migration file
ls -la migrations/v3/CodeGen_Run_*.sql
# Skip database operations for a faster run
mj codegen --skipdb
Use forceRegeneration in mj.config.cjs:
module.exports = {
forceRegeneration: {
enabled: true,
entityWhereClause: "SchemaName = 'CRM' AND Name LIKE 'Customer%'",
baseViews: true,
spUpdate: true,
},
};
Then run mj codegen. Set enabled: false afterward to avoid unnecessary work on future runs.
Symptom: CodeGen fails immediately with a connection error or timeout.
Common fixes:
Wrong credentials — Verify dbHost, dbDatabase, codeGenLogin, and codeGenPassword in mj.config.cjs. Environment variables DB_HOST, DB_DATABASE, CODEGEN_DB_USERNAME, CODEGEN_DB_PASSWORD serve as fallbacks.
Named instance — If using a named instance (e.g., localhost\SQLEXPRESS), set dbInstanceName in config.
Certificate trust — For self-signed certificates, set dbTrustServerCertificate: true.
Symptom: ENOENT errors when writing generated files.
Fix: Ensure all directories listed in the output array of mj.config.cjs exist. CodeGen does not create parent directories automatically.
Common causes:
Missing referenced tables — If a foreign key references a table excluded via excludeSchemas or excludeTables, either include the referenced table or remove the foreign key.
Stale metadata — If you dropped and recreated tables, metadata may be out of sync. Run a full mj codegen (without --skipdb) to refresh.
Use it when:
Don't use it when:
forceRegeneration settingsSymptom: Zod validation errors at startup.
CodeGen validates all configuration using Zod schemas. Common issues:
dbHost, dbDatabase, codeGenLogin, codeGenPassword)dbPort must be a positive integer, verboseOutput must be a boolean)output array (each entry needs type and directory properties)advancedGeneration.enableAdvancedGeneration is true in mj.config.cjsenabled: true in the features array