@memberjunction/server 4.1.0 → 4.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/server",
3
- "version": "4.1.0",
3
+ "version": "4.3.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",
@@ -18,65 +18,65 @@
18
18
  "build": "tsc && tsc-alias -f",
19
19
  "bundle": "tsc -noEmit && pkgroll --sourcemap --minify",
20
20
  "watch": "tsc -w",
21
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
22
- "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
23
- "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "test:coverage": "vitest run --coverage"
24
24
  },
25
25
  "dependencies": {
26
26
  "@apollo/server": "^4.9.1",
27
27
  "@graphql-tools/schema": "latest",
28
28
  "@graphql-tools/utils": "^11.0.0",
29
- "@memberjunction/actions": "4.1.0",
30
- "@memberjunction/actions-base": "4.1.0",
31
- "@memberjunction/actions-apollo": "4.1.0",
32
- "@memberjunction/actions-bizapps-accounting": "4.1.0",
33
- "@memberjunction/actions-bizapps-crm": "4.1.0",
34
- "@memberjunction/actions-bizapps-formbuilders": "4.1.0",
35
- "@memberjunction/actions-bizapps-lms": "4.1.0",
36
- "@memberjunction/actions-bizapps-social": "4.1.0",
37
- "@memberjunction/ai": "4.1.0",
38
- "@memberjunction/ai-mcp-client": "4.1.0",
39
- "@memberjunction/ai-agent-manager": "4.1.0",
40
- "@memberjunction/ai-agent-manager-actions": "4.1.0",
41
- "@memberjunction/ai-agents": "4.1.0",
42
- "@memberjunction/ai-core-plus": "4.1.0",
43
- "@memberjunction/ai-prompts": "4.1.0",
44
- "@memberjunction/ai-provider-bundle": "4.1.0",
45
- "@memberjunction/ai-vectors-pinecone": "4.1.0",
46
- "@memberjunction/aiengine": "4.1.0",
47
- "@memberjunction/communication-ms-graph": "4.1.0",
48
- "@memberjunction/communication-sendgrid": "4.1.0",
49
- "@memberjunction/communication-types": "4.1.0",
50
- "@memberjunction/component-registry-client-sdk": "4.1.0",
51
- "@memberjunction/config": "4.1.0",
52
- "@memberjunction/core": "4.1.0",
53
- "@memberjunction/core-actions": "4.1.0",
54
- "@memberjunction/core-entities": "4.1.0",
55
- "@memberjunction/core-entities-server": "4.1.0",
56
- "@memberjunction/data-context": "4.1.0",
57
- "@memberjunction/data-context-server": "4.1.0",
58
- "@memberjunction/doc-utils": "4.1.0",
59
- "@memberjunction/api-keys": "4.1.0",
60
- "@memberjunction/encryption": "4.1.0",
61
- "@memberjunction/entity-communications-base": "4.1.0",
62
- "@memberjunction/entity-communications-server": "4.1.0",
63
- "@memberjunction/external-change-detection": "4.1.0",
64
- "@memberjunction/global": "4.1.0",
65
- "@memberjunction/graphql-dataprovider": "4.1.0",
66
- "@memberjunction/interactive-component-types": "4.1.0",
67
- "@memberjunction/notifications": "4.1.0",
68
- "@memberjunction/queue": "4.1.0",
69
- "@memberjunction/scheduling-actions": "4.1.0",
70
- "@memberjunction/scheduling-base-types": "4.1.0",
71
- "@memberjunction/scheduling-engine": "4.1.0",
72
- "@memberjunction/scheduling-engine-base": "4.1.0",
73
- "@memberjunction/skip-types": "4.1.0",
74
- "@memberjunction/sqlserver-dataprovider": "4.1.0",
75
- "@memberjunction/storage": "4.1.0",
76
- "@memberjunction/templates": "4.1.0",
77
- "@memberjunction/testing-engine": "4.1.0",
78
- "@memberjunction/testing-engine-base": "4.1.0",
79
- "@memberjunction/version-history": "4.1.0",
29
+ "@memberjunction/actions": "4.3.0",
30
+ "@memberjunction/actions-base": "4.3.0",
31
+ "@memberjunction/actions-apollo": "4.3.0",
32
+ "@memberjunction/actions-bizapps-accounting": "4.3.0",
33
+ "@memberjunction/actions-bizapps-crm": "4.3.0",
34
+ "@memberjunction/actions-bizapps-formbuilders": "4.3.0",
35
+ "@memberjunction/actions-bizapps-lms": "4.3.0",
36
+ "@memberjunction/actions-bizapps-social": "4.3.0",
37
+ "@memberjunction/ai": "4.3.0",
38
+ "@memberjunction/ai-mcp-client": "4.3.0",
39
+ "@memberjunction/ai-agent-manager": "4.3.0",
40
+ "@memberjunction/ai-agent-manager-actions": "4.3.0",
41
+ "@memberjunction/ai-agents": "4.3.0",
42
+ "@memberjunction/ai-core-plus": "4.3.0",
43
+ "@memberjunction/ai-prompts": "4.3.0",
44
+ "@memberjunction/ai-provider-bundle": "4.3.0",
45
+ "@memberjunction/ai-vectors-pinecone": "4.3.0",
46
+ "@memberjunction/aiengine": "4.3.0",
47
+ "@memberjunction/communication-ms-graph": "4.3.0",
48
+ "@memberjunction/communication-sendgrid": "4.3.0",
49
+ "@memberjunction/communication-types": "4.3.0",
50
+ "@memberjunction/component-registry-client-sdk": "4.3.0",
51
+ "@memberjunction/config": "4.3.0",
52
+ "@memberjunction/core": "4.3.0",
53
+ "@memberjunction/core-actions": "4.3.0",
54
+ "@memberjunction/core-entities": "4.3.0",
55
+ "@memberjunction/core-entities-server": "4.3.0",
56
+ "@memberjunction/data-context": "4.3.0",
57
+ "@memberjunction/data-context-server": "4.3.0",
58
+ "@memberjunction/doc-utils": "4.3.0",
59
+ "@memberjunction/api-keys": "4.3.0",
60
+ "@memberjunction/encryption": "4.3.0",
61
+ "@memberjunction/entity-communications-base": "4.3.0",
62
+ "@memberjunction/entity-communications-server": "4.3.0",
63
+ "@memberjunction/external-change-detection": "4.3.0",
64
+ "@memberjunction/global": "4.3.0",
65
+ "@memberjunction/graphql-dataprovider": "4.3.0",
66
+ "@memberjunction/interactive-component-types": "4.3.0",
67
+ "@memberjunction/notifications": "4.3.0",
68
+ "@memberjunction/queue": "4.3.0",
69
+ "@memberjunction/scheduling-actions": "4.3.0",
70
+ "@memberjunction/scheduling-base-types": "4.3.0",
71
+ "@memberjunction/scheduling-engine": "4.3.0",
72
+ "@memberjunction/scheduling-engine-base": "4.3.0",
73
+ "@memberjunction/skip-types": "4.3.0",
74
+ "@memberjunction/sqlserver-dataprovider": "4.3.0",
75
+ "@memberjunction/storage": "4.3.0",
76
+ "@memberjunction/templates": "4.3.0",
77
+ "@memberjunction/testing-engine": "4.3.0",
78
+ "@memberjunction/testing-engine-base": "4.3.0",
79
+ "@memberjunction/version-history": "4.3.0",
80
80
  "@types/compression": "^1.8.1",
81
81
  "@types/cors": "^2.8.19",
82
82
  "@types/jsonwebtoken": "9.0.10",
@@ -110,11 +110,8 @@
110
110
  "zod": "~3.24.4"
111
111
  },
112
112
  "devDependencies": {
113
- "@jest/globals": "^30.2.0",
114
- "@types/jest": "^30.0.0",
115
- "jest": "^30.2.0",
116
113
  "pkgroll": "2.23.0",
117
- "ts-jest": "^29.4.6",
114
+ "vitest": "^3.1.1",
118
115
  "typescript": "^5.9.3"
119
116
  },
120
117
  "repository": {
@@ -1,21 +1,19 @@
1
+ /// <reference types="vitest/globals" />
1
2
  /**
2
- * Jest setup file for MJServer tests
3
+ * Vitest setup file for MJServer tests
3
4
  * Runs before each test file
4
5
  */
5
6
 
6
7
  // Increase timeout for async operations
7
- // Using globalThis for ESM compatibility
8
- if (typeof jest !== 'undefined') {
9
- jest.setTimeout(30000);
10
- }
8
+ vi.setConfig({ testTimeout: 30000 });
11
9
 
12
10
  // Mock console methods to reduce noise (can be enabled per test)
13
11
  // Uncomment to silence logs during tests
14
12
  // global.console = {
15
13
  // ...console,
16
- // log: jest.fn(),
17
- // debug: jest.fn(),
18
- // info: jest.fn(),
19
- // warn: jest.fn(),
20
- // error: jest.fn(),
14
+ // log: vi.fn(),
15
+ // debug: vi.fn(),
16
+ // info: vi.fn(),
17
+ // warn: vi.fn(),
18
+ // error: vi.fn(),
21
19
  // };
@@ -1,4 +1,16 @@
1
- import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+
3
+ // Mock the config module before any imports that depend on it
4
+ vi.mock('../../config', () => ({
5
+ configInfo: {
6
+ authProviders: [],
7
+ databaseSettings: {},
8
+ graphqlPort: 4000,
9
+ baseUrl: 'http://localhost',
10
+ graphqlRootPath: '/',
11
+ },
12
+ }));
13
+
2
14
  import { AuthProviderFactory } from '../AuthProviderFactory';
3
15
  import { IAuthProvider } from '../IAuthProvider';
4
16
  import { initializeAuthProviders } from '../initializeProviders';
@@ -18,36 +30,32 @@ describe('Authentication Provider Backward Compatibility', () => {
18
30
  factory.clear();
19
31
  });
20
32
 
21
- describe('Legacy Configuration Support', () => {
22
- it('should create MSAL provider from legacy config', () => {
23
- // Simulate legacy environment variables
24
- process.env.TENANT_ID = 'test-tenant-id';
25
- process.env.WEB_CLIENT_ID = 'test-client-id';
26
-
27
- // Initialize with legacy config
33
+ describe('Configuration-Based Provider Initialization', () => {
34
+ it('should initialize with no providers when config is empty', () => {
28
35
  initializeAuthProviders();
29
-
30
- // Check that MSAL provider was created
31
- const msalProvider = factory.getByName('msal');
32
- expect(msalProvider).toBeDefined();
33
- expect(msalProvider?.issuer).toContain('test-tenant-id');
34
- expect(msalProvider?.audience).toBe('test-client-id');
36
+
37
+ // With empty authProviders array, no providers should be registered
38
+ expect(factory.hasProviders()).toBe(false);
35
39
  });
36
-
37
- it('should create Auth0 provider from legacy config', () => {
38
- // Simulate legacy environment variables
39
- process.env.AUTH0_DOMAIN = 'test.auth0.com';
40
- process.env.AUTH0_CLIENT_ID = 'auth0-client-id';
41
- process.env.AUTH0_CLIENT_SECRET = 'auth0-secret';
42
-
43
- // Initialize with legacy config
40
+
41
+ it('should clear existing providers before re-initializing', () => {
42
+ // Register a manual provider first
43
+ const testProvider = {
44
+ name: 'test',
45
+ issuer: 'https://test.com',
46
+ audience: 'test',
47
+ jwksUri: 'https://test.com/jwks',
48
+ validateConfig: () => true,
49
+ getSigningKey: vi.fn(),
50
+ extractUserInfo: vi.fn(),
51
+ matchesIssuer: vi.fn(),
52
+ } as IAuthProvider;
53
+ factory.register(testProvider);
54
+ expect(factory.hasProviders()).toBe(true);
55
+
56
+ // Re-initialize clears everything
44
57
  initializeAuthProviders();
45
-
46
- // Check that Auth0 provider was created
47
- const auth0Provider = factory.getByName('auth0');
48
- expect(auth0Provider).toBeDefined();
49
- expect(auth0Provider?.issuer).toBe('https://test.auth0.com/');
50
- expect(auth0Provider?.audience).toBe('auth0-client-id');
58
+ expect(factory.hasProviders()).toBe(false);
51
59
  });
52
60
  });
53
61
 
@@ -61,8 +69,8 @@ describe('Authentication Provider Backward Compatibility', () => {
61
69
  audience: 'test-audience',
62
70
  jwksUri: 'https://test.provider.com/.well-known/jwks.json',
63
71
  validateConfig: () => true,
64
- getSigningKey: jest.fn(),
65
- extractUserInfo: jest.fn(),
72
+ getSigningKey: vi.fn(),
73
+ extractUserInfo: vi.fn(),
66
74
  matchesIssuer: (issuer: string) => {
67
75
  const normalized = issuer.toLowerCase().replace(/\/$/, '');
68
76
  return normalized === 'https://test.provider.com/oauth2';
@@ -88,9 +96,9 @@ describe('Authentication Provider Backward Compatibility', () => {
88
96
  audience: 'test',
89
97
  jwksUri: 'https://test.provider.com/jwks',
90
98
  validateConfig: () => true,
91
- getSigningKey: jest.fn(),
92
- extractUserInfo: jest.fn(),
93
- matchesIssuer: jest.fn((issuer: string): boolean => issuer === 'https://test.provider.com')
99
+ getSigningKey: vi.fn(),
100
+ extractUserInfo: vi.fn(),
101
+ matchesIssuer: vi.fn((issuer: string): boolean => issuer === 'https://test.provider.com')
94
102
  } as IAuthProvider;
95
103
 
96
104
  factory.register(testProvider);
@@ -106,55 +114,36 @@ describe('Authentication Provider Backward Compatibility', () => {
106
114
  });
107
115
 
108
116
  describe('User Info Extraction', () => {
109
- it('should extract user info from different token formats', () => {
110
- // Test MSAL token format
111
- const msalPayload = {
112
- iss: 'https://login.microsoftonline.com/tenant/v2.0',
113
- email: 'user@example.com',
114
- given_name: 'John',
115
- family_name: 'Doe',
116
- name: 'John Doe',
117
- preferred_username: 'john.doe@example.com'
118
- };
119
-
120
- // Test Auth0 token format
121
- const auth0Payload = {
122
- iss: 'https://test.auth0.com/',
123
- email: 'user@example.com',
124
- given_name: 'Jane',
125
- family_name: 'Smith',
126
- name: 'Jane Smith'
127
- };
128
-
129
- // Test Okta token format
130
- const oktaPayload = {
131
- iss: 'https://test.okta.com/oauth2/default',
132
- email: 'user@example.com',
133
- given_name: 'Bob',
134
- family_name: 'Johnson',
135
- name: 'Bob Johnson',
136
- preferred_username: 'bob.johnson'
137
- };
138
-
139
- // Initialize providers
140
- initializeAuthProviders();
141
-
142
- // Test extraction for each provider type
143
- const msalProvider = factory.getByIssuer(msalPayload.iss);
144
- if (msalProvider) {
145
- const msalUserInfo = msalProvider.extractUserInfo(msalPayload);
146
- expect(msalUserInfo.email).toBe('user@example.com');
147
- expect(msalUserInfo.firstName).toBe('John');
148
- expect(msalUserInfo.lastName).toBe('Doe');
149
- }
150
-
151
- const auth0Provider = factory.getByIssuer(auth0Payload.iss);
152
- if (auth0Provider) {
153
- const auth0UserInfo = auth0Provider.extractUserInfo(auth0Payload);
154
- expect(auth0UserInfo.email).toBe('user@example.com');
155
- expect(auth0UserInfo.firstName).toBe('Jane');
156
- expect(auth0UserInfo.lastName).toBe('Smith');
157
- }
117
+ it('should return undefined for unregistered issuer', () => {
118
+ const provider = factory.getByIssuer('https://unknown.issuer.com');
119
+ expect(provider).toBeUndefined();
120
+ });
121
+
122
+ it('should support extractUserInfo on manually registered providers', () => {
123
+ const mockExtract = vi.fn().mockReturnValue({
124
+ email: 'user@test.com',
125
+ firstName: 'Test',
126
+ lastName: 'User',
127
+ });
128
+ const testProvider = {
129
+ name: 'test-extract',
130
+ issuer: 'https://test-extract.com',
131
+ audience: 'test',
132
+ jwksUri: 'https://test-extract.com/jwks',
133
+ validateConfig: () => true,
134
+ getSigningKey: vi.fn(),
135
+ extractUserInfo: mockExtract,
136
+ matchesIssuer: (iss: string) => iss === 'https://test-extract.com',
137
+ } as IAuthProvider;
138
+
139
+ factory.register(testProvider);
140
+
141
+ const found = factory.getByIssuer('https://test-extract.com');
142
+ expect(found).toBeDefined();
143
+
144
+ const userInfo = found!.extractUserInfo({ email: 'user@test.com' });
145
+ expect(userInfo.email).toBe('user@test.com');
146
+ expect(mockExtract).toHaveBeenCalledTimes(1);
158
147
  });
159
148
  });
160
149
 
@@ -172,9 +161,9 @@ describe('Authentication Provider Backward Compatibility', () => {
172
161
  audience: 'test',
173
162
  jwksUri: 'https://test.com/jwks',
174
163
  validateConfig: () => false,
175
- getSigningKey: jest.fn(),
176
- extractUserInfo: jest.fn(),
177
- matchesIssuer: jest.fn()
164
+ getSigningKey: vi.fn(),
165
+ extractUserInfo: vi.fn(),
166
+ matchesIssuer: vi.fn()
178
167
  } as IAuthProvider;
179
168
 
180
169
  expect(() => factory.register(invalidProvider)).toThrow();
@@ -59681,6 +59681,14 @@ export class MJSchemaInfo_ {
59681
59681
  @Field({nullable: true})
59682
59682
  Description?: string;
59683
59683
 
59684
+ @Field({nullable: true, description: `Optional prefix to prepend to entity names generated for this schema. For example, setting this to "Committees: " would result in entity names like "Committees: Individuals". Can be overridden by mj.config.cjs NameRulesBySchema settings.`})
59685
+ @MaxLength(50)
59686
+ EntityNamePrefix?: string;
59687
+
59688
+ @Field({nullable: true, description: `Optional suffix to append to entity names generated for this schema. Can be overridden by mj.config.cjs NameRulesBySchema settings.`})
59689
+ @MaxLength(50)
59690
+ EntityNameSuffix?: string;
59691
+
59684
59692
  }
59685
59693
 
59686
59694
  //****************************************************************************
@@ -59705,6 +59713,12 @@ export class CreateMJSchemaInfoInput {
59705
59713
 
59706
59714
  @Field({ nullable: true })
59707
59715
  Description: string | null;
59716
+
59717
+ @Field({ nullable: true })
59718
+ EntityNamePrefix: string | null;
59719
+
59720
+ @Field({ nullable: true })
59721
+ EntityNameSuffix: string | null;
59708
59722
  }
59709
59723
 
59710
59724
 
@@ -59731,6 +59745,12 @@ export class UpdateMJSchemaInfoInput {
59731
59745
  @Field({ nullable: true })
59732
59746
  Description?: string | null;
59733
59747
 
59748
+ @Field({ nullable: true })
59749
+ EntityNamePrefix?: string | null;
59750
+
59751
+ @Field({ nullable: true })
59752
+ EntityNameSuffix?: string | null;
59753
+
59734
59754
  @Field(() => [KeyValuePairInput], { nullable: true })
59735
59755
  OldValues___?: KeyValuePairInput[];
59736
59756
  }
@@ -1,13 +1,30 @@
1
1
  import { Field, InputType } from "type-graphql";
2
2
 
3
3
  /**
4
- * GraphQL InputType for the DeleteOptions
4
+ * GraphQL InputType for the DeleteOptions.
5
+ * Must be kept in sync with EntityDeleteOptions in @memberjunction/core.
5
6
  */
6
7
  @InputType()
7
8
  export class DeleteOptionsInput {
8
9
  @Field(() => Boolean)
9
10
  SkipEntityAIActions: boolean;
10
-
11
- @Field(() => Boolean)
11
+
12
+ @Field(() => Boolean)
12
13
  SkipEntityActions: boolean;
14
+
15
+ /**
16
+ * When set to true, the delete operation will BYPASS Validate() and the actual
17
+ * process of deleting the record from the database but WILL invoke any associated
18
+ * actions (AI Actions, Entity Actions, etc...).
19
+ */
20
+ @Field(() => Boolean)
21
+ ReplayOnly: boolean;
22
+
23
+ /**
24
+ * When true, this entity is being deleted as part of an IS-A parent chain
25
+ * initiated by a child entity. The child deletes itself first (FK constraint),
26
+ * then cascades deletion to its parent.
27
+ */
28
+ @Field(() => Boolean)
29
+ IsParentEntityDelete: boolean;
13
30
  }
package/src/index.ts CHANGED
@@ -91,6 +91,7 @@ export * from './resolvers/MCPResolver.js';
91
91
  export * from './resolvers/ActionResolver.js';
92
92
  export * from './resolvers/EntityCommunicationsResolver.js';
93
93
  export * from './resolvers/EntityResolver.js';
94
+ export * from './resolvers/ISAEntityResolver.js';
94
95
  export * from './resolvers/FileCategoryResolver.js';
95
96
  export * from './resolvers/FileResolver.js';
96
97
  export * from './resolvers/InfoResolver.js';
@@ -816,7 +816,9 @@ export class MJQueryResolverExtended extends MJQueryResolver {
816
816
  // Provide default options if none provided
817
817
  const deleteOptions = options || {
818
818
  SkipEntityAIActions: false,
819
- SkipEntityActions: false
819
+ SkipEntityActions: false,
820
+ ReplayOnly: false,
821
+ IsParentEntityDelete: false
820
822
  };
821
823
 
822
824
  // Use inherited DeleteRecord method from ResolverBase
@@ -0,0 +1,96 @@
1
+ import { EntityInfo, IEntityDataProvider, Metadata, UserInfo } from '@memberjunction/core';
2
+ import { Arg, Ctx, Field, ObjectType, Query, Resolver } from 'type-graphql';
3
+ import { AppContext } from '../types.js';
4
+ import { GetReadOnlyProvider } from '../util.js';
5
+
6
+ /**
7
+ * Result type for the IS-A child entity discovery query.
8
+ * Returns the name of the child entity type that has a record matching
9
+ * the given parent entity's primary key, or null if no child exists.
10
+ */
11
+ @ObjectType()
12
+ export class ISAChildEntityResult {
13
+ @Field(() => Boolean)
14
+ Success: boolean;
15
+
16
+ @Field(() => String, { nullable: true })
17
+ ChildEntityName?: string;
18
+
19
+ @Field(() => String, { nullable: true })
20
+ ErrorMessage?: string;
21
+ }
22
+
23
+ /**
24
+ * Resolver for IS-A entity hierarchy discovery.
25
+ *
26
+ * Provides a GraphQL endpoint for client-side code to discover child entity
27
+ * records in an IS-A hierarchy. This enables bidirectional chain construction
28
+ * where a loaded entity discovers its more-derived child type.
29
+ */
30
+ @Resolver(ISAChildEntityResult)
31
+ export class ISAEntityResolver {
32
+ /**
33
+ * Discovers which IS-A child entity, if any, has a record with the given
34
+ * primary key value. The server executes a single UNION ALL query across
35
+ * all child entity tables for maximum efficiency.
36
+ *
37
+ * @param EntityName The parent entity name to check children for
38
+ * @param RecordID The primary key value to search for in child tables
39
+ * @returns The child entity name if found, or null with Success=true if no child exists
40
+ */
41
+ @Query(() => ISAChildEntityResult)
42
+ async FindISAChildEntity(
43
+ @Arg('EntityName', () => String) EntityName: string,
44
+ @Arg('RecordID', () => String) RecordID: string,
45
+ @Ctx() { providers, userPayload }: AppContext
46
+ ): Promise<ISAChildEntityResult> {
47
+ try {
48
+ const provider = GetReadOnlyProvider(providers, { allowFallbackToReadWrite: true });
49
+ const md = new Metadata();
50
+ const entityInfo = md.Entities.find(e => e.Name === EntityName);
51
+
52
+ if (!entityInfo) {
53
+ return {
54
+ Success: false,
55
+ ErrorMessage: `Entity '${EntityName}' not found`
56
+ };
57
+ }
58
+
59
+ if (!entityInfo.IsParentType) {
60
+ return { Success: true };
61
+ }
62
+
63
+ // Cast to IEntityDataProvider to access the optional FindISAChildEntity method
64
+ const entityProvider = provider as unknown as IEntityDataProvider;
65
+ if (!entityProvider.FindISAChildEntity) {
66
+ return {
67
+ Success: false,
68
+ ErrorMessage: 'Provider does not support FindISAChildEntity'
69
+ };
70
+ }
71
+
72
+ const result = await entityProvider.FindISAChildEntity(
73
+ entityInfo,
74
+ RecordID,
75
+ userPayload?.userRecord
76
+ );
77
+
78
+ if (result) {
79
+ return {
80
+ Success: true,
81
+ ChildEntityName: result.ChildEntityName
82
+ };
83
+ }
84
+
85
+ return { Success: true };
86
+ }
87
+ catch (e) {
88
+ return {
89
+ Success: false,
90
+ ErrorMessage: e instanceof Error ? e.message : String(e)
91
+ };
92
+ }
93
+ }
94
+ }
95
+
96
+ export default ISAEntityResolver;