@memberjunction/core-entities-server 2.46.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 +227 -0
- package/dist/custom/AIPromptEntityExtended.server.d.ts +93 -0
- package/dist/custom/AIPromptEntityExtended.server.d.ts.map +1 -0
- package/dist/custom/AIPromptEntityExtended.server.js +265 -0
- package/dist/custom/AIPromptEntityExtended.server.js.map +1 -0
- package/dist/custom/DuplicateRunEntity.server.d.ts +6 -0
- package/dist/custom/DuplicateRunEntity.server.d.ts.map +1 -0
- package/dist/custom/DuplicateRunEntity.server.js +37 -0
- package/dist/custom/DuplicateRunEntity.server.js.map +1 -0
- package/dist/custom/reportEntity.server.d.ts +11 -0
- package/dist/custom/reportEntity.server.d.ts.map +1 -0
- package/dist/custom/reportEntity.server.js +129 -0
- package/dist/custom/reportEntity.server.js.map +1 -0
- package/dist/custom/userViewEntity.server.d.ts +30 -0
- package/dist/custom/userViewEntity.server.d.ts.map +1 -0
- package/dist/custom/userViewEntity.server.js +235 -0
- package/dist/custom/userViewEntity.server.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# MemberJunction Core Entities Server
|
|
2
|
+
|
|
3
|
+
Server-side entity subclasses for MemberJunction that provide extended functionality and business logic for core entities when running in a Node.js environment.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package contains server-side implementations of MemberJunction entity subclasses that require server-specific functionality such as:
|
|
8
|
+
- Direct database access
|
|
9
|
+
- File system operations
|
|
10
|
+
- Server-side API integrations
|
|
11
|
+
- Complex business logic that should only run on the server
|
|
12
|
+
- Automatic creation/management of related entities
|
|
13
|
+
|
|
14
|
+
## Purpose
|
|
15
|
+
|
|
16
|
+
While the base `@memberjunction/core-entities` package provides entity classes that work in any JavaScript environment (browser or server), some entity operations require server-specific capabilities. This package provides those extended implementations.
|
|
17
|
+
|
|
18
|
+
### Key Differences from Core Entities
|
|
19
|
+
|
|
20
|
+
- **Core Entities**: Work everywhere, basic CRUD operations, suitable for client-side usage
|
|
21
|
+
- **Core Entities Server**: Server-only, extended functionality, can directly interact with system resources
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @memberjunction/core-entities-server
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Basic Setup
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { LoadCoreEntitiesServerSubClasses } from '@memberjunction/core-entities-server';
|
|
35
|
+
|
|
36
|
+
// Load all server-side entity subclasses at application startup
|
|
37
|
+
LoadCoreEntitiesServerSubClasses();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Important**: Call `LoadCoreEntitiesServerSubClasses()` early in your application initialization to ensure the server-side subclasses are registered with the MemberJunction metadata system before any entities are instantiated.
|
|
41
|
+
|
|
42
|
+
### Example: AI Prompt Entity
|
|
43
|
+
|
|
44
|
+
The package includes an extended AI Prompt entity that automatically manages Template and Template Contents records:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { Metadata } from '@memberjunction/core';
|
|
48
|
+
|
|
49
|
+
// The metadata system will automatically use the server-side subclass
|
|
50
|
+
const md = new Metadata();
|
|
51
|
+
const aiPrompt = await md.GetEntityObject<AIPromptEntity>('AI Prompts');
|
|
52
|
+
|
|
53
|
+
// Setting TemplateText will automatically create/update Template records
|
|
54
|
+
aiPrompt.TemplateText = `You are a helpful assistant...`;
|
|
55
|
+
|
|
56
|
+
// Save will handle all the Template management automatically
|
|
57
|
+
await aiPrompt.Save();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Available Server-Side Entities
|
|
61
|
+
|
|
62
|
+
### AIPromptEntityExtendedServer
|
|
63
|
+
|
|
64
|
+
Extends the base AI Prompt entity with automatic Template management:
|
|
65
|
+
|
|
66
|
+
- **Virtual Property**: `TemplateText` - A convenient way to get/set the prompt template content
|
|
67
|
+
- **Automatic Template Creation**: Creates Template and Template Contents records when saving
|
|
68
|
+
- **Automatic Template Updates**: Updates existing templates when content changes
|
|
69
|
+
- **Proper Relationship Management**: Maintains proper foreign key relationships
|
|
70
|
+
|
|
71
|
+
## Architecture
|
|
72
|
+
|
|
73
|
+
### Entity Registration
|
|
74
|
+
|
|
75
|
+
The package uses MemberJunction's entity subclassing system:
|
|
76
|
+
|
|
77
|
+
1. At startup, `LoadCoreEntitiesServerSubClasses()` is called
|
|
78
|
+
2. This registers all server-side entity subclasses with the metadata system
|
|
79
|
+
3. When code requests an entity (e.g., "AI Prompts"), the metadata system returns the server-side subclass
|
|
80
|
+
4. The server-side subclass extends the base entity with additional functionality
|
|
81
|
+
|
|
82
|
+
### Virtual Properties
|
|
83
|
+
|
|
84
|
+
Server-side entities can expose virtual properties that don't exist in the database but provide convenient access to complex data:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
export class AIPromptEntityExtendedServer extends AIPromptEntity {
|
|
88
|
+
private _TemplateText?: string;
|
|
89
|
+
|
|
90
|
+
get TemplateText(): string | undefined {
|
|
91
|
+
return this._TemplateText;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
set TemplateText(value: string | undefined) {
|
|
95
|
+
this._TemplateText = value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Development Guidelines
|
|
101
|
+
|
|
102
|
+
### Creating New Server-Side Entities
|
|
103
|
+
|
|
104
|
+
1. Create a new file in `src/custom/`
|
|
105
|
+
2. Import and extend the base entity class
|
|
106
|
+
3. Add server-specific functionality
|
|
107
|
+
4. Register the subclass in the loader
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { BaseEntity, RegisterClass } from '@memberjunction/core';
|
|
113
|
+
import { UserEntity } from '@memberjunction/core-entities';
|
|
114
|
+
|
|
115
|
+
@RegisterClass(BaseEntity, 'Users')
|
|
116
|
+
export class UserEntityExtendedServer extends UserEntity {
|
|
117
|
+
async BeforeSave(): Promise<boolean> {
|
|
118
|
+
// Server-side validation or processing
|
|
119
|
+
if (this.Email) {
|
|
120
|
+
// Verify email domain against company whitelist
|
|
121
|
+
const validDomain = await this.checkEmailDomain(this.Email);
|
|
122
|
+
if (!validDomain) {
|
|
123
|
+
throw new Error('Invalid email domain');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return super.BeforeSave();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async checkEmailDomain(email: string): Promise<boolean> {
|
|
130
|
+
// Server-side logic to validate email domain
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Best Practices
|
|
137
|
+
|
|
138
|
+
1. **Keep It Server-Side**: Only include code that must run on the server
|
|
139
|
+
2. **Extend, Don't Replace**: Always extend the base entity class, don't replace core functionality
|
|
140
|
+
3. **Handle Errors Gracefully**: Server-side operations can fail - handle errors appropriately
|
|
141
|
+
4. **Document Virtual Properties**: Clearly document any virtual properties and their behavior
|
|
142
|
+
5. **Test Thoroughly**: Server-side logic can be complex - ensure comprehensive testing
|
|
143
|
+
|
|
144
|
+
## Integration with Other MJ Packages
|
|
145
|
+
|
|
146
|
+
This package works seamlessly with:
|
|
147
|
+
|
|
148
|
+
- `@memberjunction/core`: Provides the base entity system
|
|
149
|
+
- `@memberjunction/core-entities`: Provides the base entity classes
|
|
150
|
+
- `@memberjunction/server`: Can be used in MJ server applications
|
|
151
|
+
- `@memberjunction/cli`: Used by CLI tools like MetadataSync
|
|
152
|
+
|
|
153
|
+
## Common Use Cases
|
|
154
|
+
|
|
155
|
+
### 1. Automatic Related Entity Management
|
|
156
|
+
```typescript
|
|
157
|
+
// AI Prompts automatically manage their Template records
|
|
158
|
+
const prompt = await md.GetEntityObject<AIPromptEntity>('AI Prompts');
|
|
159
|
+
prompt.TemplateText = "New prompt content";
|
|
160
|
+
await prompt.Save(); // Template and Template Contents created/updated automatically
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 2. Server-Side Validation
|
|
164
|
+
```typescript
|
|
165
|
+
@RegisterClass(BaseEntity, 'Documents')
|
|
166
|
+
export class DocumentEntityServer extends DocumentEntity {
|
|
167
|
+
async BeforeSave(): Promise<boolean> {
|
|
168
|
+
// Scan document for viruses using server-side scanner
|
|
169
|
+
if (this.Content) {
|
|
170
|
+
const isSafe = await virusScanner.scan(this.Content);
|
|
171
|
+
if (!isSafe) {
|
|
172
|
+
throw new Error('Document failed security scan');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return super.BeforeSave();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 3. Complex Business Logic
|
|
181
|
+
```typescript
|
|
182
|
+
@RegisterClass(BaseEntity, 'Orders')
|
|
183
|
+
export class OrderEntityServer extends OrderEntity {
|
|
184
|
+
async AfterSave(): Promise<boolean> {
|
|
185
|
+
// Send order to fulfillment system
|
|
186
|
+
await fulfillmentAPI.submitOrder(this);
|
|
187
|
+
|
|
188
|
+
// Update inventory
|
|
189
|
+
await inventorySystem.decrementStock(this.OrderItems);
|
|
190
|
+
|
|
191
|
+
return super.AfterSave();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Troubleshooting
|
|
197
|
+
|
|
198
|
+
### Entity subclass not being used
|
|
199
|
+
Ensure `LoadCoreEntitiesServerSubClasses()` is called before any entity instantiation.
|
|
200
|
+
|
|
201
|
+
### Module loading errors
|
|
202
|
+
This package uses CommonJS. Ensure your Node.js application is configured to handle CommonJS modules.
|
|
203
|
+
|
|
204
|
+
### Virtual properties not persisting
|
|
205
|
+
Virtual properties are not saved to the database. Use them as convenient accessors that manage real database fields behind the scenes.
|
|
206
|
+
|
|
207
|
+
## Future Enhancements
|
|
208
|
+
|
|
209
|
+
- Additional server-side entity implementations
|
|
210
|
+
- Integration with file storage providers
|
|
211
|
+
- Advanced caching mechanisms
|
|
212
|
+
- Batch processing utilities
|
|
213
|
+
- Background job integration
|
|
214
|
+
|
|
215
|
+
## Contributing
|
|
216
|
+
|
|
217
|
+
When contributing new server-side entities:
|
|
218
|
+
|
|
219
|
+
1. Follow the existing patterns
|
|
220
|
+
2. Document all virtual properties
|
|
221
|
+
3. Include comprehensive tests
|
|
222
|
+
4. Ensure no client-side dependencies
|
|
223
|
+
5. Update this README with new entities
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
This package is part of the MemberJunction open-source project.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { EntitySaveOptions, IMetadataProvider, TransactionGroupBase } from "@memberjunction/core";
|
|
2
|
+
import { TemplateEntity, AIPromptEntityExtended } from "@memberjunction/core-entities";
|
|
3
|
+
/**
|
|
4
|
+
* Server specific sub-class that handles the automatic creation and updating of
|
|
5
|
+
* Templates and Template Contents entity records linked to the AI Prompt.
|
|
6
|
+
* This class extends the AIPromptEntityExtended class and overrides the Save() method and
|
|
7
|
+
* also provides additional utility functionality that is used within Save() and can be overridden
|
|
8
|
+
* by subclasses to provide custom logic for creating or updating the linked Template and Template Contents, etc.
|
|
9
|
+
*/
|
|
10
|
+
export declare class AIPromptEntityExtendedServer extends AIPromptEntityExtended {
|
|
11
|
+
/**
|
|
12
|
+
* private property to hold the template text.
|
|
13
|
+
*/
|
|
14
|
+
private _templateText;
|
|
15
|
+
/**
|
|
16
|
+
* Virtual property to hold the template text.
|
|
17
|
+
* This property is used to create or update the Template and Template Contents entity records automatically
|
|
18
|
+
* whenever the AIPrompt is saved.
|
|
19
|
+
*/
|
|
20
|
+
get TemplateText(): string;
|
|
21
|
+
set TemplateText(value: string);
|
|
22
|
+
private static _templateContentTypeID;
|
|
23
|
+
/**
|
|
24
|
+
* The Template Content Type ID that will be used for creating new Template Contents
|
|
25
|
+
* linked to this AI Prompt. This is automatically set the first time the AI Prompt is saved
|
|
26
|
+
* and can be manually set ahead of that if desired to a different Template Content Types ID
|
|
27
|
+
*/
|
|
28
|
+
static get TemplateContentTypeID(): string | null;
|
|
29
|
+
static set TemplateContentTypeID(value: string | null);
|
|
30
|
+
private static _rootTemplateCategoryID;
|
|
31
|
+
/**
|
|
32
|
+
* The root category ID in the Template Categories entity that will be used for creating new
|
|
33
|
+
* templates linked to this AI Prompt. The way it works is we automatically create new sub-categories
|
|
34
|
+
* of the root category for each AI Prompt Category. Those sub-categories are below the
|
|
35
|
+
* root category. If RootTemplateCategoryID is null when the first such operation occurs upon
|
|
36
|
+
* Save() of a given AIPrompt in a given process space, we will load a category with the name
|
|
37
|
+
* of "AI Prompts" and use that as the root category ID. If such a category does not exist, we will
|
|
38
|
+
* create it automatically.
|
|
39
|
+
* @returns {string | null} The root template category ID or null if not set.
|
|
40
|
+
* @static
|
|
41
|
+
*/
|
|
42
|
+
static get RootTemplateCategoryID(): string | null;
|
|
43
|
+
static set RootTemplateCategoryID(value: string | null);
|
|
44
|
+
/**
|
|
45
|
+
* This internal method can be overriden by subclasses
|
|
46
|
+
* to provide a custom way of getting or creating the root template category ID.
|
|
47
|
+
* The default implementation checks if the RootTemplateCategoryID is set, and
|
|
48
|
+
* if not, it attempts to find a category with the name "AI Prompts" (or you can override
|
|
49
|
+
* this with a Setting in the AI Prompts entity called "Root Template Category Name").
|
|
50
|
+
* If such a category does not exist, it creates one and sets the RootTemplateCategoryID.
|
|
51
|
+
* @returns {string} The root template category ID.
|
|
52
|
+
* @protected
|
|
53
|
+
*/
|
|
54
|
+
protected getOrCreateRootTemplateCategoryID(): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* This method creates a new root template category with the given name.
|
|
57
|
+
* It will create a new Template Category entity record and return its ID.
|
|
58
|
+
* @param name The name of the root template category to create.
|
|
59
|
+
* @returns {string} The ID of the newly created root template category.
|
|
60
|
+
* @protected
|
|
61
|
+
*/
|
|
62
|
+
protected createRootTemplateCategory(name: string): Promise<string>;
|
|
63
|
+
/**
|
|
64
|
+
* The reason for this override is to automatically create and update Templates and Template Contents
|
|
65
|
+
* entity records that are linked to this AI Prompt. The logic works like this:
|
|
66
|
+
* 1. If the TemplateID is null (initial state), and the TemplateText virtual property is not empty, a new Templates entity record and a new Template Contents entity record will be created and linked to this record's TemplateID property.
|
|
67
|
+
* 2. If the TemplateID is not null, the TemplateText changing will result in the linked Template Contents (via the TemplateID) being updated.
|
|
68
|
+
* @param options
|
|
69
|
+
*/
|
|
70
|
+
Save(options?: EntitySaveOptions): Promise<boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* This internal method is used to update the linked Template Contents entity record and can
|
|
73
|
+
* be overridden by subclasses to provide custom logic for updating the Template Contents.
|
|
74
|
+
* @param md
|
|
75
|
+
* @param tg
|
|
76
|
+
* @returns
|
|
77
|
+
*/
|
|
78
|
+
protected UpdateLinkedTemplateContents(md: IMetadataProvider, tg?: TransactionGroupBase): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* This method is used to create a linked Template record and Template Contents record for this AI Prompt.
|
|
81
|
+
* It is called automatically when the AI Prompt is saved and the TemplateID is null.
|
|
82
|
+
* It can be overridden by subclasses to provide custom logic for creating the linked Template.
|
|
83
|
+
* @returns {Promise<TemplateEntity | null>} The created Template entity record or null if not created.
|
|
84
|
+
*/
|
|
85
|
+
protected CreateLinkedTemplateAndTemplateContents(md: IMetadataProvider): Promise<TemplateEntity | null>;
|
|
86
|
+
/**
|
|
87
|
+
* This method retrieves the Template Content Type ID that will be used for creating new Template Contents
|
|
88
|
+
* @returns
|
|
89
|
+
*/
|
|
90
|
+
protected getTemplateContentTypeID(): Promise<string>;
|
|
91
|
+
}
|
|
92
|
+
export declare function LoadAIPromptEntityExtendedServerSubClass(): void;
|
|
93
|
+
//# sourceMappingURL=AIPromptEntityExtended.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AIPromptEntityExtended.server.d.ts","sourceRoot":"","sources":["../../src/custom/AIPromptEntityExtended.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,iBAAiB,EAAE,iBAAiB,EAA6B,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEzI,OAAO,EAA4E,cAAc,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEjK;;;;;;GAMG;AACH,qBACa,4BAA6B,SAAQ,sBAAsB;IACpE;;OAEG;IACH,OAAO,CAAC,aAAa,CAAc;IACnC;;;;OAIG;IACH,IAAW,YAAY,IAAI,MAAM,CAEhC;IACD,IAAW,YAAY,CAAC,KAAK,EAAE,MAAM,EAEpC;IAED,OAAO,CAAC,MAAM,CAAC,sBAAsB,CAAuB;IAC5D;;;;OAIG;IACH,WAAkB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CAEvD;IACD,WAAkB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAE3D;IAED,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAuB;IAC7D;;;;;;;;;;OAUG;IACH,WAAkB,sBAAsB,IAAI,MAAM,GAAG,IAAI,CAExD;IACD,WAAkB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAE5D;IAED;;;;;;;;;OASG;cACa,iCAAiC,IAAI,OAAO,CAAC,MAAM,CAAC;IA0BpE;;;;;;OAMG;cACa,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBzE;;;;;;OAMG;IACY,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAkBlE;;;;;;OAMG;cACa,4BAA4B,CAAC,EAAE,EAAE,iBAAiB,EAAE,EAAE,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0C7G;;;;;OAKG;cACa,uCAAuC,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IA8B9G;;;OAGG;cACa,wBAAwB,IAAI,OAAO,CAAC,MAAM,CAAC;CAoB9D;AAGD,wBAAgB,wCAAwC,SAAK"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var AIPromptEntityExtendedServer_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.LoadAIPromptEntityExtendedServerSubClass = exports.AIPromptEntityExtendedServer = void 0;
|
|
11
|
+
const core_1 = require("@memberjunction/core");
|
|
12
|
+
const global_1 = require("@memberjunction/global");
|
|
13
|
+
const core_entities_1 = require("@memberjunction/core-entities");
|
|
14
|
+
/**
|
|
15
|
+
* Server specific sub-class that handles the automatic creation and updating of
|
|
16
|
+
* Templates and Template Contents entity records linked to the AI Prompt.
|
|
17
|
+
* This class extends the AIPromptEntityExtended class and overrides the Save() method and
|
|
18
|
+
* also provides additional utility functionality that is used within Save() and can be overridden
|
|
19
|
+
* by subclasses to provide custom logic for creating or updating the linked Template and Template Contents, etc.
|
|
20
|
+
*/
|
|
21
|
+
let AIPromptEntityExtendedServer = AIPromptEntityExtendedServer_1 = class AIPromptEntityExtendedServer extends core_entities_1.AIPromptEntityExtended {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(...arguments);
|
|
24
|
+
/**
|
|
25
|
+
* private property to hold the template text.
|
|
26
|
+
*/
|
|
27
|
+
this._templateText = "";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Virtual property to hold the template text.
|
|
31
|
+
* This property is used to create or update the Template and Template Contents entity records automatically
|
|
32
|
+
* whenever the AIPrompt is saved.
|
|
33
|
+
*/
|
|
34
|
+
get TemplateText() {
|
|
35
|
+
return this._templateText;
|
|
36
|
+
}
|
|
37
|
+
set TemplateText(value) {
|
|
38
|
+
this._templateText = value;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The Template Content Type ID that will be used for creating new Template Contents
|
|
42
|
+
* linked to this AI Prompt. This is automatically set the first time the AI Prompt is saved
|
|
43
|
+
* and can be manually set ahead of that if desired to a different Template Content Types ID
|
|
44
|
+
*/
|
|
45
|
+
static get TemplateContentTypeID() {
|
|
46
|
+
return this._templateContentTypeID;
|
|
47
|
+
}
|
|
48
|
+
static set TemplateContentTypeID(value) {
|
|
49
|
+
this._templateContentTypeID = value;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The root category ID in the Template Categories entity that will be used for creating new
|
|
53
|
+
* templates linked to this AI Prompt. The way it works is we automatically create new sub-categories
|
|
54
|
+
* of the root category for each AI Prompt Category. Those sub-categories are below the
|
|
55
|
+
* root category. If RootTemplateCategoryID is null when the first such operation occurs upon
|
|
56
|
+
* Save() of a given AIPrompt in a given process space, we will load a category with the name
|
|
57
|
+
* of "AI Prompts" and use that as the root category ID. If such a category does not exist, we will
|
|
58
|
+
* create it automatically.
|
|
59
|
+
* @returns {string | null} The root template category ID or null if not set.
|
|
60
|
+
* @static
|
|
61
|
+
*/
|
|
62
|
+
static get RootTemplateCategoryID() {
|
|
63
|
+
return this._rootTemplateCategoryID;
|
|
64
|
+
}
|
|
65
|
+
static set RootTemplateCategoryID(value) {
|
|
66
|
+
this._rootTemplateCategoryID = value;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* This internal method can be overriden by subclasses
|
|
70
|
+
* to provide a custom way of getting or creating the root template category ID.
|
|
71
|
+
* The default implementation checks if the RootTemplateCategoryID is set, and
|
|
72
|
+
* if not, it attempts to find a category with the name "AI Prompts" (or you can override
|
|
73
|
+
* this with a Setting in the AI Prompts entity called "Root Template Category Name").
|
|
74
|
+
* If such a category does not exist, it creates one and sets the RootTemplateCategoryID.
|
|
75
|
+
* @returns {string} The root template category ID.
|
|
76
|
+
* @protected
|
|
77
|
+
*/
|
|
78
|
+
async getOrCreateRootTemplateCategoryID() {
|
|
79
|
+
if (AIPromptEntityExtendedServer_1.RootTemplateCategoryID) {
|
|
80
|
+
return AIPromptEntityExtendedServer_1.RootTemplateCategoryID;
|
|
81
|
+
}
|
|
82
|
+
// look for an existing root level category with the name "AI Prompts"
|
|
83
|
+
const e = this.EntityInfo;
|
|
84
|
+
const catName = e.Settings.find(s => s.Name.trim().toLowerCase() ===
|
|
85
|
+
"Root Template Category Name")?.Value || "AI Prompts";
|
|
86
|
+
const rv = new core_1.RunView(this.ProviderToUse);
|
|
87
|
+
const result = await rv.RunView({
|
|
88
|
+
EntityName: "Template Categories",
|
|
89
|
+
ExtraFilter: `Name='${catName}' AND ParentID IS NULL`,
|
|
90
|
+
OrderBy: "__mj_CreatedAt ASC" // first one
|
|
91
|
+
}, this.ContextCurrentUser);
|
|
92
|
+
if (result && result.Success && result.Results.length > 0) {
|
|
93
|
+
// we found an existing root category, cache it and return it
|
|
94
|
+
AIPromptEntityExtendedServer_1.RootTemplateCategoryID = result.Results[0].ID;
|
|
95
|
+
return AIPromptEntityExtendedServer_1.RootTemplateCategoryID;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// we did not find an existing root category, so create one
|
|
99
|
+
return await this.createRootTemplateCategory(catName);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* This method creates a new root template category with the given name.
|
|
104
|
+
* It will create a new Template Category entity record and return its ID.
|
|
105
|
+
* @param name The name of the root template category to create.
|
|
106
|
+
* @returns {string} The ID of the newly created root template category.
|
|
107
|
+
* @protected
|
|
108
|
+
*/
|
|
109
|
+
async createRootTemplateCategory(name) {
|
|
110
|
+
const md = this.ProviderToUse;
|
|
111
|
+
const rootCategory = await md.GetEntityObject("Template Categories", this.ContextCurrentUser);
|
|
112
|
+
rootCategory.NewRecord();
|
|
113
|
+
rootCategory.Name = name;
|
|
114
|
+
rootCategory.Description = "Root category for AI Prompts (Auto-Created by AI Prompts Entity)";
|
|
115
|
+
rootCategory.UserID = this.ContextCurrentUser.ID;
|
|
116
|
+
// save the category
|
|
117
|
+
if (rootCategory.Save()) {
|
|
118
|
+
AIPromptEntityExtendedServer_1.RootTemplateCategoryID = rootCategory.ID;
|
|
119
|
+
return AIPromptEntityExtendedServer_1.RootTemplateCategoryID;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// this is an error state, we failed to create the root category
|
|
123
|
+
throw new Error(`Failed to create root template category with name '${name}'. Please check the logs for more details.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The reason for this override is to automatically create and update Templates and Template Contents
|
|
128
|
+
* entity records that are linked to this AI Prompt. The logic works like this:
|
|
129
|
+
* 1. If the TemplateID is null (initial state), and the TemplateText virtual property is not empty, a new Templates entity record and a new Template Contents entity record will be created and linked to this record's TemplateID property.
|
|
130
|
+
* 2. If the TemplateID is not null, the TemplateText changing will result in the linked Template Contents (via the TemplateID) being updated.
|
|
131
|
+
* @param options
|
|
132
|
+
*/
|
|
133
|
+
async Save(options) {
|
|
134
|
+
const md = this.ProviderToUse;
|
|
135
|
+
// now do the work of creating or updating the Template and Template Contents
|
|
136
|
+
if (this.TemplateID === null && this.TemplateText?.trim().length > 0) {
|
|
137
|
+
const template = await this.CreateLinkedTemplateAndTemplateContents(md);
|
|
138
|
+
if (template) {
|
|
139
|
+
this.TemplateID = template.ID; // link the Template to this AI Prompt
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (this.TemplateID && this.TemplateText?.trim().length > 0) {
|
|
143
|
+
// we have a linked Template, so update the Template Contents associated with it
|
|
144
|
+
await this.UpdateLinkedTemplateContents(md);
|
|
145
|
+
}
|
|
146
|
+
// now save the AI Prompt itself
|
|
147
|
+
return await super.Save(options);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* This internal method is used to update the linked Template Contents entity record and can
|
|
151
|
+
* be overridden by subclasses to provide custom logic for updating the Template Contents.
|
|
152
|
+
* @param md
|
|
153
|
+
* @param tg
|
|
154
|
+
* @returns
|
|
155
|
+
*/
|
|
156
|
+
async UpdateLinkedTemplateContents(md, tg) {
|
|
157
|
+
// we have a linked Template, so update the Template Contents associated with it
|
|
158
|
+
if (!this.TemplateID) {
|
|
159
|
+
throw new Error("Cannot update linked Template Contents because TemplateID is null.");
|
|
160
|
+
}
|
|
161
|
+
const rv = new core_1.RunView(this.ProviderToUse);
|
|
162
|
+
const result = await rv.RunView({
|
|
163
|
+
EntityName: "Template Contents",
|
|
164
|
+
ExtraFilter: `TemplateID='${this.TemplateID}'`,
|
|
165
|
+
OrderBy: "__mj_CreatedAt ASC", // first one
|
|
166
|
+
ResultType: 'entity_object',
|
|
167
|
+
MaxRows: 1 // should only be one row
|
|
168
|
+
}, this.ContextCurrentUser);
|
|
169
|
+
let tc = undefined;
|
|
170
|
+
if (result && result.Success && result.Results.length > 0) {
|
|
171
|
+
// we found an existing Template Content, so update it
|
|
172
|
+
tc = result.Results[0];
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
tc = await md.GetEntityObject("Template Contents", this.ContextCurrentUser);
|
|
176
|
+
tc.NewRecord();
|
|
177
|
+
tc.TemplateID = this.TemplateID; // link to the existing Template
|
|
178
|
+
tc.TypeID = await this.getTemplateContentTypeID(); // use the Template Content Type ID for AI Prompts
|
|
179
|
+
tc.Priority = 1; // default priority
|
|
180
|
+
tc.IsActive = true; // set it to active by default
|
|
181
|
+
}
|
|
182
|
+
// for both new and existing Template Contents, we set the Template Text
|
|
183
|
+
tc.TemplateText = this.TemplateText; // set the Template Text
|
|
184
|
+
if (tg) {
|
|
185
|
+
tc.TransactionGroup = tg; // link to the transaction group if provided
|
|
186
|
+
}
|
|
187
|
+
// now save the Template Contents
|
|
188
|
+
if (await tc.Save()) {
|
|
189
|
+
// if we saved successfully, we can return
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// if we failed to save, throw an error
|
|
194
|
+
throw new Error("Failed to update linked Template Contents for the AI Prompt.");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* This method is used to create a linked Template record and Template Contents record for this AI Prompt.
|
|
199
|
+
* It is called automatically when the AI Prompt is saved and the TemplateID is null.
|
|
200
|
+
* It can be overridden by subclasses to provide custom logic for creating the linked Template.
|
|
201
|
+
* @returns {Promise<TemplateEntity | null>} The created Template entity record or null if not created.
|
|
202
|
+
*/
|
|
203
|
+
async CreateLinkedTemplateAndTemplateContents(md) {
|
|
204
|
+
// we have no linked template, but we have template text, so create a new template and template contents
|
|
205
|
+
const t = await md.GetEntityObject("Templates", this.ContextCurrentUser);
|
|
206
|
+
t.NewRecord();
|
|
207
|
+
t.Name = this.Name; // propagate the name
|
|
208
|
+
t.Description = "Template for AI Prompt: " + this.Name;
|
|
209
|
+
t.UserID = this.ContextCurrentUser.ID;
|
|
210
|
+
t.CategoryID = await this.getOrCreateRootTemplateCategoryID();
|
|
211
|
+
t.IsActive = true;
|
|
212
|
+
if (await t.Save()) {
|
|
213
|
+
// now create the template contents
|
|
214
|
+
const tc = await md.GetEntityObject("Template Contents", this.ContextCurrentUser);
|
|
215
|
+
tc.NewRecord();
|
|
216
|
+
tc.TemplateID = t.ID;
|
|
217
|
+
tc.TypeID = await this.getTemplateContentTypeID();
|
|
218
|
+
tc.TemplateText = this.TemplateText;
|
|
219
|
+
tc.Priority = 1; // default priority
|
|
220
|
+
tc.IsActive = true;
|
|
221
|
+
if (await tc.Save()) {
|
|
222
|
+
return t;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
throw new Error("Failed to save Template Contents for the new Template.");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
throw new Error("Failed to save new Template for the AI Prompt.");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* This method retrieves the Template Content Type ID that will be used for creating new Template Contents
|
|
234
|
+
* @returns
|
|
235
|
+
*/
|
|
236
|
+
async getTemplateContentTypeID() {
|
|
237
|
+
if (AIPromptEntityExtendedServer_1.TemplateContentTypeID) {
|
|
238
|
+
return AIPromptEntityExtendedServer_1.TemplateContentTypeID;
|
|
239
|
+
}
|
|
240
|
+
// we will use the TemplateEntityType for AI Prompts
|
|
241
|
+
const rv = this.ProviderToUse;
|
|
242
|
+
const result = await rv.RunView({
|
|
243
|
+
EntityName: "Template Content Types",
|
|
244
|
+
ExtraFilter: "Name='Text'",
|
|
245
|
+
OrderBy: "__mj_CreatedAt ASC" // first one
|
|
246
|
+
}, this.ContextCurrentUser);
|
|
247
|
+
if (result && result.Success && result.Results.length > 0) {
|
|
248
|
+
// we found a Template Content Type, return its ID but cache it first
|
|
249
|
+
AIPromptEntityExtendedServer_1.TemplateContentTypeID = result.Results[0].ID;
|
|
250
|
+
return result.Results[0].ID;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
throw new Error("Failed to find Template Content Type ID (Text) for AI Prompts.");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
exports.AIPromptEntityExtendedServer = AIPromptEntityExtendedServer;
|
|
258
|
+
AIPromptEntityExtendedServer._templateContentTypeID = null;
|
|
259
|
+
AIPromptEntityExtendedServer._rootTemplateCategoryID = null;
|
|
260
|
+
exports.AIPromptEntityExtendedServer = AIPromptEntityExtendedServer = AIPromptEntityExtendedServer_1 = __decorate([
|
|
261
|
+
(0, global_1.RegisterClass)(core_1.BaseEntity, "AI Prompts")
|
|
262
|
+
], AIPromptEntityExtendedServer);
|
|
263
|
+
function LoadAIPromptEntityExtendedServerSubClass() { }
|
|
264
|
+
exports.LoadAIPromptEntityExtendedServerSubClass = LoadAIPromptEntityExtendedServerSubClass;
|
|
265
|
+
//# sourceMappingURL=AIPromptEntityExtended.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AIPromptEntityExtended.server.js","sourceRoot":"","sources":["../../src/custom/AIPromptEntityExtended.server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DuplicateRunEntity } from "@memberjunction/core-entities";
|
|
2
|
+
export declare class DuplicateRunEntity_Server extends DuplicateRunEntity {
|
|
3
|
+
Save(): Promise<boolean>;
|
|
4
|
+
}
|
|
5
|
+
export declare function LoadDuplicateRunEntityServerSubClass(): void;
|
|
6
|
+
//# sourceMappingURL=DuplicateRunEntity.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DuplicateRunEntity.server.d.ts","sourceRoot":"","sources":["../../src/custom/DuplicateRunEntity.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,qBACa,yBAA0B,SAAQ,kBAAkB;IAChD,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;CAiBxC;AAED,wBAAgB,oCAAoC,SAAK"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.LoadDuplicateRunEntityServerSubClass = exports.DuplicateRunEntity_Server = void 0;
|
|
10
|
+
const core_1 = require("@memberjunction/core");
|
|
11
|
+
const global_1 = require("@memberjunction/global");
|
|
12
|
+
const core_entities_1 = require("@memberjunction/core-entities");
|
|
13
|
+
const ai_vector_dupe_1 = require("@memberjunction/ai-vector-dupe");
|
|
14
|
+
let DuplicateRunEntity_Server = class DuplicateRunEntity_Server extends core_entities_1.DuplicateRunEntity {
|
|
15
|
+
async Save() {
|
|
16
|
+
const saveResult = await super.Save();
|
|
17
|
+
if (saveResult && this.EndedAt === null) {
|
|
18
|
+
// do something
|
|
19
|
+
const duplicateRecordDetector = new ai_vector_dupe_1.DuplicateRecordDetector();
|
|
20
|
+
let request = new core_1.PotentialDuplicateRequest();
|
|
21
|
+
request.EntityID = this.EntityID;
|
|
22
|
+
request.ListID = this.SourceListID;
|
|
23
|
+
request.Options = {
|
|
24
|
+
DuplicateRunID: this.ID,
|
|
25
|
+
};
|
|
26
|
+
const response = await duplicateRecordDetector.getDuplicateRecords(request, this.ContextCurrentUser);
|
|
27
|
+
}
|
|
28
|
+
return saveResult;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
exports.DuplicateRunEntity_Server = DuplicateRunEntity_Server;
|
|
32
|
+
exports.DuplicateRunEntity_Server = DuplicateRunEntity_Server = __decorate([
|
|
33
|
+
(0, global_1.RegisterClass)(core_1.BaseEntity, 'Duplicate Runs')
|
|
34
|
+
], DuplicateRunEntity_Server);
|
|
35
|
+
function LoadDuplicateRunEntityServerSubClass() { }
|
|
36
|
+
exports.LoadDuplicateRunEntityServerSubClass = LoadDuplicateRunEntityServerSubClass;
|
|
37
|
+
//# sourceMappingURL=DuplicateRunEntity.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DuplicateRunEntity.server.js","sourceRoot":"","sources":["../../src/custom/DuplicateRunEntity.server.ts"],"names":[],"mappings":";;;;;;;;;AAAA,+CAA6E;AAC7E,mDAAuD;AACvD,iEAAmE;AACnE,mEAAyE;AAGlE,IAAM,yBAAyB,GAA/B,MAAM,yBAA0B,SAAQ,kCAAkB;IACtD,KAAK,CAAC,IAAI;QACb,MAAM,UAAU,GAAY,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;QAC/C,IAAI,UAAU,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YACtC,eAAe;YACf,MAAM,uBAAuB,GAA4B,IAAI,wCAAuB,EAAE,CAAC;YACvF,IAAI,OAAO,GAA8B,IAAI,gCAAyB,EAAE,CAAC;YACzE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;YACjC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC;YACnC,OAAO,CAAC,OAAO,GAAG;gBACd,cAAc,EAAE,IAAI,CAAC,EAAE;aAC1B,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,uBAAuB,CAAC,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACzG,CAAC;QAED,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAA;AAlBY,8DAAyB;oCAAzB,yBAAyB;IADrC,IAAA,sBAAa,EAAC,iBAAU,EAAE,gBAAgB,CAAC;GAC/B,yBAAyB,CAkBrC;AAED,SAAgB,oCAAoC,KAAI,CAAC;AAAzD,oFAAyD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ReportEntity } from "@memberjunction/core-entities";
|
|
2
|
+
export declare class ReportEntity_Server extends ReportEntity {
|
|
3
|
+
/**
|
|
4
|
+
* The server side Report Entity sub-class has a simple logic that will create a snapshot of the report when it is first created and each time it is modified, but only
|
|
5
|
+
* if it is either newly created or if the Configuration field has changed.
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
Save(): Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
export declare function LoadReportEntityServerSubClass(): void;
|
|
11
|
+
//# sourceMappingURL=reportEntity.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportEntity.server.d.ts","sourceRoot":"","sources":["../../src/custom/reportEntity.server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAA6C,MAAM,+BAA+B,CAAC;AAGxG,qBACa,mBAAoB,SAAQ,YAAY;IACjD;;;;OAIG;IACU,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;CAmHxC;AAED,wBAAgB,8BAA8B,SAAK"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.LoadReportEntityServerSubClass = exports.ReportEntity_Server = void 0;
|
|
10
|
+
const core_1 = require("@memberjunction/core");
|
|
11
|
+
const global_1 = require("@memberjunction/global");
|
|
12
|
+
const core_entities_1 = require("@memberjunction/core-entities");
|
|
13
|
+
let ReportEntity_Server = class ReportEntity_Server extends core_entities_1.ReportEntity {
|
|
14
|
+
/**
|
|
15
|
+
* The server side Report Entity sub-class has a simple logic that will create a snapshot of the report when it is first created and each time it is modified, but only
|
|
16
|
+
* if it is either newly created or if the Configuration field has changed.
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
async Save() {
|
|
20
|
+
try {
|
|
21
|
+
// first let's get the actual parsed object for the Configuration field
|
|
22
|
+
const config = this.Configuration && this.Configuration.length > 0 ? (0, global_1.SafeJSONParse)(this.Configuration) : null;
|
|
23
|
+
const oldTextConfig = this.GetFieldByName('Configuration')?.OldValue;
|
|
24
|
+
const oldConfig = oldTextConfig && oldTextConfig.length > 0 ? (0, global_1.SafeJSONParse)(oldTextConfig) : null;
|
|
25
|
+
if ((config && oldConfig) || (config && !this.IsSaved)) {
|
|
26
|
+
let createSnapshot = false;
|
|
27
|
+
let createReportVersion = false;
|
|
28
|
+
if (this.IsSaved) {
|
|
29
|
+
// existing record
|
|
30
|
+
// we nave a configuration and an old configuration, so we can compare them, cast them to their strong types first
|
|
31
|
+
const castedConfig = config;
|
|
32
|
+
const castedOldConfig = oldConfig;
|
|
33
|
+
// Next, we need to see if the data context has changed at all. We do this by comparing the # of items in the data context. a Data context's items are immutable and we only add new Data Context Items
|
|
34
|
+
// so we can simply check to see if we have new data context items or not
|
|
35
|
+
const dataContextChanged = Object.keys(castedConfig.dataContext || {})?.length !== Object.keys(castedOldConfig.dataContext || {})?.length;
|
|
36
|
+
// next up, we check to see if anything in the execution results changed. The simple way to do that is convert the execution results back to a string and compare those strings
|
|
37
|
+
const executionResultsChanged = JSON.stringify(castedConfig.executionResults) !== JSON.stringify(castedOldConfig.executionResults);
|
|
38
|
+
// next up we check to see if the analysis changed, which would also represent a "data" change
|
|
39
|
+
const analysisChanged = castedConfig.analysis !== castedOldConfig.analysis;
|
|
40
|
+
// finally, we check to see if the configuration itself has OUTSIDE of the data elements we just checked. We do that by deleting those properties from those objects, converting to strings
|
|
41
|
+
// and comparing them
|
|
42
|
+
delete castedConfig.dataContext;
|
|
43
|
+
delete castedConfig.analysis;
|
|
44
|
+
delete castedConfig.executionResults;
|
|
45
|
+
delete castedOldConfig.dataContext;
|
|
46
|
+
delete castedOldConfig.executionResults;
|
|
47
|
+
delete castedOldConfig.analysis;
|
|
48
|
+
// we can now compare the two objects
|
|
49
|
+
const configChanged = JSON.stringify(castedConfig) !== JSON.stringify(castedOldConfig);
|
|
50
|
+
// now our logic is this - if the DATA changed, but the configuration did NOT change, that means we want to create a new SNAPSHOT of the report - which is the data changing side
|
|
51
|
+
// if the configuration changed at all we wnat to create a new Report Version. Sometimes both changd and we do both
|
|
52
|
+
createSnapshot = dataContextChanged || executionResultsChanged || analysisChanged;
|
|
53
|
+
createReportVersion = configChanged;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// we have a new record set createSnapshot to true and createReportVersion to true
|
|
57
|
+
createSnapshot = true;
|
|
58
|
+
createReportVersion = true;
|
|
59
|
+
}
|
|
60
|
+
const wasNewRecord = !this.IsSaved;
|
|
61
|
+
const saveResult = await super.Save();
|
|
62
|
+
if (saveResult && (createSnapshot || createReportVersion)) {
|
|
63
|
+
// here we either have a new record or the configuration has changed, so we need to create a snapshot of the report
|
|
64
|
+
let success = true;
|
|
65
|
+
const md = new core_1.Metadata();
|
|
66
|
+
if (createReportVersion) {
|
|
67
|
+
const reportVersion = await md.GetEntityObject('MJ: Report Versions', this.ContextCurrentUser);
|
|
68
|
+
reportVersion.ReportID = this.ID;
|
|
69
|
+
// const get highest new version number already in DB
|
|
70
|
+
let newVersionNumber = 1;
|
|
71
|
+
if (!wasNewRecord) {
|
|
72
|
+
const rv = new core_1.RunView();
|
|
73
|
+
const rvResult = await rv.RunView({
|
|
74
|
+
Fields: ['VersionNumber'],
|
|
75
|
+
EntityName: 'MJ: Report Versions',
|
|
76
|
+
ExtraFilter: `ReportID = '${this.ID}'`,
|
|
77
|
+
MaxRows: 1,
|
|
78
|
+
OrderBy: 'VersionNumber DESC',
|
|
79
|
+
}, this.ContextCurrentUser);
|
|
80
|
+
if (rvResult.Success && rvResult.Results.length > 0) {
|
|
81
|
+
newVersionNumber = parseInt(rvResult.Results[0].VersionNumber) + 1;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
newVersionNumber = 1; // this is an error state as we previously had a saved report, BUT there were no saved versions for the report, so we are going to use 1 here, but let's log a warning
|
|
85
|
+
console.warn('ReportEntity_Server.Save(): No report versions found for report ID:', this.ID);
|
|
86
|
+
console.warn('ReportEntity_Server.Save(): Using version number 1 for new report version');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
reportVersion.Name = this.Name; // copy current name to the new version
|
|
90
|
+
reportVersion.Description = this.Description; // copy current description to the new version
|
|
91
|
+
reportVersion.VersionNumber = newVersionNumber;
|
|
92
|
+
reportVersion.Configuration = JSON.stringify(this.Configuration);
|
|
93
|
+
success = success && await reportVersion.Save();
|
|
94
|
+
if (!success) {
|
|
95
|
+
console.error('ReportEntity_Server.Save(): Error saving report version\n' + reportVersion.LatestResult.Message);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (createSnapshot) {
|
|
99
|
+
const snapshot = await md.GetEntityObject('Report Snapshots', this.ContextCurrentUser);
|
|
100
|
+
snapshot.ReportID = this.ID;
|
|
101
|
+
snapshot.UserID = this.ContextCurrentUser.ID;
|
|
102
|
+
// in the snapshot entity the ResultSet column is the Configuration column from the Report entity
|
|
103
|
+
snapshot.ResultSet = JSON.stringify(this.Configuration);
|
|
104
|
+
success = success && await snapshot.Save();
|
|
105
|
+
}
|
|
106
|
+
return success;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
return saveResult;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// no configuration or no parseable configuration, so we just call super.Save();
|
|
114
|
+
return super.Save();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
console.error('Error in ReportEntity_Server.Save():', e);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
exports.ReportEntity_Server = ReportEntity_Server;
|
|
124
|
+
exports.ReportEntity_Server = ReportEntity_Server = __decorate([
|
|
125
|
+
(0, global_1.RegisterClass)(core_1.BaseEntity, 'Reports')
|
|
126
|
+
], ReportEntity_Server);
|
|
127
|
+
function LoadReportEntityServerSubClass() { }
|
|
128
|
+
exports.LoadReportEntityServerSubClass = LoadReportEntityServerSubClass;
|
|
129
|
+
//# sourceMappingURL=reportEntity.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportEntity.server.js","sourceRoot":"","sources":["../../src/custom/reportEntity.server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { EntityInfo } from "@memberjunction/core";
|
|
2
|
+
import { AIModelEntityExtended, UserViewEntityExtended } from '@memberjunction/core-entities';
|
|
3
|
+
export declare class UserViewEntity_Server extends UserViewEntityExtended {
|
|
4
|
+
/**
|
|
5
|
+
* This property is hard-coded to true in this class because we DO support smart filters in this class. If you want to disable smart filters for a specific view you can override this property in your subclass and set it to false.
|
|
6
|
+
*/
|
|
7
|
+
protected get SmartFilterImplemented(): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Default implementation simply returns 'OpenAI' - override this in your subclass if you are using a different AI vendor.
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
12
|
+
protected get AIVendorName(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Default implementation simply grabs the first AI model that matches GetAIModelName().
|
|
15
|
+
* @returns
|
|
16
|
+
*/
|
|
17
|
+
protected GetAIModel(): Promise<AIModelEntityExtended>;
|
|
18
|
+
/**
|
|
19
|
+
* This method will use AI to return a valid WHERE clause based on the provided prompt. This is automatically called at the right time if the view has SmartFilterEnabled turned on and the SmartFilterPrompt is set. If you want
|
|
20
|
+
* to call this directly to get back a WHERE clause for other purposes you can call this method directly and provide both a prompt and the entity that the view is based on.
|
|
21
|
+
* @param prompt
|
|
22
|
+
*/
|
|
23
|
+
GenerateSmartFilterWhereClause(prompt: string, entityInfo: EntityInfo): Promise<{
|
|
24
|
+
whereClause: string;
|
|
25
|
+
userExplanation: string;
|
|
26
|
+
}>;
|
|
27
|
+
GenerateSysPrompt(entityInfo: EntityInfo): string;
|
|
28
|
+
}
|
|
29
|
+
export declare function LoadUserViewEntityServerSubClass(): void;
|
|
30
|
+
//# sourceMappingURL=userViewEntity.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userViewEntity.server.d.ts","sourceRoot":"","sources":["../../src/custom/userViewEntity.server.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,UAAU,EAAsB,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAiB,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAM5G,qBACa,qBAAsB,SAAQ,sBAAsB;IAC7D;;OAEG;IACH,cAAuB,sBAAsB,IAAI,OAAO,CAEvD;IAED;;;OAGG;IACH,SAAS,KAAK,YAAY,IAAI,MAAM,CAEnC;IAED;;;OAGG;cACa,UAAU,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAS5D;;;;OAIG;IACU,8BAA8B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAC,CAAC;IAuErI,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM;CAoH3D;AAGD,wBAAgB,gCAAgC,SAE/C"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.LoadUserViewEntityServerSubClass = exports.UserViewEntity_Server = void 0;
|
|
10
|
+
const global_1 = require("@memberjunction/global");
|
|
11
|
+
const core_1 = require("@memberjunction/core");
|
|
12
|
+
const core_entities_1 = require("@memberjunction/core-entities");
|
|
13
|
+
const ai_1 = require("@memberjunction/ai");
|
|
14
|
+
const aiengine_1 = require("@memberjunction/aiengine");
|
|
15
|
+
const ai_openai_1 = require("@memberjunction/ai-openai");
|
|
16
|
+
(0, ai_openai_1.LoadOpenAILLM)(); // this is to prevent tree shaking since the openai package is not directly used and rather instantiated dynamically in the LoadOpenAILLM function. Since no static code path exists tree shaking can result in this class being optimized out
|
|
17
|
+
let UserViewEntity_Server = class UserViewEntity_Server extends core_entities_1.UserViewEntityExtended {
|
|
18
|
+
/**
|
|
19
|
+
* This property is hard-coded to true in this class because we DO support smart filters in this class. If you want to disable smart filters for a specific view you can override this property in your subclass and set it to false.
|
|
20
|
+
*/
|
|
21
|
+
get SmartFilterImplemented() {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Default implementation simply returns 'OpenAI' - override this in your subclass if you are using a different AI vendor.
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
get AIVendorName() {
|
|
29
|
+
return 'OpenAI';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Default implementation simply grabs the first AI model that matches GetAIModelName().
|
|
33
|
+
* @returns
|
|
34
|
+
*/
|
|
35
|
+
async GetAIModel() {
|
|
36
|
+
await aiengine_1.AIEngine.Instance.Config(false, this.ContextCurrentUser); // most of the time this is already loaded, but just in case it isn't we will load it here
|
|
37
|
+
const models = aiengine_1.AIEngine.Instance.Models.filter(m => m.AIModelType.trim().toLowerCase() === 'llm' &&
|
|
38
|
+
m.Vendor.trim().toLowerCase() === this.AIVendorName.trim().toLowerCase());
|
|
39
|
+
// next, sort the models by the PowerRank field so that the highest power rank model is the first array element
|
|
40
|
+
models.sort((a, b) => b.PowerRank - a.PowerRank); // highest power rank first
|
|
41
|
+
return models[0];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* This method will use AI to return a valid WHERE clause based on the provided prompt. This is automatically called at the right time if the view has SmartFilterEnabled turned on and the SmartFilterPrompt is set. If you want
|
|
45
|
+
* to call this directly to get back a WHERE clause for other purposes you can call this method directly and provide both a prompt and the entity that the view is based on.
|
|
46
|
+
* @param prompt
|
|
47
|
+
*/
|
|
48
|
+
async GenerateSmartFilterWhereClause(prompt, entityInfo) {
|
|
49
|
+
try {
|
|
50
|
+
const model = await this.GetAIModel();
|
|
51
|
+
const llm = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(ai_1.BaseLLM, model.DriverClass, (0, ai_1.GetAIAPIKey)(model.DriverClass));
|
|
52
|
+
const chatParams = {
|
|
53
|
+
model: model.APINameOrName,
|
|
54
|
+
messages: [
|
|
55
|
+
{
|
|
56
|
+
role: 'system',
|
|
57
|
+
content: this.GenerateSysPrompt(entityInfo)
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
role: 'user',
|
|
61
|
+
content: `${prompt}`,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
const result = await llm.ChatCompletion(chatParams);
|
|
66
|
+
if (result && result.data) {
|
|
67
|
+
const llmResponse = result.data.choices[0].message.content;
|
|
68
|
+
if (llmResponse) {
|
|
69
|
+
// try to parse it as JSON
|
|
70
|
+
try {
|
|
71
|
+
const cleansed = (0, global_1.CleanJSON)(llmResponse);
|
|
72
|
+
if (!cleansed)
|
|
73
|
+
throw new Error('Invalid JSON response from AI: ' + llmResponse);
|
|
74
|
+
const parsed = JSON.parse(cleansed);
|
|
75
|
+
if (parsed.whereClause && parsed.whereClause.length > 0) {
|
|
76
|
+
// we have the where clause. Sometimes the LLM prefixes it with WHERE and somtimes not, we need to strip WHERE if it is there
|
|
77
|
+
const trimmed = parsed.whereClause.trim();
|
|
78
|
+
let ret = '';
|
|
79
|
+
if (trimmed.toLowerCase().startsWith('where '))
|
|
80
|
+
ret = trimmed.substring(6);
|
|
81
|
+
else
|
|
82
|
+
ret = parsed.whereClause;
|
|
83
|
+
return {
|
|
84
|
+
whereClause: ret,
|
|
85
|
+
userExplanation: parsed.userExplanationMessage
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
else if (parsed.whereClause !== undefined && parsed.whereClause !== null) {
|
|
89
|
+
return {
|
|
90
|
+
whereClause: '', // empty string is valid, it means no where clause
|
|
91
|
+
userExplanation: parsed.userExplanationMessage
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// if we get here, no whereClause property was provided by the LLM, that is an error
|
|
96
|
+
throw new Error('Invalid response from AI, no whereClause property found in response: ' + llmResponse);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
(0, core_1.LogError)(e);
|
|
101
|
+
throw new Error('Error parsing JSON response from AI: ' + llmResponse);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
throw new Error('Null response from AI');
|
|
106
|
+
}
|
|
107
|
+
else
|
|
108
|
+
throw new Error('No result returned from AI');
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
(0, core_1.LogError)(e);
|
|
112
|
+
throw e;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
GenerateSysPrompt(entityInfo) {
|
|
116
|
+
const processedViews = [entityInfo.BaseView];
|
|
117
|
+
const md = new core_1.Metadata();
|
|
118
|
+
const listsEntity = md.EntityByName("Lists");
|
|
119
|
+
const listDetailsEntity = md.EntityByName("List Details");
|
|
120
|
+
const gptSysPrompt = `You are an expert in SQL and Microsoft SQL Server.
|
|
121
|
+
You will be provided a user prompt representing how they want to filter the data.
|
|
122
|
+
You may *NOT* use JOINS, only sub-queries for related tables.
|
|
123
|
+
|
|
124
|
+
I am a bot and can only understand JSON. Your response must be parsable into this type:
|
|
125
|
+
const returnType = {
|
|
126
|
+
whereClause: string,
|
|
127
|
+
orderByClause: string
|
|
128
|
+
userExplanationMessage: string
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
In MemberJunction, we have a concept called "Entities" and these are metadata constructs that wrap SQL tables and views. The entity that we are currently
|
|
132
|
+
building a filter for is called "${entityInfo.Name}" and has an ID of "${entityInfo.ID}"
|
|
133
|
+
|
|
134
|
+
You won't be using this Entity name or ID in your SQL, unless you need to use it for List filtering (more on that below).
|
|
135
|
+
|
|
136
|
+
The view that the user is querying is called ${entityInfo.BaseView} and has these fields:
|
|
137
|
+
${entityInfo.Fields.map(f => {
|
|
138
|
+
let ret = `${f.Name} (${f.Type})`;
|
|
139
|
+
if (f.RelatedEntity) {
|
|
140
|
+
ret += ` (fkey to ${f.RelatedEntityBaseView})`;
|
|
141
|
+
}
|
|
142
|
+
return ret;
|
|
143
|
+
}).join(',')}`;
|
|
144
|
+
const fkeyFields = entityInfo.Fields.filter(f => f.RelatedEntity && f.RelatedEntity.length > 0);
|
|
145
|
+
const fkeyBaseViewsDistinct = fkeyFields.map(f => f.RelatedEntityBaseView).filter((v, i, a) => a.indexOf(v) === i);
|
|
146
|
+
const relationships = `
|
|
147
|
+
In addition, ${entityInfo.BaseView} has links to other views, as shown here, you can use these views in sub-queries to achieve the request from the user.
|
|
148
|
+
If there are multiple filters related to a single related view, attempt to combine them into a single sub-query for efficiency.
|
|
149
|
+
${
|
|
150
|
+
// this part returns a list of all the related views and the fields that are related to the current view via fkeys in the current view
|
|
151
|
+
fkeyBaseViewsDistinct.map(v => {
|
|
152
|
+
if (processedViews.indexOf(v) === -1) {
|
|
153
|
+
const e = md.Entities.find(e => e.BaseView === v);
|
|
154
|
+
if (e) {
|
|
155
|
+
processedViews.push(v); // already processed this view now, so we won't repeat it
|
|
156
|
+
return `* ${e.SchemaName}.${e.BaseView}: ${e.Fields.map(ef => {
|
|
157
|
+
return ef.Name + ' (' + ef.Type + ')';
|
|
158
|
+
}).join(',')}`;
|
|
159
|
+
}
|
|
160
|
+
else
|
|
161
|
+
return '';
|
|
162
|
+
}
|
|
163
|
+
else
|
|
164
|
+
return ''; // already did this at some point
|
|
165
|
+
}).join('\n')}
|
|
166
|
+
${
|
|
167
|
+
// this part returns a list of all the related views and the fields that are related to the current view fkeys in THOSE views
|
|
168
|
+
entityInfo.RelatedEntities.map(r => {
|
|
169
|
+
const e = md.Entities.find(e => e.Name === r.RelatedEntity);
|
|
170
|
+
if (e) {
|
|
171
|
+
if (processedViews.indexOf(e.BaseView) === -1) {
|
|
172
|
+
processedViews.push(e.BaseView); // note that we are processing this view now, so we won't repeat it
|
|
173
|
+
return `* ${e.SchemaName}.${e.BaseView}: ${e.Fields.map(ef => {
|
|
174
|
+
let ret = `${ef.Name} (${ef.Type})`;
|
|
175
|
+
if (ef.RelatedEntity) {
|
|
176
|
+
ret += ` (fkey to ${ef.RelatedEntityBaseView})`;
|
|
177
|
+
}
|
|
178
|
+
return ret;
|
|
179
|
+
}).join(',')}`;
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
return ''; // already did this at some point
|
|
183
|
+
}
|
|
184
|
+
else
|
|
185
|
+
return '';
|
|
186
|
+
}).join('\n')}
|
|
187
|
+
|
|
188
|
+
<IMPORTANT - LISTS FEATURE>
|
|
189
|
+
In addition to the above related views, a user may talk about "Lists" in their request. A List is a static set of records modeled in our database with the ${listsEntity.SchemaName}.vwLists view and
|
|
190
|
+
the ${listsEntity.SchemaName}.vwListDetails view. ${listsEntity.SchemaName}.vwLists contains the list "header" information with these columns:
|
|
191
|
+
${listsEntity.Fields.map(f => {
|
|
192
|
+
return f.Name + ' (' + f.SQLFullType + ')';
|
|
193
|
+
}).join(', ')}
|
|
194
|
+
The vwListDetails view contains the list "detail" information with these columns which is basically the records that are part of the list. ${listsEntity.SchemaName}.vwListDetails contains these columns:
|
|
195
|
+
${listDetailsEntity.Fields.map(f => {
|
|
196
|
+
return f.Name + ' (' + f.SQLFullType + ')';
|
|
197
|
+
}).join(', ')}.
|
|
198
|
+
|
|
199
|
+
If a user is asking to use a list in creating a view, you need to use a sub-query along these lines:
|
|
200
|
+
|
|
201
|
+
ID IN (SELECT RecordID FROM ${listsEntity.SchemaName}.vwListDetails WHERE ListID='My List ID')
|
|
202
|
+
|
|
203
|
+
In this example we're assuming the user has asked us to filter to include only records that are part of the list with the ID of 'My List ID'. In reality the prompt you will have will have a UUID/GUID type ID not a text string like this.
|
|
204
|
+
You can use any fields at the detail level filter the records and of course combine this type of list-oriented sub-query with other filters as appropriate to satisfy the user's request.
|
|
205
|
+
|
|
206
|
+
It is also possible that a user will provide ONLY the name of the list they want to filter on. If they provide the List ID, use it with a query like the above. However, if ONLY a List name is provided, you can do as follows: use this style of query with a join to the vwLists view (the list "header") to filter on the name
|
|
207
|
+
of the view or other header information if they want to filter on other list header attributes you can do this. Here is an example:
|
|
208
|
+
|
|
209
|
+
ID IN (SELECT ld.RecordID FROM ${listsEntity.SchemaName}.vwListDetails ld INNER JOIN ${listsEntity.SchemaName}.vwLists l ON ld.ListID=l.ID WHERE l.Name='My List Name')
|
|
210
|
+
|
|
211
|
+
No need to use table aliasing if you're just using the vwListDetails view, in that simple subquery it is automatic and unnecessary. If you need to join to the vwLists view, you can use the aliases "l" and "ld", as shown in the example above.
|
|
212
|
+
|
|
213
|
+
</IMPORTANT - LISTS FEATURE>
|
|
214
|
+
<IMPORTANT - OTHER VIEWS>
|
|
215
|
+
The user might reference other "views" in their request. In the user's terminology a view is what we call a "User View" in MemberJunction system-speak. The idea is a user might ask for a filter that includes or excludes
|
|
216
|
+
records from another view. Unlike Lists, which are STATIC sets of records, User Views are dynamic and can change based on the underlying data. So, what we need to do is use a sub-query that pulls in the SQL for the other view
|
|
217
|
+
The user will be referring to the other view by name, so what you need to do when generating SQL for this scenario is to simply embed a sub-query along the lines of this example, updated of course in the context of the user's request and the rest of any filters you are building:
|
|
218
|
+
ID IN ({%UserView "ViewID"%}) -- this is for filtering the primary key using other User Views for this entity: "${entityInfo.Name}"
|
|
219
|
+
AccountID IN ({%UserView "ViewID"%}) -- this is an example where the foreign key relationship for the AccountID field in this entity (${entityInfo.Name}) links to another entity hypothetically called "Accounts" and we want to use an Accounts view for filtering in some way
|
|
220
|
+
By including this sub-query in your generated WHERE clause, it will give me what I need to dynamically replace the {%UserView "View Name"%} with the actual SQL for the view that the user is referring to. Since that actual SQL
|
|
221
|
+
can change over time, I don't want to embed it directly here but rather have it templatized and each time it is run, I will pre-process the SQL to replace the template with the actual SQL for the view.
|
|
222
|
+
</IMPORTANT - OTHER VIEWS>
|
|
223
|
+
`;
|
|
224
|
+
return gptSysPrompt + (processedViews.length > 1 /*we always have 1 from the entity base view*/ ? relationships : '') + `
|
|
225
|
+
**** REMEMBER **** I am a BOT, do not return anything other than JSON to me or I will choke on your response!`;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
exports.UserViewEntity_Server = UserViewEntity_Server;
|
|
229
|
+
exports.UserViewEntity_Server = UserViewEntity_Server = __decorate([
|
|
230
|
+
(0, global_1.RegisterClass)(core_1.BaseEntity, 'User Views')
|
|
231
|
+
], UserViewEntity_Server);
|
|
232
|
+
function LoadUserViewEntityServerSubClass() {
|
|
233
|
+
}
|
|
234
|
+
exports.LoadUserViewEntityServerSubClass = LoadUserViewEntityServerSubClass;
|
|
235
|
+
//# sourceMappingURL=userViewEntity.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userViewEntity.server.js","sourceRoot":"","sources":["../../src/custom/userViewEntity.server.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './custom/AIPromptEntityExtended.server';
|
|
2
|
+
export * from './custom/DuplicateRunEntity.server';
|
|
3
|
+
export * from './custom/reportEntity.server';
|
|
4
|
+
export * from './custom/userViewEntity.server';
|
|
5
|
+
export declare function LoadCoreEntitiesServerSubClasses(): void;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAG/C,wBAAgB,gCAAgC,IAAI,IAAI,CAKvD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.LoadCoreEntitiesServerSubClasses = void 0;
|
|
18
|
+
const AIPromptEntityExtended_server_1 = require("./custom/AIPromptEntityExtended.server");
|
|
19
|
+
const DuplicateRunEntity_server_1 = require("./custom/DuplicateRunEntity.server");
|
|
20
|
+
const reportEntity_server_1 = require("./custom/reportEntity.server");
|
|
21
|
+
const userViewEntity_server_1 = require("./custom/userViewEntity.server");
|
|
22
|
+
__exportStar(require("./custom/AIPromptEntityExtended.server"), exports);
|
|
23
|
+
__exportStar(require("./custom/DuplicateRunEntity.server"), exports);
|
|
24
|
+
__exportStar(require("./custom/reportEntity.server"), exports);
|
|
25
|
+
__exportStar(require("./custom/userViewEntity.server"), exports);
|
|
26
|
+
// Call the stub functions to ensure that the custom subclasses are not tree shaken out.
|
|
27
|
+
function LoadCoreEntitiesServerSubClasses() {
|
|
28
|
+
(0, AIPromptEntityExtended_server_1.LoadAIPromptEntityExtendedServerSubClass)();
|
|
29
|
+
(0, DuplicateRunEntity_server_1.LoadDuplicateRunEntityServerSubClass)();
|
|
30
|
+
(0, reportEntity_server_1.LoadReportEntityServerSubClass)();
|
|
31
|
+
(0, userViewEntity_server_1.LoadUserViewEntityServerSubClass)();
|
|
32
|
+
}
|
|
33
|
+
exports.LoadCoreEntitiesServerSubClasses = LoadCoreEntitiesServerSubClasses;
|
|
34
|
+
LoadCoreEntitiesServerSubClasses();
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0FAAkG;AAClG,kFAA0F;AAC1F,sEAA8E;AAC9E,0EAAkF;AAElF,yEAAuD;AACvD,qEAAmD;AACnD,+DAA6C;AAC7C,iEAA+C;AAE/C,wFAAwF;AACxF,SAAgB,gCAAgC;IAC5C,IAAA,wEAAwC,GAAE,CAAC;IAC3C,IAAA,gEAAoC,GAAE,CAAC;IACvC,IAAA,oDAA8B,GAAE,CAAC;IACjC,IAAA,wDAAgC,GAAE,CAAC;AACvC,CAAC;AALD,4EAKC;AACD,gCAAgC,EAAE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memberjunction/core-entities-server",
|
|
3
|
+
"version": "2.46.0",
|
|
4
|
+
"description": "Entity subclasses for the metadata layer of MemberJunction that are only used on server side applications like MJAPI, distributed as part of each release of MemberJunction",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"/dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "ts-node-dev src/index.ts",
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
},
|
|
15
|
+
"author": "MemberJunction.com",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"ts-node-dev": "^2.0.0",
|
|
19
|
+
"typescript": "^5.4.5"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@memberjunction/core": "2.46.0",
|
|
23
|
+
"@memberjunction/global": "2.46.0",
|
|
24
|
+
"@memberjunction/core-entities": "2.46.0",
|
|
25
|
+
"@memberjunction/ai-vector-dupe": "2.46.0",
|
|
26
|
+
"@memberjunction/skip-types": "2.46.0",
|
|
27
|
+
"@memberjunction/aiengine": "2.46.0",
|
|
28
|
+
"zod": "^3.23.8"
|
|
29
|
+
}
|
|
30
|
+
}
|