@memberjunction/server 5.27.1 → 5.29.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/config.d.ts +151 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +959 -5
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +4639 -280
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +14 -0
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +37 -3
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RestoreContextInput.d.ts +27 -0
- package/dist/generic/RestoreContextInput.d.ts.map +1 -0
- package/dist/generic/RestoreContextInput.js +39 -0
- package/dist/generic/RestoreContextInput.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -4
- package/dist/index.js.map +1 -1
- package/dist/resolvers/FeedbackResolver.d.ts +150 -0
- package/dist/resolvers/FeedbackResolver.d.ts.map +1 -0
- package/dist/resolvers/FeedbackResolver.js +876 -0
- package/dist/resolvers/FeedbackResolver.js.map +1 -0
- package/dist/resolvers/FileResolver.d.ts +27 -0
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +32 -3
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +18 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +247 -22
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts +77 -0
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +300 -1
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +87 -32
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/package.json +68 -66
- package/src/config.ts +19 -0
- package/src/generated/generated.ts +3430 -281
- package/src/generic/ResolverBase.ts +41 -4
- package/src/generic/RestoreContextInput.ts +32 -0
- package/src/index.ts +22 -5
- package/src/resolvers/FeedbackResolver.ts +940 -0
- package/src/resolvers/FileResolver.ts +33 -4
- package/src/resolvers/IntegrationDiscoveryResolver.ts +224 -20
- package/src/resolvers/MCPResolver.ts +297 -1
- package/src/resolvers/RunAIAgentResolver.ts +89 -32
|
@@ -153,6 +153,10 @@ export class ResolverBase {
|
|
|
153
153
|
Key: mapper.ReverseMapFieldName(item.Key),
|
|
154
154
|
Value: item.Value,
|
|
155
155
|
}));
|
|
156
|
+
} else if (key === 'RestoreContext___') {
|
|
157
|
+
// Pass through the restore-context blob unchanged — its inner field
|
|
158
|
+
// names (SourceChangeID, Reason) are not entity-field names.
|
|
159
|
+
mapped[key] = input[key];
|
|
156
160
|
} else {
|
|
157
161
|
mapped[mapper.ReverseMapFieldName(key)] = input[key];
|
|
158
162
|
}
|
|
@@ -160,6 +164,24 @@ export class ResolverBase {
|
|
|
160
164
|
return mapped;
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Applies an inbound RestoreContext___ blob to a server-side BaseEntity.
|
|
169
|
+
* Mirrors the OldValues___ pattern — the client-side BaseEntity's
|
|
170
|
+
* `_restoreContext` doesn't traverse the network, so the server must
|
|
171
|
+
* reconstruct it from the mutation input before calling Save().
|
|
172
|
+
*
|
|
173
|
+
* Returns true when context was applied; false when no context was on the input.
|
|
174
|
+
*/
|
|
175
|
+
protected applyRestoreContext(
|
|
176
|
+
entityObject: BaseEntity,
|
|
177
|
+
input: { RestoreContext___?: { SourceChangeID?: string; Reason?: string | null } | null },
|
|
178
|
+
): boolean {
|
|
179
|
+
const ctx = input?.RestoreContext___;
|
|
180
|
+
if (!ctx || !ctx.SourceChangeID) return false;
|
|
181
|
+
entityObject.SetRestoreContext(ctx.SourceChangeID, ctx.Reason ?? null);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
163
185
|
protected async ArrayMapFieldNamesToCodeNames(entityName: string, dataObjectArray: any[], contextUser?: UserInfo): Promise<any[]> {
|
|
164
186
|
// iterate through the array and call MapFieldNamesToCodeNames for each element
|
|
165
187
|
if (dataObjectArray && dataObjectArray.length > 0) {
|
|
@@ -1053,7 +1075,17 @@ export class ResolverBase {
|
|
|
1053
1075
|
// fire event and proceed if it wasn't cancelled
|
|
1054
1076
|
const entityObject = await provider.GetEntityObject(entityName, this.GetUserFromPayload(userPayload));
|
|
1055
1077
|
entityObject.NewRecord();
|
|
1056
|
-
|
|
1078
|
+
// Strip the RestoreContext___ blob from the field assignments — it's
|
|
1079
|
+
// metadata for the upcoming Save(), not a field on the record.
|
|
1080
|
+
const fieldsForSet: Record<string, unknown> = {};
|
|
1081
|
+
for (const key of Object.keys(input)) {
|
|
1082
|
+
if (key !== 'RestoreContext___') fieldsForSet[key] = input[key];
|
|
1083
|
+
}
|
|
1084
|
+
entityObject.SetMany(fieldsForSet);
|
|
1085
|
+
|
|
1086
|
+
// Reconstruct the client-side restore context, if any, on this server
|
|
1087
|
+
// entity so the data provider writes the lineage columns on Save().
|
|
1088
|
+
this.applyRestoreContext(entityObject, input);
|
|
1057
1089
|
|
|
1058
1090
|
this.ListenForEntityMessages(entityObject, pubSub, userPayload);
|
|
1059
1091
|
|
|
@@ -1095,10 +1127,11 @@ export class ResolverBase {
|
|
|
1095
1127
|
const entityInfo = entityObject.EntityInfo;
|
|
1096
1128
|
const clientNewValues = {};
|
|
1097
1129
|
Object.keys(input).forEach((key) => {
|
|
1098
|
-
|
|
1130
|
+
// Skip metadata blobs that aren't actual entity fields.
|
|
1131
|
+
if (key !== 'OldValues___' && key !== 'RestoreContext___') {
|
|
1099
1132
|
clientNewValues[key] = input[key];
|
|
1100
1133
|
}
|
|
1101
|
-
});
|
|
1134
|
+
});
|
|
1102
1135
|
|
|
1103
1136
|
if (entityInfo.TrackRecordChanges || !input.OldValues___) {
|
|
1104
1137
|
// We get here because EITHER the entity tracks record changes OR the client did not provide OldValues, so we need to load the old values from the DB
|
|
@@ -1140,8 +1173,12 @@ export class ResolverBase {
|
|
|
1140
1173
|
entityObject.SetMany(clientNewValues);
|
|
1141
1174
|
}
|
|
1142
1175
|
|
|
1176
|
+
// Reconstruct the client-side restore context, if any, on this server
|
|
1177
|
+
// entity so the data provider writes the lineage columns on Save().
|
|
1178
|
+
this.applyRestoreContext(entityObject, input);
|
|
1179
|
+
|
|
1143
1180
|
this.ListenForEntityMessages(entityObject, pubSub, userPayload);
|
|
1144
|
-
|
|
1181
|
+
|
|
1145
1182
|
if (await entityObject.Save()) {
|
|
1146
1183
|
// save worked, fire afterevent and return all the data
|
|
1147
1184
|
await this.AfterUpdate(provider, input); // fire event
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GraphQL InputType carrying restore-lineage context across the network.
|
|
5
|
+
*
|
|
6
|
+
* Set as the `RestoreContext___` reserved field on any entity Create or
|
|
7
|
+
* Update mutation input when the operation is a restore. The server-side
|
|
8
|
+
* resolver detects it, calls `BaseEntity.SetRestoreContext()` on the
|
|
9
|
+
* server-side entity instance before `Save()`, and the data provider then
|
|
10
|
+
* writes the resulting RecordChange row with `Source='Restore'`,
|
|
11
|
+
* `RestoredFromID = SourceChangeID`, and `RestoreReason = Reason`.
|
|
12
|
+
*
|
|
13
|
+
* Mirrors the pattern used by `OldValues___` (KeyValuePairInput[]) — a
|
|
14
|
+
* non-field metadata blob carried alongside the regular field values
|
|
15
|
+
* through the GraphQL mutation input.
|
|
16
|
+
*/
|
|
17
|
+
@InputType()
|
|
18
|
+
export class RestoreContextInput {
|
|
19
|
+
/**
|
|
20
|
+
* ID of the historical RecordChange row whose state is being restored.
|
|
21
|
+
* Persisted to RecordChange.RestoredFromID on the new change row.
|
|
22
|
+
*/
|
|
23
|
+
@Field(() => String)
|
|
24
|
+
SourceChangeID: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Optional user-entered explanation for the restore. Persisted to
|
|
28
|
+
* RecordChange.RestoreReason. NULL when the user did not enter one.
|
|
29
|
+
*/
|
|
30
|
+
@Field(() => String, { nullable: true })
|
|
31
|
+
Reason?: string | null;
|
|
32
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -104,6 +104,7 @@ export * from './resolvers/TaskResolver.js';
|
|
|
104
104
|
export * from './generic/KeyValuePairInput.js';
|
|
105
105
|
export * from './generic/KeyInputOutputTypes.js';
|
|
106
106
|
export * from './generic/DeleteOptionsInput.js';
|
|
107
|
+
export * from './generic/RestoreContextInput.js';
|
|
107
108
|
|
|
108
109
|
export * from './agents/skip-agent.js';
|
|
109
110
|
export * from './agents/skip-sdk.js';
|
|
@@ -130,6 +131,7 @@ export * from './resolvers/MCPResolver.js';
|
|
|
130
131
|
export * from './resolvers/ActionResolver.js';
|
|
131
132
|
export * from './resolvers/CacheStatsResolver.js';
|
|
132
133
|
export * from './resolvers/EntityCommunicationsResolver.js';
|
|
134
|
+
export * from './resolvers/FeedbackResolver.js';
|
|
133
135
|
export * from './resolvers/EntityResolver.js';
|
|
134
136
|
export * from './resolvers/ISAEntityResolver.js';
|
|
135
137
|
export * from './resolvers/ArtifactFileResolver.js';
|
|
@@ -867,6 +869,13 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
867
869
|
// Process pending RSU work from pre-restart (entity maps, field maps, sync)
|
|
868
870
|
processRSUPendingWork().catch(err => console.warn(`RSU pending work processing failed: ${err}`));
|
|
869
871
|
|
|
872
|
+
// Resume any integration syncs that were orphaned by the previous process restart
|
|
873
|
+
const resumeUser = UserCache.Instance.GetSystemUser();
|
|
874
|
+
if (resumeUser) {
|
|
875
|
+
IntegrationEngine.Instance.ResumeOrphanedSyncs(resumeUser)
|
|
876
|
+
.catch(err => console.warn(`[IntegrationEngine] Orphaned sync resume failed: ${err}`));
|
|
877
|
+
}
|
|
878
|
+
|
|
870
879
|
// Set up graceful shutdown handlers
|
|
871
880
|
const gracefulShutdown = async (signal: string) => {
|
|
872
881
|
console.log(`\n${signal} received, shutting down gracefully...`);
|
|
@@ -980,6 +989,11 @@ async function processRSUPendingWork(): Promise<void> {
|
|
|
980
989
|
const rvPending = new RunView();
|
|
981
990
|
const sourceObjectFields: Record<string, string[] | null> = item.SourceObjectFields ?? {};
|
|
982
991
|
|
|
992
|
+
// Introspect schema ONCE for the entire connector, then reuse per object
|
|
993
|
+
const introspect = connector.IntrospectSchema.bind(connector) as
|
|
994
|
+
(ci: unknown, u: unknown) => Promise<{ Objects: Array<{ ExternalName: string; Fields: Array<{ Name: string; IsPrimaryKey?: boolean; IsRequired?: boolean }> }> }>;
|
|
995
|
+
const schema = await introspect(companyIntegration, systemUser);
|
|
996
|
+
|
|
983
997
|
for (const objName of item.SourceObjectNames) {
|
|
984
998
|
const tableName = objName.replace(/[^A-Za-z0-9_]/g, '_').toLowerCase();
|
|
985
999
|
const entity = md.Entities.find(
|
|
@@ -1028,9 +1042,6 @@ async function processRSUPendingWork(): Promise<void> {
|
|
|
1028
1042
|
|
|
1029
1043
|
// Create field maps — filter by SourceObjectFields (null = all)
|
|
1030
1044
|
try {
|
|
1031
|
-
const introspect = connector.IntrospectSchema.bind(connector) as
|
|
1032
|
-
(ci: unknown, u: unknown) => Promise<{ Objects: Array<{ ExternalName: string; Fields: Array<{ Name: string }> }> }>;
|
|
1033
|
-
const schema = await introspect(companyIntegration, systemUser);
|
|
1034
1045
|
const sourceObj = schema.Objects.find(o => o.ExternalName.toLowerCase() === objName.toLowerCase());
|
|
1035
1046
|
|
|
1036
1047
|
const selectedFields = sourceObjectFields[objName]; // null = all, string[] = specific
|
|
@@ -1059,6 +1070,9 @@ async function processRSUPendingWork(): Promise<void> {
|
|
|
1059
1070
|
fieldMap.EntityMapID = entityMapID;
|
|
1060
1071
|
fieldMap.SourceFieldName = field.Name;
|
|
1061
1072
|
fieldMap.DestinationFieldName = field.Name.replace(/[^A-Za-z0-9_]/g, '_');
|
|
1073
|
+
fieldMap.IsKeyField = field.IsPrimaryKey ?? false;
|
|
1074
|
+
fieldMap.IsRequired = field.IsRequired ?? false;
|
|
1075
|
+
fieldMap.Direction = 'SourceToDest';
|
|
1062
1076
|
fieldMap.Status = 'Active';
|
|
1063
1077
|
if (await fieldMap.Save()) fieldCount++;
|
|
1064
1078
|
}
|
|
@@ -1075,9 +1089,10 @@ async function processRSUPendingWork(): Promise<void> {
|
|
|
1075
1089
|
const syncOptions: IntegrationSyncOptions = {};
|
|
1076
1090
|
if (item.SyncScope !== 'all' && createdEntityMapIDs.length > 0) syncOptions.EntityMapIDs = createdEntityMapIDs;
|
|
1077
1091
|
if (item.FullSync) syncOptions.FullSync = true;
|
|
1092
|
+
if (item.SyncDirection) syncOptions.SyncDirection = item.SyncDirection;
|
|
1078
1093
|
const opts = Object.keys(syncOptions).length > 0 ? syncOptions : undefined;
|
|
1079
1094
|
IntegrationEngine.Instance.RunSync(item.CompanyIntegrationID, systemUser, 'Manual', undefined, undefined, opts);
|
|
1080
|
-
console.log(`[RSU] Sync started for ${item.CompanyIntegrationID} (EntityMaps: ${createdEntityMapIDs.length}, FullSync: ${!!item.FullSync})`);
|
|
1095
|
+
console.log(`[RSU] Sync started for ${item.CompanyIntegrationID} (EntityMaps: ${createdEntityMapIDs.length}, FullSync: ${!!item.FullSync}, SyncDirection: ${item.SyncDirection ?? 'entity-map default'})`);
|
|
1081
1096
|
} catch (syncErr) {
|
|
1082
1097
|
console.warn(`[RSU] Sync start failed: ${syncErr}`);
|
|
1083
1098
|
}
|
|
@@ -1134,7 +1149,9 @@ async function processRSUPendingWork(): Promise<void> {
|
|
|
1134
1149
|
job.NewRecord();
|
|
1135
1150
|
job.JobTypeID = jobTypeResult.Results[0].ID;
|
|
1136
1151
|
job.OwnerUserID = systemUser.ID;
|
|
1137
|
-
|
|
1152
|
+
const schedConfig: Record<string, unknown> = { CompanyIntegrationID: item.CompanyIntegrationID };
|
|
1153
|
+
if (item.ScheduleSyncDirection) schedConfig.SyncDirection = item.ScheduleSyncDirection;
|
|
1154
|
+
job.Configuration = JSON.stringify(schedConfig);
|
|
1138
1155
|
}
|
|
1139
1156
|
|
|
1140
1157
|
job.Name = `${integrationName} Scheduled Sync`;
|