@memberjunction/server 2.95.0 → 2.97.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 (92) hide show
  1. package/dist/context.d.ts.map +1 -1
  2. package/dist/context.js +4 -0
  3. package/dist/context.js.map +1 -1
  4. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  5. package/dist/resolvers/ActionResolver.js +5 -4
  6. package/dist/resolvers/ActionResolver.js.map +1 -1
  7. package/dist/resolvers/AskSkipResolver.d.ts +4 -4
  8. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  9. package/dist/resolvers/AskSkipResolver.js +9 -9
  10. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  11. package/dist/resolvers/CreateQueryResolver.d.ts +2 -2
  12. package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
  13. package/dist/resolvers/CreateQueryResolver.js +30 -18
  14. package/dist/resolvers/CreateQueryResolver.js.map +1 -1
  15. package/dist/resolvers/DatasetResolver.d.ts +2 -2
  16. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  17. package/dist/resolvers/DatasetResolver.js +6 -5
  18. package/dist/resolvers/DatasetResolver.js.map +1 -1
  19. package/dist/resolvers/EntityRecordNameResolver.d.ts +4 -4
  20. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  21. package/dist/resolvers/EntityRecordNameResolver.js +6 -5
  22. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  23. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  24. package/dist/resolvers/FileCategoryResolver.js +10 -11
  25. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  26. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  27. package/dist/resolvers/FileResolver.js +5 -6
  28. package/dist/resolvers/FileResolver.js.map +1 -1
  29. package/dist/resolvers/GetDataContextDataResolver.js +2 -2
  30. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  31. package/dist/resolvers/GetDataResolver.js +3 -3
  32. package/dist/resolvers/GetDataResolver.js.map +1 -1
  33. package/dist/resolvers/MergeRecordsResolver.d.ts +3 -3
  34. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  35. package/dist/resolvers/MergeRecordsResolver.js +8 -7
  36. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  37. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts +1 -1
  38. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
  39. package/dist/resolvers/PotentialDuplicateRecordResolver.js +4 -3
  40. package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
  41. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  42. package/dist/resolvers/QueryResolver.js +19 -14
  43. package/dist/resolvers/QueryResolver.js.map +1 -1
  44. package/dist/resolvers/ReportResolver.d.ts +2 -2
  45. package/dist/resolvers/ReportResolver.d.ts.map +1 -1
  46. package/dist/resolvers/ReportResolver.js +8 -6
  47. package/dist/resolvers/ReportResolver.js.map +1 -1
  48. package/dist/resolvers/RunAIAgentResolver.d.ts +3 -7
  49. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  50. package/dist/resolvers/RunAIAgentResolver.js +8 -5
  51. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  52. package/dist/resolvers/RunAIPromptResolver.d.ts +3 -7
  53. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  54. package/dist/resolvers/RunAIPromptResolver.js +10 -8
  55. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  56. package/dist/resolvers/RunTemplateResolver.d.ts +2 -4
  57. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  58. package/dist/resolvers/RunTemplateResolver.js +5 -4
  59. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  60. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  61. package/dist/resolvers/SqlLoggingConfigResolver.js +7 -7
  62. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  63. package/dist/resolvers/UserFavoriteResolver.d.ts +3 -4
  64. package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
  65. package/dist/resolvers/UserFavoriteResolver.js +10 -68
  66. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  67. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  68. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  69. package/dist/resolvers/UserViewResolver.js +3 -4
  70. package/dist/resolvers/UserViewResolver.js.map +1 -1
  71. package/package.json +39 -39
  72. package/src/context.ts +5 -0
  73. package/src/resolvers/ActionResolver.ts +5 -4
  74. package/src/resolvers/AskSkipResolver.ts +13 -13
  75. package/src/resolvers/CreateQueryResolver.ts +34 -19
  76. package/src/resolvers/DatasetResolver.ts +5 -4
  77. package/src/resolvers/EntityRecordNameResolver.ts +8 -6
  78. package/src/resolvers/FileCategoryResolver.ts +9 -10
  79. package/src/resolvers/FileResolver.ts +6 -7
  80. package/src/resolvers/GetDataContextDataResolver.ts +2 -2
  81. package/src/resolvers/GetDataResolver.ts +2 -2
  82. package/src/resolvers/InfoResolver.ts +1 -1
  83. package/src/resolvers/MergeRecordsResolver.ts +7 -6
  84. package/src/resolvers/PotentialDuplicateRecordResolver.ts +3 -2
  85. package/src/resolvers/QueryResolver.ts +22 -15
  86. package/src/resolvers/ReportResolver.ts +9 -6
  87. package/src/resolvers/RunAIAgentResolver.ts +12 -4
  88. package/src/resolvers/RunAIPromptResolver.ts +12 -8
  89. package/src/resolvers/RunTemplateResolver.ts +5 -5
  90. package/src/resolvers/SqlLoggingConfigResolver.ts +7 -6
  91. package/src/resolvers/UserFavoriteResolver.ts +8 -67
  92. package/src/resolvers/UserViewResolver.ts +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/server",
3
- "version": "2.95.0",
3
+ "version": "2.97.0",
4
4
  "description": "MemberJunction: This project provides API access via GraphQL to the common data store.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./src/index.ts",
@@ -22,44 +22,44 @@
22
22
  "dependencies": {
23
23
  "@apollo/server": "^4.9.1",
24
24
  "@graphql-tools/utils": "^10.0.1",
25
- "@memberjunction/actions": "2.95.0",
26
- "@memberjunction/ai": "2.95.0",
27
- "@memberjunction/ai-core-plus": "2.95.0",
28
- "@memberjunction/ai-agents": "2.95.0",
29
- "@memberjunction/aiengine": "2.95.0",
30
- "@memberjunction/ai-prompts": "2.95.0",
31
- "@memberjunction/ai-agent-manager-actions": "2.95.0",
32
- "@memberjunction/ai-mistral": "2.95.0",
33
- "@memberjunction/ai-openai": "2.95.0",
34
- "@memberjunction/ai-anthropic": "2.95.0",
35
- "@memberjunction/ai-groq": "2.95.0",
36
- "@memberjunction/ai-cerebras": "2.95.0",
37
- "@memberjunction/ai-lmstudio": "2.95.0",
38
- "@memberjunction/ai-openrouter": "2.95.0",
39
- "@memberjunction/ai-ollama": "2.95.0",
40
- "@memberjunction/ai-vectors-pinecone": "2.95.0",
41
- "@memberjunction/ai-local-embeddings": "2.95.0",
42
- "@memberjunction/core": "2.95.0",
43
- "@memberjunction/core-actions": "2.95.0",
44
- "@memberjunction/actions-apollo": "2.95.0",
45
- "@memberjunction/actions-bizapps-accounting": "2.95.0",
46
- "@memberjunction/actions-bizapps-crm": "2.95.0",
47
- "@memberjunction/actions-bizapps-lms": "2.95.0",
48
- "@memberjunction/actions-bizapps-social": "2.95.0",
49
- "@memberjunction/core-entities": "2.95.0",
50
- "@memberjunction/core-entities-server": "2.95.0",
51
- "@memberjunction/data-context": "2.95.0",
52
- "@memberjunction/data-context-server": "2.95.0",
53
- "@memberjunction/doc-utils": "2.95.0",
54
- "@memberjunction/entity-communications-server": "2.95.0",
55
- "@memberjunction/external-change-detection": "2.95.0",
56
- "@memberjunction/global": "2.95.0",
57
- "@memberjunction/graphql-dataprovider": "2.95.0",
58
- "@memberjunction/queue": "2.95.0",
59
- "@memberjunction/skip-types": "2.95.0",
60
- "@memberjunction/sqlserver-dataprovider": "2.95.0",
61
- "@memberjunction/storage": "2.95.0",
62
- "@memberjunction/templates": "2.95.0",
25
+ "@memberjunction/actions": "2.97.0",
26
+ "@memberjunction/ai": "2.97.0",
27
+ "@memberjunction/ai-core-plus": "2.97.0",
28
+ "@memberjunction/ai-agents": "2.97.0",
29
+ "@memberjunction/aiengine": "2.97.0",
30
+ "@memberjunction/ai-prompts": "2.97.0",
31
+ "@memberjunction/ai-agent-manager-actions": "2.97.0",
32
+ "@memberjunction/ai-mistral": "2.97.0",
33
+ "@memberjunction/ai-openai": "2.97.0",
34
+ "@memberjunction/ai-anthropic": "2.97.0",
35
+ "@memberjunction/ai-groq": "2.97.0",
36
+ "@memberjunction/ai-cerebras": "2.97.0",
37
+ "@memberjunction/ai-lmstudio": "2.97.0",
38
+ "@memberjunction/ai-openrouter": "2.97.0",
39
+ "@memberjunction/ai-ollama": "2.97.0",
40
+ "@memberjunction/ai-vectors-pinecone": "2.97.0",
41
+ "@memberjunction/ai-local-embeddings": "2.97.0",
42
+ "@memberjunction/core": "2.97.0",
43
+ "@memberjunction/core-actions": "2.97.0",
44
+ "@memberjunction/actions-apollo": "2.97.0",
45
+ "@memberjunction/actions-bizapps-accounting": "2.97.0",
46
+ "@memberjunction/actions-bizapps-crm": "2.97.0",
47
+ "@memberjunction/actions-bizapps-lms": "2.97.0",
48
+ "@memberjunction/actions-bizapps-social": "2.97.0",
49
+ "@memberjunction/core-entities": "2.97.0",
50
+ "@memberjunction/core-entities-server": "2.97.0",
51
+ "@memberjunction/data-context": "2.97.0",
52
+ "@memberjunction/data-context-server": "2.97.0",
53
+ "@memberjunction/doc-utils": "2.97.0",
54
+ "@memberjunction/entity-communications-server": "2.97.0",
55
+ "@memberjunction/external-change-detection": "2.97.0",
56
+ "@memberjunction/global": "2.97.0",
57
+ "@memberjunction/graphql-dataprovider": "2.97.0",
58
+ "@memberjunction/queue": "2.97.0",
59
+ "@memberjunction/skip-types": "2.97.0",
60
+ "@memberjunction/sqlserver-dataprovider": "2.97.0",
61
+ "@memberjunction/storage": "2.97.0",
62
+ "@memberjunction/templates": "2.97.0",
63
63
  "@types/compression": "^1.7.5",
64
64
  "@types/cors": "^2.8.13",
65
65
  "@types/jsonwebtoken": "9.0.6",
package/src/context.ts CHANGED
@@ -15,6 +15,7 @@ import e from 'express';
15
15
  import { DatabaseProviderBase } from '@memberjunction/core';
16
16
  import { SQLServerDataProvider, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
17
17
  import { AuthProviderFactory } from './auth/AuthProviderFactory.js';
18
+ import { Metadata } from '@memberjunction/core';
18
19
 
19
20
  const verifyAsync = async (issuer: string, token: string): Promise<jwt.JwtPayload> =>
20
21
  new Promise((resolve, reject) => {
@@ -157,6 +158,10 @@ export const contextFunction =
157
158
  apiKey
158
159
  );
159
160
 
161
+ if (Metadata.Provider.Entities.length === 0 ) {
162
+ console.warn('WARNING: No entities found in global/shared metadata, this can often be due to the use of **global** Metadata/RunView/DB Providers in a multi-user environment. Check your code to make sure you are using the providers passed to you in AppContext by MJServer and not calling new Metadata() new RunView() new RunQuery() and similar patterns as those are unstable at times in multi-user server environments!!!');
163
+ }
164
+
160
165
  // now create a new instance of SQLServerDataProvider for each request
161
166
  const config = new SQLServerProviderConfigData(dataSource, mj_core_schema, 0, undefined, undefined, false);
162
167
  const p = new SQLServerDataProvider();
@@ -5,8 +5,9 @@ import { Metadata, UserInfo, BaseEntity, CompositeKey, KeyValuePair, LogError }
5
5
  import { ActionParam, ActionResult } from "@memberjunction/actions-base";
6
6
  import { Field, InputType, ObjectType } from "type-graphql";
7
7
  import { KeyValuePairInput } from "../generic/KeyValuePairInput.js";
8
- import { AppContext } from "../types.js";
8
+ import { AppContext, ProviderInfo } from "../types.js";
9
9
  import { CopyScalarsAndArrays } from "@memberjunction/global";
10
+ import { GetReadOnlyProvider } from "../util.js";
10
11
 
11
12
  /**
12
13
  * Input type for action parameters
@@ -341,7 +342,7 @@ export class ActionResolver {
341
342
 
342
343
  // Add entity object if we have entity information and primary key
343
344
  if ((input.EntityID || input.EntityName) && input.PrimaryKey && input.PrimaryKey.KeyValuePairs.length > 0) {
344
- await this.addEntityObject(params, input, user);
345
+ await this.addEntityObject(ctx.providers, params, input, user);
345
346
  }
346
347
 
347
348
  // Add other parameters
@@ -400,8 +401,8 @@ export class ActionResolver {
400
401
  * @param user The authenticated user
401
402
  * @private
402
403
  */
403
- private async addEntityObject(params: any, input: EntityActionInput, user: UserInfo): Promise<void> {
404
- const md = new Metadata();
404
+ private async addEntityObject(providers: Array<ProviderInfo>, params: any, input: EntityActionInput, user: UserInfo): Promise<void> {
405
+ const md = GetReadOnlyProvider(providers);
405
406
 
406
407
  // Find the entity by ID or name
407
408
  let entity;
@@ -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, EntitySaveOptions, EntityDeleteOptions } from '@memberjunction/core';
2
+ import { LogError, LogStatus, Metadata, RunView, UserInfo, CompositeKey, EntityFieldInfo, EntityInfo, EntityRelationshipInfo, EntitySaveOptions, EntityDeleteOptions, IMetadataProvider } 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';
@@ -59,7 +59,7 @@ import mssql from 'mssql';
59
59
 
60
60
  import { registerEnumType } from 'type-graphql';
61
61
  import { MJGlobal, CopyScalarsAndArrays } from '@memberjunction/global';
62
- import { sendPostRequest } from '../util.js';
62
+ import { GetReadWriteProvider, sendPostRequest } from '../util.js';
63
63
  import { GetAIAPIKey } from '@memberjunction/ai';
64
64
  import { CompositeKeyInputType } from '../generic/KeyInputOutputTypes.js';
65
65
  import { AIEngine } from '@memberjunction/aiengine';
@@ -479,7 +479,7 @@ export class AskSkipResolver {
479
479
  @Arg('ConversationId', () => String) ConversationId: string,
480
480
  @Arg('EntityName', () => String) EntityName: string,
481
481
  @Arg('CompositeKey', () => CompositeKeyInputType) compositeKey: CompositeKeyInputType,
482
- @Ctx() { dataSource, userPayload }: AppContext,
482
+ @Ctx() { dataSource, userPayload, providers }: AppContext,
483
483
  @PubSub() pubSub: PubSubEngine
484
484
  ) {
485
485
  // In this function we're simply going to call the Skip API and pass along the message from the user
@@ -498,14 +498,14 @@ export class AskSkipResolver {
498
498
  );
499
499
  }
500
500
 
501
- const md = new Metadata();
501
+ const md = GetReadWriteProvider(providers);
502
502
  const { convoEntity, dataContextEntity, convoDetailEntity, dataContext } = await this.HandleSkipChatInitialObjectLoading(
503
503
  dataSource,
504
504
  ConversationId,
505
505
  UserQuestion,
506
506
  user,
507
507
  userPayload,
508
- md,
508
+ md as unknown as Metadata,
509
509
  null
510
510
  );
511
511
 
@@ -558,7 +558,7 @@ export class AskSkipResolver {
558
558
  */
559
559
  @Mutation(() => AskSkipResultType)
560
560
  async ExecuteAskSkipLearningCycle(
561
- @Ctx() { dataSource, userPayload }: AppContext,
561
+ @Ctx() { dataSource, userPayload, providers }: AppContext,
562
562
  @Arg('ForceEntityRefresh', () => Boolean, { nullable: true }) ForceEntityRefresh?: boolean
563
563
  ) {
564
564
  const skipConfigInfo = configInfo.askSkip;
@@ -612,7 +612,7 @@ export class AskSkipResolver {
612
612
  }
613
613
 
614
614
  // Get the Skip agent ID
615
- const md = new Metadata();
615
+ const md = GetReadWriteProvider(providers);
616
616
  const skipAgent = AIEngine.Instance.GetAgentByName('Skip');
617
617
  if (!skipAgent) {
618
618
  throw new Error("Skip agent not found in AIEngine");
@@ -1467,11 +1467,11 @@ cycle.`);
1467
1467
  @Query(() => ReattachConversationResponse)
1468
1468
  async ReattachToProcessingConversation(
1469
1469
  @Arg('ConversationId', () => String) ConversationId: string,
1470
- @Ctx() { userPayload }: AppContext,
1470
+ @Ctx() { userPayload, providers }: AppContext,
1471
1471
  @PubSub() pubSub: PubSubEngine
1472
1472
  ): Promise<ReattachConversationResponse | null> {
1473
1473
  try {
1474
- const md = new Metadata();
1474
+ const md = GetReadWriteProvider(providers);
1475
1475
  const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
1476
1476
  if (!user) {
1477
1477
  LogError(`User ${userPayload.email} not found in UserCache`);
@@ -1581,13 +1581,13 @@ cycle.`);
1581
1581
  async ExecuteAskSkipAnalysisQuery(
1582
1582
  @Arg('UserQuestion', () => String) UserQuestion: string,
1583
1583
  @Arg('ConversationId', () => String) ConversationId: string,
1584
- @Ctx() { dataSource, userPayload }: AppContext,
1584
+ @Ctx() { dataSource, userPayload, providers }: AppContext,
1585
1585
  @PubSub() pubSub: PubSubEngine,
1586
1586
  @Arg('DataContextId', () => String, { nullable: true }) DataContextId?: string,
1587
1587
  @Arg('ForceEntityRefresh', () => Boolean, { nullable: true }) ForceEntityRefresh?: boolean,
1588
1588
  @Arg('StartTime', () => Date, { nullable: true }) StartTime?: Date
1589
1589
  ) {
1590
- const md = new Metadata();
1590
+ const md = GetReadWriteProvider(providers);
1591
1591
  const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
1592
1592
  if (!user) throw new Error(`User ${userPayload.email} not found in UserCache`);
1593
1593
 
@@ -1600,7 +1600,7 @@ cycle.`);
1600
1600
  UserQuestion,
1601
1601
  user,
1602
1602
  userPayload,
1603
- md,
1603
+ md as unknown as Metadata,
1604
1604
  DataContextId
1605
1605
  );
1606
1606
 
@@ -1625,7 +1625,7 @@ cycle.`);
1625
1625
  ConversationId,
1626
1626
  userPayload,
1627
1627
  pubSub,
1628
- md,
1628
+ md as unknown as Metadata,
1629
1629
  convoEntity,
1630
1630
  convoDetailEntity,
1631
1631
  dataContext,
@@ -1,10 +1,10 @@
1
1
  import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType, Resolver, PubSub, PubSubEngine } from 'type-graphql';
2
2
  import { AppContext } from '../types.js';
3
- import { LogError, Metadata, RunView, UserInfo, CompositeKey } from '@memberjunction/core';
3
+ import { LogError, Metadata, RunView, UserInfo, CompositeKey, DatabaseProviderBase } from '@memberjunction/core';
4
4
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
5
5
  import { QueryCategoryEntity, QueryPermissionEntity } from '@memberjunction/core-entities';
6
6
  import { QueryResolver } from '../generated/generated.js';
7
- import { GetReadWriteProvider } from '../util.js';
7
+ import { GetReadOnlyProvider, GetReadWriteProvider } from '../util.js';
8
8
  import { DeleteOptionsInput } from '../generic/DeleteOptionsInput.js';
9
9
  import { QueryEntityExtended } from '@memberjunction/core-entities-server';
10
10
 
@@ -311,13 +311,12 @@ export class QueryResolverExtended extends QueryResolver {
311
311
  try {
312
312
  // Handle CategoryPath if provided
313
313
  let finalCategoryID = input.CategoryID;
314
+ const provider = GetReadWriteProvider(context.providers);
314
315
  if (input.CategoryPath) {
315
- const md = new Metadata();
316
- finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord);
316
+ finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, provider, context.userPayload.userRecord);
317
317
  }
318
318
 
319
319
  // Use QueryEntityExtended which handles AI processing
320
- const provider = GetReadWriteProvider(context.providers);
321
320
  const record = await provider.GetEntityObject<QueryEntityExtended>("Queries", context.userPayload.userRecord);
322
321
 
323
322
  // Set the fields from input, handling CategoryPath resolution
@@ -345,7 +344,7 @@ export class QueryResolverExtended extends QueryResolver {
345
344
  const queryID = record.ID;
346
345
 
347
346
  if (input.Permissions && input.Permissions.length > 0) {
348
- await this.createPermissions(input.Permissions, queryID, context.userPayload.userRecord);
347
+ await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
349
348
  await record.RefreshRelatedMetadata(true); // force DB update since we just created new permissions
350
349
  }
351
350
 
@@ -354,7 +353,12 @@ export class QueryResolverExtended extends QueryResolver {
354
353
  QueryData: JSON.stringify(record.GetAll()),
355
354
  Fields: record.QueryFields,
356
355
  Parameters: record.QueryParameters,
357
- Entities: record.QueryEntities,
356
+ Entities: record.QueryEntities.map(e => {
357
+ return {
358
+ ...e,
359
+ EntityName: e.Entity // alias this to fix variable name mismatch
360
+ }
361
+ }),
358
362
  Permissions: record.QueryPermissions
359
363
  };
360
364
  }
@@ -374,13 +378,12 @@ export class QueryResolverExtended extends QueryResolver {
374
378
  }
375
379
  }
376
380
 
377
- protected async createPermissions(permissions: QueryPermissionInputType[], queryID: string, contextUser: UserInfo): Promise<QueryPermissionType[]> {
381
+ protected async createPermissions(p: DatabaseProviderBase, permissions: QueryPermissionInputType[], queryID: string, contextUser: UserInfo): Promise<QueryPermissionType[]> {
378
382
  // Create permissions if provided
379
383
  const createdPermissions: QueryPermissionType[] = [];
380
384
  if (permissions && permissions.length > 0) {
381
- const md = new Metadata();
382
385
  for (const perm of permissions) {
383
- const permissionEntity = await md.GetEntityObject<QueryPermissionEntity>('Query Permissions', contextUser);
386
+ const permissionEntity = await p.GetEntityObject<QueryPermissionEntity>('Query Permissions', contextUser);
384
387
  if (permissionEntity) {
385
388
  permissionEntity.QueryID = queryID;
386
389
  permissionEntity.RoleID = perm.RoleID;
@@ -427,8 +430,20 @@ export class QueryResolverExtended extends QueryResolver {
427
430
  // Handle CategoryPath if provided
428
431
  let finalCategoryID = input.CategoryID;
429
432
  if (input.CategoryPath) {
430
- const md = new Metadata();
431
- finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord);
433
+ finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, provider, context.userPayload.userRecord);
434
+ }
435
+
436
+ // now make sure there is NO existing query by the same name in the specified category
437
+ const existingQueryResult = await provider.RunView({
438
+ EntityName: 'Queries',
439
+ ExtraFilter: `Name='${input.Name}' AND CategoryID='${finalCategoryID}'`
440
+ }, context.userPayload.userRecord);
441
+ if (existingQueryResult.Success && existingQueryResult.Results?.length > 0) {
442
+ // we have a match! Let's return an error
443
+ return {
444
+ Success: false,
445
+ ErrorMessage: `Query with name '${input.Name}' already exists in the specified ${input.CategoryID ? 'category' : 'categoryPath'}`
446
+ };
432
447
  }
433
448
 
434
449
  // Update fields that were provided
@@ -481,7 +496,7 @@ export class QueryResolverExtended extends QueryResolver {
481
496
  }
482
497
 
483
498
  // Create new permissions
484
- await this.createPermissions(input.Permissions, queryID, context.userPayload.userRecord);
499
+ await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
485
500
 
486
501
  // Refresh the metadata to get updated permissions
487
502
  await queryEntity.RefreshRelatedMetadata(true);
@@ -608,7 +623,7 @@ export class QueryResolverExtended extends QueryResolver {
608
623
  * @param contextUser - User context for operations
609
624
  * @returns The ID of the final category in the path
610
625
  */
611
- private async findOrCreateCategoryPath(categoryPath: string, md: Metadata, contextUser: UserInfo): Promise<string> {
626
+ private async findOrCreateCategoryPath(categoryPath: string, p: DatabaseProviderBase, contextUser: UserInfo): Promise<string> {
612
627
  if (!categoryPath || categoryPath.trim() === '') {
613
628
  throw new Error('CategoryPath cannot be empty');
614
629
  }
@@ -625,7 +640,7 @@ export class QueryResolverExtended extends QueryResolver {
625
640
  const categoryName = pathParts[i];
626
641
 
627
642
  // Look for existing category at this level
628
- const existingCategory = await this.findCategoryByNameAndParent(categoryName, currentParentID, contextUser);
643
+ const existingCategory = await this.findCategoryByNameAndParent(p, categoryName, currentParentID, contextUser);
629
644
 
630
645
  if (existingCategory) {
631
646
  currentCategoryID = existingCategory.ID;
@@ -633,7 +648,7 @@ export class QueryResolverExtended extends QueryResolver {
633
648
  } else {
634
649
  try {
635
650
  // Create new category
636
- const newCategory = await md.GetEntityObject<QueryCategoryEntity>("Query Categories", contextUser);
651
+ const newCategory = await p.GetEntityObject<QueryCategoryEntity>("Query Categories", contextUser);
637
652
  if (!newCategory) {
638
653
  throw new Error(`Failed to create entity object for Query Categories`);
639
654
  }
@@ -652,7 +667,7 @@ export class QueryResolverExtended extends QueryResolver {
652
667
  currentParentID = newCategory.ID;
653
668
 
654
669
  // Refresh metadata after each category creation to ensure it's available for subsequent lookups
655
- await md.Refresh();
670
+ await p.Refresh();
656
671
  } catch (error) {
657
672
  throw new Error(`Failed to create category '${categoryName}': ${error instanceof Error ? error.message : String(error)}`);
658
673
  }
@@ -673,9 +688,9 @@ export class QueryResolverExtended extends QueryResolver {
673
688
  * @param contextUser - User context for database operations
674
689
  * @returns The matching category entity or null if not found
675
690
  */
676
- private async findCategoryByNameAndParent(categoryName: string, parentID: string | null, contextUser: UserInfo): Promise<QueryCategoryEntity | null> {
691
+ private async findCategoryByNameAndParent(provider: DatabaseProviderBase, categoryName: string, parentID: string | null, contextUser: UserInfo): Promise<QueryCategoryEntity | null> {
677
692
  try {
678
- const rv = new RunView();
693
+ const rv = provider;
679
694
  const parentFilter = parentID ? `ParentID='${parentID}'` : 'ParentID IS NULL';
680
695
  const nameFilter = `LOWER(Name) = LOWER('${categoryName.replace(/'/g, "''")}')`; // Escape single quotes
681
696
 
@@ -1,6 +1,7 @@
1
1
  import { Arg, Ctx, Field, InputType, Int, ObjectType, Query, Resolver } from 'type-graphql';
2
2
  import { AppContext } from '../types.js';
3
3
  import { LogError, Metadata } from '@memberjunction/core';
4
+ import { GetReadOnlyProvider } from '../util.js';
4
5
 
5
6
  @ObjectType()
6
7
  export class DatasetResultType {
@@ -38,11 +39,11 @@ export class DatasetResolverExtended {
38
39
  @Query(() => DatasetResultType)
39
40
  async GetDatasetByName(
40
41
  @Arg('DatasetName', () => String) DatasetName: string,
41
- @Ctx() {}: AppContext,
42
+ @Ctx() {providers}: AppContext,
42
43
  @Arg('ItemFilters', () => [DatasetItemFilterTypeGQL], { nullable: 'itemsAndList' }) ItemFilters?: DatasetItemFilterTypeGQL[]
43
44
  ) {
44
45
  try {
45
- const md = new Metadata();
46
+ const md = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true});
46
47
  const result = await md.GetDatasetByName(DatasetName, ItemFilters);
47
48
  if (result) {
48
49
  return {
@@ -89,11 +90,11 @@ export class DatasetStatusResolver {
89
90
  @Query(() => DatasetStatusResultType)
90
91
  async GetDatasetStatusByName(
91
92
  @Arg('DatasetName', () => String) DatasetName: string,
92
- @Ctx() {}: AppContext,
93
+ @Ctx() {providers}: AppContext,
93
94
  @Arg('ItemFilters', () => [DatasetItemFilterTypeGQL], { nullable: 'itemsAndList' }) ItemFilters?: DatasetItemFilterTypeGQL[]
94
95
  ) {
95
96
  try {
96
- const md = new Metadata();
97
+ const md = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true});
97
98
  const result = await md.GetDatasetStatusByName(DatasetName, ItemFilters);
98
99
  if (result) {
99
100
  return {
@@ -1,7 +1,8 @@
1
- import { Metadata, CompositeKey } from '@memberjunction/core';
1
+ import { Metadata, CompositeKey, DatabaseProviderBase } from '@memberjunction/core';
2
2
  import { Arg, Ctx, Field, InputType, ObjectType, Query, Resolver } from 'type-graphql';
3
3
  import { AppContext } from '../types.js';
4
4
  import { CompositeKeyInputType, CompositeKeyOutputType } from '../generic/KeyInputOutputTypes.js';
5
+ import { GetReadOnlyProvider } from '../util.js';
5
6
 
6
7
  @InputType()
7
8
  export class EntityRecordNameInput {
@@ -36,26 +37,27 @@ export class EntityRecordNameResolver {
36
37
  async GetEntityRecordName(
37
38
  @Arg('EntityName', () => String) EntityName: string,
38
39
  @Arg('CompositeKey', () => CompositeKeyInputType) primaryKey: CompositeKey,
39
- @Ctx() { userPayload }: AppContext
40
+ @Ctx() { providers, userPayload }: AppContext
40
41
  ): Promise<EntityRecordNameResult> {
41
- const md = new Metadata();
42
+ const md = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true});
43
+
42
44
  return await this.InnerGetEntityRecordName(md, EntityName, primaryKey);
43
45
  }
44
46
 
45
47
  @Query(() => [EntityRecordNameResult])
46
48
  async GetEntityRecordNames(
47
49
  @Arg('info', () => [EntityRecordNameInput]) info: EntityRecordNameInput[],
48
- @Ctx() {}: AppContext
50
+ @Ctx() {providers}: AppContext
49
51
  ): Promise<EntityRecordNameResult[]> {
50
52
  const result: EntityRecordNameResult[] = [];
51
- const md = new Metadata();
53
+ const md = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true});
52
54
  for (const i of info) {
53
55
  result.push(await this.InnerGetEntityRecordName(md, i.EntityName, i.CompositeKey));
54
56
  }
55
57
  return result;
56
58
  }
57
59
 
58
- async InnerGetEntityRecordName(md: Metadata, EntityName: string, primaryKey: CompositeKeyInputType): Promise<EntityRecordNameResult> {
60
+ async InnerGetEntityRecordName(md: DatabaseProviderBase, EntityName: string, primaryKey: CompositeKeyInputType): Promise<EntityRecordNameResult> {
59
61
  const pk = new CompositeKey(primaryKey.KeyValuePairs);
60
62
  const e = md.Entities.find((e) => e.Name === EntityName);
61
63
  if (e) {
@@ -16,16 +16,15 @@ export class FileResolver extends FileCategoryResolverBase {
16
16
  ) {
17
17
  const key = new CompositeKey();
18
18
  key.LoadFromSingleKeyValuePair('ID', ID);
19
- const provider = GetReadWriteProvider(providers);
19
+ const p = GetReadWriteProvider(providers);
20
20
 
21
- if (!(await this.BeforeDelete(provider, key))) {
21
+ if (!(await this.BeforeDelete(p, key))) {
22
22
  return null;
23
23
  }
24
24
 
25
- const md = new Metadata();
26
25
  const user = this.GetUserFromPayload(userPayload);
27
- const fileEntity = await md.GetEntityObject<FileEntity>('Files', user);
28
- const fileCategoryEntity = await md.GetEntityObject<FileCategoryEntity>('File Categories', user);
26
+ const fileEntity = await p.GetEntityObject<FileEntity>('Files', user);
27
+ const fileCategoryEntity = await p.GetEntityObject<FileCategoryEntity>('File Categories', user);
29
28
 
30
29
  fileEntity.CheckPermissions(EntityPermissionType.Update, true);
31
30
  fileCategoryEntity.CheckPermissions(EntityPermissionType.Delete, true);
@@ -34,7 +33,7 @@ export class FileResolver extends FileCategoryResolverBase {
34
33
  const returnValue = fileCategoryEntity.GetAll();
35
34
 
36
35
  // Any files using the deleted category fall back to its parent
37
- await provider.BeginTransaction();
36
+ await p.BeginTransaction();
38
37
  try {
39
38
  // SHOULD USE BaseEntity for each of these records to ensure object model
40
39
  // is used everywhere - new code below. The below is SLOWER than a single
@@ -60,20 +59,20 @@ export class FileResolver extends FileCategoryResolverBase {
60
59
  // iterate through each of the files in filesResult.Results
61
60
  // and update the CategoryID to fileCategoryEntity.ParentID
62
61
  for (const file of filesResult.Results) {
63
- const fileEntity = await md.GetEntityObject<FileEntity>('Files', user);
62
+ const fileEntity = await p.GetEntityObject<FileEntity>('Files', user);
64
63
  await fileEntity.Load(file.ID);
65
64
  fileEntity.CategoryID = fileCategoryEntity.ParentID;
66
65
  await fileEntity.Save();
67
66
  }
68
67
  }
69
68
  await fileCategoryEntity.Delete(options);
70
- await provider.CommitTransaction();
69
+ await p.CommitTransaction();
71
70
  } catch (error) {
72
- await provider.RollbackTransaction();
71
+ await p.RollbackTransaction();
73
72
  throw error;
74
73
  }
75
74
 
76
- await this.AfterDelete(provider, key); // fire event
75
+ await this.AfterDelete(p, key); // fire event
77
76
  return returnValue;
78
77
  }
79
78
  }
@@ -51,14 +51,13 @@ export class FileResolver extends FileResolverBase {
51
51
  @Ctx() context: AppContext,
52
52
  @PubSub() pubSub: PubSubEngine
53
53
  ) {
54
- const md = new Metadata();
54
+ // Check to see if there's already an object with that name
55
+ const provider = GetReadOnlyProvider(context.providers, {allowFallbackToReadWrite: true})
55
56
  const user = this.GetUserFromPayload(context.userPayload);
56
- const fileEntity = await md.GetEntityObject<FileEntity>('Files', user);
57
- const providerEntity = await md.GetEntityObject<FileStorageProviderEntity>('File Storage Providers', user);
57
+ const fileEntity = await provider.GetEntityObject<FileEntity>('Files', user);
58
+ const providerEntity = await provider.GetEntityObject<FileStorageProviderEntity>('File Storage Providers', user);
58
59
  fileEntity.CheckPermissions(EntityPermissionType.Create, true);
59
60
 
60
- // Check to see if there's already an object with that name
61
- const provider = GetReadOnlyProvider(context.providers, {allowFallbackToReadWrite: true})
62
61
  const [sameName] = await this.findBy(
63
62
  provider,
64
63
  'Files',
@@ -108,7 +107,7 @@ export class FileResolver extends FileResolverBase {
108
107
  @PubSub() pubSub: PubSubEngine
109
108
  ) {
110
109
  // if the name is changing, rename the target object as well
111
- const md = new Metadata();
110
+ const md = GetReadOnlyProvider(context.providers);
112
111
  const user = this.GetUserFromPayload(context.userPayload);
113
112
  const fileEntity = await md.GetEntityObject<FileEntity>('Files', user);
114
113
  fileEntity.CheckPermissions(EntityPermissionType.Update, true);
@@ -136,7 +135,7 @@ export class FileResolver extends FileResolverBase {
136
135
  @Ctx() context: AppContext,
137
136
  @PubSub() pubSub: PubSubEngine
138
137
  ) {
139
- const md = new Metadata();
138
+ const md = GetReadOnlyProvider(context.providers);
140
139
  const userInfo = this.GetUserFromPayload(context.userPayload);
141
140
 
142
141
  const fileEntity = await md.GetEntityObject<FileEntity>('Files', userInfo);
@@ -1,7 +1,7 @@
1
1
  import { Arg, Ctx, Field, ObjectType, Query } from "type-graphql";
2
2
  import { AppContext } from "../types.js";
3
3
  import { DataContext } from "@memberjunction/data-context";
4
- import { GetReadOnlyDataSource } from "../util.js";
4
+ import { GetReadOnlyDataSource, GetReadOnlyProvider } from "../util.js";
5
5
  import { Metadata } from "@memberjunction/core";
6
6
  import { DataContextItemEntity } from "@memberjunction/core-entities";
7
7
 
@@ -53,7 +53,7 @@ export class GetDataContextDataResolver {
53
53
  const ds = GetReadOnlyDataSource(appCtx.dataSources, {
54
54
  allowFallbackToReadWrite: true,
55
55
  })
56
- const md = new Metadata();
56
+ const md = GetReadOnlyProvider(appCtx.providers, {allowFallbackToReadWrite: true});
57
57
  const dciData = await md.GetEntityObject<DataContextItemEntity>("Data Context Items", appCtx.userPayload.userRecord);
58
58
  if (await dciData.Load(DataContextItemID)) {
59
59
  const dci = DataContext.CreateDataContextItem(); // use class factory to get whatever lowest level sub-class is registered
@@ -3,7 +3,7 @@ import { AppContext } from '../types.js';
3
3
  import { LogError, LogStatus, Metadata } from '@memberjunction/core';
4
4
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
5
5
  import { v4 as uuidv4 } from 'uuid';
6
- import { GetReadOnlyDataSource } from '../util.js';
6
+ import { GetReadOnlyDataSource, GetReadOnlyProvider } from '../util.js';
7
7
  import sql from 'mssql';
8
8
 
9
9
  @InputType()
@@ -169,7 +169,7 @@ export class GetDataResolver {
169
169
  @Ctx() context: AppContext
170
170
  ): Promise<SimpleEntityResultType> {
171
171
  try {
172
- const md = new Metadata();
172
+ const md = GetReadOnlyProvider(context.providers);
173
173
  const result = md.Entities.map((e) => {
174
174
  return {
175
175
  ID: e.ID,
@@ -30,7 +30,7 @@ export class Info {
30
30
  }
31
31
 
32
32
  @Resolver(Info)
33
- export class InfoResolver {
33
+ export class InfoResolver {
34
34
  @Public()
35
35
  @Query(() => Info)
36
36
  Info(@Ctx() context: AppContext): Info {