@memberjunction/server 1.7.1 → 1.8.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.
@@ -24,10 +24,16 @@ export class ResolverBase {
24
24
  const entityInfo = md.Entities.find((e) => e.Name === entityName);
25
25
  if (!entityInfo)
26
26
  throw new Error(`Entity ${entityName} not found in metadata`);
27
- const fields = entityInfo.Fields.filter((f) => f.Name !== f.CodeName);
27
+ const fields = entityInfo.Fields.filter((f) => f.Name !== f.CodeName || f.Name.startsWith('__mj_'));
28
28
  fields.forEach((f) => {
29
29
  if (dataObject.hasOwnProperty(f.Name)) {
30
- dataObject[f.CodeName] = dataObject[f.Name];
30
+ if (f.CodeName.startsWith('__mj_')) { // GraphQL doesn't allow us to pass back fields with __ so we are mapping our special field cases that start with __mj_ to _mj__ for transport - they are converted back on the other side automatically
31
+ const newCodeName = `_mj__${f.CodeName.substring(5)}`;
32
+ dataObject[newCodeName] = dataObject[f.Name];
33
+ }
34
+ else {
35
+ dataObject[f.CodeName] = dataObject[f.Name];
36
+ }
31
37
  delete dataObject[f.Name];
32
38
  }
33
39
  });
@@ -205,14 +211,12 @@ export class ResolverBase {
205
211
  // figure out the result type from the input string (if provided)
206
212
  let rt: 'simple' | 'entity_object' | 'count_only' = 'simple';
207
213
  switch (resultType?.trim().toLowerCase()) {
208
- case 'entity_object':
209
- rt = 'entity_object';
210
- break;
211
214
  case 'count_only':
212
215
  rt = 'count_only';
213
216
  break;
217
+ case 'entity_object':
214
218
  default:
215
- rt = 'simple';
219
+ rt = 'simple'; // use simple as the default AND for entity_object becuase on teh server we don't really pass back a true entity_object anyway, just passing back the simple object anyway
216
220
  break;
217
221
  }
218
222
 
@@ -236,9 +240,25 @@ export class ResolverBase {
236
240
  },
237
241
  user
238
242
  );
243
+ // go through the result and convert all fields that start with __mj_*** to _mj__*** for GraphQL transport
244
+ if (result && result.Success) {
245
+ for (const r of result.Results) {
246
+ const keys = Object.keys(r);
247
+ keys.forEach((k) => {
248
+ if (k.trim().toLowerCase().startsWith('__mj_')) {
249
+ r[`_mj__${k.substring(5)}`] = r[k];
250
+ delete r[k];
251
+ }
252
+ });
253
+
254
+ }
255
+ }
239
256
  return result;
240
- } else return null;
241
- } catch (err) {
257
+ }
258
+ else
259
+ return null;
260
+ }
261
+ catch (err) {
242
262
  console.log(err);
243
263
  throw err;
244
264
  }
@@ -385,7 +405,7 @@ export class ResolverBase {
385
405
  if (await entityObject.Save()) {
386
406
  // save worked, fire the AfterCreate event and then return all the data
387
407
  await this.AfterCreate(dataSource, input); // fire event
388
- return entityObject.GetAll();
408
+ return this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll());
389
409
  }
390
410
  else
391
411
  // save failed, return null
@@ -458,7 +478,7 @@ export class ResolverBase {
458
478
  if (await entityObject.Save()) {
459
479
  // save worked, fire afterevent and return all the data
460
480
  await this.AfterUpdate(dataSource, input); // fire event
461
- return entityObject.GetAll();
481
+ return this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll());
462
482
  }
463
483
  else {
464
484
  throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
@@ -499,6 +519,10 @@ export class ResolverBase {
499
519
  val = (val === null || val === undefined || val === 'false' || val === '0' || parseInt(val) === 0) ? false : true;
500
520
  break;
501
521
  case EntityFieldTSType.Date:
522
+ // first, if val is a string and it is actually a number (milliseconds since epoch), convert it to a number.
523
+ if (val !== null && val !== undefined && val.toString().trim() !== '' && !isNaN(val))
524
+ val = parseInt(val);
525
+
502
526
  val = val !== null && val !== undefined ? new Date(val) : null;
503
527
  break;
504
528
  default:
@@ -516,7 +540,24 @@ export class ResolverBase {
516
540
  const dbDifferences = [];
517
541
  Object.keys(clientOldValues).forEach((key) => {
518
542
  const f = entityObject.EntityInfo.Fields.find((f) => f.CodeName === key);
519
- if (clientOldValues[key] !== dbValues[key] && f && f.AllowUpdateAPI && !f.IsPrimaryKey ) {
543
+ let different = false;
544
+ switch (typeof clientOldValues[key]) {
545
+ case 'number':
546
+ different = clientOldValues[key] !== dbValues[key];
547
+ break;
548
+ case 'boolean':
549
+ different = clientOldValues[key] !== dbValues[key];
550
+ break;
551
+ case 'object':
552
+ if (clientOldValues[key] instanceof Date) {
553
+ different = clientOldValues[key].getTime() !== dbValues[key].getTime();
554
+ }
555
+ break;
556
+ default:
557
+ different = clientOldValues[key] !== dbValues[key];
558
+ break;
559
+ }
560
+ if (different && f && !f.ReadOnly ) {
520
561
  // only include updateable fields
521
562
  dbDifferences.push({
522
563
  FieldName: key,
package/src/index.ts CHANGED
@@ -89,18 +89,25 @@ export const serve = async (resolverPaths: Array<string>) => {
89
89
  const config = new SQLServerProviderConfigData(dataSource, '', mj_core_schema, cacheRefreshInterval);
90
90
  await setupSQLServerClient(config); // datasource is already initialized, so we can setup the client right away
91
91
  const md = new Metadata();
92
-
92
+ console.log(`Data Source has been initialized. ${md?.Entities ? md.Entities.length : 0} entities loaded.`);
93
+ setupComplete$.next(true);
93
94
 
94
95
  /******TEST HARNESS FOR CHANGE DETECTION */
95
- const cd = ExternalChangeDetectorEngine.Instance;
96
- await cd.Config(false, UserCache.Users[0]);
97
-
98
- // don't wait for this, just run it and show in console whenever done.
99
- cd.DetectChangesForAllEligibleEntities().then(result => console.log(result));
96
+ /******TEST HARNESS FOR CHANGE DETECTION */
97
+ // const cd = ExternalChangeDetectorEngine.Instance;
98
+ // await cd.Config(false, UserCache.Users[0]);
99
+
100
+ // // don't wait for this, just run it and show in console whenever done.
101
+ // cd.DetectChangesForAllEligibleEntities().then(result => {
102
+ // console.log(result)
103
+ // cd.ReplayChanges(result.Changes).then(replayResult => {
104
+ // console.log(replayResult)
105
+ // });
106
+ // });
107
+ /******TEST HARNESS FOR CHANGE DETECTION */
108
+ /******TEST HARNESS FOR CHANGE DETECTION */
100
109
 
101
- console.log(`Data Source has been initialized. ${md?.Entities ? md.Entities.length : 0} entities loaded.`);
102
110
 
103
- setupComplete$.next(true);
104
111
 
105
112
  const dynamicModules = await Promise.all(paths.map((modulePath) => import(modulePath.replace(/\.[jt]s$/, ''))));
106
113
  const resolvers = dynamicModules.flatMap((module) =>
@@ -275,8 +275,8 @@ export class AskSkipResolver {
275
275
  feedback: q.Feedback,
276
276
  status: q.Status,
277
277
  qualityRank: q.QualityRank,
278
- createdAt: q.CreatedAt,
279
- updatedAt: q.UpdatedAt,
278
+ createdAt: q.__mj_CreatedAt,
279
+ updatedAt: q.__mj_UpdatedAt,
280
280
  categoryID: q.CategoryID,
281
281
  fields: q.Fields.map((f) => {
282
282
  return {
@@ -294,8 +294,8 @@ export class AskSkipResolver {
294
294
  computationDescription: f.ComputationDescription,
295
295
  isSummary: f.IsSummary,
296
296
  summaryDescription: f.SummaryDescription,
297
- createdAt: f.CreatedAt,
298
- updatedAt: f.UpdatedAt,
297
+ createdAt: f.__mj_CreatedAt,
298
+ updatedAt: f.__mj_UpdatedAt,
299
299
  }
300
300
  })
301
301
  }
@@ -470,23 +470,23 @@ export class AskSkipResolver {
470
470
  const md = new Metadata();
471
471
  const e = md.Entities.find((e) => e.Name === 'Conversation Details');
472
472
  const sql = `SELECT
473
- ${maxHistoricalMessages ? 'TOP ' + maxHistoricalMessages : ''} ID, Message, Role, CreatedAt
473
+ ${maxHistoricalMessages ? 'TOP ' + maxHistoricalMessages : ''} ID, Message, Role, __mj_CreatedAt
474
474
  FROM
475
475
  ${e.SchemaName}.${e.BaseView}
476
476
  WHERE
477
477
  ConversationID = ${ConversationId}
478
478
  ORDER
479
- BY CreatedAt DESC`;
479
+ BY __mj_CreatedAt DESC`;
480
480
  const result = await dataSource.query(sql);
481
481
  if (!result)
482
482
  throw new Error(`Error running SQL: ${sql}`);
483
483
  else {
484
- // first, let's sort the result array into a local variable called returnData and in that we will sort by CreatedAt in ASCENDING order so we have the right chronological order
484
+ // first, let's sort the result array into a local variable called returnData and in that we will sort by __mj_CreatedAt in ASCENDING order so we have the right chronological order
485
485
  // the reason we're doing a LOCAL sort here is because in the SQL query above, we're sorting in DESCENDING order so we can use the TOP clause to limit the number of records and get the
486
486
  // N most recent records. We want to sort in ASCENDING order because we want to send the messages to the Skip API in the order they were created.
487
487
  const returnData = result.sort((a: any, b: any) => {
488
- const aDate = new Date(a.CreatedAt);
489
- const bDate = new Date(b.CreatedAt);
488
+ const aDate = new Date(a.__mj_CreatedAt);
489
+ const bDate = new Date(b.__mj_CreatedAt);
490
490
  return aDate.getTime() - bDate.getTime();
491
491
  });
492
492
 
@@ -29,10 +29,10 @@ export class CommunicationProviderMessageType {
29
29
  AdditionalAttributes: string;
30
30
 
31
31
  @Field()
32
- CreatedAt: Date;
32
+ _mj_CreatedAt: Date;
33
33
 
34
34
  @Field()
35
- UpdatedAt: Date;
35
+ _mj_UpdatedAt: Date;
36
36
 
37
37
  @Field()
38
38
  CommunicationProvider?: string;
@@ -71,10 +71,10 @@ export class TemplateInputType {
71
71
  IsActive: boolean;
72
72
 
73
73
  @Field()
74
- CreatedAt: Date;
74
+ _mj_CreatedAt: Date;
75
75
 
76
76
  @Field()
77
- UpdatedAt: Date;
77
+ _mj_UpdatedAt: Date;
78
78
 
79
79
  @Field({ nullable: true})
80
80
  Category?: string;
@@ -169,7 +169,7 @@ export class ReportResolver {
169
169
  @Ctx() { userPayload }: AppContext): Promise<RunEntityCommunicationResultType> {
170
170
  try {
171
171
  await EntityCommunicationsEngine.Instance.Config(false, userPayload.userRecord);
172
- const newMessage = new Message(<Message>message);
172
+ const newMessage = new Message(message as unknown as Message);
173
173
  await TemplateEngineServer.Instance.Config(false, userPayload.userRecord);
174
174
  // for the templates, replace the values from the input with the objects from the Template Engine we have here
175
175
  if (newMessage.BodyTemplate) {
@@ -10,12 +10,14 @@ export class UserResolver extends UserResolverBase {
10
10
 
11
11
  @Query(() => User_)
12
12
  async UserByID(@Arg('ID', () => Int) ID: number, @Ctx() { dataSource }: AppContext) {
13
- return super.safeFirstArrayElement(await this.findBy(dataSource, 'Users', { ID }));
13
+ const retVal = super.safeFirstArrayElement(await this.findBy(dataSource, 'Users', { ID }));
14
+ return this.MapFieldNamesToCodeNames('Users', retVal);
14
15
  }
15
16
 
16
17
  @Query(() => User_)
17
18
  async UserByEmployeeID(@Arg('EmployeeID', () => Int) EmployeeID: number, @Ctx() { dataSource }: AppContext) {
18
- return super.safeFirstArrayElement(await this.findBy(dataSource, 'Users', { EmployeeID }));
19
+ const retVal = super.safeFirstArrayElement(await this.findBy(dataSource, 'Users', { EmployeeID }));
20
+ return this.MapFieldNamesToCodeNames('Users', retVal);
19
21
  }
20
22
 
21
23
  @Query(() => User_)
@@ -23,7 +25,7 @@ export class UserResolver extends UserResolverBase {
23
25
  // const searchEmail = userEmailMap[Email] ?? Email;
24
26
  const searchEmail = Email;
25
27
  const returnVal = super.safeFirstArrayElement(await this.findBy(dataSource, 'Users', { Email: searchEmail }));
26
- return returnVal;
28
+ return this.MapFieldNamesToCodeNames('Users', returnVal);
27
29
  }
28
30
  }
29
31
  export default UserResolver;