@memberjunction/server 5.26.0 → 5.27.1
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/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/resolvers/ReportResolver.d.ts.map +1 -1
- package/dist/resolvers/ReportResolver.js.map +1 -1
- package/package.json +66 -66
- package/src/agents/skip-sdk.ts +59 -226
- package/src/index.ts +3 -2
- package/src/resolvers/ReportResolver.ts +1 -2
- package/src/__tests__/skip-sdk-organic-keys.test.ts +0 -274
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/index.ts
CHANGED
|
@@ -713,8 +713,9 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
713
713
|
|
|
714
714
|
// Health check endpoint - registered before auth middleware so cloud
|
|
715
715
|
// platform probes (Azure App Service, AWS ALB, k8s, etc.) don't
|
|
716
|
-
// generate noisy auth errors in the logs.
|
|
717
|
-
|
|
716
|
+
// generate noisy auth errors in the logs. CORS is enabled so browser-based
|
|
717
|
+
// clients (e.g. MJExplorer's connectivity poller) can read the response.
|
|
718
|
+
app.get('/healthcheck', cors<cors.CorsRequest>(), (_req, res) => {
|
|
718
719
|
res.status(200).json({ status: 'ok' });
|
|
719
720
|
});
|
|
720
721
|
|
|
@@ -2,7 +2,6 @@ import { EntitySaveOptions, IRunReportProvider, Metadata, RunReport } from '@mem
|
|
|
2
2
|
import { Arg, Ctx, Field, Int, Mutation, ObjectType, Query, Resolver } from 'type-graphql';
|
|
3
3
|
import { AppContext } from '../types.js';
|
|
4
4
|
import { MJConversationDetailEntity, MJReportEntity } from '@memberjunction/core-entities';
|
|
5
|
-
import { SkipAPIAnalysisCompleteResponse } from '@memberjunction/skip-types';
|
|
6
5
|
import { DataContext } from '@memberjunction/data-context';
|
|
7
6
|
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
8
7
|
import { z } from 'zod';
|
|
@@ -104,7 +103,7 @@ export class ReportResolverExtended extends ResolverBase {
|
|
|
104
103
|
const request = new mssql.Request(dataSource);
|
|
105
104
|
const result = await request.query(sql);
|
|
106
105
|
if (!result || !result.recordset || result.recordset.length === 0) throw new Error('Unable to retrieve converation details');
|
|
107
|
-
const skipData =
|
|
106
|
+
const skipData: { title?: string; reportTitle?: string; userExplanation?: string; messages?: unknown[] } = JSON.parse(result.recordset[0].Message);
|
|
108
107
|
|
|
109
108
|
const report = await md.GetEntityObject<MJReportEntity>('MJ: Reports', u);
|
|
110
109
|
report.NewRecord();
|