@memberjunction/codegen-lib 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +826 -639
- package/dist/Angular/angular-codegen.d.ts +9 -2
- package/dist/Angular/angular-codegen.d.ts.map +1 -1
- package/dist/Angular/angular-codegen.js +84 -62
- package/dist/Angular/angular-codegen.js.map +1 -1
- package/dist/Angular/entity-data-grid-related-entity-component.d.ts.map +1 -1
- package/dist/Angular/entity-data-grid-related-entity-component.js +2 -1
- package/dist/Angular/entity-data-grid-related-entity-component.js.map +1 -1
- package/dist/Config/config.d.ts.map +1 -1
- package/dist/Config/config.js +10 -0
- package/dist/Config/config.js.map +1 -1
- package/dist/Database/manage-metadata.d.ts +300 -4
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +1024 -147
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts +13 -0
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +74 -17
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/Misc/advanced_generation.d.ts +68 -6
- package/dist/Misc/advanced_generation.d.ts.map +1 -1
- package/dist/Misc/advanced_generation.js +94 -49
- package/dist/Misc/advanced_generation.js.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts +5 -0
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +24 -6
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/dist/Misc/graphql_server_codegen.d.ts.map +1 -1
- package/dist/Misc/graphql_server_codegen.js +3 -1
- package/dist/Misc/graphql_server_codegen.js.map +1 -1
- package/dist/__tests__/metadataConfig.test.d.ts +12 -0
- package/dist/__tests__/metadataConfig.test.d.ts.map +1 -0
- package/dist/__tests__/metadataConfig.test.js +604 -0
- package/dist/__tests__/metadataConfig.test.js.map +1 -0
- package/package.json +21 -17
- package/dist/Angular/user-view-grid-related-entity-component.d.ts +0 -43
- package/dist/Angular/user-view-grid-related-entity-component.d.ts.map +0 -1
- package/dist/Angular/user-view-grid-related-entity-component.js +0 -85
- package/dist/Angular/user-view-grid-related-entity-component.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
# @memberjunction/codegen-lib
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## What Makes This Badass?
|
|
6
|
-
|
|
7
|
-
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**.
|
|
8
|
-
|
|
9
|
-
### 🧠 AI-Powered Intelligence
|
|
10
|
-
- **CHECK Constraint Translation**: Our AI automatically translates complex SQL CHECK constraints into **perfect TypeScript union types** and **Zod validation schemas**
|
|
11
|
-
- **Smart Type Inference**: Analyzes relationships and generates **contextually appropriate Angular form controls** (dropdowns, search boxes, checkboxes)
|
|
12
|
-
- **Intelligent Naming**: AI-driven naming conventions ensure your generated code follows best practices
|
|
13
|
-
|
|
14
|
-
### ⚡ Synchronization Across Everything
|
|
15
|
-
Watch your database changes **instantly propagate** through your entire stack:
|
|
16
|
-
```
|
|
17
|
-
Database Schema Change → TypeScript Entities → Angular Forms → SQL Procedures → GraphQL Schema
|
|
18
|
-
```
|
|
19
|
-
**One command. Complete synchronization. Zero breaking changes.**
|
|
20
|
-
|
|
21
|
-
### 🎯 What Gets Generated (Automatically)
|
|
22
|
-
- **TypeScript Entity Classes** with full type safety and validation
|
|
23
|
-
- **Angular Form Components** with proper field types and validation
|
|
24
|
-
- **SQL Stored Procedures** for all CRUD operations
|
|
25
|
-
- **Database Views** with optimized joins and indexing
|
|
26
|
-
- **GraphQL Schemas** and resolvers
|
|
27
|
-
- **Zod Validation Schemas** from SQL constraints
|
|
28
|
-
- **Complete API Endpoints** with type-safe parameters
|
|
3
|
+
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, and Action subclasses -- all from a single `mj codegen` invocation.
|
|
29
4
|
|
|
30
5
|
## Installation
|
|
31
6
|
|
|
@@ -33,824 +8,1036 @@ Database Schema Change → TypeScript Entities → Angular Forms → SQL Procedu
|
|
|
33
8
|
npm install @memberjunction/codegen-lib
|
|
34
9
|
```
|
|
35
10
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
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.
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
```mermaid
|
|
18
|
+
flowchart TD
|
|
19
|
+
subgraph Input["Input Sources"]
|
|
20
|
+
DB["SQL Server\nDatabase Schema"]
|
|
21
|
+
CFG["mj.config.cjs\nConfiguration"]
|
|
22
|
+
AI["AI Prompts\n(Advanced Generation)"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
subgraph Pipeline["CodeGen Pipeline"]
|
|
26
|
+
META["Metadata\nManagement"]
|
|
27
|
+
SQLGEN["SQL\nGeneration"]
|
|
28
|
+
ENTITY["Entity Class\nGeneration"]
|
|
29
|
+
ANGULAR["Angular\nGeneration"]
|
|
30
|
+
GQL["GraphQL\nGeneration"]
|
|
31
|
+
ACTION["Action\nGeneration"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
subgraph Output["Generated Outputs"]
|
|
35
|
+
VIEWS["Views &\nStored Procedures"]
|
|
36
|
+
TS["TypeScript Entity\nClasses + Zod"]
|
|
37
|
+
NG["Angular Form\nComponents"]
|
|
38
|
+
GQLR["GraphQL\nResolvers"]
|
|
39
|
+
ACTS["Action\nSubclasses"]
|
|
40
|
+
JSON["DB Schema\nJSON"]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
DB --> META
|
|
44
|
+
CFG --> META
|
|
45
|
+
AI --> META
|
|
46
|
+
|
|
47
|
+
META --> SQLGEN
|
|
48
|
+
META --> ENTITY
|
|
49
|
+
META --> ANGULAR
|
|
50
|
+
META --> GQL
|
|
51
|
+
META --> ACTION
|
|
52
|
+
|
|
53
|
+
SQLGEN --> VIEWS
|
|
54
|
+
ENTITY --> TS
|
|
55
|
+
ANGULAR --> NG
|
|
56
|
+
GQL --> GQLR
|
|
57
|
+
ACTION --> ACTS
|
|
58
|
+
META --> JSON
|
|
59
|
+
|
|
60
|
+
style Input fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
61
|
+
style Pipeline fill:#7c5295,stroke:#563a6b,color:#fff
|
|
62
|
+
style Output fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
43
63
|
```
|
|
44
64
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
## Key Features
|
|
66
|
+
|
|
67
|
+
- **Full-Stack Synchronization**: A single schema change propagates to TypeScript entities, Angular forms, SQL procedures, and GraphQL resolvers automatically
|
|
68
|
+
- **AI-Powered Intelligence**: Uses AI prompts to translate CHECK constraints into Zod schemas, generate semantic form layouts, identify name fields, and create entity descriptions
|
|
69
|
+
- **Extensible Architecture**: Every generator (`SQLCodeGenBase`, `EntitySubClassGeneratorBase`, `AngularClientGeneratorBase`, etc.) can be subclassed and overridden via MJ's class factory
|
|
70
|
+
- **Zod Validation Schemas**: Generates Zod schemas from SQL CHECK constraints with proper union types and refinements
|
|
71
|
+
- **Recursive Hierarchy Support**: Automatically detects self-referential foreign keys and generates CTE-based `Root{FieldName}` columns in views
|
|
72
|
+
- **Cascade Delete Generation**: Produces cursor-based cascade delete procedures that call child entity stored procedures, respecting business logic at every level
|
|
73
|
+
- **Force Regeneration**: Surgically regenerate specific SQL objects for specific entities without requiring schema changes
|
|
74
|
+
- **Class Registration Manifests**: Prevents tree-shaking of `@RegisterClass`-decorated classes by generating static import manifests
|
|
75
|
+
- **SQL Migration Logging**: Outputs all generated SQL as Flyway-compatible migration files with schema placeholder support
|
|
76
|
+
- **Configurable via Zod-Validated Config**: All settings validated at startup through comprehensive Zod schemas with sensible defaults
|
|
77
|
+
|
|
78
|
+
## Architecture
|
|
79
|
+
|
|
80
|
+
### Pipeline Stages
|
|
81
|
+
|
|
82
|
+
The code generation process follows a well-defined pipeline orchestrated by the `RunCodeGenBase` class:
|
|
83
|
+
|
|
84
|
+
```mermaid
|
|
85
|
+
flowchart LR
|
|
86
|
+
S0["BEFORE\nCommands"]
|
|
87
|
+
S1["Metadata\nManagement"]
|
|
88
|
+
S2["SQL Object\nGeneration"]
|
|
89
|
+
S3["GraphQL\nResolvers"]
|
|
90
|
+
S4["Entity\nSubclasses"]
|
|
91
|
+
S5["Angular\nComponents"]
|
|
92
|
+
S6["DB Schema\nJSON"]
|
|
93
|
+
S7["Action\nSubclasses"]
|
|
94
|
+
S8["Integrity\nChecks"]
|
|
95
|
+
S9["AFTER\nCommands"]
|
|
96
|
+
|
|
97
|
+
S0 --> S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7 --> S8 --> S9
|
|
98
|
+
|
|
99
|
+
style S0 fill:#64748b,stroke:#475569,color:#fff
|
|
100
|
+
style S1 fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
101
|
+
style S2 fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
102
|
+
style S3 fill:#7c5295,stroke:#563a6b,color:#fff
|
|
103
|
+
style S4 fill:#7c5295,stroke:#563a6b,color:#fff
|
|
104
|
+
style S5 fill:#7c5295,stroke:#563a6b,color:#fff
|
|
105
|
+
style S6 fill:#7c5295,stroke:#563a6b,color:#fff
|
|
106
|
+
style S7 fill:#7c5295,stroke:#563a6b,color:#fff
|
|
107
|
+
style S8 fill:#b8762f,stroke:#8a5722,color:#fff
|
|
108
|
+
style S9 fill:#64748b,stroke:#475569,color:#fff
|
|
53
109
|
```
|
|
54
110
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
111
|
+
| Stage | Class | Description |
|
|
112
|
+
|-------|-------|-------------|
|
|
113
|
+
| BEFORE Commands | `RunCommandsBase` | Execute pre-generation shell commands and SQL scripts |
|
|
114
|
+
| Metadata Management | `ManageMetadataBase` | Analyze schema changes, create/update entity metadata, run AI-powered field analysis |
|
|
115
|
+
| SQL Generation | `SQLCodeGenBase` | Generate base views (with recursive CTEs), stored procedures (create/update/delete), foreign key indexes, permissions |
|
|
116
|
+
| GraphQL Resolvers | `GraphQLServerGeneratorBase` | Generate TypeGraphQL resolver and type definitions for all API-enabled entities |
|
|
117
|
+
| Entity Subclasses | `EntitySubClassGeneratorBase` | Generate TypeScript entity classes with Zod validation schemas, typed getters/setters, and value list types |
|
|
118
|
+
| Angular Components | `AngularClientGeneratorBase` | Generate Angular form components with smart field types, category-based layouts, and related entity tabs |
|
|
119
|
+
| DB Schema JSON | `DBSchemaGeneratorBase` | Export database schema as JSON for documentation and AI consumption |
|
|
120
|
+
| Action Subclasses | `ActionSubClassGeneratorBase` | Generate Action implementation classes from metadata-defined business logic |
|
|
121
|
+
| Integrity Checks | `SystemIntegrityBase` | Validate entity field sequences and other system consistency rules |
|
|
122
|
+
| AFTER Commands | `RunCommandsBase` | Execute post-generation commands (typically package builds) |
|
|
64
123
|
|
|
65
|
-
###
|
|
66
|
-
```sql
|
|
67
|
-
CREATE PROCEDURE [spCreateAIPrompt]
|
|
68
|
-
@PromptRole nvarchar(20),
|
|
69
|
-
-- 20+ other parameters auto-generated
|
|
70
|
-
AS BEGIN
|
|
71
|
-
-- Complete CRUD logic with validation
|
|
72
|
-
END
|
|
73
|
-
```
|
|
124
|
+
### Core vs Non-Core Entity Separation
|
|
74
125
|
|
|
75
|
-
|
|
126
|
+
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.
|
|
76
127
|
|
|
77
|
-
|
|
128
|
+
### Class Factory Extensibility
|
|
78
129
|
|
|
79
|
-
|
|
80
|
-
import { initializeConfig, runCodeGen } from '@memberjunction/codegen-lib';
|
|
130
|
+
Every generator base class can be subclassed and registered with a higher priority to customize behavior:
|
|
81
131
|
|
|
82
|
-
|
|
83
|
-
|
|
132
|
+
```mermaid
|
|
133
|
+
classDiagram
|
|
134
|
+
class RunCodeGenBase {
|
|
135
|
+
+setupDataSource() SQLServerDataProvider
|
|
136
|
+
+Run(skipDatabaseGeneration) void
|
|
137
|
+
}
|
|
84
138
|
|
|
85
|
-
|
|
86
|
-
|
|
139
|
+
class ManageMetadataBase {
|
|
140
|
+
+manageMetadata(pool, user) boolean
|
|
141
|
+
+loadGeneratedCode(pool, user) boolean
|
|
142
|
+
}
|
|
87
143
|
|
|
88
|
-
|
|
89
|
-
|
|
144
|
+
class SQLCodeGenBase {
|
|
145
|
+
+manageSQLScriptsAndExecution(pool, entities, dir, user) boolean
|
|
146
|
+
+runCustomSQLScripts(pool, when) boolean
|
|
147
|
+
}
|
|
90
148
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
149
|
+
class EntitySubClassGeneratorBase {
|
|
150
|
+
+generateAllEntitySubClasses(pool, entities, dir, skipDB) boolean
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
class AngularClientGeneratorBase {
|
|
154
|
+
+generateAngularCode(entities, dir, prefix, user) boolean
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
class GraphQLServerGeneratorBase {
|
|
158
|
+
+generateGraphQLServerCode(entities, dir, lib, exclude) boolean
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
class ActionSubClassGeneratorBase {
|
|
162
|
+
+generateActions(actions, dir) boolean
|
|
163
|
+
}
|
|
97
164
|
|
|
98
|
-
|
|
165
|
+
RunCodeGenBase --> ManageMetadataBase : creates via ClassFactory
|
|
166
|
+
RunCodeGenBase --> SQLCodeGenBase : creates via ClassFactory
|
|
167
|
+
RunCodeGenBase --> EntitySubClassGeneratorBase : creates via ClassFactory
|
|
168
|
+
RunCodeGenBase --> AngularClientGeneratorBase : creates via ClassFactory
|
|
169
|
+
RunCodeGenBase --> GraphQLServerGeneratorBase : creates via ClassFactory
|
|
170
|
+
RunCodeGenBase --> ActionSubClassGeneratorBase : creates via ClassFactory
|
|
171
|
+
|
|
172
|
+
style RunCodeGenBase fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
173
|
+
style ManageMetadataBase fill:#7c5295,stroke:#563a6b,color:#fff
|
|
174
|
+
style SQLCodeGenBase fill:#7c5295,stroke:#563a6b,color:#fff
|
|
175
|
+
style EntitySubClassGeneratorBase fill:#7c5295,stroke:#563a6b,color:#fff
|
|
176
|
+
style AngularClientGeneratorBase fill:#7c5295,stroke:#563a6b,color:#fff
|
|
177
|
+
style GraphQLServerGeneratorBase fill:#7c5295,stroke:#563a6b,color:#fff
|
|
178
|
+
style ActionSubClassGeneratorBase fill:#7c5295,stroke:#563a6b,color:#fff
|
|
179
|
+
```
|
|
99
180
|
|
|
100
|
-
|
|
101
|
-
Generates **bullet-proof TypeScript classes** from your database schema:
|
|
181
|
+
To override any generator, subclass the base and register it:
|
|
102
182
|
|
|
103
183
|
```typescript
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
validate(): ValidationResult {
|
|
111
|
-
return this.validateWithZod(AIPromptSchema);
|
|
112
|
-
}
|
|
184
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
185
|
+
import { EntitySubClassGeneratorBase } from '@memberjunction/codegen-lib';
|
|
186
|
+
|
|
187
|
+
@RegisterClass(EntitySubClassGeneratorBase, undefined, 1) // priority 1 overrides default (0)
|
|
188
|
+
export class CustomEntityGenerator extends EntitySubClassGeneratorBase {
|
|
189
|
+
// Override methods to customize generation
|
|
113
190
|
}
|
|
114
191
|
```
|
|
115
192
|
|
|
116
|
-
|
|
117
|
-
|
|
193
|
+
## Usage
|
|
194
|
+
|
|
195
|
+
### Running the Full Pipeline
|
|
118
196
|
|
|
119
197
|
```typescript
|
|
120
|
-
|
|
121
|
-
<mj-form-field
|
|
122
|
-
FieldName="CategoryID"
|
|
123
|
-
Type="textbox" // Smart field type selection
|
|
124
|
-
LinkType="Record" // Auto-detected relationship
|
|
125
|
-
LinkComponentType="Search" // AI chose search over dropdown
|
|
126
|
-
></mj-form-field>
|
|
127
|
-
```
|
|
198
|
+
import { RunCodeGenBase, initializeConfig } from '@memberjunction/codegen-lib';
|
|
128
199
|
|
|
129
|
-
|
|
130
|
-
|
|
200
|
+
// Initialize configuration from working directory
|
|
201
|
+
const config = initializeConfig(process.cwd());
|
|
131
202
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
ON [AIPrompt] ([CategoryID]);
|
|
203
|
+
// Run the complete code generation pipeline
|
|
204
|
+
const codeGen = new RunCodeGenBase();
|
|
205
|
+
await codeGen.Run();
|
|
136
206
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
@PromptRole nvarchar(20) -- Validated against CHECK constraint
|
|
140
|
-
-- Full implementation auto-generated
|
|
207
|
+
// Or skip database operations for faster UI-only regeneration
|
|
208
|
+
await codeGen.Run(true);
|
|
141
209
|
```
|
|
142
210
|
|
|
143
|
-
|
|
211
|
+
The convenience function provides a simpler entry point:
|
|
144
212
|
|
|
145
|
-
|
|
213
|
+
```typescript
|
|
214
|
+
import { runMemberJunctionCodeGeneration } from '@memberjunction/codegen-lib';
|
|
146
215
|
|
|
147
|
-
|
|
216
|
+
await runMemberJunctionCodeGeneration();
|
|
217
|
+
```
|
|
148
218
|
|
|
149
|
-
|
|
219
|
+
### Using Individual Generators
|
|
150
220
|
|
|
151
|
-
|
|
152
|
-
2. **Generates a recursive CTE** - Creates SQL that traverses the hierarchy to find the root
|
|
153
|
-
3. **Adds Root columns** - Exposes `Root{FieldName}` in the base view (e.g., `RootParentTaskID`)
|
|
154
|
-
4. **Zero-overhead when unused** - SQL optimizer eliminates the CTE when column not selected
|
|
221
|
+
Each generator can be used independently:
|
|
155
222
|
|
|
156
|
-
|
|
223
|
+
```typescript
|
|
224
|
+
import { EntitySubClassGeneratorBase } from '@memberjunction/codegen-lib';
|
|
225
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
157
226
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
[ID] uniqueidentifier PRIMARY KEY,
|
|
161
|
-
[ParentTaskID] uniqueidentifier FOREIGN KEY REFERENCES [Task]([ID]),
|
|
162
|
-
[Name] nvarchar(255)
|
|
227
|
+
const generator = MJGlobal.Instance.ClassFactory.CreateInstance<EntitySubClassGeneratorBase>(
|
|
228
|
+
EntitySubClassGeneratorBase
|
|
163
229
|
);
|
|
230
|
+
|
|
231
|
+
await generator.generateAllEntitySubClasses(pool, entities, outputDir, false);
|
|
164
232
|
```
|
|
165
233
|
|
|
166
|
-
|
|
234
|
+
### Generating Class Registration Manifests
|
|
167
235
|
|
|
168
|
-
|
|
169
|
-
CREATE VIEW [vwTasks]
|
|
170
|
-
AS
|
|
171
|
-
WITH
|
|
172
|
-
CTE_RootParentTaskID AS (
|
|
173
|
-
-- Anchor: rows with no parent (root nodes)
|
|
174
|
-
SELECT
|
|
175
|
-
[ID],
|
|
176
|
-
[ID] AS [RootParentTaskID]
|
|
177
|
-
FROM
|
|
178
|
-
[__mj].[Task]
|
|
179
|
-
WHERE
|
|
180
|
-
[ParentTaskID] IS NULL
|
|
181
|
-
|
|
182
|
-
UNION ALL
|
|
183
|
-
|
|
184
|
-
-- Recursive: traverse up the hierarchy
|
|
185
|
-
SELECT
|
|
186
|
-
child.[ID],
|
|
187
|
-
parent.[RootParentTaskID]
|
|
188
|
-
FROM
|
|
189
|
-
[__mj].[Task] child
|
|
190
|
-
INNER JOIN
|
|
191
|
-
CTE_RootParentTaskID parent ON child.[ParentTaskID] = parent.[ID]
|
|
192
|
-
)
|
|
193
|
-
SELECT
|
|
194
|
-
t.*,
|
|
195
|
-
CTE_RootParentTaskID.[RootParentTaskID] -- Auto-generated root column
|
|
196
|
-
FROM
|
|
197
|
-
[__mj].[Task] AS t
|
|
198
|
-
LEFT OUTER JOIN
|
|
199
|
-
CTE_RootParentTaskID
|
|
200
|
-
ON
|
|
201
|
-
t.[ID] = CTE_RootParentTaskID.[ID]
|
|
202
|
-
```
|
|
236
|
+
The manifest generator prevents tree-shaking of `@RegisterClass`-decorated classes:
|
|
203
237
|
|
|
204
|
-
|
|
238
|
+
```typescript
|
|
239
|
+
import { generateClassRegistrationsManifest } from '@memberjunction/codegen-lib';
|
|
205
240
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
- ✅ **Naming Convention** - Consistent `Root{FieldName}` pattern across all entities
|
|
241
|
+
const result = await generateClassRegistrationsManifest({
|
|
242
|
+
outputPath: './src/generated/class-registrations-manifest.ts',
|
|
243
|
+
appDir: './packages/MJAPI',
|
|
244
|
+
excludePackages: ['@memberjunction'], // Use pre-built manifest for MJ packages
|
|
245
|
+
});
|
|
212
246
|
|
|
213
|
-
|
|
247
|
+
if (result.success) {
|
|
248
|
+
console.log(`${result.packages.length} packages, ${result.classes.length} classes`);
|
|
249
|
+
}
|
|
250
|
+
```
|
|
214
251
|
|
|
215
|
-
|
|
216
|
-
- **Task Hierarchies** - `Task.ParentTaskID` → `RootParentTaskID` finds root project
|
|
217
|
-
- **Category Trees** - `Category.ParentCategoryID` → `RootParentCategoryID` finds top level
|
|
218
|
-
- **Comment Threads** - `Comment.ParentCommentID` → `RootParentCommentID` finds original post
|
|
219
|
-
- **Bill of Materials** - `Part.ParentPartID` → `RootParentPartID` finds top-level assembly
|
|
252
|
+
See the [Class Manifest Guide](CLASS_MANIFEST_GUIDE.md) for comprehensive documentation on the manifest system.
|
|
220
253
|
|
|
221
|
-
|
|
254
|
+
## Configuration
|
|
222
255
|
|
|
223
|
-
|
|
256
|
+
CodeGenLib uses [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig) to locate configuration. The recommended approach is a `mj.config.cjs` file at the repository root:
|
|
224
257
|
|
|
225
|
-
|
|
258
|
+
```javascript
|
|
259
|
+
module.exports = {
|
|
260
|
+
// Database connection
|
|
261
|
+
dbHost: 'localhost',
|
|
262
|
+
dbPort: 1433,
|
|
263
|
+
dbDatabase: 'YourDatabase',
|
|
264
|
+
codeGenLogin: 'codegen_user',
|
|
265
|
+
codeGenPassword: 'your_password',
|
|
266
|
+
mjCoreSchema: '__mj',
|
|
267
|
+
|
|
268
|
+
// Output directories for each generator
|
|
269
|
+
output: [
|
|
270
|
+
{ type: 'SQL', directory: '../../SQL Scripts/generated' },
|
|
271
|
+
{ type: 'Angular', directory: '../MJExplorer/src/app/generated' },
|
|
272
|
+
{ type: 'GraphQLServer', directory: '../MJAPI/src/generated' },
|
|
273
|
+
{ type: 'CoreEntitySubclasses', directory: '../MJCoreEntities/src/generated' },
|
|
274
|
+
{ type: 'EntitySubclasses', directory: '../GeneratedEntities/src/generated' },
|
|
275
|
+
],
|
|
276
|
+
|
|
277
|
+
// AI-powered features
|
|
278
|
+
advancedGeneration: {
|
|
279
|
+
enableAdvancedGeneration: true,
|
|
280
|
+
features: [
|
|
281
|
+
{ name: 'SmartFieldIdentification', enabled: true },
|
|
282
|
+
{ name: 'FormLayoutGeneration', enabled: true },
|
|
283
|
+
{ name: 'ParseCheckConstraints', enabled: true },
|
|
284
|
+
{ name: 'TransitiveJoinIntelligence', enabled: true },
|
|
285
|
+
],
|
|
286
|
+
},
|
|
226
287
|
|
|
227
|
-
|
|
288
|
+
// SQL output for Flyway migrations
|
|
289
|
+
SQLOutput: {
|
|
290
|
+
enabled: true,
|
|
291
|
+
folderPath: './migrations/v3/',
|
|
292
|
+
convertCoreSchemaToFlywayMigrationFile: true,
|
|
293
|
+
},
|
|
228
294
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
295
|
+
// Force regeneration of specific objects
|
|
296
|
+
forceRegeneration: {
|
|
297
|
+
enabled: false,
|
|
298
|
+
entityWhereClause: "SchemaName = 'dbo'",
|
|
299
|
+
baseViews: true,
|
|
300
|
+
spUpdate: true,
|
|
301
|
+
},
|
|
302
|
+
};
|
|
236
303
|
```
|
|
237
304
|
|
|
238
|
-
|
|
305
|
+
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.
|
|
239
306
|
|
|
240
|
-
|
|
307
|
+
### Key Configuration Sections
|
|
241
308
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
309
|
+
| Section | Purpose |
|
|
310
|
+
|---------|---------|
|
|
311
|
+
| `output` | Maps each generator type to its output directory |
|
|
312
|
+
| `advancedGeneration` | Controls which AI-powered features are enabled |
|
|
313
|
+
| `newEntityDefaults` | Default settings for newly discovered entities (permissions, tracking, API access) |
|
|
314
|
+
| `forceRegeneration` | Surgically regenerate specific SQL object types for filtered entities |
|
|
315
|
+
| `SQLOutput` | Controls Flyway migration file generation from SQL logging |
|
|
316
|
+
| `commands` | Shell commands to run before/after generation (typically package builds) |
|
|
317
|
+
| `excludeSchemas` / `excludeTables` | Filter schemas and tables from metadata discovery |
|
|
245
318
|
|
|
246
|
-
|
|
247
|
-
DECLARE @RelatedItemID INT
|
|
248
|
-
DECLARE cascade_delete_OrderItem_cursor CURSOR FOR
|
|
249
|
-
SELECT [ItemID] FROM [OrderItems] WHERE [OrderID] = @OrderID
|
|
319
|
+
## What Gets Generated
|
|
250
320
|
|
|
251
|
-
|
|
252
|
-
FETCH NEXT FROM cascade_delete_OrderItem_cursor INTO @RelatedItemID
|
|
321
|
+
### TypeScript Entity Classes
|
|
253
322
|
|
|
254
|
-
|
|
255
|
-
BEGIN
|
|
256
|
-
-- Calls YOUR stored procedure, enabling N-level cascades
|
|
257
|
-
EXEC [spDeleteOrderItem] @RelatedItemID
|
|
258
|
-
FETCH NEXT FROM cascade_delete_OrderItem_cursor INTO @RelatedItemID
|
|
259
|
-
END
|
|
323
|
+
From a SQL table with CHECK constraints, CodeGen produces a complete entity class:
|
|
260
324
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
325
|
+
```typescript
|
|
326
|
+
// Auto-generated from database schema
|
|
327
|
+
export class AIPromptEntity extends BaseEntity {
|
|
328
|
+
// Typed getter/setter for CHECK-constrained field
|
|
329
|
+
get PromptRole(): 'System' | 'User' | 'Assistant' | 'SystemOrUser' {
|
|
330
|
+
return this.Get('PromptRole');
|
|
331
|
+
}
|
|
332
|
+
set PromptRole(value: 'System' | 'User' | 'Assistant' | 'SystemOrUser') {
|
|
333
|
+
this.Set('PromptRole', value);
|
|
334
|
+
}
|
|
264
335
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
336
|
+
// Zod validation from CHECK constraint
|
|
337
|
+
validate(): ValidationResult {
|
|
338
|
+
return this.validateWithZod(AIPromptSchema);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Zod schema with union types from CHECK constraint
|
|
343
|
+
export const AIPromptSchema = z.object({
|
|
344
|
+
PromptRole: z.union([
|
|
345
|
+
z.literal('System'),
|
|
346
|
+
z.literal('User'),
|
|
347
|
+
z.literal('Assistant'),
|
|
348
|
+
z.literal('SystemOrUser'),
|
|
349
|
+
]),
|
|
350
|
+
// ... all other fields
|
|
351
|
+
});
|
|
352
|
+
```
|
|
270
353
|
|
|
271
|
-
|
|
354
|
+
### SQL Views with Recursive Hierarchy Support
|
|
272
355
|
|
|
273
|
-
For
|
|
356
|
+
For tables with self-referential foreign keys, CodeGen automatically generates recursive CTEs:
|
|
274
357
|
|
|
275
358
|
```sql
|
|
276
|
-
--
|
|
277
|
-
|
|
278
|
-
|
|
359
|
+
-- Auto-detected: Task.ParentTaskID references Task.ID
|
|
360
|
+
CREATE VIEW [vwTasks] AS
|
|
361
|
+
WITH CTE_RootParentTaskID AS (
|
|
362
|
+
SELECT [ID], [ID] AS [RootParentTaskID]
|
|
363
|
+
FROM [__mj].[Task]
|
|
364
|
+
WHERE [ParentTaskID] IS NULL
|
|
365
|
+
|
|
366
|
+
UNION ALL
|
|
367
|
+
|
|
368
|
+
SELECT child.[ID], parent.[RootParentTaskID]
|
|
369
|
+
FROM [__mj].[Task] child
|
|
370
|
+
INNER JOIN CTE_RootParentTaskID parent
|
|
371
|
+
ON child.[ParentTaskID] = parent.[ID]
|
|
372
|
+
)
|
|
373
|
+
SELECT t.*, cte.[RootParentTaskID]
|
|
374
|
+
FROM [__mj].[Task] AS t
|
|
375
|
+
LEFT OUTER JOIN CTE_RootParentTaskID cte ON t.[ID] = cte.[ID]
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The CTE is zero-overhead: the SQL optimizer eliminates it entirely when the root column is not selected.
|
|
379
|
+
|
|
380
|
+
### Angular Form Components
|
|
279
381
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
382
|
+
CodeGen creates production-ready Angular forms with AI-determined field categories and smart field types:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
@Component({
|
|
386
|
+
selector: 'mj-ai-prompt-form',
|
|
387
|
+
template: `
|
|
388
|
+
<mj-form-field [record]="record"
|
|
389
|
+
FieldName="PromptRole"
|
|
390
|
+
Type="dropdownlist"
|
|
391
|
+
[EditMode]="EditMode">
|
|
392
|
+
</mj-form-field>
|
|
393
|
+
`
|
|
394
|
+
})
|
|
395
|
+
export class AIPromptFormComponent extends BaseFormComponent { }
|
|
283
396
|
```
|
|
284
397
|
|
|
285
|
-
|
|
398
|
+
### Cascade Delete Procedures
|
|
286
399
|
|
|
287
|
-
|
|
400
|
+
Delete procedures use cursor-based stored procedure calls to respect business logic at every level of the hierarchy:
|
|
288
401
|
|
|
289
402
|
```sql
|
|
290
|
-
|
|
291
|
-
|
|
403
|
+
CREATE PROCEDURE [spDeleteOrder] @ID UNIQUEIDENTIFIER AS
|
|
404
|
+
BEGIN
|
|
405
|
+
-- Cascade through child stored procedures
|
|
406
|
+
DECLARE @ItemID UNIQUEIDENTIFIER
|
|
407
|
+
DECLARE cascade_cursor CURSOR FOR
|
|
408
|
+
SELECT [ID] FROM [OrderItems] WHERE [OrderID] = @ID
|
|
409
|
+
|
|
410
|
+
OPEN cascade_cursor
|
|
411
|
+
FETCH NEXT FROM cascade_cursor INTO @ItemID
|
|
412
|
+
WHILE @@FETCH_STATUS = 0
|
|
413
|
+
BEGIN
|
|
414
|
+
EXEC [spDeleteOrderItem] @ItemID -- Respects OrderItem's own cascade logic
|
|
415
|
+
FETCH NEXT FROM cascade_cursor INTO @ItemID
|
|
416
|
+
END
|
|
417
|
+
CLOSE cascade_cursor
|
|
418
|
+
DEALLOCATE cascade_cursor
|
|
419
|
+
|
|
420
|
+
DELETE FROM [Orders] WHERE [ID] = @ID
|
|
421
|
+
END
|
|
292
422
|
```
|
|
293
423
|
|
|
294
|
-
|
|
424
|
+
## AI-Powered Advanced Generation
|
|
425
|
+
|
|
426
|
+
When `advancedGeneration.enableAdvancedGeneration` is enabled, CodeGen uses AI prompts (stored in the database as AI Prompt entities) to enhance the generation process:
|
|
427
|
+
|
|
428
|
+
```mermaid
|
|
429
|
+
flowchart TD
|
|
430
|
+
subgraph Features["AI-Powered Features"]
|
|
431
|
+
SF["Smart Field\nIdentification"]
|
|
432
|
+
FL["Form Layout\nGeneration"]
|
|
433
|
+
CC["CHECK Constraint\nParsing"]
|
|
434
|
+
ED["Entity\nDescriptions"]
|
|
435
|
+
TJ["Transitive Join\nIntelligence"]
|
|
436
|
+
EN["Entity Name\nGeneration"]
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
subgraph Results["What AI Determines"]
|
|
440
|
+
SF --> R1["Name fields, Default-in-View\nfields, Searchable fields"]
|
|
441
|
+
FL --> R2["Field categories, Icons\nDisplay names, Extended types"]
|
|
442
|
+
CC --> R3["Zod schemas, Validation\nfunctions, Descriptions"]
|
|
443
|
+
ED --> R4["Entity descriptions\nfor new entities"]
|
|
444
|
+
TJ --> R5["Junction table detection\nMany-to-many relationships"]
|
|
445
|
+
EN --> R6["Human-friendly entity\nnames from table names"]
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
style Features fill:#7c5295,stroke:#563a6b,color:#fff
|
|
449
|
+
style Results fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
450
|
+
```
|
|
295
451
|
|
|
296
|
-
|
|
452
|
+
| Feature | Purpose | When It Runs |
|
|
453
|
+
|---------|---------|-------------|
|
|
454
|
+
| `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 |
|
|
455
|
+
| `FormLayoutGeneration` | Groups fields into semantic categories with icons and display names | Every run (forms are always regenerated) |
|
|
456
|
+
| `ParseCheckConstraints` | Translates SQL CHECK constraints into Zod validation schemas and TypeScript union types | When CHECK constraints are detected |
|
|
457
|
+
| `EntityDescriptions` | Generates human-readable descriptions for entities | Entity creation only |
|
|
458
|
+
| `TransitiveJoinIntelligence` | Detects junction tables and many-to-many relationships | Entity/relationship creation |
|
|
459
|
+
| `EntityNames` | Converts technical table names to user-friendly entity names | Entity creation only |
|
|
460
|
+
| `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) |
|
|
297
461
|
|
|
298
|
-
|
|
462
|
+
### Form Layout Stability Guarantees
|
|
299
463
|
|
|
300
|
-
|
|
301
|
-
-- Check if update affected any rows
|
|
302
|
-
IF @@ROWCOUNT = 0
|
|
303
|
-
-- Return empty result set (maintains column structure)
|
|
304
|
-
SELECT TOP 0 * FROM [vwCustomer] WHERE 1=0
|
|
305
|
-
ELSE
|
|
306
|
-
-- Return the updated record with calculated fields
|
|
307
|
-
SELECT * FROM [vwCustomer] WHERE [CustomerID] = @CustomerID
|
|
308
|
-
```
|
|
464
|
+
The form layout system enforces stability to prevent unnecessary churn:
|
|
309
465
|
|
|
310
|
-
|
|
311
|
-
-
|
|
312
|
-
-
|
|
313
|
-
-
|
|
314
|
-
- **Includes calculated fields** = Get the latest computed values
|
|
466
|
+
- Existing category names and icons are never changed by AI
|
|
467
|
+
- AI can assign fields to existing categories or create categories for genuinely new field groups
|
|
468
|
+
- Existing fields cannot be moved to newly created categories (prevents renaming)
|
|
469
|
+
- Per-field `AutoUpdateCategory` and `AutoUpdateDisplayName` flags provide granular control
|
|
315
470
|
|
|
316
|
-
|
|
317
|
-
Creates **type-safe GraphQL APIs** from your entities:
|
|
471
|
+
## Force Regeneration
|
|
318
472
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
473
|
+
Regenerate specific SQL objects without schema changes using surgical filtering:
|
|
474
|
+
|
|
475
|
+
```javascript
|
|
476
|
+
// In mj.config.cjs
|
|
477
|
+
forceRegeneration: {
|
|
478
|
+
enabled: true,
|
|
479
|
+
// Filter to specific entities
|
|
480
|
+
entityWhereClause: "SchemaName = 'CRM' AND __mj_UpdatedAt >= '2025-06-24'",
|
|
481
|
+
// Control which object types regenerate
|
|
482
|
+
baseViews: true,
|
|
483
|
+
spCreate: false,
|
|
484
|
+
spUpdate: true,
|
|
485
|
+
spDelete: false,
|
|
486
|
+
indexes: true,
|
|
324
487
|
}
|
|
488
|
+
```
|
|
325
489
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
490
|
+
Only the intersection of matched entities and enabled object types gets regenerated.
|
|
491
|
+
|
|
492
|
+
## SQL Migration Logging
|
|
493
|
+
|
|
494
|
+
All SQL generated during metadata management and object generation is logged to a Flyway-compatible migration file. The `SQLOutput` configuration controls this behavior:
|
|
495
|
+
|
|
496
|
+
```javascript
|
|
497
|
+
SQLOutput: {
|
|
498
|
+
enabled: true,
|
|
499
|
+
folderPath: './migrations/v3/',
|
|
500
|
+
appendToFile: true,
|
|
501
|
+
convertCoreSchemaToFlywayMigrationFile: true,
|
|
502
|
+
schemaPlaceholders: [
|
|
503
|
+
{ schema: '__mj', placeholder: '${flyway:defaultSchema}' },
|
|
504
|
+
],
|
|
331
505
|
}
|
|
332
506
|
```
|
|
333
507
|
|
|
334
|
-
|
|
335
|
-
|
|
508
|
+
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.
|
|
509
|
+
|
|
510
|
+
## Source Structure
|
|
336
511
|
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
512
|
+
```
|
|
513
|
+
src/
|
|
514
|
+
index.ts # Public API exports
|
|
515
|
+
runCodeGen.ts # RunCodeGenBase - main pipeline orchestrator
|
|
516
|
+
|
|
517
|
+
Config/
|
|
518
|
+
config.ts # Zod-validated configuration schemas and loaders
|
|
519
|
+
db-connection.ts # SQL Server connection pool management
|
|
520
|
+
|
|
521
|
+
Database/
|
|
522
|
+
manage-metadata.ts # ManageMetadataBase - schema analysis and metadata sync
|
|
523
|
+
sql_codegen.ts # SQLCodeGenBase - views, procedures, indexes, permissions
|
|
524
|
+
sql.ts # SQLUtilityBase - SQL file management and execution
|
|
525
|
+
dbSchema.ts # DBSchemaGeneratorBase - JSON schema export
|
|
526
|
+
reorder-columns.ts # Table column reordering utilities
|
|
527
|
+
|
|
528
|
+
Angular/
|
|
529
|
+
angular-codegen.ts # AngularClientGeneratorBase - form and module generation
|
|
530
|
+
related-entity-components.ts # Base classes for related entity display components
|
|
531
|
+
entity-data-grid-related-entity-component.ts # Data grid component generator
|
|
532
|
+
join-grid-related-entity-component.ts # Join grid component generator
|
|
533
|
+
timeline-related-entity-component.ts # Timeline component generator
|
|
534
|
+
|
|
535
|
+
Misc/
|
|
536
|
+
entity_subclasses_codegen.ts # EntitySubClassGeneratorBase - TypeScript entity generation
|
|
537
|
+
action_subclasses_codegen.ts # ActionSubClassGeneratorBase - Action class generation
|
|
538
|
+
graphql_server_codegen.ts # GraphQLServerGeneratorBase - resolver generation
|
|
539
|
+
advanced_generation.ts # AdvancedGeneration - AI-powered enhancement features
|
|
540
|
+
status_logging.ts # Spinner and log utilities (ora-based)
|
|
541
|
+
sql_logging.ts # SQLLogging - migration file accumulator
|
|
542
|
+
system_integrity.ts # SystemIntegrityBase - post-generation validation
|
|
543
|
+
createNewUser.ts # CreateNewUserBase - initial user setup
|
|
544
|
+
runCommand.ts # RunCommandsBase - shell command execution
|
|
545
|
+
util.ts # File system and sorting utilities
|
|
546
|
+
|
|
547
|
+
Manifest/
|
|
548
|
+
GenerateClassRegistrationsManifest.ts # Tree-shaking prevention manifest generator
|
|
341
549
|
```
|
|
342
550
|
|
|
343
|
-
##
|
|
551
|
+
## API Reference
|
|
344
552
|
|
|
345
|
-
###
|
|
553
|
+
### RunCodeGenBase
|
|
346
554
|
|
|
347
|
-
|
|
555
|
+
The main orchestrator class. Creates instances of all generator classes via `MJGlobal.ClassFactory` and runs the pipeline.
|
|
348
556
|
|
|
349
|
-
|
|
557
|
+
| Method | Description |
|
|
558
|
+
|--------|-------------|
|
|
559
|
+
| `Run(skipDatabaseGeneration?)` | Execute the full code generation pipeline. Pass `true` to skip database operations. |
|
|
560
|
+
| `setupDataSource()` | Initialize the SQL Server connection pool and data provider. |
|
|
350
561
|
|
|
351
|
-
|
|
352
|
-
2. **Category Icons** - Assigns Font Awesome icons to each category for visual navigation
|
|
353
|
-
3. **Category Descriptions** - Generates tooltip descriptions for UX enhancement
|
|
354
|
-
4. **Entity Importance Analysis** - Determines if entities should appear in navigation for new users
|
|
355
|
-
5. **Smart Display Names** - Converts technical field names to user-friendly labels (e.g., `BillToAddress1` → "Billing Address Line 1")
|
|
562
|
+
### ManageMetadataBase
|
|
356
563
|
|
|
357
|
-
|
|
564
|
+
Analyzes database schema changes and updates MJ metadata tables.
|
|
358
565
|
|
|
359
|
-
|
|
566
|
+
| Method | Description |
|
|
567
|
+
|--------|-------------|
|
|
568
|
+
| `manageMetadata(pool, user)` | Full metadata management: detect schema changes, create entities/fields, run AI features. |
|
|
569
|
+
| `loadGeneratedCode(pool, user)` | Load previously generated AI code from database (used when skipping DB generation). |
|
|
360
570
|
|
|
361
|
-
|
|
362
|
-
|-------------|----------|---------|-------------------|
|
|
363
|
-
| **Primary** | 10-30% | Contact, Order, Deal | ✅ Yes |
|
|
364
|
-
| **Supporting** | 20-40% | OrderItem, Address | Sometimes |
|
|
365
|
-
| **Reference/Type** | 0-20% | OrderStatus, ContactType | ❌ No |
|
|
366
|
-
| **Junction** | 40-80% | UserRole, ContactAccount | ❌ No |
|
|
571
|
+
### SQLCodeGenBase
|
|
367
572
|
|
|
368
|
-
|
|
573
|
+
Generates database objects: views, stored procedures, indexes, and permissions.
|
|
369
574
|
|
|
370
|
-
|
|
575
|
+
| Method | Description |
|
|
576
|
+
|--------|-------------|
|
|
577
|
+
| `manageSQLScriptsAndExecution(pool, entities, dir, user)` | Generate and execute all SQL objects for the given entities. |
|
|
578
|
+
| `runCustomSQLScripts(pool, when)` | Execute custom SQL scripts configured for the specified timing. |
|
|
371
579
|
|
|
372
|
-
|
|
373
|
-
- ✅ Existing category names - AI cannot rename "Personal Info" to "Personal Details"
|
|
374
|
-
- ✅ Existing category icons - Icons set by admins or previous runs are preserved
|
|
375
|
-
- ✅ Existing category descriptions - Descriptions are only added, never modified
|
|
580
|
+
### EntitySubClassGeneratorBase
|
|
376
581
|
|
|
377
|
-
|
|
378
|
-
- ✅ Assign NEW fields to existing categories
|
|
379
|
-
- ✅ Assign NEW fields to NEW categories (when no existing category fits)
|
|
380
|
-
- ✅ Move existing fields between EXISTING categories (with discretion)
|
|
381
|
-
- ❌ Move existing fields to NEW categories (blocked - prevents renaming)
|
|
582
|
+
Generates TypeScript entity classes with Zod validation.
|
|
382
583
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
→ Field stays in 'Contact Info'
|
|
389
|
-
```
|
|
584
|
+
| Method | Description |
|
|
585
|
+
|--------|-------------|
|
|
586
|
+
| `generateAllEntitySubClasses(pool, entities, dir, skipDB)` | Generate all entity subclass files including Zod schemas. |
|
|
587
|
+
| `generateEntitySubClass(pool, entity, includeHeader, skipDB)` | Generate a single entity subclass. |
|
|
588
|
+
| `GenerateSchemaAndType(entity)` | Generate Zod schema and TypeScript type for an entity. |
|
|
390
589
|
|
|
391
|
-
|
|
392
|
-
- `AutoUpdateCategory` - If FALSE, field's category is locked
|
|
393
|
-
- `AutoUpdateDisplayName` - If FALSE, display name is locked
|
|
394
|
-
- `AutoUpdateIsNameField` - If FALSE, name field designation is locked
|
|
590
|
+
### AngularClientGeneratorBase
|
|
395
591
|
|
|
396
|
-
|
|
592
|
+
Generates Angular form components, section components, and Angular modules.
|
|
397
593
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
- Existing entity configurations remain stable
|
|
594
|
+
| Method | Description |
|
|
595
|
+
|--------|-------------|
|
|
596
|
+
| `generateAngularCode(entities, dir, prefix, user)` | Generate all Angular components and modules. |
|
|
402
597
|
|
|
403
|
-
|
|
598
|
+
### GraphQLServerGeneratorBase
|
|
404
599
|
|
|
405
|
-
|
|
600
|
+
Generates TypeGraphQL resolver and type definitions.
|
|
406
601
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
"Billing Address": {
|
|
411
|
-
"icon": "fa fa-file-invoice",
|
|
412
|
-
"description": "Address for invoice delivery and billing correspondence"
|
|
413
|
-
},
|
|
414
|
-
"System Metadata": {
|
|
415
|
-
"icon": "fa fa-cog",
|
|
416
|
-
"description": "System-managed audit and tracking fields"
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
```
|
|
602
|
+
| Method | Description |
|
|
603
|
+
|--------|-------------|
|
|
604
|
+
| `generateGraphQLServerCode(entities, dir, lib, exclude)` | Generate GraphQL resolvers for all entities. |
|
|
420
605
|
|
|
421
|
-
|
|
422
|
-
```json
|
|
423
|
-
{
|
|
424
|
-
"Billing Address": "fa fa-file-invoice",
|
|
425
|
-
"System Metadata": "fa fa-cog"
|
|
426
|
-
}
|
|
427
|
-
```
|
|
606
|
+
### generateClassRegistrationsManifest
|
|
428
607
|
|
|
429
|
-
|
|
430
|
-
Our AI doesn't just copy constraints - it **understands intent**:
|
|
608
|
+
Generates an import manifest that prevents tree-shaking of `@RegisterClass` decorated classes. See the [Class Manifest Guide](CLASS_MANIFEST_GUIDE.md) for full documentation.
|
|
431
609
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
610
|
+
| Option | Description |
|
|
611
|
+
|--------|-------------|
|
|
612
|
+
| `outputPath` | Path for the generated manifest file |
|
|
613
|
+
| `appDir` | Directory containing the app's `package.json` (default: `process.cwd()`) |
|
|
614
|
+
| `filterBaseClasses` | Only include classes extending specific base classes |
|
|
615
|
+
| `excludePackages` | Skip packages matching name prefixes (e.g., `['@memberjunction']`) |
|
|
437
616
|
|
|
438
|
-
|
|
617
|
+
### Configuration Functions
|
|
439
618
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
})
|
|
447
|
-
```
|
|
619
|
+
| Function | Description |
|
|
620
|
+
|----------|-------------|
|
|
621
|
+
| `initializeConfig(cwd)` | Load and validate configuration from the given directory |
|
|
622
|
+
| `outputDir(type, fallback)` | Get the configured output directory for a generator type |
|
|
623
|
+
| `getSettingValue(name, default)` | Get a named setting value from configuration |
|
|
624
|
+
| `mj_core_schema()` | Get the MJ core schema name (typically `__mj`) |
|
|
448
625
|
|
|
449
|
-
|
|
450
|
-
Change your database schema → **Everything updates automatically**:
|
|
626
|
+
## Dependencies
|
|
451
627
|
|
|
452
|
-
|
|
453
|
-
2. **CodeGen detects changes**
|
|
454
|
-
3. **Regenerates affected code**
|
|
455
|
-
4. **Type safety maintained** across entire stack
|
|
456
|
-
5. **Zero manual intervention**
|
|
628
|
+
This package depends on:
|
|
457
629
|
|
|
458
|
-
|
|
459
|
-
-
|
|
460
|
-
-
|
|
461
|
-
-
|
|
462
|
-
-
|
|
630
|
+
- [@memberjunction/core](../MJCore) - Entity framework, metadata system, and type utilities
|
|
631
|
+
- [@memberjunction/core-entities](../MJCoreEntities) - Generated entity classes for MJ system entities
|
|
632
|
+
- [@memberjunction/global](../MJGlobal) - `@RegisterClass` decorator and `MJGlobal.ClassFactory`
|
|
633
|
+
- [@memberjunction/sqlserver-dataprovider](../SQLServerDataProvider) - SQL Server data provider and connection management
|
|
634
|
+
- [@memberjunction/ai](../AI) - AI provider abstraction layer
|
|
635
|
+
- [@memberjunction/ai-prompts](../AI/Prompts) - AI prompt execution for advanced generation features
|
|
636
|
+
- [@memberjunction/ai-core-plus](../AI/CorePlus) - AI prompt parameter types
|
|
637
|
+
- [@memberjunction/aiengine](../AIEngine) - AI engine for model and prompt configuration
|
|
638
|
+
- [@memberjunction/actions](../Actions/Engine) - Action engine for action code generation
|
|
639
|
+
- [@memberjunction/actions-base](../Actions/Base) - Action base classes and types
|
|
640
|
+
- [@memberjunction/config](../Config) - Configuration merging utilities
|
|
641
|
+
- [@memberjunction/server-bootstrap-lite](../server-bootstrap-lite) - Pre-built class registration manifest
|
|
463
642
|
|
|
464
|
-
|
|
465
|
-
- **Parameterized queries** in all generated SQL
|
|
466
|
-
- **Input validation** at every layer
|
|
467
|
-
- **SQL injection protection** built-in
|
|
468
|
-
- **Type-safe APIs** prevent runtime errors
|
|
643
|
+
## Related Packages
|
|
469
644
|
|
|
470
|
-
|
|
645
|
+
- [@memberjunction/cli](../MJCLI) - CLI that invokes CodeGenLib (`mj codegen` commands)
|
|
646
|
+
- [@memberjunction/core-entities](../MJCoreEntities) - Contains the generated `entity_subclasses.ts` output
|
|
647
|
+
- [@memberjunction/server-bootstrap](../server-bootstrap) - Ships pre-built server-side class manifest
|
|
648
|
+
- [@memberjunction/ng-bootstrap](../Angular/ng-bootstrap) - Ships pre-built Angular class manifest
|
|
471
649
|
|
|
472
|
-
|
|
650
|
+
## Documentation
|
|
473
651
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
"database": {
|
|
478
|
-
"server": "localhost",
|
|
479
|
-
"database": "YourDatabase",
|
|
480
|
-
"trustedConnection": true
|
|
481
|
-
},
|
|
482
|
-
"directories": {
|
|
483
|
-
"output": "./generated",
|
|
484
|
-
"entities": "./generated/entities",
|
|
485
|
-
"actions": "./generated/actions",
|
|
486
|
-
"angular": "./generated/angular",
|
|
487
|
-
"sql": "./generated/sql"
|
|
488
|
-
},
|
|
489
|
-
"ai": {
|
|
490
|
-
"enabled": true,
|
|
491
|
-
"provider": "openai" // Powers constraint translation
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
```
|
|
652
|
+
- [Class Manifest Guide](CLASS_MANIFEST_GUIDE.md) - Comprehensive guide to the manifest system for preventing tree-shaking of `@RegisterClass` classes
|
|
653
|
+
- [EXAMPLE_MANIFEST_MJAPI.md](EXAMPLE_MANIFEST_MJAPI.md) - Example server-side manifest (54 packages, 715 classes)
|
|
654
|
+
- [EXAMPLE_MANIFEST_MJEXPLORER.md](EXAMPLE_MANIFEST_MJEXPLORER.md) - Example client-side manifest (17 packages, 721 classes)
|
|
496
655
|
|
|
497
|
-
##
|
|
656
|
+
## IS-A Type Relationships in CodeGen
|
|
498
657
|
|
|
499
|
-
|
|
658
|
+
MemberJunction 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.
|
|
500
659
|
|
|
501
|
-
|
|
502
|
-
CREATE TABLE [Customer] (
|
|
503
|
-
[ID] uniqueidentifier PRIMARY KEY DEFAULT newsequentialid(),
|
|
504
|
-
[Name] nvarchar(255) NOT NULL,
|
|
505
|
-
[Status] nvarchar(20) CHECK ([Status] IN ('Active', 'Inactive', 'Suspended')),
|
|
506
|
-
[CreatedAt] datetimeoffset DEFAULT getutcdate()
|
|
507
|
-
);
|
|
508
|
-
```
|
|
660
|
+
For comprehensive conceptual documentation, see the **[IS-A Relationships Guide](../../MJCore/docs/isa-relationships.md)** in MJCore.
|
|
509
661
|
|
|
510
|
-
|
|
662
|
+
### How CodeGen Handles IS-A Entities
|
|
511
663
|
|
|
512
|
-
|
|
513
|
-
```typescript
|
|
514
|
-
export class CustomerEntity extends BaseEntity {
|
|
515
|
-
Status: 'Active' | 'Inactive' | 'Suspended';
|
|
516
|
-
// + complete validation, save methods, relationships
|
|
517
|
-
}
|
|
518
|
-
```
|
|
664
|
+
When an entity has a `ParentEntity` relationship (IS-A child):
|
|
519
665
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
@Component({
|
|
523
|
-
template: `Complete form with validation and smart controls`
|
|
524
|
-
})
|
|
525
|
-
export class CustomerDetailsComponent {
|
|
526
|
-
// Ready for production use
|
|
527
|
-
}
|
|
528
|
-
```
|
|
666
|
+
#### 1. SQL View Generation - Parent JOINs
|
|
667
|
+
**Child views automatically JOIN to parent views** to provide a complete record with all inherited fields:
|
|
529
668
|
|
|
530
|
-
### SQL Procedures (200+ lines)
|
|
531
669
|
```sql
|
|
532
|
-
--
|
|
533
|
-
|
|
670
|
+
-- CodeGen automatically generates:
|
|
671
|
+
CREATE VIEW [vwEmployee]
|
|
672
|
+
AS
|
|
673
|
+
SELECT
|
|
674
|
+
e.*, -- All Employee fields
|
|
675
|
+
p.FirstName, -- Inherited from Person
|
|
676
|
+
p.LastName, -- Inherited from Person
|
|
677
|
+
p.DateOfBirth -- Inherited from Person
|
|
678
|
+
FROM
|
|
679
|
+
[__mj].[Employee] AS e
|
|
680
|
+
INNER JOIN
|
|
681
|
+
[__mj].[vwPerson] AS p ON e.[ID] = p.[ID]
|
|
534
682
|
```
|
|
535
683
|
|
|
536
|
-
|
|
537
|
-
```graphql
|
|
538
|
-
type Customer {
|
|
539
|
-
# Complete type-safe schema
|
|
540
|
-
}
|
|
541
|
-
```
|
|
684
|
+
This ensures querying the child view returns a complete record including all parent fields.
|
|
542
685
|
|
|
543
|
-
|
|
686
|
+
#### 2. Stored Procedure Generation - Child Fields Only
|
|
687
|
+
**Create and Update procedures only include the child's own fields**, not parent fields:
|
|
544
688
|
|
|
545
|
-
|
|
689
|
+
```sql
|
|
690
|
+
-- spCreateEmployee only has Employee-specific parameters
|
|
691
|
+
CREATE PROCEDURE [spCreateEmployee]
|
|
692
|
+
@ID uniqueidentifier,
|
|
693
|
+
@EmployeeNumber nvarchar(50),
|
|
694
|
+
@HireDate date,
|
|
695
|
+
@Salary decimal(18,2)
|
|
696
|
+
-- No FirstName, LastName (those are Person fields)
|
|
697
|
+
AS BEGIN
|
|
698
|
+
-- Only inserts into Employee table
|
|
699
|
+
INSERT INTO [__mj].[Employee] (ID, EmployeeNumber, HireDate, Salary)
|
|
700
|
+
VALUES (@ID, @EmployeeNumber, @HireDate, @Salary)
|
|
701
|
+
END
|
|
702
|
+
```
|
|
546
703
|
|
|
547
|
-
|
|
704
|
+
**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.
|
|
548
705
|
|
|
549
|
-
|
|
706
|
+
#### 3. GraphQL Schema Generation - Complete Field Set
|
|
707
|
+
**GraphQL input types include ALL fields (parent + child)** for seamless API usage:
|
|
550
708
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
709
|
+
```graphql
|
|
710
|
+
input CreateEmployeeInput {
|
|
711
|
+
# Parent fields (from Person)
|
|
712
|
+
firstName: String!
|
|
713
|
+
lastName: String!
|
|
714
|
+
dateOfBirth: Date
|
|
715
|
+
|
|
716
|
+
# Child fields (from Employee)
|
|
717
|
+
employeeNumber: String!
|
|
718
|
+
hireDate: Date!
|
|
719
|
+
salary: Decimal!
|
|
720
|
+
}
|
|
721
|
+
```
|
|
555
722
|
|
|
556
|
-
|
|
723
|
+
This provides a convenient single-operation API while the resolver handles the underlying two-step creation.
|
|
557
724
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
| MJAPI | 985 | 54 | 715 |
|
|
561
|
-
| MJExplorer | 1179 | 17 | 721 |
|
|
725
|
+
#### 4. TypeScript Entity Classes - JSDoc Annotations
|
|
726
|
+
**Generated entity classes include JSDoc annotations** on getter/setter methods to indicate IS-A relationships:
|
|
562
727
|
|
|
563
|
-
**Programmatic usage:**
|
|
564
728
|
```typescript
|
|
565
|
-
|
|
729
|
+
export class EmployeeEntity extends BaseEntity {
|
|
730
|
+
/**
|
|
731
|
+
* Inherited from Person entity
|
|
732
|
+
*/
|
|
733
|
+
get FirstName(): string {
|
|
734
|
+
return this.Get('FirstName');
|
|
735
|
+
}
|
|
566
736
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
filterBaseClasses: ['BaseEngine'], // optional filter
|
|
571
|
-
});
|
|
737
|
+
set FirstName(value: string) {
|
|
738
|
+
this.Set('FirstName', value);
|
|
739
|
+
}
|
|
572
740
|
|
|
573
|
-
|
|
574
|
-
|
|
741
|
+
// Own fields have no annotation
|
|
742
|
+
get EmployeeNumber(): string {
|
|
743
|
+
return this.Get('EmployeeNumber');
|
|
744
|
+
}
|
|
575
745
|
}
|
|
576
746
|
```
|
|
577
747
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
mj codegen manifest --output ./src/generated/class-registrations-manifest.ts
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
See the [MJCLI README](../MJCLI/README.md) for full CLI documentation.
|
|
584
|
-
|
|
585
|
-
**Pre-built manifests for npm distribution:**
|
|
748
|
+
#### 5. EntityField Metadata Synchronization
|
|
749
|
+
**`manageEntityFields()` respects IS-A hierarchy** when syncing field metadata:
|
|
586
750
|
|
|
587
|
-
|
|
751
|
+
- Fields from parent entities are NOT duplicated in child entity metadata
|
|
752
|
+
- Only the child's own fields appear in `EntityField` for the child entity
|
|
753
|
+
- `RelatedEntityID` and field relationships are preserved across the hierarchy
|
|
754
|
+
- Prevents metadata pollution from inherited fields
|
|
588
755
|
|
|
589
|
-
|
|
590
|
-
- `@memberjunction/ng-bootstrap` — 383 Angular classes from 14 packages
|
|
756
|
+
### Configuration in Database Metadata
|
|
591
757
|
|
|
592
|
-
|
|
758
|
+
IS-A relationships are defined in the `Entity` table:
|
|
593
759
|
|
|
594
|
-
|
|
760
|
+
```sql
|
|
761
|
+
-- Person is the base entity
|
|
762
|
+
INSERT INTO Entity (ID, ParentEntity, Name)
|
|
763
|
+
VALUES (NEWID(), NULL, 'Person')
|
|
595
764
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
765
|
+
-- Employee IS-A Person
|
|
766
|
+
INSERT INTO Entity (ID, ParentEntity, Name)
|
|
767
|
+
VALUES (NEWID(), 'Person', 'Employee')
|
|
768
|
+
```
|
|
599
769
|
|
|
600
|
-
|
|
770
|
+
CodeGen detects the `ParentEntity` relationship and applies the specialized generation logic automatically.
|
|
601
771
|
|
|
602
|
-
###
|
|
772
|
+
### Use Cases for IS-A Relationships
|
|
603
773
|
|
|
604
|
-
|
|
605
|
-
// Generate everything at once
|
|
606
|
-
await runCodeGen();
|
|
774
|
+
Common scenarios where IS-A relationships improve your schema:
|
|
607
775
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
await generateGraphQLServerCode(options);
|
|
613
|
-
```
|
|
776
|
+
- **Person → Employee, Customer, Vendor** - Shared contact information with role-specific fields
|
|
777
|
+
- **Document → Invoice, PurchaseOrder, Contract** - Common document metadata with type-specific data
|
|
778
|
+
- **Product → PhysicalProduct, DigitalProduct** - Shared catalog info with delivery-specific fields
|
|
779
|
+
- **Communication → Email, SMS, PhoneCall** - Common tracking with channel-specific metadata
|
|
614
780
|
|
|
615
|
-
###
|
|
781
|
+
### Best Practices
|
|
616
782
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
outputDirectory: './generated/entities',
|
|
622
|
-
generateLoader: true,
|
|
623
|
-
generateCustomEntityClasses: true,
|
|
624
|
-
aiEnhanced: true, // Enable AI features
|
|
625
|
-
incrementalMode: true, // Only update changed entities
|
|
626
|
-
validateGenerated: true // Compile-check generated code
|
|
627
|
-
});
|
|
628
|
-
```
|
|
783
|
+
1. **Keep hierarchies shallow** - One or two levels is ideal (Person → Employee, not Person → Worker → Employee)
|
|
784
|
+
2. **Parent entities should be meaningful** - Don't create artificial base classes just for inheritance
|
|
785
|
+
3. **Child fields should be truly specific** - If a field applies to all children, put it in the parent
|
|
786
|
+
4. **ID management is manual** - When creating child records, explicitly use the parent's ID
|
|
629
787
|
|
|
630
|
-
|
|
788
|
+
## Virtual Entity Support
|
|
631
789
|
|
|
632
|
-
|
|
633
|
-
import { generateActionSubClasses } from '@memberjunction/codegen-lib';
|
|
790
|
+
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.
|
|
634
791
|
|
|
635
|
-
|
|
636
|
-
outputDirectory: './generated/actions',
|
|
637
|
-
generateLoader: true
|
|
638
|
-
});
|
|
639
|
-
```
|
|
792
|
+
For comprehensive conceptual documentation, see the **[Virtual Entities Guide](../../MJCore/docs/virtual-entities.md)** in MJCore.
|
|
640
793
|
|
|
641
|
-
###
|
|
794
|
+
### Configuration-Driven Virtual Entity Creation
|
|
642
795
|
|
|
643
|
-
|
|
644
|
-
import { generateGraphQLServerCode } from '@memberjunction/codegen-lib';
|
|
796
|
+
Virtual entities are defined in `database-metadata-config.json` under the `VirtualEntities` array:
|
|
645
797
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
798
|
+
```json
|
|
799
|
+
{
|
|
800
|
+
"VirtualEntities": [
|
|
801
|
+
{
|
|
802
|
+
"ViewName": "vwSalesSummary",
|
|
803
|
+
"EntityName": "Sales Summary",
|
|
804
|
+
"SchemaName": "__mj",
|
|
805
|
+
"Description": "Aggregated sales data by region and period",
|
|
806
|
+
"PrimaryKey": ["SummaryID"],
|
|
807
|
+
"ForeignKeys": [
|
|
808
|
+
{
|
|
809
|
+
"FieldName": "RegionID",
|
|
810
|
+
"SchemaName": "__mj",
|
|
811
|
+
"RelatedTable": "Region",
|
|
812
|
+
"RelatedField": "ID",
|
|
813
|
+
"Description": "FK to Region table"
|
|
814
|
+
}
|
|
815
|
+
]
|
|
816
|
+
}
|
|
817
|
+
]
|
|
818
|
+
}
|
|
650
819
|
```
|
|
651
820
|
|
|
652
|
-
|
|
821
|
+
#### Key Configuration Properties
|
|
653
822
|
|
|
654
|
-
|
|
655
|
-
|
|
823
|
+
- **`ViewName`**: The SQL view name (must already exist in the database)
|
|
824
|
+
- **`EntityName`**: The MemberJunction entity name (appears in metadata, UI, APIs)
|
|
825
|
+
- **`SchemaName`**: Database schema (typically `__mj` for core entities)
|
|
826
|
+
- **`Description`**: Entity description for metadata and documentation
|
|
827
|
+
- **`PrimaryKey`**: Array of column names forming the primary key (supports composite keys)
|
|
828
|
+
- **`ForeignKeys`**: Optional array of foreign key relationships to other entities (if omitted, LLM decoration discovers them)
|
|
656
829
|
|
|
657
|
-
|
|
658
|
-
outputDirectory: './generated/sql',
|
|
659
|
-
includeStoredProcedures: true,
|
|
660
|
-
includeViews: true
|
|
661
|
-
});
|
|
662
|
-
```
|
|
830
|
+
### CodeGen Pipeline for Virtual Entities
|
|
663
831
|
|
|
664
|
-
|
|
832
|
+
CodeGen processes virtual entities through several specialized steps:
|
|
665
833
|
|
|
666
|
-
|
|
667
|
-
|
|
834
|
+
#### 1. `processVirtualEntityConfig()` - Entity Creation
|
|
835
|
+
Reads the `VirtualEntities` configuration and calls `spCreateVirtualEntity` for each entry:
|
|
668
836
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
837
|
+
```typescript
|
|
838
|
+
// CodeGen calls this stored procedure for each virtual entity
|
|
839
|
+
EXEC spCreateVirtualEntity
|
|
840
|
+
@Name = 'Sales Summary',
|
|
841
|
+
@SchemaName = '__mj',
|
|
842
|
+
@BaseView = 'vwSalesSummary',
|
|
843
|
+
@Description = 'Aggregated sales data...',
|
|
844
|
+
@PrimaryKeyColumnName = 'SummaryID'
|
|
673
845
|
```
|
|
674
846
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
On a typical MemberJunction database with **150+ tables**:
|
|
847
|
+
This creates the `Entity` metadata record with `VirtualEntity = 1`.
|
|
678
848
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
- **SQL Procedures**: 1.8 seconds
|
|
682
|
-
- **Total Stack Generation**: **<10 seconds**
|
|
849
|
+
#### 2. `manageVirtualEntities()` - Field Synchronization
|
|
850
|
+
Scans `sys.columns` on the virtual entity's view and creates `EntityField` metadata for each column:
|
|
683
851
|
|
|
684
|
-
|
|
852
|
+
- Automatically detects data types, nullability, and max lengths
|
|
853
|
+
- Creates `EntityField` records for all view columns
|
|
854
|
+
- Updates existing fields if column definitions change
|
|
855
|
+
- Marks fields as `IsVirtual = 1` in metadata
|
|
685
856
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
857
|
+
```typescript
|
|
858
|
+
// CodeGen inspects the view schema
|
|
859
|
+
SELECT
|
|
860
|
+
c.name,
|
|
861
|
+
t.name AS TypeName,
|
|
862
|
+
c.max_length,
|
|
863
|
+
c.is_nullable
|
|
864
|
+
FROM
|
|
865
|
+
sys.columns c
|
|
866
|
+
INNER JOIN
|
|
867
|
+
sys.types t ON c.user_type_id = t.user_type_id
|
|
868
|
+
WHERE
|
|
869
|
+
object_id = OBJECT_ID('__mj.vwSalesSummary')
|
|
870
|
+
```
|
|
689
871
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
- `@memberjunction/angular-explorer` - UI framework
|
|
693
|
-
- `@memberjunction/graphql-dataprovider` - API layer
|
|
694
|
-
- `@memberjunction/sqlserver-dataprovider` - Data access
|
|
872
|
+
#### 3. `applySoftPKFKConfig()` - Explicit Relationship Overrides
|
|
873
|
+
Applies the `primaryKeyColumnName` and `foreignKeyDefinitions` from the config:
|
|
695
874
|
|
|
696
|
-
|
|
875
|
+
```typescript
|
|
876
|
+
// Sets the primary key field
|
|
877
|
+
UPDATE EntityField
|
|
878
|
+
SET IsPrimaryKey = 1
|
|
879
|
+
WHERE EntityID = @VirtualEntityID
|
|
880
|
+
AND Name = 'SummaryID'
|
|
881
|
+
|
|
882
|
+
// Creates foreign key relationships
|
|
883
|
+
INSERT INTO EntityRelationship (...)
|
|
884
|
+
SELECT ... FROM foreignKeyDefinitions
|
|
885
|
+
```
|
|
697
886
|
|
|
698
|
-
|
|
887
|
+
**Why explicit FK definitions?** Views don't have database-level foreign keys, so CodeGen can't detect relationships automatically. The config provides this metadata.
|
|
699
888
|
|
|
700
|
-
|
|
889
|
+
#### 4. LLM-Assisted Field Decoration (Advanced Feature)
|
|
890
|
+
The **`decorateVirtualEntitiesWithLLM()`** pipeline step uses AI to enhance virtual entity field metadata:
|
|
701
891
|
|
|
702
892
|
```typescript
|
|
703
|
-
import {
|
|
704
|
-
|
|
705
|
-
|
|
893
|
+
import { AIPromptRunner } from '@memberjunction/ai-prompts';
|
|
894
|
+
|
|
895
|
+
// CodeGen calls a database-driven prompt to decorate fields
|
|
896
|
+
const promptParams = new AIPromptParams();
|
|
897
|
+
promptParams.prompt = 'Decorate Virtual Entity Fields';
|
|
898
|
+
promptParams.data = {
|
|
899
|
+
entityName: 'Sales Summary',
|
|
900
|
+
viewDefinition: viewSQL,
|
|
901
|
+
existingFields: fieldsFromMetadata
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
const runner = new AIPromptRunner();
|
|
905
|
+
const result = await runner.ExecutePrompt(promptParams);
|
|
706
906
|
```
|
|
707
907
|
|
|
708
|
-
|
|
908
|
+
The LLM analyzes the view definition and provides:
|
|
909
|
+
- **Display names** - User-friendly field labels (e.g., `TotalRevenue` → "Total Revenue")
|
|
910
|
+
- **Descriptions** - Field-level documentation explaining what each column represents
|
|
911
|
+
- **Category assignments** - Semantic grouping for form layouts
|
|
912
|
+
- **DefaultInView flags** - Recommended visibility settings for grid displays
|
|
709
913
|
|
|
710
|
-
|
|
711
|
-
import { analyzeSchema } from '@memberjunction/codegen-lib';
|
|
914
|
+
This is controlled by the `VirtualEntityFieldDecoration` feature in the Advanced Generation Features configuration.
|
|
712
915
|
|
|
713
|
-
|
|
714
|
-
// Work with schema information
|
|
715
|
-
```
|
|
916
|
+
### Virtual Entity Metadata Structure
|
|
716
917
|
|
|
717
|
-
|
|
918
|
+
After CodeGen processing, virtual entities have complete metadata:
|
|
718
919
|
|
|
719
|
-
```
|
|
720
|
-
|
|
920
|
+
```sql
|
|
921
|
+
-- Entity record
|
|
922
|
+
SELECT * FROM Entity WHERE Name = 'Sales Summary'
|
|
923
|
+
-- VirtualEntity = 1, BaseView = 'vwSalesSummary'
|
|
721
924
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
925
|
+
-- EntityField records (auto-detected from view)
|
|
926
|
+
SELECT * FROM EntityField WHERE EntityID = @SalesEntityID
|
|
927
|
+
-- Name, Type, Description, IsVirtual = 1
|
|
928
|
+
|
|
929
|
+
-- EntityRelationship records (from config)
|
|
930
|
+
SELECT * FROM EntityRelationship WHERE EntityID = @SalesEntityID
|
|
931
|
+
-- Foreign keys defined in foreignKeyDefinitions
|
|
725
932
|
```
|
|
726
933
|
|
|
727
|
-
###
|
|
934
|
+
### Generated Code for Virtual Entities
|
|
728
935
|
|
|
729
|
-
|
|
936
|
+
Virtual entities generate the same TypeScript, GraphQL, and Angular code as table-based entities:
|
|
730
937
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
// Granular control over what gets regenerated
|
|
739
|
-
baseViews: true, // Regenerate base views
|
|
740
|
-
spCreate: false, // Skip create procedures
|
|
741
|
-
spUpdate: true, // Regenerate update procedures
|
|
742
|
-
spDelete: false, // Skip delete procedures
|
|
743
|
-
indexes: true, // Regenerate foreign key indexes
|
|
744
|
-
fullTextSearch: false // Skip full-text search components
|
|
745
|
-
}
|
|
746
|
-
```
|
|
938
|
+
**TypeScript Entity Class:**
|
|
939
|
+
```typescript
|
|
940
|
+
export class SalesSummaryEntity extends BaseEntity {
|
|
941
|
+
get SummaryID(): string {
|
|
942
|
+
return this.Get('SummaryID');
|
|
943
|
+
}
|
|
747
944
|
|
|
748
|
-
|
|
945
|
+
get RegionID(): string {
|
|
946
|
+
return this.Get('RegionID');
|
|
947
|
+
}
|
|
749
948
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
enabled: true,
|
|
754
|
-
entityWhereClause: "__mj_UpdatedAt >= '2025-06-24 22:00:00'",
|
|
755
|
-
baseViews: true
|
|
756
|
-
}
|
|
757
|
-
```
|
|
949
|
+
get TotalRevenue(): number {
|
|
950
|
+
return this.Get('TotalRevenue');
|
|
951
|
+
}
|
|
758
952
|
|
|
759
|
-
|
|
760
|
-
```javascript
|
|
761
|
-
forceRegeneration: {
|
|
762
|
-
enabled: true,
|
|
763
|
-
entityWhereClause: "SchemaName = 'Sales'",
|
|
764
|
-
allStoredProcedures: true
|
|
953
|
+
// Save/Delete methods throw errors (read-only entity)
|
|
765
954
|
}
|
|
766
955
|
```
|
|
767
956
|
|
|
768
|
-
**
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
957
|
+
**GraphQL Schema:**
|
|
958
|
+
```graphql
|
|
959
|
+
type SalesSummary {
|
|
960
|
+
summaryID: ID!
|
|
961
|
+
regionID: ID!
|
|
962
|
+
region: Region # Auto-resolved from FK definition
|
|
963
|
+
totalRevenue: Float!
|
|
774
964
|
}
|
|
775
|
-
```
|
|
776
965
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
forceRegeneration: {
|
|
780
|
-
enabled: true,
|
|
781
|
-
// No entityWhereClause = regenerate for ALL entities
|
|
782
|
-
allStoredProcedures: true,
|
|
783
|
-
baseViews: true,
|
|
784
|
-
indexes: true
|
|
966
|
+
type Query {
|
|
967
|
+
SalesSummaries(filter: String): [SalesSummary!]!
|
|
785
968
|
}
|
|
786
969
|
```
|
|
787
970
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
971
|
+
**Angular Form:**
|
|
972
|
+
```html
|
|
973
|
+
<mj-form-field
|
|
974
|
+
[record]="record"
|
|
975
|
+
FieldName="TotalRevenue"
|
|
976
|
+
Type="textbox"
|
|
977
|
+
[ReadOnly]="true" <!-- Virtual entities are read-only -->
|
|
978
|
+
></mj-form-field>
|
|
979
|
+
```
|
|
793
980
|
|
|
794
|
-
|
|
981
|
+
### Virtual Entity Limitations
|
|
795
982
|
|
|
796
|
-
|
|
983
|
+
Virtual entities are **read-only** by design:
|
|
797
984
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
// Handle missing configuration
|
|
804
|
-
} else if (error.code === 'DB_CONNECTION_FAILED') {
|
|
805
|
-
// Handle database connection errors
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
```
|
|
985
|
+
- No `spCreate`, `spUpdate`, or `spDelete` procedures generated
|
|
986
|
+
- `AllowCreateAPI`, `AllowUpdateAPI`, `AllowDeleteAPI` set to `0` in metadata
|
|
987
|
+
- Entity class `Save()` and `Delete()` methods throw errors
|
|
988
|
+
- GraphQL mutations not generated for virtual entities
|
|
989
|
+
- Angular forms display in read-only mode
|
|
809
990
|
|
|
810
|
-
|
|
991
|
+
### Advanced Generation Features Configuration
|
|
811
992
|
|
|
812
|
-
|
|
813
|
-
- Weeks of manual entity creation
|
|
814
|
-
- Inconsistent validation logic
|
|
815
|
-
- Type mismatches between layers
|
|
816
|
-
- Manual Angular form creation
|
|
817
|
-
- Brittle SQL procedures
|
|
818
|
-
- Schema changes break everything
|
|
993
|
+
Virtual entity LLM decoration is controlled in the `advancedGeneration.features` array in `mj.config.cjs`:
|
|
819
994
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
995
|
+
```javascript
|
|
996
|
+
advancedGeneration: {
|
|
997
|
+
enableAdvancedGeneration: true,
|
|
998
|
+
features: [
|
|
999
|
+
{
|
|
1000
|
+
name: 'VirtualEntityFieldDecoration',
|
|
1001
|
+
enabled: true,
|
|
1002
|
+
// Optional: force re-decoration even if entities already have soft PK/FK annotations
|
|
1003
|
+
options: [{ name: 'forceRegenerate', value: true }],
|
|
1004
|
+
},
|
|
1005
|
+
],
|
|
1006
|
+
},
|
|
1007
|
+
```
|
|
826
1008
|
|
|
827
|
-
|
|
1009
|
+
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).
|
|
828
1010
|
|
|
829
|
-
|
|
830
|
-
2. **Output Organization** - Keep generated code in separate directories
|
|
831
|
-
3. **Version Control** - Consider excluding generated files from version control
|
|
832
|
-
4. **Regular Updates** - Regenerate code when metadata changes
|
|
833
|
-
5. **Custom Extensions** - Extend generated classes rather than modifying them
|
|
1011
|
+
When active, CodeGen calls `decorateVirtualEntitiesWithLLM()` after field synchronization.
|
|
834
1012
|
|
|
835
|
-
|
|
1013
|
+
### Use Cases for Virtual Entities
|
|
836
1014
|
|
|
837
|
-
|
|
1015
|
+
Virtual entities excel at:
|
|
838
1016
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1017
|
+
- **Reporting and Analytics** - Pre-aggregated views for dashboards (sales summaries, usage metrics)
|
|
1018
|
+
- **External Data Sources** - Linked server views, API-backed views, federated queries
|
|
1019
|
+
- **Denormalized Views** - Flattened data for grid displays without JOIN overhead
|
|
1020
|
+
- **Legacy System Integration** - Expose legacy tables through normalized MJ entity layer
|
|
1021
|
+
- **Calculated Fields** - Complex computed columns not suitable for table storage
|
|
1022
|
+
- **Security Views** - Row-level security via filtered views with full MJ API access
|
|
843
1023
|
|
|
844
|
-
|
|
1024
|
+
### Best Practices
|
|
845
1025
|
|
|
846
|
-
|
|
1026
|
+
1. **View must exist first** - Create the SQL view before running CodeGen with virtual entity config
|
|
1027
|
+
2. **Primary key is required** - Views must have a unique identifier column
|
|
1028
|
+
3. **Use meaningful names** - `entityName` appears throughout UI and APIs
|
|
1029
|
+
4. **Document foreign keys** - Explicit FK definitions enable relationship navigation
|
|
1030
|
+
5. **Enable LLM decoration** - Let AI generate field descriptions for better UX
|
|
1031
|
+
6. **Keep views simple** - Complex views with subqueries may have performance issues
|
|
1032
|
+
7. **Test read operations** - Verify grid displays and API queries perform acceptably
|
|
847
1033
|
|
|
848
|
-
|
|
1034
|
+
## Contributing
|
|
849
1035
|
|
|
850
|
-
|
|
1036
|
+
See the [MemberJunction Contributing Guide](../../CONTRIBUTING.md) for development setup and guidelines.
|
|
851
1037
|
|
|
852
|
-
|
|
853
|
-
npm install @memberjunction/codegen-lib
|
|
854
|
-
```
|
|
1038
|
+
When contributing to CodeGenLib:
|
|
855
1039
|
|
|
856
|
-
|
|
1040
|
+
1. All generator base classes use the class factory pattern -- always subclass and register rather than modifying base classes directly
|
|
1041
|
+
2. Generated SQL must be compatible with SQL Server and produce valid Flyway migration output
|
|
1042
|
+
3. Generated TypeScript must compile without errors and follow MJ naming conventions (PascalCase public members)
|
|
1043
|
+
4. AI-powered features must enforce stability guarantees (existing categories and icons are never changed)
|