🚀 The most sophisticated code generation engine you've ever seen - automatically transforms your database schema into a complete, type-safe, full-stack application with AI-powered intelligence.
MemberJunction's CodeGen doesn't just generate boilerplate code. It's an AI-powered, metadata-driven architecture that creates bulletproof, production-ready applications from your database schema with zero manual intervention.
Watch your database changes instantly propagate through your entire stack:
Database Schema Change → TypeScript Entities → Angular Forms → SQL Procedures → GraphQL Schema
One command. Complete synchronization. Zero breaking changes.
npm install @memberjunction/codegen-lib
ALTER TABLE [AIPrompt]
ADD [PromptRole] nvarchar(20) NOT NULL
CONSTRAINT [CK_AIPrompt_PromptRole] CHECK ([PromptRole] IN (N'System', N'User', N'Assistant', N'SystemOrUser'))
PromptRole: z.union([
z.literal('System'),
z.literal('User'),
z.literal('Assistant'),
z.literal('SystemOrUser')
]).describe('Determines how the prompt is used in conversation...')
<mj-form-field
[record]="record"
FieldName="PromptRole"
Type="dropdownlist" // AI chose dropdown based on constraint
[EditMode]="EditMode"
></mj-form-field>
CREATE PROCEDURE [spCreateAIPrompt]
@PromptRole nvarchar(20),
-- 20+ other parameters auto-generated
AS BEGIN
-- Complete CRUD logic with validation
END
All from ONE schema change. All type-safe. All production-ready.
import { initializeConfig, runCodeGen } from '@memberjunction/codegen-lib';
// Initialize configuration
await initializeConfig();
// Generate your entire application stack
await runCodeGen();
// That's it. Seriously.
Your database schema just became:
Generates bullet-proof TypeScript classes from your database schema:
// Auto-generated from your schema
export class AIPromptEntity extends BaseEntity {
// 30+ properties with perfect types
PromptRole: 'System' | 'User' | 'Assistant' | 'SystemOrUser';
// AI-powered validation from CHECK constraints
validate(): ValidationResult {
return this.validateWithZod(AIPromptSchema);
}
}
Creates production-ready Angular forms with intelligent field types:
// Auto-detects relationships and creates search components
<mj-form-field
FieldName="CategoryID"
Type="textbox" // Smart field type selection
LinkType="Record" // Auto-detected relationship
LinkComponentType="Search" // AI chose search over dropdown
></mj-form-field>
Generates optimized database objects with best practices:
-- Auto-generated indexes for performance
CREATE INDEX IDX_AUTO_MJ_FKEY_AIPrompt_CategoryID
ON [AIPrompt] ([CategoryID]);
-- Complete CRUD procedures with validation
CREATE PROCEDURE [spCreateAIPrompt]
@PromptRole nvarchar(20) -- Validated against CHECK constraint
-- Full implementation auto-generated
CodeGen automatically detects self-referential foreign keys and generates Root{FieldName} columns in base views using efficient recursive CTEs. This enables instant root node lookup for hierarchical data structures with zero overhead when not selected.
How It Works:
For any table with a self-referential foreign key (like ParentTaskID → Task.ID), CodeGen automatically:
RelatedEntityID === entity.IDRoot{FieldName} in the base view (e.g., RootParentTaskID)Example - Task Hierarchy:
CREATE TABLE [Task] (
[ID] uniqueidentifier PRIMARY KEY,
[ParentTaskID] uniqueidentifier FOREIGN KEY REFERENCES [Task]([ID]),
[Name] nvarchar(255)
);
CodeGen Automatically Generates:
CREATE VIEW [vwTasks]
AS
WITH
CTE_RootParentTaskID AS (
-- Anchor: rows with no parent (root nodes)
SELECT
[ID],
[ID] AS [RootParentTaskID]
FROM
[__mj].[Task]
WHERE
[ParentTaskID] IS NULL
UNION ALL
-- Recursive: traverse up the hierarchy
SELECT
child.[ID],
parent.[RootParentTaskID]
FROM
[__mj].[Task] child
INNER JOIN
CTE_RootParentTaskID parent ON child.[ParentTaskID] = parent.[ID]
)
SELECT
t.*,
CTE_RootParentTaskID.[RootParentTaskID] -- Auto-generated root column
FROM
[__mj].[Task] AS t
LEFT OUTER JOIN
CTE_RootParentTaskID
ON
t.[ID] = CTE_RootParentTaskID.[ID]
Benefits:
RootParentTaskID is selectedRoot{FieldName} pattern across all entitiesUse Cases:
Employee.ManagerID → RootManagerID finds CEOTask.ParentTaskID → RootParentTaskID finds root projectCategory.ParentCategoryID → RootParentCategoryID finds top levelComment.ParentCommentID → RootParentCommentID finds original postPart.ParentPartID → RootParentPartID finds top-level assemblyPerformance Note:
The CTE approach is ideal for read-heavy workloads (typical in business applications). The SQL optimizer completely eliminates the CTE from the execution plan when the root column isn't selected, meaning zero overhead for queries that don't need hierarchy information.
Our generated delete procedures are production-grade with intelligent handling of:
1. Result Feedback - Know What Happened
-- Returns NULL for all PKs when no record found
IF @@ROWCOUNT = 0
SELECT NULL AS [CustomerID], NULL AS [OrderID]
ELSE
SELECT @CustomerID AS [CustomerID], @OrderID AS [OrderID]
2. Cascade Deletes via Stored Procedure Calls
Instead of basic DELETE statements, we generate cursor-based cascade operations that respect your business logic:
-- BAD: Direct DELETE (bypasses business logic)
DELETE FROM OrderItems WHERE OrderID = @OrderID
-- GOOD: Cursor-based SP calls (respects custom logic)
DECLARE @RelatedItemID INT
DECLARE cascade_delete_OrderItem_cursor CURSOR FOR
SELECT [ItemID] FROM [OrderItems] WHERE [OrderID] = @OrderID
OPEN cascade_delete_OrderItem_cursor
FETCH NEXT FROM cascade_delete_OrderItem_cursor INTO @RelatedItemID
WHILE @@FETCH_STATUS = 0
BEGIN
-- Calls YOUR stored procedure, enabling N-level cascades
EXEC [spDeleteOrderItem] @RelatedItemID
FETCH NEXT FROM cascade_delete_OrderItem_cursor INTO @RelatedItemID
END
CLOSE cascade_delete_OrderItem_cursor
DEALLOCATE cascade_delete_OrderItem_cursor
Benefits:
3. Nullable Foreign Key Handling
For nullable FKs, we update via stored procedures too:
-- Fetch all fields, update only the FK to NULL
DECLARE cascade_update_Customer_cursor CURSOR FOR
SELECT * FROM [Customers] WHERE [RegionID] = @RegionID
-- In cursor loop:
SET @UpdateRegionID = NULL -- Only FK changes
EXEC [spUpdateCustomer] @CustomerID, @Name, @Email, @UpdateRegionID
4. Configuration Error Detection
CodeGen warns you about misconfigured cascade scenarios:
-- WARNING: Orders has non-nullable FK to Customer but doesn't allow delete API
-- This will cause a referential integrity violation
The warnings appear both in generated SQL and console output during generation.
Our update procedures also provide clear feedback when operating on non-existent records:
-- Check if update affected any rows
IF @@ROWCOUNT = 0
-- Return empty result set (maintains column structure)
SELECT TOP 0 * FROM [vwCustomer] WHERE 1=0
ELSE
-- Return the updated record with calculated fields
SELECT * FROM [vwCustomer] WHERE [CustomerID] = @CustomerID
Why This Matters:
Creates type-safe GraphQL APIs from your entities:
type AIPrompt {
id: ID!
promptRole: PromptRoleEnum! # Auto-generated from CHECK constraint
category: AIPromptCategory # Auto-resolved relationships
}
enum PromptRoleEnum {
SYSTEM
USER
ASSISTANT
SYSTEMORUSER
}
Reverse-engineers your entire database into metadata:
const schemaInfo = await analyzeSchema(connection);
// Discovers tables, relationships, constraints, indexes
// Feeds AI engine for intelligent code generation
CodeGen uses AI to automatically organize entity fields into semantic categories with icons, creating intuitive form layouts without manual configuration.
BillToAddress1 → "Billing Address Line 1")The AI uses FK ratio analysis to classify entities:
| Entity Type | FK Ratio | Example | DefaultForNewUser |
|---|---|---|---|
| Primary | 10-30% | Contact, Order, Deal | ✅ Yes |
| Supporting | 20-40% | OrderItem, Address | Sometimes |
| Reference/Type | 0-20% | OrderStatus, ContactType | ❌ No |
| Junction | 40-80% | UserRole, ContactAccount | ❌ No |
The system enforces category stability to prevent unnecessary churn on existing entities:
What's Preserved (Never Changed by AI):
What AI Can Do:
Enforcement Example:
Field 'Email' is in category 'Contact Info'
LLM suggests moving to 'Communication Details' (new category)
→ REJECTED: Cannot move existing field to new category
→ Field stays in 'Contact Info'
Control Flags on EntityField:
AutoUpdateCategory - If FALSE, field's category is lockedAutoUpdateDisplayName - If FALSE, display name is lockedAutoUpdateIsNameField - If FALSE, name field designation is lockedThe DefaultForNewUser flag is only set when an entity is first created, not on subsequent updates. This ensures:
Category information is stored in EntitySetting with two formats for compatibility:
New Format (FieldCategoryInfo):
{
"Billing Address": {
"icon": "fa fa-file-invoice",
"description": "Address for invoice delivery and billing correspondence"
},
"System Metadata": {
"icon": "fa fa-cog",
"description": "System-managed audit and tracking fields"
}
}
Legacy Format (FieldCategoryIcons) - maintained for backwards compatibility:
{
"Billing Address": "fa fa-file-invoice",
"System Metadata": "fa fa-cog"
}
Our AI doesn't just copy constraints - it understands intent:
-- Complex constraint
CHECK ([Status] IN ('Draft', 'Published', 'Archived')
AND [PublishedAt] IS NOT NULL WHEN [Status] = 'Published')
Becomes perfect TypeScript:
Status: z.union([z.literal('Draft'), z.literal('Published'), z.literal('Archived')])
.refine((status, ctx) => {
if (status === 'Published' && !this.PublishedAt) {
ctx.addIssue({ code: 'custom', message: 'Published items must have PublishedAt' });
}
})
Change your database schema → Everything updates automatically:
Create a .memberjunctionrc file:
{
"memberjunction": {
"database": {
"server": "localhost",
"database": "YourDatabase",
"trustedConnection": true
},
"directories": {
"output": "./generated",
"entities": "./generated/entities",
"actions": "./generated/actions",
"angular": "./generated/angular",
"sql": "./generated/sql"
},
"ai": {
"enabled": true,
"provider": "openai" // Powers constraint translation
}
}
}
Starting with a simple table:
CREATE TABLE [Customer] (
[ID] uniqueidentifier PRIMARY KEY DEFAULT newsequentialid(),
[Name] nvarchar(255) NOT NULL,
[Status] nvarchar(20) CHECK ([Status] IN ('Active', 'Inactive', 'Suspended')),
[CreatedAt] datetimeoffset DEFAULT getutcdate()
);
One CodeGen run produces:
export class CustomerEntity extends BaseEntity {
Status: 'Active' | 'Inactive' | 'Suspended';
// + complete validation, save methods, relationships
}
@Component({
template: `Complete form with validation and smart controls`
})
export class CustomerDetailsComponent {
// Ready for production use
}
-- spCreateCustomer, spUpdateCustomer, spDeleteCustomer
-- Complete with validation and error handling
type Customer {
# Complete type-safe schema
}
Total: 500+ lines of production code from 6 lines of SQL.
// Generate everything at once
await runCodeGen();
// Generate specific components
await generateEntitySubClasses(options);
await generateAngularEntityCode(options);
await generateSQLScripts(options);
await generateGraphQLServerCode(options);
import { generateEntitySubClasses } from '@memberjunction/codegen-lib';
const result = await generateEntitySubClasses({
outputDirectory: './generated/entities',
generateLoader: true,
generateCustomEntityClasses: true,
aiEnhanced: true, // Enable AI features
incrementalMode: true, // Only update changed entities
validateGenerated: true // Compile-check generated code
});
import { generateActionSubClasses } from '@memberjunction/codegen-lib';
const result = await generateActionSubClasses({
outputDirectory: './generated/actions',
generateLoader: true
});
import { generateGraphQLServerCode } from '@memberjunction/codegen-lib';
await generateGraphQLServerCode({
outputDirectory: './generated/graphql',
entities: entityMetadata
});
import { generateSQLScripts } from '@memberjunction/codegen-lib';
await generateSQLScripts({
outputDirectory: './generated/sql',
includeStoredProcedures: true,
includeViews: true
});
import { generateAllAngularEntityCode } from '@memberjunction/codegen-lib';
await generateAllAngularEntityCode({
outputDirectory: './generated/angular',
entities: entityMetadata
});
On a typical MemberJunction database with 150+ tables:
For 295 entity classes and thousands of generated files.
Works seamlessly with:
@memberjunction/core - Entity framework@memberjunction/ai - AI-powered features @memberjunction/angular-explorer - UI framework@memberjunction/graphql-dataprovider - API layer@memberjunction/sqlserver-dataprovider - Data accessYou can provide custom templates for code generation:
import { setCustomTemplate } from '@memberjunction/codegen-lib';
setCustomTemplate('entity', myCustomEntityTemplate);
import { analyzeSchema } from '@memberjunction/codegen-lib';
const schemaInfo = await analyzeSchema(databaseConnection);
// Work with schema information
import { onProgress } from '@memberjunction/codegen-lib';
onProgress((status) => {
console.log(`Progress: ${status.message} (${status.percentage}%)`);
});
Need to regenerate specific SQL objects without schema changes? Our force regeneration feature gives you surgical precision over what gets regenerated:
// In mj.config.cjs
forceRegeneration: {
enabled: true,
// Filter to specific entities using SQL WHERE clause
entityWhereClause: "SchemaName = 'CRM' AND __mj_UpdatedAt >= '2025-06-24'",
// Granular control over what gets regenerated
baseViews: true, // Regenerate base views
spCreate: false, // Skip create procedures
spUpdate: true, // Regenerate update procedures
spDelete: false, // Skip delete procedures
indexes: true, // Regenerate foreign key indexes
fullTextSearch: false // Skip full-text search components
}
Regenerate views for recently modified entities:
forceRegeneration: {
enabled: true,
entityWhereClause: "__mj_UpdatedAt >= '2025-06-24 22:00:00'",
baseViews: true
}
Regenerate all stored procedures for a specific schema:
forceRegeneration: {
enabled: true,
entityWhereClause: "SchemaName = 'Sales'",
allStoredProcedures: true
}
Regenerate specific SQL object for a single entity:
forceRegeneration: {
enabled: true,
entityWhereClause: "Name = 'Customer'",
spUpdate: true // Just regenerate the update procedure
}
Regenerate everything (no filtering):
forceRegeneration: {
enabled: true,
// No entityWhereClause = regenerate for ALL entities
allStoredProcedures: true,
baseViews: true,
indexes: true
}
entityWhereClause runs against the Entity metadata table to select which entities qualifyThe library provides comprehensive error handling:
try {
await runCodeGen();
} catch (error) {
if (error.code === 'CONFIG_NOT_FOUND') {
// Handle missing configuration
} else if (error.code === 'DB_CONNECTION_FAILED') {
// Handle database connection errors
}
}
Before MemberJunction CodeGen:
After MemberJunction CodeGen:
When contributing to this package:
This package is part of the MemberJunction ecosystem and follows the same licensing terms.
Ready to experience the future of application development?
npm install @memberjunction/codegen-lib
Your database schema deserves better than manual code generation. Give it the AI-powered, production-ready, full-stack treatment it deserves.