@memberjunction/server 2.23.1 → 2.24.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/generated/generated.d.ts +1304 -1301
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2620 -1304
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +3 -3
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +14 -11
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +8 -0
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts +2 -2
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +5 -5
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -1
- package/package.json +22 -22
- package/src/generated/generated.ts +2615 -1304
- package/src/generic/ResolverBase.ts +18 -11
- package/src/index.ts +2 -2
- package/src/resolvers/AskSkipResolver.ts +11 -0
- package/src/resolvers/SyncDataResolver.ts +5 -5
- package/src/types.ts +28 -0
|
@@ -27,9 +27,9 @@ import { FieldMapper } from '@memberjunction/graphql-dataprovider';
|
|
|
27
27
|
import { Subscription } from 'rxjs';
|
|
28
28
|
|
|
29
29
|
export class ResolverBase {
|
|
30
|
-
private _emit = process.env.CLOUDEVENTS_HTTP_TRANSPORT ? emitterFor(httpTransport(process.env.CLOUDEVENTS_HTTP_TRANSPORT)) : null;
|
|
31
|
-
private _cloudeventsHeaders = process.env.CLOUDEVENTS_HTTP_HEADERS ? JSON.parse(process.env.CLOUDEVENTS_HTTP_HEADERS) : {};
|
|
32
|
-
private
|
|
30
|
+
private static _emit = process.env.CLOUDEVENTS_HTTP_TRANSPORT ? emitterFor(httpTransport(process.env.CLOUDEVENTS_HTTP_TRANSPORT)) : null;
|
|
31
|
+
private static _cloudeventsHeaders = process.env.CLOUDEVENTS_HTTP_HEADERS ? JSON.parse(process.env.CLOUDEVENTS_HTTP_HEADERS) : {};
|
|
32
|
+
private static _eventSubscriptions = new Map<string, Subscription>;
|
|
33
33
|
|
|
34
34
|
protected MapFieldNamesToCodeNames(entityName: string, dataObject: any) {
|
|
35
35
|
// for the given entity name provided, check to see if there are any fields
|
|
@@ -245,7 +245,7 @@ export class ResolverBase {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
protected async EmitCloudEvent({ component, event, eventCode, args }: MJEvent) {
|
|
248
|
-
if (
|
|
248
|
+
if (ResolverBase._emit && event === MJEventType.ComponentEvent && eventCode === BaseEntity.BaseEventCode) {
|
|
249
249
|
const extendedType = args instanceof BaseEntityEvent ? `.${args.type}` : '';
|
|
250
250
|
const type = `MemberJunction.${event}${extendedType}`;
|
|
251
251
|
const source = `${process.env.CLOUDEVENTS_SOURCE ?? 'MemberJunction'}`;
|
|
@@ -255,7 +255,7 @@ export class ResolverBase {
|
|
|
255
255
|
const cloudEvent = new CloudEvent({ type, source, subject, data });
|
|
256
256
|
|
|
257
257
|
try {
|
|
258
|
-
const cloudeventTransportResponse = await
|
|
258
|
+
const cloudeventTransportResponse = await ResolverBase._emit(cloudEvent, { headers: ResolverBase._cloudeventsHeaders });
|
|
259
259
|
const cloudeventResponse = JSON.stringify(cloudeventTransportResponse);
|
|
260
260
|
if (/error/i.test(cloudeventResponse)) {
|
|
261
261
|
console.error('CloudEvent ERROR', cloudeventResponse);
|
|
@@ -548,27 +548,34 @@ export class ResolverBase {
|
|
|
548
548
|
}
|
|
549
549
|
|
|
550
550
|
protected ListenForEntityMessages(entityObject: BaseEntity, pubSub: PubSubEngine, userPayload: UserPayload) {
|
|
551
|
-
|
|
551
|
+
// The unique key is set up for each entity object via it's primary key to ensure that we only have one listener at most for each unique
|
|
552
|
+
// entity in the system. This is important because we don't want to have multiple listeners for the same entity as it could
|
|
553
|
+
// cause issues with multiple messages for the same event.
|
|
554
|
+
const uniqueKey = entityObject.EntityInfo.Name;
|
|
555
|
+
|
|
556
|
+
if (!ResolverBase._eventSubscriptions.has(uniqueKey)) {
|
|
552
557
|
// listen for events from the entityObject in case it is a long running task and we can push messages back to the client via pubSub
|
|
553
|
-
|
|
558
|
+
const theSub = MJGlobal.Instance.GetEventListener(false).subscribe(async (event: MJEvent) => {
|
|
554
559
|
if (event) {
|
|
555
560
|
await this.EmitCloudEvent(event);
|
|
556
561
|
|
|
557
|
-
if (event.
|
|
562
|
+
if (event.args && event.args instanceof BaseEntityEvent) {
|
|
563
|
+
const baseEntityEvent = event.args as BaseEntityEvent;
|
|
558
564
|
// message from our entity object, relay it to the client
|
|
559
565
|
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
560
566
|
message: JSON.stringify({
|
|
561
567
|
status: 'OK',
|
|
562
568
|
type: 'EntityObjectStatusMessage',
|
|
563
|
-
entityName:
|
|
564
|
-
primaryKey:
|
|
565
|
-
message: event.args.
|
|
569
|
+
entityName: baseEntityEvent.baseEntity.EntityInfo.Name,
|
|
570
|
+
primaryKey: baseEntityEvent.baseEntity.PrimaryKey,
|
|
571
|
+
message: event.args.payload,
|
|
566
572
|
}),
|
|
567
573
|
sessionId: userPayload.sessionId,
|
|
568
574
|
});
|
|
569
575
|
}
|
|
570
576
|
}
|
|
571
577
|
});
|
|
578
|
+
ResolverBase._eventSubscriptions.set(uniqueKey, theSub);
|
|
572
579
|
}
|
|
573
580
|
}
|
|
574
581
|
|
package/src/index.ts
CHANGED
|
@@ -69,7 +69,7 @@ export { GetReadOnlyDataSource, GetReadWriteDataSource } from './util.js';
|
|
|
69
69
|
export * from './generated/generated.js';
|
|
70
70
|
|
|
71
71
|
import { resolve } from 'node:path';
|
|
72
|
-
import { DataSourceInfo } from './types.js';
|
|
72
|
+
import { DataSourceInfo, raiseEvent } from './types.js';
|
|
73
73
|
|
|
74
74
|
export type MJServerOptions = {
|
|
75
75
|
onBeforeServe?: () => void | Promise<void>;
|
|
@@ -125,8 +125,8 @@ export const serve = async (resolverPaths: Array<string>, app = createApp(), opt
|
|
|
125
125
|
console.log('Read-only Data Source has been initialized.');
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
129
128
|
setupComplete$.next(true);
|
|
129
|
+
raiseEvent('setupComplete', dataSources, null, this);
|
|
130
130
|
|
|
131
131
|
/******TEST HARNESS FOR CHANGE DETECTION */
|
|
132
132
|
/******TEST HARNESS FOR CHANGE DETECTION */
|
|
@@ -1456,6 +1456,17 @@ export class AskSkipResolver {
|
|
|
1456
1456
|
LogError(`Error saving user notification entity for AI message: ${sResult}`, undefined, userNotification.LatestResult);
|
|
1457
1457
|
}
|
|
1458
1458
|
|
|
1459
|
+
// check to see if Skip retrieved additional data on his own outside of the DATA_REQUEST phase/process. It is possible for Skip to call back
|
|
1460
|
+
// to the MJAPI in the instance using the GetData() query in the MJAPI. If Skip did this, we need to save the data context items here.
|
|
1461
|
+
if (apiResponse.newDataItems) {
|
|
1462
|
+
apiResponse.newDataItems.forEach((skipItem) => {
|
|
1463
|
+
const newItem = dataContext.AddDataContextItem();
|
|
1464
|
+
newItem.Type = 'sql';
|
|
1465
|
+
newItem.SQL = skipItem.text;
|
|
1466
|
+
newItem.AdditionalDescription = skipItem.description;
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1459
1470
|
// Save the data context items...
|
|
1460
1471
|
// FOR NOW, we don't want to store the data in the database, we will just load it from the data context when we need it
|
|
1461
1472
|
// we need a better strategy to persist because the cost of storage and retrieval/parsing is higher than just running the query again in many/most cases
|
|
@@ -118,7 +118,7 @@ export class SyncDataResolver {
|
|
|
118
118
|
results.push(await this.SyncSingleItem(item, context, md));
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
if (await this.DoSyncItemsAffectMetadata(items)) {
|
|
121
|
+
if (await this.DoSyncItemsAffectMetadata(context.userPayload.userRecord, items)) {
|
|
122
122
|
await md.Refresh(); // force refesh the metadata which will cause a reload from the DB
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -131,13 +131,13 @@ export class SyncDataResolver {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
protected async
|
|
134
|
+
protected async GetLowercaseMetadataEntitiesList(user: UserInfo, forceRefresh: boolean = false): Promise<string[]> {
|
|
135
135
|
if (forceRefresh || __metadata_DatasetItems.length === 0) {
|
|
136
136
|
const rv = new RunView(); // cache this, veyr simple - should use an engine for this stuff later
|
|
137
137
|
const result = await rv.RunView<DatasetItemEntity>({
|
|
138
138
|
EntityName: "Dataset Items",
|
|
139
139
|
ExtraFilter: "Dataset = 'MJ_Metadata'",
|
|
140
|
-
})
|
|
140
|
+
}, user)
|
|
141
141
|
if (result && result.Success) {
|
|
142
142
|
__metadata_DatasetItems.length = 0;
|
|
143
143
|
__metadata_DatasetItems.push(...result.Results.map((r) => {
|
|
@@ -149,9 +149,9 @@ export class SyncDataResolver {
|
|
|
149
149
|
return __metadata_DatasetItems;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
protected async DoSyncItemsAffectMetadata(items: ActionItemInputType[]): Promise<boolean> {
|
|
152
|
+
protected async DoSyncItemsAffectMetadata(user: UserInfo, items: ActionItemInputType[]): Promise<boolean> {
|
|
153
153
|
// check to see if any of the items affect any of these entities:
|
|
154
|
-
const entitiesToCheck = await this.
|
|
154
|
+
const entitiesToCheck = await this.GetLowercaseMetadataEntitiesList(user, false);
|
|
155
155
|
for (const item of items) {
|
|
156
156
|
if (entitiesToCheck.find(e => e === item.EntityName.trim().toLowerCase()) ) {
|
|
157
157
|
return true;
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { UserInfo } from '@memberjunction/core';
|
|
1
2
|
import { UserViewEntity } from '@memberjunction/core-entities';
|
|
2
3
|
import { GraphQLSchema } from 'graphql';
|
|
3
4
|
import { PubSubEngine } from 'type-graphql';
|
|
4
5
|
import { DataSource, QueryRunner } from 'typeorm';
|
|
6
|
+
import { getSystemUser } from './auth';
|
|
7
|
+
import { MJEvent, MJEventType, MJGlobal } from '@memberjunction/global';
|
|
5
8
|
|
|
6
9
|
export type UserPayload = {
|
|
7
10
|
email: string;
|
|
@@ -69,3 +72,28 @@ export type RunViewGenericParams = {
|
|
|
69
72
|
userPayload?: UserPayload;
|
|
70
73
|
pubSub: PubSubEngine;
|
|
71
74
|
};
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
export class MJServerEvent {
|
|
78
|
+
type: 'setupComplete' | 'requestReceived' | 'requestCompleted' | 'requestFailed';
|
|
79
|
+
dataSources: DataSourceInfo[];
|
|
80
|
+
userPayload: UserPayload;
|
|
81
|
+
systemUser: UserInfo;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const MJ_SERVER_EVENT_CODE = 'MJ_SERVER_EVENT';
|
|
85
|
+
|
|
86
|
+
export async function raiseEvent(type: MJServerEvent['type'], dataSources: DataSourceInfo[], userPayload: UserPayload, component?: any) {
|
|
87
|
+
const event = new MJServerEvent();
|
|
88
|
+
event.type = type;
|
|
89
|
+
event.dataSources = dataSources;
|
|
90
|
+
event.userPayload = userPayload;
|
|
91
|
+
event.systemUser = await getSystemUser();
|
|
92
|
+
|
|
93
|
+
const mje = new MJEvent();
|
|
94
|
+
mje.args = event;
|
|
95
|
+
mje.component = component;
|
|
96
|
+
mje.event = MJEventType.ComponentEvent;
|
|
97
|
+
mje.eventCode = MJ_SERVER_EVENT_CODE;
|
|
98
|
+
MJGlobal.Instance.RaiseEvent(mje);
|
|
99
|
+
}
|