@memberjunction/core-entities-server 6.2.0-edge.5 → 6.2.0-edge.7
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 +155 -100
- package/dist/custom/MJAIAgentExampleEntityServer.server.d.ts +2 -0
- package/dist/custom/MJAIAgentExampleEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJAIAgentExampleEntityServer.server.js +8 -5
- package/dist/custom/MJAIAgentExampleEntityServer.server.js.map +1 -1
- package/dist/custom/MJAIAgentNoteEntityServer.server.d.ts +2 -0
- package/dist/custom/MJAIAgentNoteEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJAIAgentNoteEntityServer.server.js +8 -5
- package/dist/custom/MJAIAgentNoteEntityServer.server.js.map +1 -1
- package/dist/custom/MJAIAgentRelationshipEntityServer.server.d.ts +17 -0
- package/dist/custom/MJAIAgentRelationshipEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIAgentRelationshipEntityServer.server.js +71 -0
- package/dist/custom/MJAIAgentRelationshipEntityServer.server.js.map +1 -0
- package/dist/custom/MJAIAgentSessionEntityServer.server.d.ts +8 -39
- package/dist/custom/MJAIAgentSessionEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJAIAgentSessionEntityServer.server.js +44 -44
- package/dist/custom/MJAIAgentSessionEntityServer.server.js.map +1 -1
- package/dist/custom/MJAIAgentUserPolicyEntityServer.server.d.ts +23 -0
- package/dist/custom/MJAIAgentUserPolicyEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIAgentUserPolicyEntityServer.server.js +53 -0
- package/dist/custom/MJAIAgentUserPolicyEntityServer.server.js.map +1 -0
- package/dist/custom/MJAIPromptEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJAIPromptEntityServer.server.js +2 -1
- package/dist/custom/MJAIPromptEntityServer.server.js.map +1 -1
- package/dist/custom/MJBrandKitEntityServer.server.d.ts +14 -0
- package/dist/custom/MJBrandKitEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJBrandKitEntityServer.server.js +75 -0
- package/dist/custom/MJBrandKitEntityServer.server.js.map +1 -0
- package/dist/custom/MJCompanyIntegrationEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJCompanyIntegrationEntityServer.server.js +1 -0
- package/dist/custom/MJCompanyIntegrationEntityServer.server.js.map +1 -1
- package/dist/custom/MJComponentEntityServer.server.d.ts +11 -0
- package/dist/custom/MJComponentEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJComponentEntityServer.server.js +30 -2
- package/dist/custom/MJComponentEntityServer.server.js.map +1 -1
- package/dist/custom/MJConversationEntityServer.server.d.ts +13 -0
- package/dist/custom/MJConversationEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJConversationEntityServer.server.js +33 -0
- package/dist/custom/MJConversationEntityServer.server.js.map +1 -0
- package/dist/custom/MJDashboardEntityServer.server.d.ts +14 -0
- package/dist/custom/MJDashboardEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJDashboardEntityServer.server.js +38 -0
- package/dist/custom/MJDashboardEntityServer.server.js.map +1 -0
- package/dist/custom/MJDuplicateRunEntityServer.server.d.ts +34 -2
- package/dist/custom/MJDuplicateRunEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJDuplicateRunEntityServer.server.js +103 -23
- package/dist/custom/MJDuplicateRunEntityServer.server.js.map +1 -1
- package/dist/custom/MJEntityAlternateBaseViewEntityServer.server.d.ts +73 -0
- package/dist/custom/MJEntityAlternateBaseViewEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJEntityAlternateBaseViewEntityServer.server.js +391 -0
- package/dist/custom/MJEntityAlternateBaseViewEntityServer.server.js.map +1 -0
- package/dist/custom/MJEntityDocumentEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJEntityDocumentEntityServer.server.js +1 -0
- package/dist/custom/MJEntityDocumentEntityServer.server.js.map +1 -1
- package/dist/custom/MJEntityFieldPermissionEntityServer.server.d.ts +96 -22
- package/dist/custom/MJEntityFieldPermissionEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJEntityFieldPermissionEntityServer.server.js +239 -55
- package/dist/custom/MJEntityFieldPermissionEntityServer.server.js.map +1 -1
- package/dist/custom/MJListDetailEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJListDetailEntityServer.server.js +1 -0
- package/dist/custom/MJListDetailEntityServer.server.js.map +1 -1
- package/dist/custom/MJQueryEntityServer.server.d.ts +184 -46
- package/dist/custom/MJQueryEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJQueryEntityServer.server.js +505 -159
- package/dist/custom/MJQueryEntityServer.server.js.map +1 -1
- package/dist/custom/MJQuerySQLEntityServer.server.d.ts +15 -18
- package/dist/custom/MJQuerySQLEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJQuerySQLEntityServer.server.js +34 -42
- package/dist/custom/MJQuerySQLEntityServer.server.js.map +1 -1
- package/dist/custom/MJTagEntityServer.server.d.ts +2 -0
- package/dist/custom/MJTagEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJTagEntityServer.server.js +13 -7
- package/dist/custom/MJTagEntityServer.server.js.map +1 -1
- package/dist/custom/MJUserRoleEntityServer.server.d.ts +74 -7
- package/dist/custom/MJUserRoleEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJUserRoleEntityServer.server.js +206 -20
- package/dist/custom/MJUserRoleEntityServer.server.js.map +1 -1
- package/dist/custom/MJUserRoutineEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJUserRoutineEntityServer.server.js +8 -1
- package/dist/custom/MJUserRoutineEntityServer.server.js.map +1 -1
- package/dist/custom/MJUserViewEntityServer.server.d.ts +17 -1
- package/dist/custom/MJUserViewEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJUserViewEntityServer.server.js +95 -7
- package/dist/custom/MJUserViewEntityServer.server.js.map +1 -1
- package/dist/custom/MJVectorIndexEntityServer.server.d.ts +2 -0
- package/dist/custom/MJVectorIndexEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJVectorIndexEntityServer.server.js +33 -0
- package/dist/custom/MJVectorIndexEntityServer.server.js.map +1 -1
- package/dist/custom/anonymousOwnershipKeyGuard.d.ts +42 -0
- package/dist/custom/anonymousOwnershipKeyGuard.d.ts.map +1 -0
- package/dist/custom/anonymousOwnershipKeyGuard.js +36 -0
- package/dist/custom/anonymousOwnershipKeyGuard.js.map +1 -0
- package/dist/custom/approvalStamp.d.ts +37 -0
- package/dist/custom/approvalStamp.d.ts.map +1 -0
- package/dist/custom/approvalStamp.js +67 -0
- package/dist/custom/approvalStamp.js.map +1 -0
- package/dist/custom/fieldPermissionAccess.d.ts +31 -0
- package/dist/custom/fieldPermissionAccess.d.ts.map +1 -0
- package/dist/custom/fieldPermissionAccess.js +38 -0
- package/dist/custom/fieldPermissionAccess.js.map +1 -0
- package/dist/custom/fieldPermissionReconciler.d.ts.map +1 -1
- package/dist/custom/fieldPermissionReconciler.js +12 -2
- package/dist/custom/fieldPermissionReconciler.js.map +1 -1
- package/dist/custom/query-extraction/childRowWriter.d.ts +77 -0
- package/dist/custom/query-extraction/childRowWriter.d.ts.map +1 -0
- package/dist/custom/query-extraction/childRowWriter.js +277 -0
- package/dist/custom/query-extraction/childRowWriter.js.map +1 -0
- package/dist/custom/query-extraction/compositionTargets.d.ts +40 -0
- package/dist/custom/query-extraction/compositionTargets.d.ts.map +1 -0
- package/dist/custom/query-extraction/compositionTargets.js +99 -0
- package/dist/custom/query-extraction/compositionTargets.js.map +1 -0
- package/dist/custom/query-extraction/declared.d.ts +79 -0
- package/dist/custom/query-extraction/declared.d.ts.map +1 -0
- package/dist/custom/query-extraction/declared.js +91 -0
- package/dist/custom/query-extraction/declared.js.map +1 -0
- package/dist/custom/query-extraction/describeFacts.d.ts +40 -0
- package/dist/custom/query-extraction/describeFacts.d.ts.map +1 -0
- package/dist/custom/query-extraction/describeFacts.js +225 -0
- package/dist/custom/query-extraction/describeFacts.js.map +1 -0
- package/dist/custom/query-extraction/descriptionPass.d.ts +23 -0
- package/dist/custom/query-extraction/descriptionPass.d.ts.map +1 -0
- package/dist/custom/query-extraction/descriptionPass.js +95 -0
- package/dist/custom/query-extraction/descriptionPass.js.map +1 -0
- package/dist/custom/query-extraction/extractQuery.d.ts +25 -0
- package/dist/custom/query-extraction/extractQuery.d.ts.map +1 -0
- package/dist/custom/query-extraction/extractQuery.js +146 -0
- package/dist/custom/query-extraction/extractQuery.js.map +1 -0
- package/dist/custom/query-extraction/extractionTypes.d.ts +133 -0
- package/dist/custom/query-extraction/extractionTypes.d.ts.map +1 -0
- package/dist/custom/query-extraction/extractionTypes.js +2 -0
- package/dist/custom/query-extraction/extractionTypes.js.map +1 -0
- package/dist/custom/query-extraction/index.d.ts +16 -0
- package/dist/custom/query-extraction/index.d.ts.map +1 -1
- package/dist/custom/query-extraction/index.js +11 -1
- package/dist/custom/query-extraction/index.js.map +1 -1
- package/dist/custom/query-extraction/pipeline.d.ts +6 -2
- package/dist/custom/query-extraction/pipeline.d.ts.map +1 -1
- package/dist/custom/query-extraction/pipeline.js +21 -14
- package/dist/custom/query-extraction/pipeline.js.map +1 -1
- package/dist/custom/query-extraction/resolve.d.ts +7 -2
- package/dist/custom/query-extraction/resolve.d.ts.map +1 -1
- package/dist/custom/query-extraction/resolve.js +41 -20
- package/dist/custom/query-extraction/resolve.js.map +1 -1
- package/dist/custom/query-extraction/sourceBinding.d.ts +57 -0
- package/dist/custom/query-extraction/sourceBinding.d.ts.map +1 -0
- package/dist/custom/query-extraction/sourceBinding.js +104 -0
- package/dist/custom/query-extraction/sourceBinding.js.map +1 -0
- package/dist/custom/query-extraction/sync.d.ts +28 -4
- package/dist/custom/query-extraction/sync.d.ts.map +1 -1
- package/dist/custom/query-extraction/sync.js +147 -59
- package/dist/custom/query-extraction/sync.js.map +1 -1
- package/dist/custom/query-extraction/templateFacts.d.ts +62 -0
- package/dist/custom/query-extraction/templateFacts.d.ts.map +1 -0
- package/dist/custom/query-extraction/templateFacts.js +229 -0
- package/dist/custom/query-extraction/templateFacts.js.map +1 -0
- package/dist/custom/query-extraction/types.d.ts +8 -0
- package/dist/custom/query-extraction/types.d.ts.map +1 -1
- package/dist/custom/query-refresh/QueryExtractionService.d.ts +54 -0
- package/dist/custom/query-refresh/QueryExtractionService.d.ts.map +1 -0
- package/dist/custom/query-refresh/QueryExtractionService.js +207 -0
- package/dist/custom/query-refresh/QueryExtractionService.js.map +1 -0
- package/dist/custom/query-refresh/QueryRefreshExtractionServerOperation.d.ts +28 -0
- package/dist/custom/query-refresh/QueryRefreshExtractionServerOperation.d.ts.map +1 -0
- package/dist/custom/query-refresh/QueryRefreshExtractionServerOperation.js +106 -0
- package/dist/custom/query-refresh/QueryRefreshExtractionServerOperation.js.map +1 -0
- package/dist/custom/query-refresh/index.d.ts +14 -0
- package/dist/custom/query-refresh/index.d.ts.map +1 -0
- package/dist/custom/query-refresh/index.js +10 -0
- package/dist/custom/query-refresh/index.js.map +1 -0
- package/dist/custom/query-refresh/queryRebind.d.ts +33 -0
- package/dist/custom/query-refresh/queryRebind.d.ts.map +1 -0
- package/dist/custom/query-refresh/queryRebind.js +85 -0
- package/dist/custom/query-refresh/queryRebind.js.map +1 -0
- package/dist/custom/query-refresh/refreshAuthorization.d.ts +29 -0
- package/dist/custom/query-refresh/refreshAuthorization.d.ts.map +1 -0
- package/dist/custom/query-refresh/refreshAuthorization.js +66 -0
- package/dist/custom/query-refresh/refreshAuthorization.js.map +1 -0
- package/dist/custom/query-refresh/refreshFormat.d.ts +16 -0
- package/dist/custom/query-refresh/refreshFormat.d.ts.map +1 -0
- package/dist/custom/query-refresh/refreshFormat.js +67 -0
- package/dist/custom/query-refresh/refreshFormat.js.map +1 -0
- package/dist/custom/query-refresh/refreshOrder.d.ts +25 -0
- package/dist/custom/query-refresh/refreshOrder.d.ts.map +1 -0
- package/dist/custom/query-refresh/refreshOrder.js +95 -0
- package/dist/custom/query-refresh/refreshOrder.js.map +1 -0
- package/dist/custom/query-refresh/refreshSelection.d.ts +15 -0
- package/dist/custom/query-refresh/refreshSelection.d.ts.map +1 -0
- package/dist/custom/query-refresh/refreshSelection.js +99 -0
- package/dist/custom/query-refresh/refreshSelection.js.map +1 -0
- package/dist/custom/query-refresh/refreshTypes.d.ts +81 -0
- package/dist/custom/query-refresh/refreshTypes.d.ts.map +1 -0
- package/dist/custom/query-refresh/refreshTypes.js +5 -0
- package/dist/custom/query-refresh/refreshTypes.js.map +1 -0
- package/dist/custom/systemUserGuardState.d.ts +73 -0
- package/dist/custom/systemUserGuardState.d.ts.map +1 -0
- package/dist/custom/systemUserGuardState.js +161 -0
- package/dist/custom/systemUserGuardState.js.map +1 -0
- package/dist/custom/template-extraction/parser.js +5 -2
- package/dist/custom/template-extraction/parser.js.map +1 -1
- package/dist/custom/template-extraction/types.d.ts +1 -1
- package/dist/custom/template-extraction/types.d.ts.map +1 -1
- package/dist/custom/util.d.ts +8 -0
- package/dist/custom/util.d.ts.map +1 -1
- package/dist/custom/util.js +18 -1
- package/dist/custom/util.js.map +1 -1
- package/dist/engines/QueryEngineServer.d.ts +4 -1
- package/dist/engines/QueryEngineServer.d.ts.map +1 -1
- package/dist/engines/QueryEngineServer.js +10 -7
- package/dist/engines/QueryEngineServer.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/package.json +30 -30
|
@@ -4,20 +4,56 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
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;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { BaseEntity, LogError, } from "@memberjunction/core";
|
|
8
|
-
import { MJQueryEntityExtended, QueryEngine } from "@memberjunction/core-entities";
|
|
9
|
-
import { Float32VectorToBase64, RegisterClass, MJGlobal, UUIDsEqual } from "@memberjunction/global";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
7
|
+
import { BaseEntity, BaseEntityResult, DatabaseProviderBase, LogError, ProviderBase, ProviderType, } from "@memberjunction/core";
|
|
8
|
+
import { ComputeQueryFingerprintAsync, MJQueryEntityExtended, QueryEngine, } from "@memberjunction/core-entities";
|
|
9
|
+
import { EscapeSQLString, Float32VectorToBase64, RegisterClass, MJGlobal, UUIDsEqual } from "@memberjunction/global";
|
|
10
|
+
import { GenericDatabaseProvider, UserCache } from "@memberjunction/generic-database-provider";
|
|
11
|
+
import { CompositionFullPath } from "@memberjunction/sql-parser";
|
|
12
|
+
import { EmbedTextLocalHelper, IsLocalEmbeddingStaleHelper } from "./util.js";
|
|
13
|
+
import { CleanupQueryData, ConvertTSQLToPostgreSQL, CollectDeclaredQueryChildren, HasDeclaredQueryChildren, FindReplacedRows, QueueReplacedRowDeletion, QueryChildNameKey, QueryEntityKey, NO_DECLARED_QUERY_CHILDREN, ComputeQueryExtraction, WriteExtractedChildRows, ApplyFieldFacts, RunDescriptionPass, SQLUsesTemplate, ReadCompositionTargetsAsync, MergeCompositionCandidates, } from "./query-extraction/index.js";
|
|
14
|
+
import { ApplyApprovalRulesAsync, ApprovalRefusalResult } from "./approvalStamp.js";
|
|
15
|
+
/** Whether `provider` can read views and create entity objects, which is all extraction's writes need. */
|
|
16
|
+
function IsExtractionProvider(provider) {
|
|
17
|
+
if (provider === null || typeof provider !== 'object')
|
|
18
|
+
return false;
|
|
19
|
+
return typeof Reflect.get(provider, 'RunView') === 'function' && typeof Reflect.get(provider, 'GetEntityObject') === 'function';
|
|
20
|
+
}
|
|
15
21
|
/**
|
|
16
|
-
* Server-side query entity with embedding generation, SQL extraction
|
|
17
|
-
*
|
|
18
|
-
*
|
|
22
|
+
* Server-side query entity with embedding generation, SQL extraction, and dialect auto-conversion.
|
|
23
|
+
* Extends `MJQueryEntityExtended`, which provides the child-relationship getters and business
|
|
24
|
+
* logic available on both client and server.
|
|
25
|
+
*
|
|
26
|
+
* **Extraction.** A save of a new query, of a query whose SQL changed, or with
|
|
27
|
+
* {@link ExtractionOverride} `Force`, establishes the query's facts (`ComputeQueryExtraction`):
|
|
28
|
+
* its parameters, compositions and the entities it reads from MJ's parser, and its output fields
|
|
29
|
+
* from the database server's description of each render. Save-time rule errors and repeated
|
|
30
|
+
* output names refuse a new or edited query, with the reasons in the save's result. Once the save
|
|
31
|
+
* of the query row succeeds, the child rows are brought in line with the facts as the system user,
|
|
32
|
+
* keeping what people authored (`WriteExtractedChildRows`), and the query's `UsesTemplate`,
|
|
33
|
+
* `ExtractedSQLHash`, `ExtractionStatus` and `ExtractionMessage` record the run. Empty
|
|
34
|
+
* descriptions are then filled by an LLM after the save's transaction commits, unless the save
|
|
35
|
+
* skips AI actions.
|
|
19
36
|
*/
|
|
20
37
|
let MJQueryEntityServer = class MJQueryEntityServer extends MJQueryEntityExtended {
|
|
38
|
+
constructor() {
|
|
39
|
+
super(...arguments);
|
|
40
|
+
/**
|
|
41
|
+
* What the next save does about extraction: `Auto` (the default) extracts when the query is new
|
|
42
|
+
* or its SQL changed; `Skip` does not extract, so the save-time rules are not applied and the
|
|
43
|
+
* child rows, the Extraction columns and `UsesTemplate` stay as they are (for bulk loaders and
|
|
44
|
+
* fixtures that write those themselves); `Force` extracts even when the SQL did not change.
|
|
45
|
+
* Reset to `Auto` after each save.
|
|
46
|
+
*/
|
|
47
|
+
this.ExtractionOverride = 'Auto';
|
|
48
|
+
/** The SQL a re-extraction reads instead of the cached platform variant (set by {@link RerunExtraction}). */
|
|
49
|
+
this.platformSQLOverride = null;
|
|
50
|
+
/** The facts an explicit refresh already established, which its save writes instead of extracting again. */
|
|
51
|
+
this.presetExtraction = null;
|
|
52
|
+
/** Whether the running save is an explicit refresh ({@link ApplyRefreshAsync}). */
|
|
53
|
+
this.refreshing = false;
|
|
54
|
+
/** What the last save's child-row writes changed; `null` when it wrote none. */
|
|
55
|
+
this.lastWriteSummary = null;
|
|
56
|
+
}
|
|
21
57
|
// ─── Embedding Methods ───────────────────────────────────────────────────────
|
|
22
58
|
/**
|
|
23
59
|
* Simple proxy to local helper method for embeddings. Needed for BaseEntity sub-classes that want to use embeddings built into BaseEntity
|
|
@@ -25,6 +61,10 @@ let MJQueryEntityServer = class MJQueryEntityServer extends MJQueryEntityExtende
|
|
|
25
61
|
async EmbedTextLocal(textToEmbed) {
|
|
26
62
|
return EmbedTextLocalHelper(this, textToEmbed);
|
|
27
63
|
}
|
|
64
|
+
/** A stored vector from a model other than the default local one is re-embedded on save. */
|
|
65
|
+
async IsEmbeddingStale(embeddingModelID) {
|
|
66
|
+
return IsLocalEmbeddingStaleHelper(this, embeddingModelID);
|
|
67
|
+
}
|
|
28
68
|
/**
|
|
29
69
|
* Generates an embedding from composite text (Name + UserQuestion + Description) for richer semantic search.
|
|
30
70
|
* Stores the vector in EmbeddingVector (JSON) and EmbeddingVectorBinary (float32 bytes, base64), and the
|
|
@@ -34,8 +74,17 @@ let MJQueryEntityServer = class MJQueryEntityServer extends MJQueryEntityExtende
|
|
|
34
74
|
* embedder error — all three columns are cleared, so a query whose text changed never keeps a vector
|
|
35
75
|
* computed from its old text. An embedder error is logged and does not fail the save; the query simply
|
|
36
76
|
* has no semantic match until its next successful embedding.
|
|
77
|
+
*
|
|
78
|
+
* Does nothing when {@link SkipEmbeddings} is set (a bulk load computes them later), so the stored
|
|
79
|
+
* vector is left as it is.
|
|
80
|
+
*
|
|
81
|
+
* @param textChanged - false when the text is unchanged and only the stored vector's model is stale:
|
|
82
|
+
* a failed embed then keeps the existing vector, which still matches the text.
|
|
37
83
|
*/
|
|
38
|
-
async GenerateCompositeEmbedding() {
|
|
84
|
+
async GenerateCompositeEmbedding(textChanged = true) {
|
|
85
|
+
if (this.SkipEmbeddings) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
39
88
|
const parts = [
|
|
40
89
|
this.Name || '',
|
|
41
90
|
this.UserQuestion || '',
|
|
@@ -58,7 +107,9 @@ let MJQueryEntityServer = class MJQueryEntityServer extends MJQueryEntityExtende
|
|
|
58
107
|
catch (error) {
|
|
59
108
|
LogError(`[MJQueryEntityServer] Embedding refresh failed for query "${this.Name}": ${error instanceof Error ? error.message : String(error)}`);
|
|
60
109
|
}
|
|
61
|
-
|
|
110
|
+
if (textChanged) {
|
|
111
|
+
this.clearEmbedding();
|
|
112
|
+
}
|
|
62
113
|
}
|
|
63
114
|
/** Clears the stored vector (both forms) and the model it was computed with. */
|
|
64
115
|
clearEmbedding() {
|
|
@@ -66,53 +117,243 @@ let MJQueryEntityServer = class MJQueryEntityServer extends MJQueryEntityExtende
|
|
|
66
117
|
this.EmbeddingVectorBinary = null;
|
|
67
118
|
this.EmbeddingModelID = null;
|
|
68
119
|
}
|
|
120
|
+
/** Re-embeds when the embedded text changed, or when the stored vector came from a model other than the default local one. */
|
|
121
|
+
async refreshEmbeddingAsync() {
|
|
122
|
+
if (this.SkipEmbeddings)
|
|
123
|
+
return;
|
|
124
|
+
const textChanged = !this.IsSaved || ['Name', 'Description', 'UserQuestion'].some(f => this.GetFieldByName(f).Dirty);
|
|
125
|
+
if (textChanged || (this.EmbeddingVector != null && await this.IsEmbeddingStale(this.EmbeddingModelID))) {
|
|
126
|
+
await this.GenerateCompositeEmbedding(textChanged);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
69
129
|
// ─── Save / Delete Overrides ─────────────────────────────────────────────────
|
|
70
130
|
async Save(options) {
|
|
131
|
+
// Over a client provider (a process that loads this package but saves over GraphQL), the server
|
|
132
|
+
// the save reaches applies the approval rules and extracts; doing it here too would write the
|
|
133
|
+
// child rows twice, the second time from the client.
|
|
134
|
+
if (this.savesOverNetwork)
|
|
135
|
+
return super.Save(options);
|
|
136
|
+
let approving;
|
|
71
137
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
else if (!this.Description || this.Description.trim().length === 0) {
|
|
83
|
-
this.EmbeddingVector = null;
|
|
84
|
-
this.EmbeddingVectorBinary = null;
|
|
85
|
-
this.EmbeddingModelID = null;
|
|
86
|
-
}
|
|
87
|
-
// Save the query first without AI processing
|
|
88
|
-
const saveResult = await super.Save(options);
|
|
89
|
-
if (!saveResult) {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
// Extract and sync parameters AFTER saving, outside of any transaction
|
|
93
|
-
if (shouldExtractData && this.SQL && this.SQL.trim().length > 0) {
|
|
94
|
-
await this.extractAndSyncDataAsync(options?.SkipEntityAIActions === true);
|
|
95
|
-
await this.autoConvertDialectsAsync();
|
|
96
|
-
}
|
|
97
|
-
else if (!this.SQL || this.SQL.trim().length === 0) {
|
|
98
|
-
this.UsesTemplate = false;
|
|
99
|
-
await this.cleanupEmptyQueryAsync();
|
|
100
|
-
}
|
|
101
|
-
return true;
|
|
138
|
+
// Approving is a server act; a ReplayOnly save writes nothing, so it has nothing to stamp.
|
|
139
|
+
approving = !options?.ReplayOnly && await ApplyApprovalRulesAsync(this, 'Status', this.Status);
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
this.RegisterResultHistoryEntry(ApprovalRefusalResult(this, e));
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return await this.saveWithExtractionAsync(options, approving);
|
|
102
147
|
}
|
|
103
148
|
catch (e) {
|
|
104
149
|
LogError('Failed to save query:', e);
|
|
105
150
|
this.LatestResult?.Errors.push(e);
|
|
106
151
|
return false;
|
|
107
152
|
}
|
|
153
|
+
finally {
|
|
154
|
+
this.ExtractionOverride = 'Auto';
|
|
155
|
+
this.platformSQLOverride = null;
|
|
156
|
+
this.presetExtraction = null;
|
|
157
|
+
this.refreshing = false;
|
|
158
|
+
}
|
|
108
159
|
}
|
|
109
|
-
|
|
160
|
+
/** Whether this object saves through a network (client) provider rather than to a database. */
|
|
161
|
+
get savesOverNetwork() {
|
|
162
|
+
const provider = this.ProviderToUse;
|
|
163
|
+
return provider instanceof ProviderBase && provider.ProviderType === ProviderType.Network;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The save: facts first (a refusal writes nothing), then the query row (a graph save with any
|
|
167
|
+
* declared children), then the child rows extraction derives, then the approval fingerprint.
|
|
168
|
+
*/
|
|
169
|
+
async saveWithExtractionAsync(options, approving) {
|
|
170
|
+
const declared = await this.PrepareDeclaredChildrenAsync(options);
|
|
171
|
+
const plan = this.planExtraction(options, HasDeclaredQueryChildren(declared));
|
|
172
|
+
const extraction = plan.Run ? await this.computeExtractionAsync(plan.Mode) : null;
|
|
173
|
+
if (extraction?.Refusal) {
|
|
174
|
+
this.RegisterResultHistoryEntry(this.refusalResult(extraction.Refusal));
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
this.applyRunColumns(extraction);
|
|
178
|
+
if (extraction)
|
|
179
|
+
this.applyFactsToDeclaredFields(extraction);
|
|
180
|
+
await this.refreshEmbeddingAsync();
|
|
181
|
+
const postCommit = this.capturePostCommitToken();
|
|
182
|
+
this.lastWriteSummary = null;
|
|
183
|
+
if (!await super.Save(options)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (extraction) {
|
|
187
|
+
await this.writeExtractionAsync(extraction, options, postCommit);
|
|
188
|
+
// A refresh brings the derived rows in line with the SQL; it never writes SQL, a dialect variant's included.
|
|
189
|
+
if (!this.refreshing)
|
|
190
|
+
await this.autoConvertDialectsAsync();
|
|
191
|
+
}
|
|
192
|
+
else if (!this.hasSQL) {
|
|
193
|
+
await this.cleanupEmptyQueryAsync();
|
|
194
|
+
}
|
|
195
|
+
// The child and dialect rows are rewritten after the Query row, so the approved fingerprint is
|
|
196
|
+
// taken last, from the definition as stored.
|
|
197
|
+
if (approving) {
|
|
198
|
+
await this.stampApprovalFingerprintAsync(options);
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
get hasSQL() {
|
|
203
|
+
return !!this.SQL && this.SQL.trim().length > 0;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Whether this save extracts, and under which rules: a new or edited query extracts under the
|
|
207
|
+
* save-time rules; a save that writes declared child rows, or a `Force` override, re-extracts an
|
|
208
|
+
* unchanged query under the refresh rules, so the facts of rows it writes are corrected again.
|
|
209
|
+
*/
|
|
210
|
+
planExtraction(options, writesDeclaredChildren) {
|
|
211
|
+
const edited = !this.IsSaved || this.GetFieldByName('SQL').Dirty;
|
|
212
|
+
if (options?.ReplayOnly || !this.hasSQL || this.ExtractionOverride === 'Skip') {
|
|
213
|
+
return { Run: false, Mode: 'Save' };
|
|
214
|
+
}
|
|
215
|
+
return { Run: edited || writesDeclaredChildren || this.ExtractionOverride === 'Force', Mode: edited ? 'Save' : 'Refresh' };
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Sets the columns a save records about the SQL before the row is written. `UsesTemplate` is a
|
|
219
|
+
* lexical fact and follows every SQL change; it is never changed by a failed extraction. A save
|
|
220
|
+
* with {@link ExtractionOverride} `Skip` leaves it as the caller set it, like the other
|
|
221
|
+
* extraction results.
|
|
222
|
+
*/
|
|
223
|
+
applyRunColumns(extraction) {
|
|
224
|
+
if (extraction) {
|
|
225
|
+
this.UsesTemplate = extraction.UsesTemplate;
|
|
226
|
+
this.ExtractedSQLHash = extraction.Hash;
|
|
227
|
+
this.ExtractionStatus = extraction.Status;
|
|
228
|
+
this.ExtractionMessage = extraction.Messages.length > 0 ? extraction.Messages.join('\n') : null;
|
|
229
|
+
}
|
|
230
|
+
else if (!this.hasSQL) {
|
|
231
|
+
this.UsesTemplate = false;
|
|
232
|
+
}
|
|
233
|
+
else if (this.ExtractionOverride !== 'Skip' && (!this.IsSaved || this.GetFieldByName('SQL').Dirty)) {
|
|
234
|
+
this.UsesTemplate = this.lexicalUsesTemplate();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Gives each field row this save declares the facts extraction established for it, before the
|
|
239
|
+
* graph is written. Facts follow the SQL and the database server whatever a caller declares, so
|
|
240
|
+
* a declaration that carries facts (types, nullability, sequence, source) for another platform is
|
|
241
|
+
* corrected before it is written instead of being written and then corrected, and a new declared
|
|
242
|
+
* row, which may declare only its name and annotations, gets the facts its columns require.
|
|
243
|
+
*/
|
|
244
|
+
applyFactsToDeclaredFields(extraction) {
|
|
245
|
+
if (!extraction.Fields)
|
|
246
|
+
return;
|
|
247
|
+
const facts = new Map(extraction.Fields.map(f => [f.Name.toLowerCase(), f]));
|
|
248
|
+
for (const row of this.DeclaredFields?.Items ?? []) {
|
|
249
|
+
const fact = facts.get((row.Name ?? '').toLowerCase());
|
|
250
|
+
if (fact)
|
|
251
|
+
ApplyFieldFacts(row, fact);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** Whether the SQL holds a template tag outside comments, read by the lexer. */
|
|
255
|
+
lexicalUsesTemplate() {
|
|
256
|
+
return SQLUsesTemplate(this.SQL ?? '', this.extractionPlatform);
|
|
257
|
+
}
|
|
258
|
+
/** The save's result entry for a refused save, carrying the reasons. */
|
|
259
|
+
refusalResult(reason) {
|
|
260
|
+
const result = new BaseEntityResult();
|
|
261
|
+
result.Success = false;
|
|
262
|
+
result.Type = this.IsSaved ? 'update' : 'create';
|
|
263
|
+
result.Message = reason;
|
|
264
|
+
result.StartedAt = new Date();
|
|
265
|
+
result.EndedAt = new Date();
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The declared-rows-win hook, run before the save writes anything.
|
|
270
|
+
*
|
|
271
|
+
* A caller can save a query together with child rows it declares, through the `Parameters`,
|
|
272
|
+
* `Fields` and `Entities` collections. When a NEW declared row has the key of a stored row with a
|
|
273
|
+
* different ID whose `DetectionMethod` is not `Manual` (a row extraction created), the stored row
|
|
274
|
+
* is deleted inside the same graph save, before the declared row is inserted. The declared row
|
|
275
|
+
* keeps the ID the caller gave it, which is what makes a metadata file that names its child IDs
|
|
276
|
+
* converge instead of colliding with rows extraction wrote earlier. A stored `Manual` row is never
|
|
277
|
+
* deleted here; a declared row that collides with one fails the save on the database's own
|
|
278
|
+
* uniqueness rule where there is one.
|
|
279
|
+
*
|
|
280
|
+
* Extraction then runs after `super.Save()`, sees the declared rows already written, and treats
|
|
281
|
+
* them like any stored row: their facts are corrected from the SQL, their authored annotations
|
|
282
|
+
* kept.
|
|
283
|
+
*
|
|
284
|
+
* @param options - The save's options. A `ReplayOnly` save, or one that skips related
|
|
285
|
+
* collections, writes no declared rows, so nothing is declared.
|
|
286
|
+
* @returns The keys of the child rows this save declares.
|
|
287
|
+
*/
|
|
288
|
+
async PrepareDeclaredChildrenAsync(options) {
|
|
289
|
+
if (options?.ReplayOnly || options?.SkipRelatedCollections) {
|
|
290
|
+
return NO_DECLARED_QUERY_CHILDREN;
|
|
291
|
+
}
|
|
292
|
+
const collections = { Parameters: this.DeclaredParameters, Fields: this.DeclaredFields, Entities: this.DeclaredEntities };
|
|
293
|
+
const declared = CollectDeclaredQueryChildren(collections);
|
|
294
|
+
if (this.IsSaved && HasDeclaredQueryChildren(declared)) {
|
|
295
|
+
await this.queueReplacedRowDeletionsAsync();
|
|
296
|
+
}
|
|
297
|
+
return declared;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Reads the stored child rows from the database (never a cache, which inside a transaction can
|
|
301
|
+
* miss rows written earlier in it) and queues the extraction-created rows each new declared row
|
|
302
|
+
* replaces for deletion in the graph save.
|
|
303
|
+
*/
|
|
304
|
+
async queueReplacedRowDeletionsAsync() {
|
|
305
|
+
const filter = `QueryID='${EscapeSQLString(this.ID)}'`;
|
|
306
|
+
const [parameters, fields, entities] = await this.RunViewProviderToUse.RunViews([
|
|
307
|
+
{ EntityName: 'MJ: Query Parameters', ExtraFilter: filter, ResultType: 'entity_object', BypassCache: true, IgnoreMaxRows: true },
|
|
308
|
+
{ EntityName: 'MJ: Query Fields', ExtraFilter: filter, ResultType: 'entity_object', BypassCache: true, IgnoreMaxRows: true },
|
|
309
|
+
{ EntityName: 'MJ: Query Entities', ExtraFilter: filter, ResultType: 'entity_object', BypassCache: true, IgnoreMaxRows: true },
|
|
310
|
+
], this.ContextCurrentUser);
|
|
311
|
+
if (!parameters?.Success || !fields?.Success || !entities?.Success) {
|
|
312
|
+
throw new Error(`could not read the query's stored child rows: ${parameters?.ErrorMessage || fields?.ErrorMessage || entities?.ErrorMessage}`);
|
|
313
|
+
}
|
|
314
|
+
const newItems = (items) => items.filter(i => !i.IsSaved);
|
|
315
|
+
const byName = (row) => QueryChildNameKey(row.Name);
|
|
316
|
+
for (const row of FindReplacedRows(newItems(this.DeclaredParameters.Items), parameters.Results, byName)) {
|
|
317
|
+
QueueReplacedRowDeletion(this.DeclaredParameters, row);
|
|
318
|
+
}
|
|
319
|
+
for (const row of FindReplacedRows(newItems(this.DeclaredFields.Items), fields.Results, byName)) {
|
|
320
|
+
QueueReplacedRowDeletion(this.DeclaredFields, row);
|
|
321
|
+
}
|
|
322
|
+
for (const row of FindReplacedRows(newItems(this.DeclaredEntities.Items), entities.Results, QueryEntityKey)) {
|
|
323
|
+
QueueReplacedRowDeletion(this.DeclaredEntities, row);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Stores the fingerprint of the definition just approved: this row's SQL, template flag and
|
|
328
|
+
* external source, with its dialect and parameter rows read back from the database. Until this
|
|
329
|
+
* write lands the approval is not current, so a failure here fails closed.
|
|
330
|
+
*/
|
|
331
|
+
async stampApprovalFingerprintAsync(options) {
|
|
110
332
|
try {
|
|
111
|
-
|
|
112
|
-
if (!
|
|
113
|
-
|
|
333
|
+
this.ApprovedFingerprint = await ComputeQueryFingerprintAsync(this, await this.loadDefinitionRowsAsync());
|
|
334
|
+
if (!await super.Save(options)) {
|
|
335
|
+
LogError(`[MJQueryEntityServer] Query "${this.Name}" was approved but its fingerprint was not saved: ${this.LatestResult?.CompleteMessage}`);
|
|
114
336
|
}
|
|
115
|
-
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
LogError(`[MJQueryEntityServer] Query "${this.Name}" was approved but its fingerprint could not be computed: ${e instanceof Error ? e.message : String(e)}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/** This query's dialect and parameter rows as stored, bypassing every cache. */
|
|
343
|
+
async loadDefinitionRowsAsync() {
|
|
344
|
+
const filter = `QueryID='${EscapeSQLString(this.ID)}'`;
|
|
345
|
+
const [sqls, parameters] = await this.RunViewProviderToUse.RunViews([
|
|
346
|
+
{ EntityName: 'MJ: Query SQLs', ExtraFilter: filter, Fields: ['SQLDialectID', 'SQL'], ResultType: 'simple', BypassCache: true },
|
|
347
|
+
{ EntityName: 'MJ: Query Parameters', ExtraFilter: filter, Fields: ['Name', 'Type', 'IsRequired', 'ValidationFilters', 'IsServerSet'], ResultType: 'simple', BypassCache: true },
|
|
348
|
+
], this.ContextCurrentUser);
|
|
349
|
+
if (!sqls?.Success || !parameters?.Success) {
|
|
350
|
+
throw new Error(`could not read the query's definition rows: ${sqls?.ErrorMessage || parameters?.ErrorMessage}`);
|
|
351
|
+
}
|
|
352
|
+
return { SQLs: sqls.Results, Parameters: parameters.Results };
|
|
353
|
+
}
|
|
354
|
+
async Delete(options) {
|
|
355
|
+
try {
|
|
356
|
+
return await super.Delete(options);
|
|
116
357
|
}
|
|
117
358
|
catch (e) {
|
|
118
359
|
LogError('Failed to delete query:', e);
|
|
@@ -120,131 +361,251 @@ let MJQueryEntityServer = class MJQueryEntityServer extends MJQueryEntityExtende
|
|
|
120
361
|
return false;
|
|
121
362
|
}
|
|
122
363
|
}
|
|
123
|
-
// ───
|
|
364
|
+
// ─── Extraction ──────────────────────────────────────────────────────────────
|
|
365
|
+
/**
|
|
366
|
+
* The platform the query is extracted for: the platform of the provider this entity saves
|
|
367
|
+
* through, so two providers on different platforms in one process each extract for their own.
|
|
368
|
+
*/
|
|
369
|
+
get extractionPlatform() {
|
|
370
|
+
const provider = this.ProviderToUse;
|
|
371
|
+
return provider instanceof DatabaseProviderBase ? provider.PlatformKey : 'sqlserver';
|
|
372
|
+
}
|
|
373
|
+
/** The provider as extraction reads and writes through it; `null` when it cannot read views and create entity objects. */
|
|
374
|
+
get extractionProvider() {
|
|
375
|
+
const provider = this.ProviderToUse;
|
|
376
|
+
return IsExtractionProvider(provider) ? provider : null;
|
|
377
|
+
}
|
|
124
378
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
379
|
+
* The user derived child rows are read and written as: the system user, once the caller's save
|
|
380
|
+
* of the query is authorized (the rows mirror SQL the caller was allowed to change, like CodeGen
|
|
381
|
+
* output). The caller when no system user is loaded.
|
|
127
382
|
*/
|
|
128
|
-
get
|
|
129
|
-
|
|
383
|
+
get extractionUser() {
|
|
384
|
+
if (this.refreshing)
|
|
385
|
+
return this.ContextCurrentUser;
|
|
386
|
+
return UserCache.Instance?.GetSystemUser?.() ?? this.ContextCurrentUser;
|
|
387
|
+
}
|
|
388
|
+
/** The SQL extraction reads: a re-extraction's override, else the query's SQL for the platform. */
|
|
389
|
+
get extractionSQL() {
|
|
390
|
+
return this.platformSQLOverride ?? (this.GetPlatformSQL(this.extractionPlatform) || this.SQL || '');
|
|
391
|
+
}
|
|
392
|
+
/** Establishes the query's facts (nothing is written); an explicit refresh's facts when it already has them. */
|
|
393
|
+
async computeExtractionAsync(mode) {
|
|
394
|
+
if (this.presetExtraction)
|
|
395
|
+
return this.presetExtraction;
|
|
396
|
+
const provider = this.ProviderToUse;
|
|
397
|
+
await this.ensureQueryEngineLoadedAsync();
|
|
398
|
+
const targets = await this.readCompositionTargetsAsync();
|
|
399
|
+
return ComputeQueryExtraction({
|
|
400
|
+
QueryID: this.IsSaved ? this.ID : null,
|
|
401
|
+
QueryName: this.Name,
|
|
402
|
+
QueryPath: CompositionFullPath({ Name: this.Name, CategoryPath: this.CategoryID ? this.BuildCategoryPath(this.CategoryID) : '' }),
|
|
403
|
+
SQL: this.extractionSQL,
|
|
404
|
+
Platform: this.extractionPlatform,
|
|
405
|
+
Mode: mode,
|
|
406
|
+
IsExternal: !!this.ExternalDataSourceID,
|
|
407
|
+
KnownParameters: await this.knownParametersAsync(),
|
|
408
|
+
ParameterHints: this.ParameterHints,
|
|
409
|
+
ContextUser: this.ContextCurrentUser,
|
|
410
|
+
Entities: provider instanceof DatabaseProviderBase ? provider.Entities : [],
|
|
411
|
+
Queries: MergeCompositionCandidates(targets.Candidates, QueryEngine.Instance.Queries),
|
|
412
|
+
CompositionDependencies: targets.InlineDependencies,
|
|
413
|
+
Describe: provider instanceof GenericDatabaseProvider ? (sql) => provider.DescribeStatement(sql, this.ContextCurrentUser) : null,
|
|
414
|
+
});
|
|
130
415
|
}
|
|
131
416
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* 2. Base Query.SQL field → fall back if no dialect variant exists
|
|
137
|
-
*
|
|
138
|
-
* After resolution, validates the SQL is compatible with the current platform's
|
|
139
|
-
* dialect by attempting a parse. Logs a warning if the SQL appears to be for a
|
|
140
|
-
* different dialect (e.g., T-SQL bracket identifiers on a PostgreSQL database).
|
|
417
|
+
* The queries the SQL composes, read on this save's provider with every cache bypassed, so a
|
|
418
|
+
* target saved earlier in the same transaction (one metadata push creating both queries) is
|
|
419
|
+
* found before the shared query cache hears of it. Nothing is read for SQL that composes
|
|
420
|
+
* nothing, or on a provider that cannot run views.
|
|
141
421
|
*/
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
422
|
+
async readCompositionTargetsAsync() {
|
|
423
|
+
const provider = this.ProviderToUse;
|
|
424
|
+
if (!(provider instanceof DatabaseProviderBase))
|
|
425
|
+
return { Candidates: [], InlineDependencies: [] };
|
|
426
|
+
const engine = QueryEngine.Instance;
|
|
427
|
+
const cachedIDs = new Set(engine.Queries.map(q => q.ID.toLowerCase()));
|
|
428
|
+
return ReadCompositionTargetsAsync(this.extractionSQL, this.extractionPlatform, provider, this.extractionUser, cachedIDs, engine.SQLDialects);
|
|
429
|
+
}
|
|
430
|
+
/** Loads QueryEngine when this process has not yet (a CLI or sync context); compositions resolve against it. */
|
|
431
|
+
async ensureQueryEngineLoadedAsync() {
|
|
432
|
+
if (QueryEngine.Instance.Loaded)
|
|
433
|
+
return;
|
|
434
|
+
try {
|
|
435
|
+
await QueryEngine.Instance.Config(false, this.ContextCurrentUser);
|
|
436
|
+
}
|
|
437
|
+
catch (e) {
|
|
438
|
+
LogError(`[MJQueryEntityServer] QueryEngine could not be loaded for extraction of "${this.Name}": ${e instanceof Error ? e.message : String(e)}`);
|
|
439
|
+
}
|
|
154
440
|
}
|
|
155
441
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
442
|
+
* The parameter rows extraction knows before it runs: the stored rows, read from the database
|
|
443
|
+
* (never the cache), overlaid by the rows this save declares.
|
|
444
|
+
*/
|
|
445
|
+
async knownParametersAsync() {
|
|
446
|
+
const byName = new Map();
|
|
447
|
+
for (const row of await this.storedParametersAsync())
|
|
448
|
+
byName.set(row.Name.toLowerCase(), row);
|
|
449
|
+
for (const row of this.DeclaredParameters?.Items ?? [])
|
|
450
|
+
byName.set(row.Name.toLowerCase(), row);
|
|
451
|
+
return [...byName.values()].map(row => ({
|
|
452
|
+
Name: row.Name, Type: row.Type, IsRequired: row.IsRequired === true, IsServerSet: row.IsServerSet === true,
|
|
453
|
+
SampleValue: row.SampleValue, DefaultValue: row.DefaultValue, ValidationFilters: row.ValidationFilters,
|
|
454
|
+
IsAuthored: row.DetectionMethod === 'Manual',
|
|
455
|
+
}));
|
|
456
|
+
}
|
|
457
|
+
async storedParametersAsync() {
|
|
458
|
+
const provider = this.extractionProvider;
|
|
459
|
+
if (!this.IsSaved || !provider)
|
|
460
|
+
return [];
|
|
461
|
+
const result = await provider.RunView({
|
|
462
|
+
EntityName: 'MJ: Query Parameters', ExtraFilter: `QueryID='${EscapeSQLString(this.ID)}'`, ResultType: 'entity_object', BypassCache: true,
|
|
463
|
+
}, this.extractionUser);
|
|
464
|
+
if (!result.Success)
|
|
465
|
+
throw new Error(`could not read the query's stored parameters: ${result.ErrorMessage}`);
|
|
466
|
+
return result.Results ?? [];
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Writes the derived child rows as the system user, inside the caller's transaction when there
|
|
470
|
+
* is one, then queues the description pass. A failed write is recorded on the query
|
|
471
|
+
* (`ExtractionStatus` `Error`) rather than failing a save whose row is already written.
|
|
163
472
|
*/
|
|
164
|
-
|
|
473
|
+
async writeExtractionAsync(extraction, options, postCommit) {
|
|
474
|
+
const provider = this.extractionProvider;
|
|
165
475
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
476
|
+
if (!provider)
|
|
477
|
+
throw new Error('the query is not saved through a database provider');
|
|
478
|
+
const summary = await WriteExtractedChildRows({ QueryID: this.ID, Provider: provider, User: this.extractionUser, EntityName: id => this.entityNameOf(id) }, extraction);
|
|
479
|
+
this.lastWriteSummary = summary;
|
|
480
|
+
this.queueDescriptionPass(summary, options, postCommit);
|
|
481
|
+
}
|
|
482
|
+
catch (e) {
|
|
483
|
+
// An explicit refresh runs in its own transaction, which a failed write rolls back.
|
|
484
|
+
if (this.refreshing)
|
|
485
|
+
throw e;
|
|
486
|
+
await this.recordWriteFailureAsync(e);
|
|
170
487
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
488
|
+
}
|
|
489
|
+
/** An entity's name from the saving provider's catalog, for reporting; the ID when it is not there. */
|
|
490
|
+
entityNameOf(entityID) {
|
|
491
|
+
const provider = this.ProviderToUse;
|
|
492
|
+
const entities = provider instanceof DatabaseProviderBase ? provider.Entities : [];
|
|
493
|
+
return entities.find(e => UUIDsEqual(e.ID, entityID))?.Name ?? entityID;
|
|
494
|
+
}
|
|
495
|
+
async recordWriteFailureAsync(error) {
|
|
496
|
+
const reason = `The extracted child rows could not be written: ${error instanceof Error ? error.message : String(error)}`;
|
|
497
|
+
LogError(`[MJQueryEntityServer] Query "${this.Name}": ${reason}`);
|
|
498
|
+
this.ExtractionStatus = 'Error';
|
|
499
|
+
this.ExtractionMessage = this.ExtractionMessage ? `${this.ExtractionMessage}\n${reason}` : reason;
|
|
500
|
+
if (!await super.Save()) {
|
|
501
|
+
LogError(`[MJQueryEntityServer] Query "${this.Name}": the extraction failure could not be recorded: ${this.LatestResult?.CompleteMessage}`);
|
|
176
502
|
}
|
|
177
503
|
}
|
|
504
|
+
/** The post-commit token of the caller's transaction, captured before the save writes anything. */
|
|
505
|
+
capturePostCommitToken() {
|
|
506
|
+
const provider = this.ProviderToUse;
|
|
507
|
+
return provider instanceof DatabaseProviderBase ? provider.CapturePostCommitToken() : undefined;
|
|
508
|
+
}
|
|
178
509
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
510
|
+
* Queues the LLM description pass to run after the caller's transaction commits, when some row
|
|
511
|
+
* has no description and the save does not skip AI actions. No LLM call is made inside the
|
|
512
|
+
* transaction. A save-triggered extraction that changed no child row does not queue it: the
|
|
513
|
+
* rows are as the last run left them, so a repeated save (a re-push of unchanged metadata) does
|
|
514
|
+
* not call the LLM again, and a pass that failed is not retried, and recorded, on every save.
|
|
515
|
+
* An explicit refresh that asks for the pass always queues it.
|
|
181
516
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
platform: this.currentPlatform,
|
|
192
|
-
parameterHints: this.ParameterHints,
|
|
193
|
-
SkipAIEnrichment: skipAIEnrichment,
|
|
194
|
-
};
|
|
517
|
+
queueDescriptionPass(summary, options, postCommit) {
|
|
518
|
+
const provider = this.ProviderToUse;
|
|
519
|
+
const undescribed = summary.FieldsWithoutDescription.length + summary.ParametersWithoutDescription.length;
|
|
520
|
+
if (options?.SkipEntityAIActions || undescribed === 0 || !(provider instanceof DatabaseProviderBase))
|
|
521
|
+
return;
|
|
522
|
+
if (!this.refreshing && summary.Changes.length === 0)
|
|
523
|
+
return;
|
|
524
|
+
const request = { QueryID: this.ID, QueryName: this.Name, SQL: this.extractionSQL, Provider: provider, User: this.extractionUser };
|
|
525
|
+
provider.RunAfterCommit(() => RunDescriptionPass(request), `query description pass for "${this.Name}"`, postCommit);
|
|
195
526
|
}
|
|
196
527
|
/**
|
|
197
|
-
* Re-
|
|
198
|
-
* when a dialect variant
|
|
199
|
-
* the timing issue where the parent Query is saved before its QuerySQL children
|
|
200
|
-
* exist in the cache.
|
|
528
|
+
* Re-extracts this query: a save with {@link ExtractionOverride} `Force`. Called by
|
|
529
|
+
* `MJQuerySQLEntityServer` when a dialect variant for the current platform is saved.
|
|
201
530
|
*
|
|
202
|
-
* @param skipAIEnrichment Skip the
|
|
531
|
+
* @param skipAIEnrichment Skip the description pass, as `SkipEntityAIActions` does for a save.
|
|
532
|
+
* @param platformSQL The SQL to extract from (the variant just saved), instead of the cached
|
|
533
|
+
* platform variant, which inside a transaction may not hold it yet.
|
|
534
|
+
*/
|
|
535
|
+
async RerunExtraction(skipAIEnrichment = false, platformSQL) {
|
|
536
|
+
this.ExtractionOverride = 'Force';
|
|
537
|
+
this.platformSQLOverride = platformSQL ?? null;
|
|
538
|
+
if (!await this.Save({ SkipEntityAIActions: skipAIEnrichment })) {
|
|
539
|
+
LogError(`[MJQueryEntityServer] Re-extraction of query "${this.Name}" failed: ${this.LatestResult?.CompleteMessage}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// ─── Explicit refresh ────────────────────────────────────────────────────────
|
|
543
|
+
/**
|
|
544
|
+
* Establishes this saved query's facts under the refresh rules (a save-time rule error is
|
|
545
|
+
* recorded, never a refusal), as the save would, and writes nothing. The facts are read on the
|
|
546
|
+
* provider this entity was loaded with, for its platform.
|
|
547
|
+
*/
|
|
548
|
+
async ComputeRefreshExtractionAsync() {
|
|
549
|
+
return this.computeExtractionAsync('Refresh');
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* The child-row changes `extraction` would make, worked out against the rows stored now and
|
|
553
|
+
* read as this entity's context user. Nothing is written.
|
|
203
554
|
*/
|
|
204
|
-
async
|
|
205
|
-
|
|
555
|
+
async PreviewRefreshAsync(extraction, entityName) {
|
|
556
|
+
const provider = this.extractionProvider;
|
|
557
|
+
if (!provider)
|
|
558
|
+
throw new Error('the query is not loaded through a database provider');
|
|
559
|
+
return WriteExtractedChildRows({ QueryID: this.ID, Provider: provider, User: this.ContextCurrentUser, DryRun: true, EntityName: entityName ?? (id => this.entityNameOf(id)) }, extraction);
|
|
206
560
|
}
|
|
207
561
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
562
|
+
* Writes `extraction` (from {@link ComputeRefreshExtractionAsync}): a save with
|
|
563
|
+
* {@link ExtractionOverride} `Force` that records the run on the query and brings the child
|
|
564
|
+
* rows in line with the facts. Unlike a save-triggered extraction, the child rows are written
|
|
565
|
+
* as this entity's context user, so each write is checked against that user's permissions, and
|
|
566
|
+
* a failed write fails the refresh instead of being recorded on the query. SQL, dialect
|
|
567
|
+
* variants and the embedding are never written.
|
|
210
568
|
*
|
|
211
|
-
* @param
|
|
569
|
+
* @param withAI Queue the LLM description pass for rows still without a description.
|
|
570
|
+
* @returns What the child-row writes changed.
|
|
571
|
+
* @throws Error when the save or a child-row write fails; the caller's transaction should be rolled back.
|
|
212
572
|
*/
|
|
213
|
-
async
|
|
573
|
+
async ApplyRefreshAsync(extraction, withAI) {
|
|
574
|
+
this.ExtractionOverride = 'Force';
|
|
575
|
+
this.presetExtraction = extraction;
|
|
576
|
+
this.refreshing = true;
|
|
577
|
+
// A refresh changes the derived rows, not the text the embedding is computed from.
|
|
578
|
+
const skipEmbeddings = this.SkipEmbeddings;
|
|
579
|
+
this.SkipEmbeddings = true;
|
|
214
580
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.UsesTemplate = pipelineResult.UsesTemplate;
|
|
218
|
-
// Save the query again to persist UsesTemplate and any changes from AI processing
|
|
219
|
-
const updateResult = await super.Save();
|
|
220
|
-
if (!updateResult) {
|
|
221
|
-
LogError('Failed to save query after AI processing completed');
|
|
581
|
+
if (!await this.Save({ SkipEntityAIActions: !withAI })) {
|
|
582
|
+
throw new Error(this.LatestResult?.CompleteMessage || `the refresh of query "${this.Name}" could not be saved`);
|
|
222
583
|
}
|
|
223
584
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.UsesTemplate = false;
|
|
227
|
-
try {
|
|
228
|
-
await super.Save();
|
|
229
|
-
}
|
|
230
|
-
catch (saveError) {
|
|
231
|
-
LogError('Failed to save query after AI processing error:', saveError);
|
|
232
|
-
}
|
|
585
|
+
finally {
|
|
586
|
+
this.SkipEmbeddings = skipEmbeddings;
|
|
233
587
|
}
|
|
588
|
+
return this.lastWriteSummary;
|
|
234
589
|
}
|
|
235
590
|
/**
|
|
236
|
-
* Cleans up all extraction data when SQL is empty
|
|
591
|
+
* Cleans up all extraction data when SQL is empty.
|
|
237
592
|
*/
|
|
238
593
|
async cleanupEmptyQueryAsync() {
|
|
594
|
+
const provider = this.extractionProvider;
|
|
595
|
+
if (!provider)
|
|
596
|
+
return;
|
|
239
597
|
try {
|
|
240
|
-
const ctx =
|
|
598
|
+
const ctx = {
|
|
599
|
+
queryID: this.ID,
|
|
600
|
+
queryName: this.Name,
|
|
601
|
+
sql: '',
|
|
602
|
+
isSaved: this.IsSaved,
|
|
603
|
+
contextUser: this.extractionUser,
|
|
604
|
+
metadataProvider: provider,
|
|
605
|
+
runViewProvider: provider,
|
|
606
|
+
platform: this.extractionPlatform,
|
|
607
|
+
};
|
|
241
608
|
await CleanupQueryData(ctx);
|
|
242
|
-
// Also remove dialect records (not managed by the pipeline)
|
|
243
|
-
await this.removeAllQuerySQLRecordsAsync();
|
|
244
|
-
const updateResult = await super.Save();
|
|
245
|
-
if (!updateResult) {
|
|
246
|
-
LogError('Failed to save query after cleanup');
|
|
247
|
-
}
|
|
248
609
|
}
|
|
249
610
|
catch (e) {
|
|
250
611
|
LogError('Error in async cleanup:', e);
|
|
@@ -309,7 +670,9 @@ let MJQueryEntityServer = class MJQueryEntityServer extends MJQueryEntityExtende
|
|
|
309
670
|
* Creates or updates a QuerySQL record for the given dialect.
|
|
310
671
|
*/
|
|
311
672
|
async upsertQuerySQLRecord(targetDialect, convertedSQL) {
|
|
312
|
-
const md = this.
|
|
673
|
+
const md = this.extractionProvider;
|
|
674
|
+
if (!md)
|
|
675
|
+
return;
|
|
313
676
|
// Look up existing record from QueryEngine cache instead of a RunView call
|
|
314
677
|
const existing = QueryEngine.Instance.QuerySQLs.find(qs => UUIDsEqual(qs.QueryID, this.ID) && UUIDsEqual(qs.SQLDialectID, targetDialect.ID));
|
|
315
678
|
let record;
|
|
@@ -327,23 +690,6 @@ let MJQueryEntityServer = class MJQueryEntityServer extends MJQueryEntityExtende
|
|
|
327
690
|
console.warn(`Query "${this.Name}" - Failed to save QuerySQL record for dialect "${targetDialect.Name}"`);
|
|
328
691
|
}
|
|
329
692
|
}
|
|
330
|
-
/**
|
|
331
|
-
* Removes all QuerySQL records for this query. Called during cleanup operations.
|
|
332
|
-
*/
|
|
333
|
-
async removeAllQuerySQLRecordsAsync() {
|
|
334
|
-
try {
|
|
335
|
-
if (!this.IsSaved)
|
|
336
|
-
return;
|
|
337
|
-
const records = QueryEngine.Instance.QuerySQLs.filter(qs => UUIDsEqual(qs.QueryID, this.ID));
|
|
338
|
-
const deletePromises = records.map(r => r.Delete());
|
|
339
|
-
if (deletePromises.length > 0) {
|
|
340
|
-
await Promise.all(deletePromises);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
catch (e) {
|
|
344
|
-
console.warn(`Query "${this.Name}" - Failed to remove QuerySQL records:`, e);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
693
|
};
|
|
348
694
|
MJQueryEntityServer = __decorate([
|
|
349
695
|
RegisterClass(BaseEntity, 'MJ: Queries')
|