@memberjunction/server 2.76.0 → 2.77.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.
Files changed (120) hide show
  1. package/README.md +61 -2
  2. package/dist/apolloServer/index.d.ts.map +1 -1
  3. package/dist/apolloServer/index.js +1 -3
  4. package/dist/apolloServer/index.js.map +1 -1
  5. package/dist/auth/exampleNewUserSubClass.d.ts +1 -1
  6. package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
  7. package/dist/auth/exampleNewUserSubClass.js +1 -1
  8. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  9. package/dist/auth/newUsers.js.map +1 -1
  10. package/dist/context.d.ts +5 -0
  11. package/dist/context.d.ts.map +1 -1
  12. package/dist/context.js +34 -3
  13. package/dist/context.js.map +1 -1
  14. package/dist/entitySubclasses/entityPermissions.server.d.ts +2 -2
  15. package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
  16. package/dist/entitySubclasses/entityPermissions.server.js +2 -2
  17. package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
  18. package/dist/generated/generated.d.ts +1648 -1648
  19. package/dist/generated/generated.d.ts.map +1 -1
  20. package/dist/generated/generated.js +4945 -4419
  21. package/dist/generated/generated.js.map +1 -1
  22. package/dist/generic/ResolverBase.d.ts +20 -21
  23. package/dist/generic/ResolverBase.d.ts.map +1 -1
  24. package/dist/generic/ResolverBase.js +75 -59
  25. package/dist/generic/ResolverBase.js.map +1 -1
  26. package/dist/generic/RunViewResolver.d.ts +8 -8
  27. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  28. package/dist/generic/RunViewResolver.js +50 -48
  29. package/dist/generic/RunViewResolver.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/resolvers/AskSkipResolver.d.ts +6 -6
  35. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  36. package/dist/resolvers/AskSkipResolver.js +33 -21
  37. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  38. package/dist/resolvers/CreateQueryResolver.d.ts +1 -1
  39. package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
  40. package/dist/resolvers/CreateQueryResolver.js +15 -9
  41. package/dist/resolvers/CreateQueryResolver.js.map +1 -1
  42. package/dist/resolvers/EntityResolver.d.ts +1 -2
  43. package/dist/resolvers/EntityResolver.d.ts.map +1 -1
  44. package/dist/resolvers/EntityResolver.js +17 -9
  45. package/dist/resolvers/EntityResolver.js.map +1 -1
  46. package/dist/resolvers/FileCategoryResolver.d.ts +1 -1
  47. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  48. package/dist/resolvers/FileCategoryResolver.js +9 -9
  49. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  50. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  51. package/dist/resolvers/FileResolver.js +3 -1
  52. package/dist/resolvers/FileResolver.js.map +1 -1
  53. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  54. package/dist/resolvers/MergeRecordsResolver.js +2 -1
  55. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  56. package/dist/resolvers/ReportResolver.js.map +1 -1
  57. package/dist/resolvers/SyncDataResolver.d.ts +8 -8
  58. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  59. package/dist/resolvers/SyncDataResolver.js +19 -19
  60. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  61. package/dist/resolvers/SyncRolesUsersResolver.d.ts +10 -10
  62. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  63. package/dist/resolvers/SyncRolesUsersResolver.js +19 -19
  64. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  65. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  66. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  67. package/dist/resolvers/UserFavoriteResolver.d.ts +2 -2
  68. package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
  69. package/dist/resolvers/UserFavoriteResolver.js +7 -4
  70. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  71. package/dist/resolvers/UserResolver.d.ts +3 -3
  72. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  73. package/dist/resolvers/UserResolver.js +10 -6
  74. package/dist/resolvers/UserResolver.js.map +1 -1
  75. package/dist/resolvers/UserViewResolver.d.ts +2 -2
  76. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  77. package/dist/resolvers/UserViewResolver.js +11 -6
  78. package/dist/resolvers/UserViewResolver.js.map +1 -1
  79. package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
  80. package/dist/scheduler/LearningCycleScheduler.js +7 -1
  81. package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
  82. package/dist/types.d.ts +7 -4
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js +4 -0
  85. package/dist/types.js.map +1 -1
  86. package/dist/util.d.ts +8 -1
  87. package/dist/util.d.ts.map +1 -1
  88. package/dist/util.js +28 -0
  89. package/dist/util.js.map +1 -1
  90. package/package.json +34 -34
  91. package/src/apolloServer/index.ts +3 -3
  92. package/src/auth/exampleNewUserSubClass.ts +3 -2
  93. package/src/auth/newUsers.ts +1 -1
  94. package/src/context.ts +49 -9
  95. package/src/entitySubclasses/entityPermissions.server.ts +3 -3
  96. package/src/generated/generated.ts +4945 -4419
  97. package/src/generic/ResolverBase.ts +103 -86
  98. package/src/generic/RunViewResolver.ts +55 -54
  99. package/src/index.ts +1 -1
  100. package/src/resolvers/AskSkipResolver.ts +44 -23
  101. package/src/resolvers/CreateQueryResolver.ts +19 -11
  102. package/src/resolvers/EntityResolver.ts +18 -9
  103. package/src/resolvers/FileCategoryResolver.ts +12 -9
  104. package/src/resolvers/FileResolver.ts +4 -2
  105. package/src/resolvers/MergeRecordsResolver.ts +2 -1
  106. package/src/resolvers/ReportResolver.ts +1 -1
  107. package/src/resolvers/SyncDataResolver.ts +21 -21
  108. package/src/resolvers/SyncRolesUsersResolver.ts +24 -21
  109. package/src/resolvers/TransactionGroupResolver.ts +1 -1
  110. package/src/resolvers/UserFavoriteResolver.ts +7 -5
  111. package/src/resolvers/UserResolver.ts +10 -6
  112. package/src/resolvers/UserViewResolver.ts +13 -7
  113. package/src/scheduler/LearningCycleScheduler.ts +10 -4
  114. package/src/types.ts +14 -4
  115. package/src/util.ts +45 -2
  116. package/dist/apolloServer/TransactionPlugin.d.ts +0 -4
  117. package/dist/apolloServer/TransactionPlugin.d.ts.map +0 -1
  118. package/dist/apolloServer/TransactionPlugin.js +0 -46
  119. package/dist/apolloServer/TransactionPlugin.js.map +0 -1
  120. package/src/apolloServer/TransactionPlugin.ts +0 -53
package/src/index.ts CHANGED
@@ -93,7 +93,7 @@ export * from './resolvers/GetDataResolver.js';
93
93
  export * from './resolvers/GetDataContextDataResolver.js';
94
94
  export * from './resolvers/TransactionGroupResolver.js';
95
95
  export * from './resolvers/CreateQueryResolver.js';
96
- export { GetReadOnlyDataSource, GetReadWriteDataSource } from './util.js';
96
+ export { GetReadOnlyDataSource, GetReadWriteDataSource, GetReadWriteProvider, GetReadOnlyProvider } from './util.js';
97
97
 
98
98
  export * from './generated/generated.js';
99
99
 
@@ -1,5 +1,5 @@
1
1
  import { Arg, Ctx, Field, Mutation, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
2
- import { LogError, LogStatus, Metadata, RunView, UserInfo, CompositeKey, EntityFieldInfo, EntityInfo, EntityRelationshipInfo } from '@memberjunction/core';
2
+ import { LogError, LogStatus, Metadata, RunView, UserInfo, CompositeKey, EntityFieldInfo, EntityInfo, EntityRelationshipInfo, EntitySaveOptions, EntityDeleteOptions } from '@memberjunction/core';
3
3
  import { AppContext, UserPayload, MJ_SERVER_EVENT_CODE } from '../types.js';
4
4
  import { BehaviorSubject } from 'rxjs';
5
5
  import { take } from 'rxjs/operators';
@@ -390,6 +390,7 @@ export class AskSkipResolver {
390
390
  const ck = new CompositeKey();
391
391
  ck.KeyValuePairs = compositeKey.KeyValuePairs;
392
392
  dci.RecordID = ck.Values();
393
+
393
394
  let dciSaveResult: boolean = await dci.Save();
394
395
  if (!dciSaveResult) {
395
396
  LogError(`Error saving DataContextItemEntity for record chat: ${EntityName} ${ck.Values()}`, undefined, dci.LatestResult);
@@ -415,7 +416,7 @@ export class AskSkipResolver {
415
416
  conversationDetailID: convoDetailEntity.ID,
416
417
  });
417
418
 
418
- return this.handleSimpleSkipChatPostRequest(input, convoEntity, convoDetailEntity, true, user);
419
+ return this.handleSimpleSkipChatPostRequest(input, convoEntity, convoDetailEntity, true, user, userPayload);
419
420
  }
420
421
 
421
422
  /**
@@ -536,7 +537,7 @@ export class AskSkipResolver {
536
537
  }
537
538
  else {
538
539
  // Make the API request
539
- const response = await this.handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID);
540
+ const response = await this.handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID, userPayload);
540
541
 
541
542
  // Update learning cycle to completed
542
543
  const endTime = new Date();
@@ -594,7 +595,8 @@ export class AskSkipResolver {
594
595
  input: SkipAPILearningCycleRequest,
595
596
  user: UserInfo,
596
597
  learningCycleId: string,
597
- agentID: string
598
+ agentID: string,
599
+ userPayload: UserPayload
598
600
  ): Promise<SkipAPILearningCycleResponse> {
599
601
  const skipConfigInfo = configInfo.askSkip;
600
602
  LogStatus(` >>> HandleSimpleSkipLearningPostRequest Sending request to Skip API: ${skipConfigInfo.learningCycleURL}`);
@@ -608,7 +610,7 @@ export class AskSkipResolver {
608
610
 
609
611
  // Process any note changes, if any
610
612
  if (apiResponse.noteChanges && apiResponse.noteChanges.length > 0) {
611
- await this.processLearningCycleNoteChanges(apiResponse.noteChanges, agentID, user);
613
+ await this.processLearningCycleNoteChanges(apiResponse.noteChanges, agentID, user, userPayload);
612
614
  }
613
615
 
614
616
  // Not yet implemented
@@ -653,7 +655,8 @@ export class AskSkipResolver {
653
655
  convoEntity: ConversationEntity = null,
654
656
  convoDetailEntity: ConversationDetailEntity = null,
655
657
  createAIMessageConversationDetail: boolean = false,
656
- user: UserInfo = null
658
+ user: UserInfo = null,
659
+ userPayload: UserPayload = null
657
660
  ): Promise<AskSkipResultType> {
658
661
  const skipConfigInfo = configInfo.askSkip;
659
662
  LogStatus(` >>> HandleSimpleSkipChatPostRequest Sending request to Skip API: ${skipConfigInfo.chatURL}`);
@@ -665,7 +668,7 @@ export class AskSkipResolver {
665
668
  // the last object in the response array is the final response from the Skip API
666
669
  const apiResponse = <SkipAPIResponse>response[response.length - 1].value;
667
670
  const AIMessageConversationDetailID = createAIMessageConversationDetail && convoEntity
668
- ? await this.CreateAIMessageConversationDetail(apiResponse, convoEntity.ID, user)
671
+ ? await this.CreateAIMessageConversationDetail(apiResponse, convoEntity.ID, user, userPayload)
669
672
  : '';
670
673
  // const apiResponse = <SkipAPIResponse>response.data;
671
674
  LogStatus(` Skip API response: ${apiResponse.responsePhase}`);
@@ -681,7 +684,7 @@ export class AskSkipResolver {
681
684
  } else {
682
685
  // Set conversation status to Available on failure so user can try again (if conversation exists)
683
686
  if (convoEntity) {
684
- await this.setConversationStatus(convoEntity, 'Available');
687
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
685
688
  }
686
689
 
687
690
  return {
@@ -697,7 +700,7 @@ export class AskSkipResolver {
697
700
  } catch (error) {
698
701
  // Set conversation status to Available on error so user can try again (if conversation exists)
699
702
  if (convoEntity) {
700
- await this.setConversationStatus(convoEntity, 'Available');
703
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
701
704
  }
702
705
 
703
706
  // Log the error for debugging
@@ -720,7 +723,8 @@ export class AskSkipResolver {
720
723
  protected async processLearningCycleNoteChanges(
721
724
  noteChanges: SkipLearningCycleNoteChange[],
722
725
  agentID: string,
723
- user: UserInfo
726
+ user: UserInfo,
727
+ userPayload: UserPayload
724
728
  ): Promise<void> {
725
729
  const md = new Metadata();
726
730
 
@@ -739,9 +743,9 @@ export class AskSkipResolver {
739
743
  await Promise.all(validNoteChanges.map(async (change) => {
740
744
  try {
741
745
  if (change.changeType === 'add' || change.changeType === 'update') {
742
- await this.processAddOrUpdateSkipNote(change, agentID, user);
746
+ await this.processAddOrUpdateSkipNote(change, agentID, user, userPayload);
743
747
  } else if (change.changeType === 'delete') {
744
- await this.processDeleteSkipNote(change, user);
748
+ await this.processDeleteSkipNote(change, user, userPayload);
745
749
  }
746
750
  } catch (e) {
747
751
  LogError(`Error processing note change: ${e}`);
@@ -758,7 +762,7 @@ export class AskSkipResolver {
758
762
  * @param user User context for the operation
759
763
  * @returns Whether the operation was successful
760
764
  */
761
- protected async processAddOrUpdateSkipNote(change: SkipLearningCycleNoteChange, agentID: string, user: UserInfo): Promise<boolean> {
765
+ protected async processAddOrUpdateSkipNote(change: SkipLearningCycleNoteChange, agentID: string, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
762
766
  try {
763
767
  // Get the note entity object
764
768
  const md = new Metadata();
@@ -805,7 +809,7 @@ export class AskSkipResolver {
805
809
  * @param user User context for the operation
806
810
  * @returns Whether the deletion was successful
807
811
  */
808
- protected async processDeleteSkipNote(change: SkipLearningCycleNoteChange, user: UserInfo): Promise<boolean> {
812
+ protected async processDeleteSkipNote(change: SkipLearningCycleNoteChange, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
809
813
  // Get the note entity object
810
814
  const md = new Metadata();
811
815
  const noteEntity = await md.GetEntityObject<AIAgentNoteEntity>('AI Agent Notes', user);
@@ -843,7 +847,7 @@ cycle.`);
843
847
  * @param user User context for the operation
844
848
  * @returns ID of the created conversation detail, or empty string if creation failed
845
849
  */
846
- protected async CreateAIMessageConversationDetail(apiResponse: SkipAPIResponse, conversationID: string, user: UserInfo): Promise<string> {
850
+ protected async CreateAIMessageConversationDetail(apiResponse: SkipAPIResponse, conversationID: string, user: UserInfo, userPayload: UserPayload): Promise<string> {
847
851
  const md = new Metadata();
848
852
  const convoDetailEntityAI = <ConversationDetailEntity>await md.GetEntityObject('Conversation Details', user);
849
853
  convoDetailEntityAI.NewRecord();
@@ -853,6 +857,7 @@ cycle.`);
853
857
  const lastSystemMessage = systemMessages[systemMessages.length - 1];
854
858
  convoDetailEntityAI.Message = lastSystemMessage?.content;
855
859
  convoDetailEntityAI.Role = 'AI';
860
+
856
861
  if (await convoDetailEntityAI.Save()) {
857
862
  return convoDetailEntityAI.ID;
858
863
  } else {
@@ -1287,7 +1292,7 @@ cycle.`);
1287
1292
  await dataContext.Load(DataContextId, dataSource, true, false, 0, user);
1288
1293
  const input = <SkipAPIRunScriptRequest>await this.buildSkipChatAPIRequest([], '', dataContext, 'run_existing_script', false, false, false, false, user, dataSource, false, false);
1289
1294
  input.scriptText = ScriptText;
1290
- return this.handleSimpleSkipChatPostRequest(input);
1295
+ return this.handleSimpleSkipChatPostRequest(input, undefined, undefined, undefined, userPayload.userRecord, userPayload);
1291
1296
  }
1292
1297
 
1293
1298
  /**
@@ -1366,7 +1371,7 @@ cycle.`);
1366
1371
  );
1367
1372
 
1368
1373
  // Set the conversation status to 'Processing' when a request is initiated
1369
- await this.setConversationStatus(convoEntity, 'Processing');
1374
+ await this.setConversationStatus(convoEntity, 'Processing', userPayload);
1370
1375
 
1371
1376
  // now load up the messages. We will load up ALL of the messages for this conversation, and then pass them to the Skip API
1372
1377
  const messages: SkipMessage[] = await this.LoadConversationDetailsIntoSkipMessages(
@@ -1455,7 +1460,19 @@ cycle.`);
1455
1460
  updatedAt: f.__mj_UpdatedAt,
1456
1461
  };
1457
1462
  }),
1458
- };
1463
+ params: q.Parameters.map((p) => {
1464
+ return {
1465
+ id: p.ID,
1466
+ name: p.Name,
1467
+ description: p.Description,
1468
+ type: p.Type,
1469
+ isRequired: p.IsRequired,
1470
+ defaultValue: p.DefaultValue,
1471
+ createdAt: p.__mj_CreatedAt,
1472
+ updatedAt: p.__mj_UpdatedAt,
1473
+ };
1474
+ })
1475
+ }
1459
1476
  });
1460
1477
  }
1461
1478
 
@@ -1986,6 +2003,7 @@ cycle.`);
1986
2003
  convoDetailEntity.Message = UserQuestion;
1987
2004
  convoDetailEntity.Role = 'User';
1988
2005
  convoDetailEntity.HiddenToUser = false;
2006
+
1989
2007
  let convoDetailSaveResult: boolean = await convoDetailEntity.Save();
1990
2008
  if (!convoDetailSaveResult) {
1991
2009
  LogError(`Error saving conversation detail entity for user message: ${UserQuestion}`, undefined, convoDetailEntity.LatestResult);
@@ -2162,7 +2180,7 @@ cycle.`);
2162
2180
 
2163
2181
  if (conversationDetailCount > 10) {
2164
2182
  // Set status of conversation to Available since we still want to allow the user to ask questions
2165
- await this.setConversationStatus(convoEntity, 'Available');
2183
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
2166
2184
 
2167
2185
  // At this point it is likely that we are stuck in a loop, so we stop here
2168
2186
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
@@ -2222,7 +2240,7 @@ cycle.`);
2222
2240
  );
2223
2241
  } catch (error) {
2224
2242
  // Set conversation status to Available on error so user can try again
2225
- await this.setConversationStatus(convoEntity, 'Available');
2243
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
2226
2244
 
2227
2245
  // Log the error for debugging
2228
2246
  LogError(`Error in HandleSkipChatRequest sendPostRequest: ${error}`);
@@ -2305,7 +2323,7 @@ cycle.`);
2305
2323
  }
2306
2324
  } else {
2307
2325
  // Set status of conversation to Available since we still want to allow the user to ask questions
2308
- await this.setConversationStatus(convoEntity, 'Available');
2326
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
2309
2327
 
2310
2328
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
2311
2329
  message: JSON.stringify({
@@ -2477,7 +2495,7 @@ cycle.`);
2477
2495
  convoDetailEntityAI.CompletionTime = endTime.getTime() - startTime.getTime();
2478
2496
 
2479
2497
  // Set conversation status back to Available since we need user input for the clarifying question
2480
- await this.setConversationStatus(convoEntity, 'Available');
2498
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
2481
2499
 
2482
2500
  if (await convoDetailEntityAI.Save()) {
2483
2501
  return {
@@ -2765,6 +2783,7 @@ cycle.`);
2765
2783
  artifactVersionEntity.ConversationArtifactID = artifactId;
2766
2784
  artifactVersionEntity.Version = newVersion;
2767
2785
  artifactVersionEntity.Configuration = sResult; // store the full response here
2786
+
2768
2787
  if (await artifactVersionEntity.Save()) {
2769
2788
  // success saving the new version, set the artifactVersionId
2770
2789
  artifactVersionId = artifactVersionEntity.ID;
@@ -2792,6 +2811,7 @@ cycle.`);
2792
2811
  convoDetailEntityAI.ArtifactVersionID = artifactVersionId;
2793
2812
  }
2794
2813
  }
2814
+
2795
2815
  const convoDetailSaveResult: boolean = await convoDetailEntityAI.Save();
2796
2816
  if (!convoDetailSaveResult) {
2797
2817
  LogError(`Error saving conversation detail entity for AI message: ${sResult}`, undefined, convoDetailEntityAI.LatestResult);
@@ -2872,9 +2892,10 @@ cycle.`);
2872
2892
  };
2873
2893
  }
2874
2894
 
2875
- private async setConversationStatus(convoEntity: ConversationEntity, status: 'Processing' | 'Available'): Promise<boolean> {
2895
+ private async setConversationStatus(convoEntity: ConversationEntity, status: 'Processing' | 'Available', userPayload: UserPayload): Promise<boolean> {
2876
2896
  if (convoEntity.Status !== status) {
2877
2897
  convoEntity.Status = status;
2898
+
2878
2899
  const convoSaveResult = await convoEntity.Save();
2879
2900
  if (!convoSaveResult) {
2880
2901
  LogError(`Error updating conversation status to '${status}'`, undefined, convoEntity.LatestResult);
@@ -1,10 +1,10 @@
1
1
  import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType, Resolver, PubSub, PubSubEngine } from 'type-graphql';
2
- import { AppContext } from '../types.js';
3
- import { LogError, Metadata, RunView, UserInfo, CompositeKey } from '@memberjunction/core';
2
+ import { AppContext, UserPayload } from '../types.js';
3
+ import { LogError, Metadata, RunView, UserInfo, CompositeKey, EntitySaveOptions } from '@memberjunction/core';
4
4
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
5
5
  import { QueryCategoryEntity } from '@memberjunction/core-entities';
6
6
  import { QueryResolver } from '../generated/generated.js';
7
- import { GetReadWriteDataSource } from '../util.js';
7
+ import { GetReadWriteProvider } from '../util.js';
8
8
  import { DeleteOptionsInput } from '../generic/DeleteOptionsInput.js';
9
9
 
10
10
  /**
@@ -108,7 +108,7 @@ export class QueryResolverExtended extends QueryResolver {
108
108
  let finalCategoryID = input.CategoryID;
109
109
  if (input.CategoryPath) {
110
110
  const md = new Metadata();
111
- finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord);
111
+ finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord, context.userPayload);
112
112
  }
113
113
 
114
114
  // Create input for the inherited CreateRecord method
@@ -128,8 +128,8 @@ export class QueryResolverExtended extends QueryResolver {
128
128
  };
129
129
 
130
130
  // Use inherited CreateRecord method which bypasses AI processing
131
- const connPool = GetReadWriteDataSource(context.dataSources);
132
- const createdQuery = await this.CreateRecord('Queries', createInput, connPool, context.userPayload, pubSub);
131
+ const provider = GetReadWriteProvider(context.providers);
132
+ const createdQuery = await this.CreateRecord('Queries', createInput, provider, context.userPayload, pubSub);
133
133
 
134
134
  if (createdQuery) {
135
135
  return {
@@ -162,14 +162,22 @@ export class QueryResolverExtended extends QueryResolver {
162
162
  @RequireSystemUser()
163
163
  @Mutation(() => DeleteQueryResultType)
164
164
  async DeleteQuerySystemResolver(
165
- @Arg('ID', () => String) queryID: string,
165
+ @Arg('ID', () => String) ID: string,
166
166
  @Arg('options', () => DeleteOptionsInput, { nullable: true }) options: DeleteOptionsInput | null,
167
167
  @Ctx() context: AppContext,
168
168
  @PubSub() pubSub: PubSubEngine
169
169
  ): Promise<DeleteQueryResultType> {
170
170
  try {
171
- const connPool = GetReadWriteDataSource(context.dataSources);
172
- const key = new CompositeKey([{FieldName: 'ID', Value: queryID}]);
171
+ // Validate ID is not null/undefined/empty
172
+ if (!ID || ID.trim() === '') {
173
+ return {
174
+ Success: false,
175
+ ErrorMessage: 'QueryResolverExtended::DeleteQuerySystemResolver --- Invalid query ID: ID cannot be null or empty'
176
+ };
177
+ }
178
+
179
+ const provider = GetReadWriteProvider(context.providers);
180
+ const key = new CompositeKey([{FieldName: 'ID', Value: ID}]);
173
181
 
174
182
  // Provide default options if none provided
175
183
  const deleteOptions = options || {
@@ -178,7 +186,7 @@ export class QueryResolverExtended extends QueryResolver {
178
186
  };
179
187
 
180
188
  // Use inherited DeleteRecord method from ResolverBase
181
- const deletedQuery = await this.DeleteRecord('Queries', key, deleteOptions, connPool, context.userPayload, pubSub);
189
+ const deletedQuery = await this.DeleteRecord('Queries', key, deleteOptions, provider, context.userPayload, pubSub);
182
190
 
183
191
  if (deletedQuery) {
184
192
  return {
@@ -209,7 +217,7 @@ export class QueryResolverExtended extends QueryResolver {
209
217
  * @param contextUser - User context for operations
210
218
  * @returns The ID of the final category in the path
211
219
  */
212
- private async findOrCreateCategoryPath(categoryPath: string, md: Metadata, contextUser: UserInfo): Promise<string> {
220
+ private async findOrCreateCategoryPath(categoryPath: string, md: Metadata, contextUser: UserInfo, userPayload: UserPayload): Promise<string> {
213
221
  if (!categoryPath || categoryPath.trim() === '') {
214
222
  throw new Error('CategoryPath cannot be empty');
215
223
  }
@@ -1,19 +1,21 @@
1
- import { EntityPermissionType } from '@memberjunction/core';
1
+ import { EntityPermissionType, IRunViewProvider } from '@memberjunction/core';
2
2
  import { AppContext } from '../types.js';
3
3
  import { Arg, Ctx, Query, Resolver, InputType, Field } from 'type-graphql';
4
4
  import { Entity_, EntityResolverBase } from '../generated/generated.js';
5
5
  import sql from 'mssql';
6
+ import { GetReadOnlyProvider } from '../util.js';
6
7
 
7
8
  @Resolver(Entity_)
8
9
  export class EntityResolver extends EntityResolverBase {
9
10
  @Query(() => [Entity_])
10
11
  async EntitiesBySchemas(
11
- @Ctx() { dataSource, userPayload }: AppContext,
12
+ @Ctx() { providers, userPayload }: AppContext,
12
13
  @Arg('IncludeSchemas', () => [String], { nullable: true }) IncludeSchemas?: string[],
13
14
  @Arg('ExcludeSchemas', () => [String], { nullable: true }) ExcludeSchemas?: string[]
14
15
  ) {
15
16
  this.CheckUserReadPermissions('Entities', userPayload);
16
- const rlsWhere = this.getRowLevelSecurityWhereClause('Entities', userPayload, EntityPermissionType.Read, ' WHERE');
17
+ const provider = GetReadOnlyProvider(providers, { allowFallbackToReadWrite: true });
18
+ const rlsWhere = this.getRowLevelSecurityWhereClause(provider, 'Entities', userPayload, EntityPermissionType.Read, ' WHERE');
17
19
  const includeSchemaSQL =
18
20
  IncludeSchemas && IncludeSchemas.length > 0 ? `SchemaName IN (${IncludeSchemas.map((s) => `'${s}'`).join(',')})` : '';
19
21
  const excludeSchemaSQL =
@@ -25,14 +27,21 @@ export class EntityResolver extends EntityResolverBase {
25
27
  else schemaSQL = excludeSchemaSQL;
26
28
  }
27
29
  let totalWhere = '';
28
- if (schemaSQL) totalWhere = ` WHERE ${schemaSQL}`;
30
+ if (schemaSQL) totalWhere = `${schemaSQL}`;
29
31
  if (rlsWhere) {
30
32
  if (totalWhere) totalWhere = `${totalWhere} AND ${rlsWhere}`;
31
- else totalWhere = ` WHERE ${rlsWhere}`;
33
+ else totalWhere = ` ${rlsWhere}`;
34
+ }
35
+ const rv = provider as any as IRunViewProvider;
36
+ const result = await rv.RunView({
37
+ EntityName: 'Entities',
38
+ ExtraFilter: totalWhere,
39
+ }, userPayload.userRecord);
40
+ if (result && result.Success) {
41
+ return result.Results;
42
+ }
43
+ else {
44
+ throw new Error(`Failed to fetch entities: ${result?.ErrorMessage || 'Unknown error'}`);
32
45
  }
33
- const sSQL = `SELECT * FROM [${this.MJCoreSchema}].vwEntities${totalWhere}`;
34
- const request = new sql.Request(dataSource);
35
- const result = await request.query(sSQL);
36
- return result.recordset;
37
46
  }
38
47
  }
@@ -1,20 +1,24 @@
1
- import { CompositeKey, EntityPermissionType, Metadata, RunView } from '@memberjunction/core';
1
+ import { CompositeKey, EntityDeleteOptions, EntityPermissionType, EntitySaveOptions, Metadata, RunView } from '@memberjunction/core';
2
2
  import { FileCategoryEntity, FileEntity } from '@memberjunction/core-entities';
3
3
  import { AppContext, Arg, Ctx, DeleteOptionsInput, Int, Mutation } from '@memberjunction/server';
4
4
  import { mj_core_schema } from '../config.js';
5
5
  import { FileCategoryResolver as FileCategoryResolverBase, FileCategory_ } from '../generated/generated.js';
6
6
  import sql from 'mssql';
7
+ import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
8
+ import { GetReadWriteProvider } from '../util.js';
7
9
 
8
10
  export class FileResolver extends FileCategoryResolverBase {
9
11
  @Mutation(() => FileCategory_)
10
12
  async DeleteFileCategory(
11
13
  @Arg('ID', () => String) ID: string,
12
14
  @Arg('options___', () => DeleteOptionsInput) options: DeleteOptionsInput,
13
- @Ctx() { dataSource, userPayload }: AppContext
15
+ @Ctx() { providers, userPayload }: AppContext
14
16
  ) {
15
17
  const key = new CompositeKey();
16
18
  key.LoadFromSingleKeyValuePair('ID', ID);
17
- if (!(await this.BeforeDelete(dataSource, key))) {
19
+ const provider = GetReadWriteProvider(providers);
20
+
21
+ if (!(await this.BeforeDelete(provider, key))) {
18
22
  return null;
19
23
  }
20
24
 
@@ -30,8 +34,7 @@ export class FileResolver extends FileCategoryResolverBase {
30
34
  const returnValue = fileCategoryEntity.GetAll();
31
35
 
32
36
  // Any files using the deleted category fall back to its parent
33
- const transaction = new sql.Transaction(dataSource);
34
- await transaction.begin();
37
+ await provider.BeginTransaction();
35
38
  try {
36
39
  // SHOULD USE BaseEntity for each of these records to ensure object model
37
40
  // is used everywhere - new code below. The below is SLOWER than a single
@@ -63,14 +66,14 @@ export class FileResolver extends FileCategoryResolverBase {
63
66
  await fileEntity.Save();
64
67
  }
65
68
  }
66
- await fileCategoryEntity.Delete();
67
- await transaction.commit();
69
+ await fileCategoryEntity.Delete(options);
70
+ await provider.CommitTransaction();
68
71
  } catch (error) {
69
- await transaction.rollback();
72
+ await provider.RollbackTransaction();
70
73
  throw error;
71
74
  }
72
75
 
73
- await this.AfterDelete(dataSource, key); // fire event
76
+ await this.AfterDelete(provider, key); // fire event
74
77
  return returnValue;
75
78
  }
76
79
  }
@@ -1,4 +1,4 @@
1
- import { EntityPermissionType, Metadata, FieldValueCollection } from '@memberjunction/core';
1
+ import { EntityPermissionType, Metadata, FieldValueCollection, EntitySaveOptions } from '@memberjunction/core';
2
2
  import { FileEntity, FileStorageProviderEntity } from '@memberjunction/core-entities';
3
3
  import {
4
4
  AppContext,
@@ -19,6 +19,7 @@ import {
19
19
  import { createDownloadUrl, createUploadUrl, deleteObject, moveObject } from '@memberjunction/storage';
20
20
  import { CreateFileInput, FileResolver as FileResolverBase, File_, UpdateFileInput } from '../generated/generated.js';
21
21
  import { FieldMapper } from '@memberjunction/graphql-dataprovider';
22
+ import { GetReadOnlyProvider } from '../util.js';
22
23
 
23
24
  @InputType()
24
25
  export class CreateUploadURLInput {
@@ -57,8 +58,9 @@ export class FileResolver extends FileResolverBase {
57
58
  fileEntity.CheckPermissions(EntityPermissionType.Create, true);
58
59
 
59
60
  // Check to see if there's already an object with that name
61
+ const provider = GetReadOnlyProvider(context.providers, {allowFallbackToReadWrite: true})
60
62
  const [sameName] = await this.findBy(
61
- context.dataSource,
63
+ provider,
62
64
  'Files',
63
65
  { Name: input.Name, ProviderID: input.ProviderID },
64
66
  context.userPayload.userRecord
@@ -163,7 +163,8 @@ export class RecordMergeResolver {
163
163
  ) {
164
164
  try {
165
165
  const md = new Metadata();
166
- const result = await md.MergeRecords(request, userPayload.userRecord);
166
+ const options = {};
167
+ const result = await md.MergeRecords(request, userPayload.userRecord, options);
167
168
  return result;
168
169
  } catch (e) {
169
170
  LogError(e);
@@ -1,4 +1,4 @@
1
- import { Metadata, RunReport } from '@memberjunction/core';
1
+ import { EntitySaveOptions, Metadata, RunReport } from '@memberjunction/core';
2
2
  import { Arg, Ctx, Field, Int, Mutation, ObjectType, Query, Resolver } from 'type-graphql';
3
3
  import { AppContext } from '../types.js';
4
4
  import { ConversationDetailEntity, ReportEntity } from '@memberjunction/core-entities';
@@ -1,6 +1,6 @@
1
1
  import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType } from 'type-graphql';
2
- import { AppContext } from '../types.js';
3
- import { BaseEntity, CompositeKey, LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
2
+ import { AppContext, UserPayload } from '../types.js';
3
+ import { BaseEntity, CompositeKey, EntityDeleteOptions, EntitySaveOptions, LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
4
4
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
5
5
  import { CompositeKeyInputType, CompositeKeyOutputType } from '../generic/KeyInputOutputTypes.js';
6
6
  import { DatasetItemEntity } from '@memberjunction/core-entities';
@@ -115,7 +115,7 @@ export class SyncDataResolver {
115
115
  const md = new Metadata();
116
116
  const results: ActionItemOutputType[] = [];
117
117
  for (const item of items) {
118
- results.push(await this.SyncSingleItem(item, context, md));
118
+ results.push(await this.SyncSingleItem(item, context, md, context.userPayload));
119
119
  }
120
120
 
121
121
  if (await this.DoSyncItemsAffectMetadata(context.userPayload.userRecord, items)) {
@@ -160,7 +160,7 @@ export class SyncDataResolver {
160
160
  return false; // didn't find any
161
161
  }
162
162
 
163
- protected async SyncSingleItem(item: ActionItemInputType, context: AppContext, md: Metadata): Promise<ActionItemOutputType> {
163
+ protected async SyncSingleItem(item: ActionItemInputType, context: AppContext, md: Metadata, userPayload: UserPayload): Promise<ActionItemOutputType> {
164
164
  const result = new ActionItemOutputType();
165
165
  result.AlternateKey = item.AlternateKey;
166
166
  result.PrimaryKey = item.PrimaryKey;
@@ -179,20 +179,20 @@ export class SyncDataResolver {
179
179
  const fieldValues = item.RecordJSON ? JSON.parse(item.RecordJSON) : {};
180
180
  switch (item.Type) {
181
181
  case SyncDataActionType.Create:
182
- await this.SyncSingleItemCreate(entityObject, fieldValues, result);
182
+ await this.SyncSingleItemCreate(entityObject, fieldValues, result, userPayload);
183
183
  break;
184
184
  case SyncDataActionType.Update:
185
- await this.SyncSingleItemUpdate(entityObject, pk, ak, fieldValues, result);
185
+ await this.SyncSingleItemUpdate(entityObject, pk, ak, fieldValues, result, userPayload);
186
186
  break;
187
187
  case SyncDataActionType.CreateOrUpdate:
188
188
  // in this case we attempt to load the item first, if it is not possible to load the item, then we create it
189
- await this.SyncSingleItemCreateOrUpdate(entityObject, pk, ak, fieldValues, result);
189
+ await this.SyncSingleItemCreateOrUpdate(entityObject, pk, ak, fieldValues, result, userPayload);
190
190
  break;
191
191
  case SyncDataActionType.Delete:
192
- await this.SyncSingleItemDelete(entityObject, pk, ak, result);
192
+ await this.SyncSingleItemDelete(entityObject, pk, ak, result, userPayload);
193
193
  break;
194
194
  case SyncDataActionType.DeleteWithFilter:
195
- await this.SyncSingleItemDeleteWithFilter(item.EntityName, item.DeleteFilter, result, context.userPayload.userRecord);
195
+ await this.SyncSingleItemDeleteWithFilter(item.EntityName, item.DeleteFilter, result, context.userPayload.userRecord, userPayload);
196
196
  break;
197
197
  default:
198
198
  throw new Error('Invalid SyncDataActionType');
@@ -211,7 +211,7 @@ export class SyncDataResolver {
211
211
  }
212
212
 
213
213
 
214
- protected async SyncSingleItemDeleteWithFilter(entityName: string, filter: string, result: ActionItemOutputType, user: UserInfo) {
214
+ protected async SyncSingleItemDeleteWithFilter(entityName: string, filter: string, result: ActionItemOutputType, user: UserInfo, userPayload: UserPayload) {
215
215
  try {
216
216
  // here we will iterate through the result of a RunView on the entityname/filter and delete each matching record
217
217
  let overallSuccess: boolean = true;
@@ -273,30 +273,30 @@ export class SyncDataResolver {
273
273
  }
274
274
  }
275
275
 
276
- protected async SyncSingleItemCreateOrUpdate(entityObject: BaseEntity, pk: CompositeKey, ak: CompositeKey, fieldValues: any, result: ActionItemOutputType) {
276
+ protected async SyncSingleItemCreateOrUpdate(entityObject: BaseEntity, pk: CompositeKey, ak: CompositeKey, fieldValues: any, result: ActionItemOutputType, userPayload: UserPayload) {
277
277
  if (!pk || pk.KeyValuePairs.length === 0) {
278
278
  // no primary key try to load from alt key
279
279
  const altKeyResult = await this.LoadFromAlternateKey(entityObject.EntityInfo.Name, ak, entityObject.ContextCurrentUser);
280
280
  if (!altKeyResult) {
281
281
  // no record found, create a new one
282
- await this.SyncSingleItemCreate(entityObject, fieldValues, result);
282
+ await this.SyncSingleItemCreate(entityObject, fieldValues, result, userPayload);
283
283
  }
284
284
  else {
285
- await this.InnerSyncSingleItemUpdate(altKeyResult, fieldValues, result);
285
+ await this.InnerSyncSingleItemUpdate(altKeyResult, fieldValues, result, userPayload);
286
286
  }
287
287
  }
288
288
  else {
289
289
  // have a primary key do the usual load
290
290
  if (await entityObject.InnerLoad(pk)) {
291
- await this.InnerSyncSingleItemUpdate(entityObject, fieldValues, result);
291
+ await this.InnerSyncSingleItemUpdate(entityObject, fieldValues, result, userPayload);
292
292
  }
293
293
  else {
294
- await this.SyncSingleItemCreate(entityObject, fieldValues, result);
294
+ await this.SyncSingleItemCreate(entityObject, fieldValues, result, userPayload);
295
295
  }
296
296
  }
297
297
  }
298
298
 
299
- protected async SyncSingleItemDelete(entityObject: BaseEntity, pk: CompositeKey, ak: CompositeKey, result: ActionItemOutputType) {
299
+ protected async SyncSingleItemDelete(entityObject: BaseEntity, pk: CompositeKey, ak: CompositeKey, result: ActionItemOutputType, userPayload: UserPayload) {
300
300
  if (!pk || pk.KeyValuePairs.length === 0) {
301
301
  const altKeyResult = await this.LoadFromAlternateKey(entityObject.EntityInfo.Name, ak, entityObject.ContextCurrentUser);
302
302
  if (!altKeyResult) {
@@ -342,7 +342,7 @@ export class SyncDataResolver {
342
342
  }
343
343
  }
344
344
 
345
- protected async SyncSingleItemCreate(entityObject: BaseEntity, fieldValues: any, result: ActionItemOutputType) {
345
+ protected async SyncSingleItemCreate(entityObject: BaseEntity, fieldValues: any, result: ActionItemOutputType, userPayload: UserPayload) {
346
346
  // make sure we strip out the primary key from fieldValues before we pass it in because otherwise it will appear to be an existing record to the BaseEntity
347
347
  const noPKValues = {...fieldValues};
348
348
  entityObject.EntityInfo.PrimaryKeys.forEach((pk) => {
@@ -367,7 +367,7 @@ export class SyncDataResolver {
367
367
  }
368
368
  }
369
369
 
370
- protected async SyncSingleItemUpdate(entityObject: BaseEntity, pk: CompositeKey, ak: CompositeKey, fieldValues: any, result: ActionItemOutputType) {
370
+ protected async SyncSingleItemUpdate(entityObject: BaseEntity, pk: CompositeKey, ak: CompositeKey, fieldValues: any, result: ActionItemOutputType, userPayload: UserPayload) {
371
371
  if (!pk || pk.KeyValuePairs.length === 0) {
372
372
  // no pk, attempt to load by alt key
373
373
  const altKeyResult = await this.LoadFromAlternateKey(entityObject.EntityInfo.Name, ak, entityObject.ContextCurrentUser);
@@ -376,11 +376,11 @@ export class SyncDataResolver {
376
376
  result.ErrorMessage = 'Failed to load the item, it is possible the record with the specified alternate key does not exist';
377
377
  }
378
378
  else {
379
- await this.InnerSyncSingleItemUpdate(altKeyResult, fieldValues, result);
379
+ await this.InnerSyncSingleItemUpdate(altKeyResult, fieldValues, result, userPayload);
380
380
  }
381
381
  }
382
382
  else if (await entityObject.InnerLoad(pk)) {
383
- await this.InnerSyncSingleItemUpdate(entityObject, fieldValues, result);
383
+ await this.InnerSyncSingleItemUpdate(entityObject, fieldValues, result, userPayload);
384
384
  }
385
385
  else {
386
386
  // failed to load the item
@@ -388,7 +388,7 @@ export class SyncDataResolver {
388
388
  }
389
389
  }
390
390
 
391
- protected async InnerSyncSingleItemUpdate(entityObject: BaseEntity, fieldValues: any, result: ActionItemOutputType) {
391
+ protected async InnerSyncSingleItemUpdate(entityObject: BaseEntity, fieldValues: any, result: ActionItemOutputType, userPayload: UserPayload) {
392
392
  entityObject.SetMany(fieldValues);
393
393
  if (await entityObject.Save()) {
394
394
  result.Success = true;