@memberjunction/server 5.25.0 → 5.27.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/dist/agents/skip-sdk.d.ts +15 -34
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +58 -209
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/config.d.ts +70 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -0
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +6 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +24 -0
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +31 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -0
- package/dist/resolvers/CacheStatsResolver.js +181 -0
- package/dist/resolvers/CacheStatsResolver.js.map +1 -0
- package/dist/resolvers/ReportResolver.d.ts.map +1 -1
- package/dist/resolvers/ReportResolver.js.map +1 -1
- package/package.json +66 -63
- package/src/agents/skip-sdk.ts +59 -226
- package/src/config.ts +24 -0
- package/src/generated/generated.ts +18 -0
- package/src/index.ts +16 -2
- package/src/resolvers/CacheStatsResolver.ts +142 -0
- package/src/resolvers/ReportResolver.ts +1 -2
- package/src/__tests__/skip-sdk-organic-keys.test.ts +0 -274
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/server",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.27.0",
|
|
4
4
|
"description": "MemberJunction: This project provides API access via GraphQL to the common data store.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -27,68 +27,71 @@
|
|
|
27
27
|
"@as-integrations/express5": "^1.0.0",
|
|
28
28
|
"@graphql-tools/schema": "latest",
|
|
29
29
|
"@graphql-tools/utils": "^11.0.0",
|
|
30
|
-
"@memberjunction/actions": "5.
|
|
31
|
-
"@memberjunction/actions-apollo": "5.
|
|
32
|
-
"@memberjunction/actions-base": "5.
|
|
33
|
-
"@memberjunction/actions-bizapps-accounting": "5.
|
|
34
|
-
"@memberjunction/actions-bizapps-crm": "5.
|
|
35
|
-
"@memberjunction/actions-bizapps-formbuilders": "5.
|
|
36
|
-
"@memberjunction/actions-bizapps-lms": "5.
|
|
37
|
-
"@memberjunction/actions-bizapps-social": "5.
|
|
38
|
-
"@memberjunction/ai": "5.
|
|
39
|
-
"@memberjunction/ai-agent-manager": "5.
|
|
40
|
-
"@memberjunction/ai-agent-manager-actions": "5.
|
|
41
|
-
"@memberjunction/ai-agents": "5.
|
|
42
|
-
"@memberjunction/ai-core-plus": "5.
|
|
43
|
-
"@memberjunction/ai-mcp-client": "5.
|
|
44
|
-
"@memberjunction/ai-prompts": "5.
|
|
45
|
-
"@memberjunction/ai-provider-bundle": "5.
|
|
46
|
-
"@memberjunction/ai-vector-sync": "5.
|
|
47
|
-
"@memberjunction/ai-
|
|
48
|
-
"@memberjunction/
|
|
49
|
-
"@memberjunction/
|
|
50
|
-
"@memberjunction/
|
|
51
|
-
"@memberjunction/
|
|
52
|
-
"@memberjunction/
|
|
53
|
-
"@memberjunction/communication-
|
|
54
|
-
"@memberjunction/communication-
|
|
55
|
-
"@memberjunction/
|
|
56
|
-
"@memberjunction/
|
|
57
|
-
"@memberjunction/
|
|
58
|
-
"@memberjunction/
|
|
59
|
-
"@memberjunction/core
|
|
60
|
-
"@memberjunction/core-
|
|
61
|
-
"@memberjunction/core-entities
|
|
62
|
-
"@memberjunction/
|
|
63
|
-
"@memberjunction/data-context
|
|
64
|
-
"@memberjunction/
|
|
65
|
-
"@memberjunction/
|
|
66
|
-
"@memberjunction/
|
|
67
|
-
"@memberjunction/entity-communications-
|
|
68
|
-
"@memberjunction/
|
|
69
|
-
"@memberjunction/
|
|
70
|
-
"@memberjunction/
|
|
71
|
-
"@memberjunction/
|
|
72
|
-
"@memberjunction/
|
|
73
|
-
"@memberjunction/integration-
|
|
74
|
-
"@memberjunction/
|
|
75
|
-
"@memberjunction/
|
|
76
|
-
"@memberjunction/
|
|
77
|
-
"@memberjunction/
|
|
78
|
-
"@memberjunction/
|
|
79
|
-
"@memberjunction/
|
|
80
|
-
"@memberjunction/scheduling-
|
|
81
|
-
"@memberjunction/scheduling-
|
|
82
|
-
"@memberjunction/scheduling-engine
|
|
83
|
-
"@memberjunction/
|
|
84
|
-
"@memberjunction/
|
|
85
|
-
"@memberjunction/
|
|
86
|
-
"@memberjunction/
|
|
87
|
-
"@memberjunction/
|
|
88
|
-
"@memberjunction/
|
|
89
|
-
"@memberjunction/
|
|
90
|
-
"@memberjunction/
|
|
91
|
-
"@memberjunction/
|
|
30
|
+
"@memberjunction/actions": "5.27.0",
|
|
31
|
+
"@memberjunction/actions-apollo": "5.27.0",
|
|
32
|
+
"@memberjunction/actions-base": "5.27.0",
|
|
33
|
+
"@memberjunction/actions-bizapps-accounting": "5.27.0",
|
|
34
|
+
"@memberjunction/actions-bizapps-crm": "5.27.0",
|
|
35
|
+
"@memberjunction/actions-bizapps-formbuilders": "5.27.0",
|
|
36
|
+
"@memberjunction/actions-bizapps-lms": "5.27.0",
|
|
37
|
+
"@memberjunction/actions-bizapps-social": "5.27.0",
|
|
38
|
+
"@memberjunction/ai": "5.27.0",
|
|
39
|
+
"@memberjunction/ai-agent-manager": "5.27.0",
|
|
40
|
+
"@memberjunction/ai-agent-manager-actions": "5.27.0",
|
|
41
|
+
"@memberjunction/ai-agents": "5.27.0",
|
|
42
|
+
"@memberjunction/ai-core-plus": "5.27.0",
|
|
43
|
+
"@memberjunction/ai-mcp-client": "5.27.0",
|
|
44
|
+
"@memberjunction/ai-prompts": "5.27.0",
|
|
45
|
+
"@memberjunction/ai-provider-bundle": "5.27.0",
|
|
46
|
+
"@memberjunction/ai-vector-sync": "5.27.0",
|
|
47
|
+
"@memberjunction/ai-vectordb": "5.27.0",
|
|
48
|
+
"@memberjunction/ai-vectors-pinecone": "5.27.0",
|
|
49
|
+
"@memberjunction/aiengine": "5.27.0",
|
|
50
|
+
"@memberjunction/api-keys": "5.27.0",
|
|
51
|
+
"@memberjunction/auth-providers": "5.27.0",
|
|
52
|
+
"@memberjunction/codegen-lib": "5.27.0",
|
|
53
|
+
"@memberjunction/communication-ms-graph": "5.27.0",
|
|
54
|
+
"@memberjunction/communication-sendgrid": "5.27.0",
|
|
55
|
+
"@memberjunction/communication-types": "5.27.0",
|
|
56
|
+
"@memberjunction/component-registry-client-sdk": "5.27.0",
|
|
57
|
+
"@memberjunction/computer-use-engine": "5.27.0",
|
|
58
|
+
"@memberjunction/config": "5.27.0",
|
|
59
|
+
"@memberjunction/core": "5.27.0",
|
|
60
|
+
"@memberjunction/core-actions": "5.27.0",
|
|
61
|
+
"@memberjunction/core-entities": "5.27.0",
|
|
62
|
+
"@memberjunction/core-entities-server": "5.27.0",
|
|
63
|
+
"@memberjunction/data-context": "5.27.0",
|
|
64
|
+
"@memberjunction/data-context-server": "5.27.0",
|
|
65
|
+
"@memberjunction/doc-utils": "5.27.0",
|
|
66
|
+
"@memberjunction/encryption": "5.27.0",
|
|
67
|
+
"@memberjunction/entity-communications-base": "5.27.0",
|
|
68
|
+
"@memberjunction/entity-communications-server": "5.27.0",
|
|
69
|
+
"@memberjunction/external-change-detection": "5.27.0",
|
|
70
|
+
"@memberjunction/generic-database-provider": "5.27.0",
|
|
71
|
+
"@memberjunction/global": "5.27.0",
|
|
72
|
+
"@memberjunction/graphql-dataprovider": "5.27.0",
|
|
73
|
+
"@memberjunction/integration-engine": "5.27.0",
|
|
74
|
+
"@memberjunction/integration-schema-builder": "5.27.0",
|
|
75
|
+
"@memberjunction/interactive-component-types": "5.27.0",
|
|
76
|
+
"@memberjunction/notifications": "5.27.0",
|
|
77
|
+
"@memberjunction/postgresql-dataprovider": "5.27.0",
|
|
78
|
+
"@memberjunction/queue": "5.27.0",
|
|
79
|
+
"@memberjunction/redis-provider": "5.27.0",
|
|
80
|
+
"@memberjunction/scheduling-actions": "5.27.0",
|
|
81
|
+
"@memberjunction/scheduling-base-types": "5.27.0",
|
|
82
|
+
"@memberjunction/scheduling-engine": "5.27.0",
|
|
83
|
+
"@memberjunction/scheduling-engine-base": "5.27.0",
|
|
84
|
+
"@memberjunction/schema-engine": "5.27.0",
|
|
85
|
+
"@memberjunction/search-engine": "5.27.0",
|
|
86
|
+
"@memberjunction/server-extensions-core": "5.27.0",
|
|
87
|
+
"@memberjunction/skip-types": "5.27.0",
|
|
88
|
+
"@memberjunction/sql-dialect": "5.27.0",
|
|
89
|
+
"@memberjunction/sqlserver-dataprovider": "5.27.0",
|
|
90
|
+
"@memberjunction/storage": "5.27.0",
|
|
91
|
+
"@memberjunction/templates": "5.27.0",
|
|
92
|
+
"@memberjunction/testing-engine": "5.27.0",
|
|
93
|
+
"@memberjunction/testing-engine-base": "5.27.0",
|
|
94
|
+
"@memberjunction/version-history": "5.27.0",
|
|
92
95
|
"@types/compression": "^1.8.1",
|
|
93
96
|
"@types/cors": "^2.8.19",
|
|
94
97
|
"@types/jsonwebtoken": "9.0.10",
|
package/src/agents/skip-sdk.ts
CHANGED
|
@@ -17,12 +17,6 @@ import {
|
|
|
17
17
|
SkipAPIRequestAPIKey,
|
|
18
18
|
SkipQueryInfo,
|
|
19
19
|
SkipQueryCatalogEntry,
|
|
20
|
-
SkipEntityInfo,
|
|
21
|
-
SkipEntityFieldInfo,
|
|
22
|
-
SkipEntityFieldValueInfo,
|
|
23
|
-
SkipEntityRelationshipInfo,
|
|
24
|
-
SkipEntityOrganicKeyInfo,
|
|
25
|
-
SkipEntityOrganicKeyRelatedEntityInfo,
|
|
26
20
|
SkipAPIAgentNote,
|
|
27
21
|
SkipAPIAgentNoteType,
|
|
28
22
|
SkipAPIArtifact,
|
|
@@ -30,7 +24,7 @@ import {
|
|
|
30
24
|
SkipAPIArtifactType
|
|
31
25
|
} from '@memberjunction/skip-types';
|
|
32
26
|
import { DataContext } from '@memberjunction/data-context';
|
|
33
|
-
import { UserInfo, LogStatus, LogError, Metadata, RunQuery, EntityInfo, EntityFieldInfo,
|
|
27
|
+
import { UserInfo, LogStatus, LogError, Metadata, RunQuery, EntityInfo, EntityFieldInfo, EntityFieldValueInfo, DatabaseProviderBase } from '@memberjunction/core';
|
|
34
28
|
import { request as httpRequest } from 'http';
|
|
35
29
|
import { request as httpsRequest } from 'https';
|
|
36
30
|
import { gzip as gzipCompress, createGunzip } from 'zlib';
|
|
@@ -206,7 +200,7 @@ export class SkipSDK {
|
|
|
206
200
|
private config: SkipSDKConfig;
|
|
207
201
|
|
|
208
202
|
// Static cache for Skip entities (shared across all instances)
|
|
209
|
-
private static __skipEntitiesCache$: BehaviorSubject<Promise<
|
|
203
|
+
private static __skipEntitiesCache$: BehaviorSubject<Promise<EntityInfo[]> | null> = new BehaviorSubject<Promise<EntityInfo[]> | null>(null);
|
|
210
204
|
private static __lastRefreshTime: number = 0;
|
|
211
205
|
|
|
212
206
|
constructor(config?: SkipSDKConfig) {
|
|
@@ -396,7 +390,7 @@ export class SkipSDK {
|
|
|
396
390
|
includeCallbackAuth: boolean,
|
|
397
391
|
additionalTokenInfo: any = {}
|
|
398
392
|
): Promise<Partial<SkipAPIRequest>> {
|
|
399
|
-
const entities = includeEntities ? await this.buildEntities(
|
|
393
|
+
const entities = includeEntities ? await this.buildEntities(forceEntityRefresh) : [];
|
|
400
394
|
const queries = includeQueries ? this.buildQueries() : [];
|
|
401
395
|
// Always build the lightweight query catalog for collision detection,
|
|
402
396
|
// regardless of whether full queries are included
|
|
@@ -442,7 +436,7 @@ export class SkipSDK {
|
|
|
442
436
|
* Build entity metadata for Skip
|
|
443
437
|
* Copied from AskSkipResolver.BuildSkipEntities - uses cached metadata with refresh logic
|
|
444
438
|
*/
|
|
445
|
-
private async buildEntities(
|
|
439
|
+
private async buildEntities(forceRefresh: boolean, refreshIntervalMinutes: number = 15): Promise<EntityInfo[]> {
|
|
446
440
|
try {
|
|
447
441
|
const now = Date.now();
|
|
448
442
|
const cacheExpired = (now - SkipSDK.__lastRefreshTime) > (refreshIntervalMinutes * 60 * 1000);
|
|
@@ -450,7 +444,7 @@ export class SkipSDK {
|
|
|
450
444
|
// If force refresh is requested OR cache expired OR cache is empty, refresh
|
|
451
445
|
if (forceRefresh || cacheExpired || SkipSDK.__skipEntitiesCache$.value === null) {
|
|
452
446
|
LogStatus(`[SkipSDK] Refreshing Skip entities cache (force: ${forceRefresh}, expired: ${cacheExpired})`);
|
|
453
|
-
const newData = this.refreshSkipEntities(
|
|
447
|
+
const newData = this.refreshSkipEntities();
|
|
454
448
|
SkipSDK.__skipEntitiesCache$.next(newData);
|
|
455
449
|
}
|
|
456
450
|
|
|
@@ -856,9 +850,11 @@ export class SkipSDK {
|
|
|
856
850
|
/**
|
|
857
851
|
* Refreshes the Skip entities cache
|
|
858
852
|
* Rebuilds the entity information that is provided to Skip
|
|
859
|
-
*
|
|
853
|
+
* Refreshes the entity metadata cache. Filters entities by schema/scope config,
|
|
854
|
+
* filters fields by AI scope, and enriches field values from the database.
|
|
855
|
+
* Returns EntityInfo objects directly — no intermediate Skip-specific types needed.
|
|
860
856
|
*/
|
|
861
|
-
private async refreshSkipEntities(
|
|
857
|
+
private async refreshSkipEntities(): Promise<EntityInfo[]> {
|
|
862
858
|
try {
|
|
863
859
|
const md = new Metadata();
|
|
864
860
|
|
|
@@ -891,12 +887,12 @@ export class SkipSDK {
|
|
|
891
887
|
LogError(`[SkipSDK.refreshSkipEntities] WARNING: No entities passed filtering! This will result in empty Skip entities list.`);
|
|
892
888
|
}
|
|
893
889
|
|
|
894
|
-
//
|
|
895
|
-
const result = await Promise.all(entities.map((e) => this.
|
|
890
|
+
// Build enriched EntityInfo objects with filtered fields and packed values
|
|
891
|
+
const result = await Promise.all(entities.map((e) => this.buildEntityForSkip(e)));
|
|
896
892
|
|
|
897
893
|
LogStatus(`[SkipSDK.refreshSkipEntities] Successfully packed ${result.length} entities for Skip`);
|
|
898
894
|
|
|
899
|
-
SkipSDK.__lastRefreshTime = Date.now();
|
|
895
|
+
SkipSDK.__lastRefreshTime = Date.now();
|
|
900
896
|
return result;
|
|
901
897
|
}
|
|
902
898
|
catch (e) {
|
|
@@ -906,258 +902,95 @@ export class SkipSDK {
|
|
|
906
902
|
}
|
|
907
903
|
|
|
908
904
|
/**
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
*
|
|
905
|
+
* Builds an EntityInfo object for Skip, filtering fields by AI scope and
|
|
906
|
+
* enriching field values from the database. Returns a new EntityInfo
|
|
907
|
+
* constructed from a plain object so it serializes cleanly via toJSON().
|
|
912
908
|
*/
|
|
913
|
-
private async
|
|
909
|
+
private async buildEntityForSkip(e: EntityInfo): Promise<EntityInfo> {
|
|
914
910
|
try {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
description: e.Description,
|
|
921
|
-
|
|
922
|
-
fields: await Promise.all(e.Fields.filter(f => {
|
|
923
|
-
// we want to check the scopes for the field level and make sure it is either All or AI or has both
|
|
924
|
-
const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
|
|
925
|
-
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
|
|
926
|
-
}).map(f => {
|
|
927
|
-
return this.packSingleSkipEntityField(f, dataSource);
|
|
928
|
-
})),
|
|
929
|
-
|
|
930
|
-
relatedEntities: e.RelatedEntities.map((r) => {
|
|
931
|
-
return this.packSingleSkipEntityRelationship(r);
|
|
932
|
-
}),
|
|
933
|
-
|
|
934
|
-
organicKeys: (e.OrganicKeys ?? [])
|
|
935
|
-
.filter((ok) => ok.Status === 'Active')
|
|
936
|
-
.map((ok) => this.packSingleSkipOrganicKey(ok))
|
|
937
|
-
.filter((ok): ok is SkipEntityOrganicKeyInfo => ok !== null)
|
|
938
|
-
};
|
|
939
|
-
return ret;
|
|
940
|
-
}
|
|
941
|
-
catch (e) {
|
|
942
|
-
LogError(`[SkipSDK] packSingleSkipEntityInfo error: ${e}`);
|
|
943
|
-
return null;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
/**
|
|
948
|
-
* Packs information about a single organic key for Skip.
|
|
949
|
-
* Organic keys express cross-entity relationships via shared business data
|
|
950
|
-
* (email, acronym, etc.) rather than database FK constraints.
|
|
951
|
-
*/
|
|
952
|
-
private packSingleSkipOrganicKey(ok: EntityOrganicKeyInfo): SkipEntityOrganicKeyInfo | null {
|
|
953
|
-
try {
|
|
954
|
-
return {
|
|
955
|
-
id: ok.ID,
|
|
956
|
-
name: ok.Name,
|
|
957
|
-
description: ok.Description ?? undefined,
|
|
958
|
-
matchFieldNames: ok.MatchFieldNamesArray,
|
|
959
|
-
normalizationStrategy: ok.NormalizationStrategy,
|
|
960
|
-
customNormalizationExpression: ok.CustomNormalizationExpression ?? undefined,
|
|
961
|
-
sequence: ok.Sequence,
|
|
962
|
-
relatedEntities: ok.RelatedEntities
|
|
963
|
-
.map((re) => this.packSingleSkipOrganicKeyRelatedEntity(re))
|
|
964
|
-
.filter((re): re is SkipEntityOrganicKeyRelatedEntityInfo => re !== null)
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
catch (e) {
|
|
968
|
-
LogError(`[SkipSDK] packSingleSkipOrganicKey error: ${e}`);
|
|
969
|
-
return null;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
911
|
+
// Filter fields by scope (only include fields visible to AI)
|
|
912
|
+
const filteredFields = e.Fields.filter(f => {
|
|
913
|
+
const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
|
|
914
|
+
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
|
|
915
|
+
});
|
|
972
916
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
* Looks up schema name and base view from metadata since they are not
|
|
976
|
-
* stored on EntityOrganicKeyRelatedEntityInfo directly.
|
|
977
|
-
*/
|
|
978
|
-
private packSingleSkipOrganicKeyRelatedEntity(
|
|
979
|
-
re: EntityOrganicKeyRelatedEntityInfo
|
|
980
|
-
): SkipEntityOrganicKeyRelatedEntityInfo | null {
|
|
981
|
-
try {
|
|
982
|
-
// Look up the related entity to obtain schema name and base view, which
|
|
983
|
-
// Skip needs in order to generate schema-qualified SQL.
|
|
984
|
-
const relatedEntity = Metadata.Provider.Entities.find(
|
|
985
|
-
(ent) => UUIDsEqual(ent.ID, re.RelatedEntityID)
|
|
986
|
-
);
|
|
987
|
-
if (!relatedEntity) {
|
|
988
|
-
LogError(
|
|
989
|
-
`[SkipSDK] packSingleSkipOrganicKeyRelatedEntity: related entity not found for ID ${re.RelatedEntityID}`
|
|
990
|
-
);
|
|
991
|
-
return null;
|
|
992
|
-
}
|
|
917
|
+
// Enrich each field with packed possible values
|
|
918
|
+
const enrichedFields = await Promise.all(filteredFields.map(f => this.enrichFieldValues(f)));
|
|
993
919
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
isTransitiveMatch: re.IsTransitiveMatch,
|
|
1002
|
-
relatedEntityFieldNames: re.IsDirectMatch
|
|
1003
|
-
? re.RelatedEntityFieldNamesArray
|
|
1004
|
-
: undefined,
|
|
1005
|
-
transitiveObjectName: re.TransitiveObjectName ?? undefined,
|
|
1006
|
-
transitiveObjectMatchFieldNames: re.IsTransitiveMatch
|
|
1007
|
-
? re.TransitiveObjectMatchFieldNamesArray
|
|
1008
|
-
: undefined,
|
|
1009
|
-
transitiveObjectOutputFieldName: re.TransitiveObjectOutputFieldName ?? undefined,
|
|
1010
|
-
relatedEntityJoinFieldName: re.RelatedEntityJoinFieldName ?? undefined,
|
|
1011
|
-
displayName: re.DisplayName ?? undefined,
|
|
1012
|
-
sequence: re.Sequence
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
catch (e) {
|
|
1016
|
-
LogError(`[SkipSDK] packSingleSkipOrganicKeyRelatedEntity error: ${e}`);
|
|
1017
|
-
return null;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
/**
|
|
1022
|
-
* Packs information about a single entity relationship
|
|
1023
|
-
* These relationships help Skip understand the data model
|
|
1024
|
-
* Copied from AskSkipResolver.PackSingleSkipEntityRelationship
|
|
1025
|
-
*/
|
|
1026
|
-
private packSingleSkipEntityRelationship(r: EntityRelationshipInfo): SkipEntityRelationshipInfo {
|
|
1027
|
-
try {
|
|
1028
|
-
return {
|
|
1029
|
-
entityID: r.EntityID,
|
|
1030
|
-
relatedEntityID: r.RelatedEntityID,
|
|
1031
|
-
type: r.Type,
|
|
1032
|
-
entityKeyField: r.EntityKeyField,
|
|
1033
|
-
relatedEntityJoinField: r.RelatedEntityJoinField,
|
|
1034
|
-
joinView: r.JoinView,
|
|
1035
|
-
joinEntityJoinField: r.JoinEntityJoinField,
|
|
1036
|
-
joinEntityInverseJoinField: r.JoinEntityInverseJoinField,
|
|
1037
|
-
entity: r.Entity,
|
|
1038
|
-
entityBaseView: r.EntityBaseView,
|
|
1039
|
-
relatedEntity: r.RelatedEntity,
|
|
1040
|
-
relatedEntityBaseView: r.RelatedEntityBaseView,
|
|
1041
|
-
};
|
|
920
|
+
// Clone the entity via toJSON, then swap in filtered+enriched fields and Skip-specific
|
|
921
|
+
// Active-only organic keys. Any future EntityInfo properties flow through automatically.
|
|
922
|
+
return new EntityInfo({
|
|
923
|
+
...e.toJSON(),
|
|
924
|
+
Fields: enrichedFields,
|
|
925
|
+
OrganicKeys: e.OrganicKeys.filter(ok => ok.Status === 'Active'),
|
|
926
|
+
});
|
|
1042
927
|
}
|
|
1043
|
-
catch (
|
|
1044
|
-
LogError(`[SkipSDK]
|
|
928
|
+
catch (err) {
|
|
929
|
+
LogError(`[SkipSDK] buildEntityForSkip error: ${err}`);
|
|
1045
930
|
return null;
|
|
1046
931
|
}
|
|
1047
932
|
}
|
|
1048
933
|
|
|
1049
934
|
/**
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1052
|
-
* Copied from AskSkipResolver.PackSingleSkipEntityField
|
|
935
|
+
* Enriches a field's EntityFieldValues with possible values from the database.
|
|
936
|
+
* Returns a plain object that can be used to construct an EntityFieldInfo.
|
|
1053
937
|
*/
|
|
1054
|
-
private async
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
name: f.Name,
|
|
1060
|
-
displayName: f.DisplayName,
|
|
1061
|
-
category: f.Category,
|
|
1062
|
-
type: f.Type,
|
|
1063
|
-
description: f.Description,
|
|
1064
|
-
isPrimaryKey: f.IsPrimaryKey,
|
|
1065
|
-
allowsNull: f.AllowsNull,
|
|
1066
|
-
isUnique: f.IsUnique,
|
|
1067
|
-
length: f.Length,
|
|
1068
|
-
precision: f.Precision,
|
|
1069
|
-
scale: f.Scale,
|
|
1070
|
-
sqlFullType: f.SQLFullType,
|
|
1071
|
-
defaultValue: f.DefaultValue,
|
|
1072
|
-
autoIncrement: f.AutoIncrement,
|
|
1073
|
-
valueListType: f.ValueListType,
|
|
1074
|
-
extendedType: f.ExtendedType,
|
|
1075
|
-
defaultInView: f.DefaultInView,
|
|
1076
|
-
defaultColumnWidth: f.DefaultColumnWidth,
|
|
1077
|
-
isVirtual: f.IsVirtual,
|
|
1078
|
-
isNameField: f.IsNameField,
|
|
1079
|
-
relatedEntityID: f.RelatedEntityID,
|
|
1080
|
-
relatedEntityFieldName: f.RelatedEntityFieldName,
|
|
1081
|
-
relatedEntity: f.RelatedEntity,
|
|
1082
|
-
relatedEntitySchemaName: f.RelatedEntitySchemaName,
|
|
1083
|
-
relatedEntityBaseView: f.RelatedEntityBaseView,
|
|
1084
|
-
possibleValues: await this.packFieldPossibleValues(f, dataSource),
|
|
1085
|
-
};
|
|
1086
|
-
}
|
|
1087
|
-
catch (e) {
|
|
1088
|
-
LogError(`[SkipSDK] packSingleSkipEntityField error: ${e}`);
|
|
1089
|
-
return null;
|
|
1090
|
-
}
|
|
938
|
+
private async enrichFieldValues(f: EntityFieldInfo): Promise<Record<string, unknown>> {
|
|
939
|
+
return {
|
|
940
|
+
...f.toJSON(),
|
|
941
|
+
EntityFieldValues: await this.packFieldValues(f),
|
|
942
|
+
};
|
|
1091
943
|
}
|
|
1092
944
|
|
|
1093
945
|
/**
|
|
1094
|
-
* Packs possible values for an entity field
|
|
1095
|
-
*
|
|
1096
|
-
* Copied from AskSkipResolver.PackFieldPossibleValues
|
|
946
|
+
* Packs possible values for an entity field based on the ValuesToPackWithSchema setting.
|
|
947
|
+
* Returns EntityFieldValueInfo-compatible objects.
|
|
1097
948
|
*/
|
|
1098
|
-
private async
|
|
949
|
+
private async packFieldValues(f: EntityFieldInfo): Promise<EntityFieldValueInfo[]> {
|
|
1099
950
|
try {
|
|
1100
951
|
if (f.ValuesToPackWithSchema === 'None') {
|
|
1101
|
-
return [];
|
|
952
|
+
return [];
|
|
1102
953
|
}
|
|
1103
954
|
else if (f.ValuesToPackWithSchema === 'All') {
|
|
1104
|
-
|
|
1105
|
-
return await this.getFieldDistinctValues(f, dataSource);
|
|
955
|
+
return await this.getFieldDistinctValues(f);
|
|
1106
956
|
}
|
|
1107
957
|
else if (f.ValuesToPackWithSchema === 'Auto') {
|
|
1108
|
-
// default setting - pack based on the ValueListType
|
|
1109
958
|
if (f.ValueListTypeEnum === 'List') {
|
|
1110
|
-
|
|
1111
|
-
return f.EntityFieldValues.map((v) => {
|
|
1112
|
-
return { value: v.Value, displayValue: v.Value };
|
|
1113
|
-
});
|
|
959
|
+
return f.EntityFieldValues.map((v) => new EntityFieldValueInfo({ Value: v.Value, Code: v.Value }));
|
|
1114
960
|
}
|
|
1115
961
|
else if (f.ValueListTypeEnum === 'ListOrUserEntry') {
|
|
1116
|
-
|
|
1117
|
-
// get the distinct list of values from the DB and concat that with the f.EntityFieldValues array - deduped and return
|
|
1118
|
-
const values = await this.getFieldDistinctValues(f, dataSource);
|
|
962
|
+
const values = await this.getFieldDistinctValues(f);
|
|
1119
963
|
if (!values || values.length === 0) {
|
|
1120
|
-
|
|
1121
|
-
return f.EntityFieldValues.map((v) => {
|
|
1122
|
-
return { value: v.Value, displayValue: v.Value };
|
|
1123
|
-
});
|
|
964
|
+
return f.EntityFieldValues.map((v) => new EntityFieldValueInfo({ Value: v.Value, Code: v.Value }));
|
|
1124
965
|
}
|
|
1125
966
|
else {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
}), ...values])];
|
|
967
|
+
const fromEntityFieldValues = f.EntityFieldValues.map((v) => new EntityFieldValueInfo({ Value: v.Value, Code: v.Value }));
|
|
968
|
+
return [...new Set([...fromEntityFieldValues, ...values])];
|
|
1129
969
|
}
|
|
1130
970
|
}
|
|
1131
971
|
}
|
|
1132
|
-
return [];
|
|
972
|
+
return [];
|
|
1133
973
|
}
|
|
1134
974
|
catch (e) {
|
|
1135
|
-
LogError(`[SkipSDK]
|
|
975
|
+
LogError(`[SkipSDK] packFieldValues error: ${e}`);
|
|
1136
976
|
return [];
|
|
1137
977
|
}
|
|
1138
978
|
}
|
|
1139
979
|
|
|
1140
980
|
/**
|
|
1141
|
-
* Gets distinct values for a field from the database
|
|
1142
|
-
*
|
|
1143
|
-
* Copied from AskSkipResolver.GetFieldDistinctValues
|
|
981
|
+
* Gets distinct values for a field from the database.
|
|
982
|
+
* Returns EntityFieldValueInfo objects.
|
|
1144
983
|
*/
|
|
1145
|
-
private async getFieldDistinctValues(f: EntityFieldInfo
|
|
984
|
+
private async getFieldDistinctValues(f: EntityFieldInfo): Promise<EntityFieldValueInfo[]> {
|
|
1146
985
|
try {
|
|
986
|
+
// Uses the provider's ExecuteSQL so this works on both SQL Server and PostgreSQL.
|
|
987
|
+
const provider = Metadata.Provider as DatabaseProviderBase;
|
|
1147
988
|
const sql = `SELECT DISTINCT ${f.Name} FROM ${f.SchemaName}.${f.BaseView}`;
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
if (!result || !result.recordset) {
|
|
989
|
+
const rows = await provider.ExecuteSQL<Record<string, unknown>>(sql);
|
|
990
|
+
if (!rows || rows.length === 0) {
|
|
1151
991
|
return [];
|
|
1152
992
|
}
|
|
1153
|
-
|
|
1154
|
-
return result.recordset.map((r) => {
|
|
1155
|
-
return {
|
|
1156
|
-
value: r[f.Name],
|
|
1157
|
-
displayValue: r[f.Name]
|
|
1158
|
-
};
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
993
|
+
return rows.map((r) => new EntityFieldValueInfo({ Value: r[f.Name] as string, Code: r[f.Name] as string }));
|
|
1161
994
|
}
|
|
1162
995
|
catch (e) {
|
|
1163
996
|
LogError(`[SkipSDK] getFieldDistinctValues error: ${e}`);
|
package/src/config.ts
CHANGED
|
@@ -192,6 +192,19 @@ const serverExtensionSchema = z.object({
|
|
|
192
192
|
Settings: z.record(z.unknown()).default({})
|
|
193
193
|
}).passthrough();
|
|
194
194
|
|
|
195
|
+
const cacheSettingsSchema = z.object({
|
|
196
|
+
/** Maximum total estimated memory for all cached results in MB. Default: 150. Set to 0 to disable memory-based eviction. */
|
|
197
|
+
maxMemoryMB: z.number().optional().default(150),
|
|
198
|
+
/** Maximum percentage of total cache memory that any single entity can occupy. Default: 50. Set to 0 to disable. */
|
|
199
|
+
maxPercentOfCachePerEntity: z.number().optional().default(50),
|
|
200
|
+
/** Default TTL in seconds. 0 = no TTL, rely on event-based invalidation. Default: 0. */
|
|
201
|
+
defaultTTLSeconds: z.number().optional().default(0),
|
|
202
|
+
/** Interval in seconds for periodic eviction sweep. 0 = disabled. Default: 300 (5 minutes). */
|
|
203
|
+
evictionSweepIntervalSeconds: z.number().optional().default(300),
|
|
204
|
+
/** Enable verbose cache logging (hits, misses, evictions). Default: false. */
|
|
205
|
+
verboseLogging: z.boolean().optional().default(false),
|
|
206
|
+
});
|
|
207
|
+
|
|
195
208
|
const configInfoSchema = z.object({
|
|
196
209
|
userHandling: userHandlingInfoSchema,
|
|
197
210
|
databaseSettings: databaseSettingsInfoSchema,
|
|
@@ -206,6 +219,7 @@ const configInfoSchema = z.object({
|
|
|
206
219
|
queryDialects: queryDialectSchema.optional().default({}),
|
|
207
220
|
multiTenancy: multiTenancySchema.optional().default({}),
|
|
208
221
|
serverExtensions: z.array(serverExtensionSchema).optional().default([]),
|
|
222
|
+
cacheSettings: cacheSettingsSchema.optional().default({}),
|
|
209
223
|
|
|
210
224
|
apiKey: z.string().optional(),
|
|
211
225
|
baseUrl: z.string().default('http://localhost'),
|
|
@@ -252,6 +266,7 @@ export type TelemetryConfig = z.infer<typeof telemetrySchema>;
|
|
|
252
266
|
export type QueryDialectConfig = z.infer<typeof queryDialectSchema>;
|
|
253
267
|
export type MultiTenancyConfig = z.infer<typeof multiTenancySchema>;
|
|
254
268
|
export type ServerExtensionConfig = z.infer<typeof serverExtensionSchema>;
|
|
269
|
+
export type CacheSettingsConfig = z.infer<typeof cacheSettingsSchema>;
|
|
255
270
|
export type ConfigInfo = z.infer<typeof configInfoSchema>;
|
|
256
271
|
|
|
257
272
|
/**
|
|
@@ -378,6 +393,15 @@ export const DEFAULT_SERVER_CONFIG: Partial<ConfigInfo> = {
|
|
|
378
393
|
level: 'standard'
|
|
379
394
|
},
|
|
380
395
|
|
|
396
|
+
// Cache settings defaults
|
|
397
|
+
cacheSettings: {
|
|
398
|
+
maxMemoryMB: 150,
|
|
399
|
+
maxPercentOfCachePerEntity: 50,
|
|
400
|
+
defaultTTLSeconds: 0,
|
|
401
|
+
evictionSweepIntervalSeconds: 300,
|
|
402
|
+
verboseLogging: false,
|
|
403
|
+
},
|
|
404
|
+
|
|
381
405
|
// Auth providers (environment-driven)
|
|
382
406
|
authProviders: [
|
|
383
407
|
// Microsoft Azure AD / Entra ID
|
|
@@ -37711,6 +37711,12 @@ export class MJEntity_ {
|
|
|
37711
37711
|
@Field(() => Boolean, {description: `When true (default), CodeGen can automatically set SupportsGeoCoding based on LLM analysis of entity fields. Set to 0 to lock the value and prevent CodeGen from changing it.`})
|
|
37712
37712
|
AutoUpdateSupportsGeoCoding: boolean;
|
|
37713
37713
|
|
|
37714
|
+
@Field(() => Boolean, {description: `Controls whether this entity participates in server-side and client-side caching. When false, all cache operations (PreRunView checks, auto-cache storage, BaseEntity event fingerprint scans, client-side IndexedDB cache) are skipped entirely. This column is the single source of truth at runtime; schema-level defaults are applied at CodeGen time via newEntityDefaults.AllowCachingBySchema.`})
|
|
37715
|
+
AllowCaching: boolean;
|
|
37716
|
+
|
|
37717
|
+
@Field(() => Boolean, {description: `When set to 1 AND TrackRecordChanges is also 1, the external change detection system will scan this entity for changes made outside the MJ framework (direct SQL, third-party tools, etc.) and replay them through Save() to create proper RecordChange audit entries. Default is 0 (opt-out) because most entities, especially __mj schema metadata tables, are managed by migrations/CodeGen and should not be scanned.`})
|
|
37718
|
+
DetectExternalChanges: boolean;
|
|
37719
|
+
|
|
37714
37720
|
@Field({nullable: true, description: `Schema-based programmatic code name derived from the entity Name. Uses GetClassNameSchemaPrefix(SchemaName) as the prefix, then strips EntityNamePrefix from the Name and removes spaces. For "__mj" schema with entity "MJ: AI Models", this produces "MJAIModels". For entities in other schemas, the sanitized schema name is prepended. Used in GraphQL type generation and internal code references.`})
|
|
37715
37721
|
CodeName?: string;
|
|
37716
37722
|
|
|
@@ -38080,6 +38086,12 @@ export class CreateMJEntityInput {
|
|
|
38080
38086
|
|
|
38081
38087
|
@Field(() => Boolean, { nullable: true })
|
|
38082
38088
|
AutoUpdateSupportsGeoCoding?: boolean;
|
|
38089
|
+
|
|
38090
|
+
@Field(() => Boolean, { nullable: true })
|
|
38091
|
+
AllowCaching?: boolean;
|
|
38092
|
+
|
|
38093
|
+
@Field(() => Boolean, { nullable: true })
|
|
38094
|
+
DetectExternalChanges?: boolean;
|
|
38083
38095
|
}
|
|
38084
38096
|
|
|
38085
38097
|
|
|
@@ -38265,6 +38277,12 @@ export class UpdateMJEntityInput {
|
|
|
38265
38277
|
@Field(() => Boolean, { nullable: true })
|
|
38266
38278
|
AutoUpdateSupportsGeoCoding?: boolean;
|
|
38267
38279
|
|
|
38280
|
+
@Field(() => Boolean, { nullable: true })
|
|
38281
|
+
AllowCaching?: boolean;
|
|
38282
|
+
|
|
38283
|
+
@Field(() => Boolean, { nullable: true })
|
|
38284
|
+
DetectExternalChanges?: boolean;
|
|
38285
|
+
|
|
38268
38286
|
@Field(() => [KeyValuePairInput], { nullable: true })
|
|
38269
38287
|
OldValues___?: KeyValuePairInput[];
|
|
38270
38288
|
}
|