@memberjunction/server 5.30.1 → 5.31.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 (104) hide show
  1. package/dist/agents/skip-sdk.d.ts +17 -1
  2. package/dist/agents/skip-sdk.d.ts.map +1 -1
  3. package/dist/agents/skip-sdk.js +18 -5
  4. package/dist/agents/skip-sdk.js.map +1 -1
  5. package/dist/auth/exampleNewUserSubClass.js +1 -1
  6. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  7. package/dist/auth/index.js +2 -2
  8. package/dist/auth/index.js.map +1 -1
  9. package/dist/auth/newUsers.js +2 -2
  10. package/dist/auth/newUsers.js.map +1 -1
  11. package/dist/context.js +3 -3
  12. package/dist/context.js.map +1 -1
  13. package/dist/generated/generated.d.ts +218 -8
  14. package/dist/generated/generated.d.ts.map +1 -1
  15. package/dist/generated/generated.js +1267 -52
  16. package/dist/generated/generated.js.map +1 -1
  17. package/dist/generic/ResolverBase.d.ts +5 -5
  18. package/dist/generic/ResolverBase.d.ts.map +1 -1
  19. package/dist/generic/ResolverBase.js +21 -18
  20. package/dist/generic/ResolverBase.js.map +1 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +9 -8
  24. package/dist/index.js.map +1 -1
  25. package/dist/multiTenancy/index.js +1 -1
  26. package/dist/multiTenancy/index.js.map +1 -1
  27. package/dist/resolvers/APIKeyResolver.d.ts.map +1 -1
  28. package/dist/resolvers/APIKeyResolver.js +5 -3
  29. package/dist/resolvers/APIKeyResolver.js.map +1 -1
  30. package/dist/resolvers/AutotagPipelineResolver.d.ts +3 -3
  31. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  32. package/dist/resolvers/AutotagPipelineResolver.js +18 -12
  33. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  34. package/dist/resolvers/ComponentRegistryResolver.d.ts +1 -1
  35. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  36. package/dist/resolvers/ComponentRegistryResolver.js +6 -4
  37. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  38. package/dist/resolvers/FileResolver.js +2 -2
  39. package/dist/resolvers/FileResolver.js.map +1 -1
  40. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  41. package/dist/resolvers/GetDataContextDataResolver.js +1 -2
  42. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  43. package/dist/resolvers/ISAEntityResolver.d.ts.map +1 -1
  44. package/dist/resolvers/ISAEntityResolver.js +2 -5
  45. package/dist/resolvers/ISAEntityResolver.js.map +1 -1
  46. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  47. package/dist/resolvers/IntegrationDiscoveryResolver.js +75 -66
  48. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  49. package/dist/resolvers/SyncDataResolver.d.ts +4 -4
  50. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  51. package/dist/resolvers/SyncDataResolver.js +9 -8
  52. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  53. package/dist/resolvers/SyncRolesUsersResolver.d.ts +6 -6
  54. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  55. package/dist/resolvers/SyncRolesUsersResolver.js +22 -18
  56. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  57. package/dist/resolvers/TagGovernanceResolver.d.ts +43 -0
  58. package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -0
  59. package/dist/resolvers/TagGovernanceResolver.js +245 -0
  60. package/dist/resolvers/TagGovernanceResolver.js.map +1 -0
  61. package/dist/resolvers/TaskResolver.d.ts +1 -1
  62. package/dist/resolvers/TaskResolver.d.ts.map +1 -1
  63. package/dist/resolvers/TaskResolver.js +4 -2
  64. package/dist/resolvers/TaskResolver.js.map +1 -1
  65. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  66. package/dist/resolvers/TransactionGroupResolver.js +2 -1
  67. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  68. package/dist/rest/EntityCRUDHandler.js +4 -4
  69. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  70. package/dist/rest/RESTEndpointHandler.js +9 -9
  71. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  72. package/dist/rest/ViewOperationsHandler.js +4 -4
  73. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  74. package/dist/services/TaskOrchestrator.d.ts +4 -2
  75. package/dist/services/TaskOrchestrator.d.ts.map +1 -1
  76. package/dist/services/TaskOrchestrator.js +16 -12
  77. package/dist/services/TaskOrchestrator.js.map +1 -1
  78. package/package.json +68 -66
  79. package/src/__tests__/TagGovernanceResolver.test.ts +255 -0
  80. package/src/agents/skip-sdk.ts +30 -7
  81. package/src/auth/exampleNewUserSubClass.ts +1 -1
  82. package/src/auth/index.ts +2 -2
  83. package/src/auth/newUsers.ts +2 -2
  84. package/src/context.ts +3 -3
  85. package/src/generated/generated.ts +872 -41
  86. package/src/generic/ResolverBase.ts +28 -21
  87. package/src/index.ts +9 -9
  88. package/src/multiTenancy/index.ts +1 -1
  89. package/src/resolvers/APIKeyResolver.ts +7 -4
  90. package/src/resolvers/AutotagPipelineResolver.ts +20 -11
  91. package/src/resolvers/ComponentRegistryResolver.ts +8 -5
  92. package/src/resolvers/FileResolver.ts +2 -2
  93. package/src/resolvers/GetDataContextDataResolver.ts +1 -2
  94. package/src/resolvers/ISAEntityResolver.ts +3 -5
  95. package/src/resolvers/IntegrationDiscoveryResolver.ts +83 -66
  96. package/src/resolvers/SyncDataResolver.ts +12 -11
  97. package/src/resolvers/SyncRolesUsersResolver.ts +23 -19
  98. package/src/resolvers/TagGovernanceResolver.ts +189 -0
  99. package/src/resolvers/TaskResolver.ts +5 -3
  100. package/src/resolvers/TransactionGroupResolver.ts +3 -2
  101. package/src/rest/EntityCRUDHandler.ts +4 -4
  102. package/src/rest/RESTEndpointHandler.ts +9 -9
  103. package/src/rest/ViewOperationsHandler.ts +4 -4
  104. package/src/services/TaskOrchestrator.ts +18 -13
@@ -1,5 +1,6 @@
1
1
  import { Resolver, Query, Mutation, Arg, Ctx, ObjectType, Field, InputType } from "type-graphql";
2
- import { CompositeKey, LocalCacheManager, Metadata, RunView, UserInfo, LogError } from "@memberjunction/core";
2
+ import { CompositeKey, LocalCacheManager, Metadata, RunView, UserInfo, LogError, IMetadataProvider } from "@memberjunction/core";
3
+ import { GetReadOnlyProvider, GetReadWriteProvider } from "../util.js";
3
4
  import { CronExpressionHelper } from "@memberjunction/scheduling-engine";
4
5
  import {
5
6
  MJCompanyIntegrationEntity,
@@ -780,7 +781,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
780
781
  ): Promise<DiscoverObjectsOutput> {
781
782
  try {
782
783
  const user = this.getAuthenticatedUser(ctx);
783
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
784
+ const provider = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true }) as unknown as IMetadataProvider;
785
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
784
786
 
785
787
  // Cast through unknown to bridge duplicate package type declarations
786
788
  // (integration-engine resolves its own node_modules copies of core/core-entities)
@@ -818,7 +820,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
818
820
  ): Promise<ListSourceObjectsOutput> {
819
821
  try {
820
822
  const user = this.getAuthenticatedUser(ctx);
821
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
823
+ const provider = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true }) as unknown as IMetadataProvider;
824
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
822
825
 
823
826
  // Use the engine cache for already-persisted IntegrationObject
824
827
  // rows — single in-memory read instead of a per-call DB roundtrip.
@@ -918,7 +921,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
918
921
  ): Promise<DiscoverFieldsOutput> {
919
922
  try {
920
923
  const user = this.getAuthenticatedUser(ctx);
921
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
924
+ const provider = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true }) as unknown as IMetadataProvider;
925
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
922
926
 
923
927
  // Cast through unknown to bridge duplicate package type declarations
924
928
  const discoverFields = connector.DiscoverFields.bind(connector) as
@@ -952,7 +956,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
952
956
  ): Promise<ConnectionTestOutput> {
953
957
  try {
954
958
  const user = this.getAuthenticatedUser(ctx);
955
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
959
+ const provider = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true }) as unknown as IMetadataProvider;
960
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
956
961
 
957
962
  // Cast through unknown to bridge duplicate package type declarations
958
963
  const testConnection = connector.TestConnection.bind(connector) as
@@ -984,7 +989,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
984
989
  ): Promise<DefaultConfigOutput> {
985
990
  try {
986
991
  const user = this.getAuthenticatedUser(ctx);
987
- const { connector } = await this.resolveConnector(companyIntegrationID, user);
992
+ const provider = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true }) as unknown as IMetadataProvider;
993
+ const { connector } = await this.resolveConnector(companyIntegrationID, user, provider);
988
994
 
989
995
  const config = connector.GetDefaultConfiguration();
990
996
  if (!config) {
@@ -1032,7 +1038,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1032
1038
  ): Promise<SchemaPreviewOutput> {
1033
1039
  try {
1034
1040
  const user = this.getAuthenticatedUser(ctx);
1035
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
1041
+ const provider = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true }) as unknown as IMetadataProvider;
1042
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
1036
1043
 
1037
1044
  // Introspect schema from the external system
1038
1045
  const introspect = connector.IntrospectSchema.bind(connector) as
@@ -1059,7 +1066,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1059
1066
  AdditionalSchemaInfoPath: process.env.RSU_ADDITIONAL_SCHEMA_INFO_PATH ?? 'additionalSchemaInfo.json',
1060
1067
  MigrationsDir: process.env.RSU_MIGRATIONS_PATH ?? 'migrations/rsu',
1061
1068
  MetadataDir: process.env.RSU_METADATA_DIR ?? 'metadata',
1062
- ExistingTables: this.buildExistingTables(targetConfigs),
1069
+ ExistingTables: this.buildExistingTables(targetConfigs, provider),
1063
1070
  EntitySettingsForTargets: {}
1064
1071
  };
1065
1072
 
@@ -1112,7 +1119,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1112
1119
  ): Promise<PreviewDataOutput> {
1113
1120
  try {
1114
1121
  const user = this.getAuthenticatedUser(ctx);
1115
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
1122
+ const provider = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true }) as unknown as IMetadataProvider;
1123
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
1116
1124
 
1117
1125
  const fetchChanges = connector.FetchChanges.bind(connector) as
1118
1126
  (ctx: unknown) => Promise<{ Records: Array<{ ExternalID: string; ObjectType: string; Fields: Record<string, unknown> }>; HasMore: boolean }>;
@@ -1268,11 +1276,10 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1268
1276
 
1269
1277
  /** Builds a lookup of object name → { objectDescription, fields: fieldName → description } from the connector's static metadata. */
1270
1278
  /** Build ExistingTableInfo[] from MJ Metadata for tables that already exist in the target schemas. */
1271
- private buildExistingTables(targetConfigs: TargetTableConfig[]): ExistingTableInfo[] {
1272
- const md = new Metadata();
1279
+ private buildExistingTables(targetConfigs: TargetTableConfig[], provider: IMetadataProvider): ExistingTableInfo[] {
1273
1280
  const result: ExistingTableInfo[] = [];
1274
1281
  for (const config of targetConfigs) {
1275
- const entity = md.Entities.find(e =>
1282
+ const entity = provider.Entities.find(e =>
1276
1283
  e.SchemaName.toLowerCase() === config.SchemaName.toLowerCase() &&
1277
1284
  e.BaseTable.toLowerCase() === config.TableName.toLowerCase()
1278
1285
  );
@@ -1597,9 +1604,10 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1597
1604
  */
1598
1605
  private async resolveConnector(
1599
1606
  companyIntegrationID: string,
1600
- contextUser: UserInfo
1607
+ contextUser: UserInfo,
1608
+ provider: IMetadataProvider
1601
1609
  ): Promise<{ connector: BaseIntegrationConnector; companyIntegration: MJCompanyIntegrationEntity }> {
1602
- const md = new Metadata();
1610
+ const md = provider;
1603
1611
 
1604
1612
  // Load the CompanyIntegration record
1605
1613
  const companyIntegration = await md.GetEntityObject<MJCompanyIntegrationEntity>(
@@ -1657,9 +1665,10 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1657
1665
  */
1658
1666
  private async testConnectionForCI(
1659
1667
  companyIntegrationID: string,
1660
- user: UserInfo
1668
+ user: UserInfo,
1669
+ provider: IMetadataProvider
1661
1670
  ): Promise<ConnectionTestResult> {
1662
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
1671
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
1663
1672
  const testFn = connector.TestConnection.bind(connector) as
1664
1673
  (ci: unknown, u: unknown) => Promise<ConnectionTestResult>;
1665
1674
  return testFn(companyIntegration, user);
@@ -1681,11 +1690,11 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1681
1690
  */
1682
1691
  private async snapshotCredentialValues(
1683
1692
  credentialID: string | undefined,
1684
- user: UserInfo
1693
+ user: UserInfo,
1694
+ provider: IMetadataProvider
1685
1695
  ): Promise<string | undefined> {
1686
1696
  if (!credentialID) return undefined;
1687
- const md = new Metadata();
1688
- const credential = await md.GetEntityObject<MJCredentialEntity>('MJ: Credentials', user);
1697
+ const credential = await provider.GetEntityObject<MJCredentialEntity>('MJ: Credentials', user);
1689
1698
  const loaded = await credential.InnerLoad(CompositeKey.FromID(credentialID));
1690
1699
  return loaded ? credential.Values : undefined;
1691
1700
  }
@@ -1699,7 +1708,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1699
1708
  oldConfiguration: string | undefined,
1700
1709
  oldExternalSystemID: string | undefined,
1701
1710
  oldCredentialValues: string | undefined,
1702
- user: UserInfo
1711
+ user: UserInfo,
1712
+ provider: IMetadataProvider
1703
1713
  ): Promise<void> {
1704
1714
  try {
1705
1715
  // Revert CI fields
@@ -1710,8 +1720,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1710
1720
 
1711
1721
  // Revert credential values
1712
1722
  if (oldCredentialValues !== undefined && ci.CredentialID) {
1713
- const md = new Metadata();
1714
- const credential = await md.GetEntityObject<MJCredentialEntity>('MJ: Credentials', user);
1723
+ const credential = await provider.GetEntityObject<MJCredentialEntity>('MJ: Credentials', user);
1715
1724
  const loaded = await credential.InnerLoad(CompositeKey.FromID(ci.CredentialID));
1716
1725
  if (loaded) {
1717
1726
  credential.Values = oldCredentialValues;
@@ -1788,7 +1797,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1788
1797
  ): Promise<CreateConnectionOutput> {
1789
1798
  try {
1790
1799
  const user = this.getAuthenticatedUser(ctx);
1791
- const md = new Metadata();
1800
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
1792
1801
 
1793
1802
  // 1. Create Credential record with encrypted values
1794
1803
  const credential = await md.GetEntityObject<MJCredentialEntity>('MJ: Credentials', user);
@@ -1824,7 +1833,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1824
1833
 
1825
1834
  // 3. Optionally test the connection; rollback on failure
1826
1835
  if (testConnection) {
1827
- const testResult = await this.testConnectionForCI(ci.ID, user);
1836
+ const testResult = await this.testConnectionForCI(ci.ID, user, md);
1828
1837
  if (!testResult.Success) {
1829
1838
  await this.rollbackCreatedConnection(ci, credential);
1830
1839
  return {
@@ -1870,13 +1879,13 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1870
1879
  ): Promise<MutationResultOutput> {
1871
1880
  try {
1872
1881
  const user = this.getAuthenticatedUser(ctx);
1873
- const md = new Metadata();
1882
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
1874
1883
  const ci = await md.GetEntityObject<MJCompanyIntegrationEntity>('MJ: Company Integrations', user);
1875
1884
  const loaded = await ci.InnerLoad(CompositeKey.FromID(companyIntegrationID));
1876
1885
  if (!loaded) return { Success: false, Message: 'CompanyIntegration not found' };
1877
1886
 
1878
1887
  // Snapshot old values for rollback if testConnection is requested
1879
- const oldCredentialValues = credentialValues ? await this.snapshotCredentialValues(ci.CredentialID, user) : undefined;
1888
+ const oldCredentialValues = credentialValues ? await this.snapshotCredentialValues(ci.CredentialID, user, md) : undefined;
1880
1889
  const oldConfiguration = ci.Configuration;
1881
1890
  const oldExternalSystemID = ci.ExternalSystemID;
1882
1891
 
@@ -1901,9 +1910,9 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1901
1910
 
1902
1911
  // Optionally test the connection; revert on failure
1903
1912
  if (testConnection) {
1904
- const testResult = await this.testConnectionForCI(companyIntegrationID, user);
1913
+ const testResult = await this.testConnectionForCI(companyIntegrationID, user, md);
1905
1914
  if (!testResult.Success) {
1906
- await this.revertUpdateConnection(ci, oldConfiguration, oldExternalSystemID, oldCredentialValues, user);
1915
+ await this.revertUpdateConnection(ci, oldConfiguration, oldExternalSystemID, oldCredentialValues, user, md);
1907
1916
  return { Success: false, Message: `Connection test failed: ${testResult.Message}. Changes have been reverted.` };
1908
1917
  }
1909
1918
  return { Success: true, Message: 'Updated and connection test passed' };
@@ -1926,7 +1935,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1926
1935
  ): Promise<MutationResultOutput> {
1927
1936
  try {
1928
1937
  const user = this.getAuthenticatedUser(ctx);
1929
- const md = new Metadata();
1938
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
1930
1939
  const ci = await md.GetEntityObject<MJCompanyIntegrationEntity>('MJ: Company Integrations', user);
1931
1940
  const loaded = await ci.InnerLoad(CompositeKey.FromID(companyIntegrationID));
1932
1941
  if (!loaded) return { Success: false, Message: 'CompanyIntegration not found' };
@@ -1949,7 +1958,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1949
1958
  ): Promise<MutationResultOutput> {
1950
1959
  try {
1951
1960
  const user = this.getAuthenticatedUser(ctx);
1952
- const md = new Metadata();
1961
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
1953
1962
  const ci = await md.GetEntityObject<MJCompanyIntegrationEntity>('MJ: Company Integrations', user);
1954
1963
  const loaded = await ci.InnerLoad(CompositeKey.FromID(companyIntegrationID));
1955
1964
  if (!loaded) return { Success: false, Message: 'CompanyIntegration not found' };
@@ -1976,7 +1985,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
1976
1985
  ): Promise<CreateEntityMapsOutput> {
1977
1986
  try {
1978
1987
  const user = this.getAuthenticatedUser(ctx);
1979
- const md = new Metadata();
1988
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
1980
1989
 
1981
1990
  // Batch resolve entity names → IDs using cached Metadata
1982
1991
  const namesToResolve = entityMaps.filter(m => m.EntityName && !m.EntityID).map(m => m.EntityName as string);
@@ -2074,7 +2083,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2074
2083
  ): Promise<ApplySchemaOutput> {
2075
2084
  try {
2076
2085
  const user = this.getAuthenticatedUser(ctx);
2077
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
2086
+ const provider = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
2087
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
2078
2088
 
2079
2089
  const introspect = connector.IntrospectSchema.bind(connector) as
2080
2090
  (ci: unknown, u: unknown) => Promise<SourceSchemaInfo>;
@@ -2099,7 +2109,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2099
2109
  AdditionalSchemaInfoPath: process.env.RSU_ADDITIONAL_SCHEMA_INFO_PATH ?? 'additionalSchemaInfo.json',
2100
2110
  MigrationsDir: process.env.RSU_MIGRATIONS_PATH ?? 'migrations/rsu',
2101
2111
  MetadataDir: process.env.RSU_METADATA_DIR ?? 'metadata',
2102
- ExistingTables: this.buildExistingTables(targetConfigs),
2112
+ ExistingTables: this.buildExistingTables(targetConfigs, provider),
2103
2113
  EntitySettingsForTargets: {}
2104
2114
  };
2105
2115
 
@@ -2147,6 +2157,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2147
2157
  ): Promise<ApplySchemaBatchOutput> {
2148
2158
  try {
2149
2159
  const user = this.getAuthenticatedUser(ctx);
2160
+ const provider = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
2150
2161
  const validatedPlatform = this.validatePlatform(platform);
2151
2162
  const pipelineInputs: RSUPipelineInput[] = [];
2152
2163
  const itemResults: ApplySchemaBatchItemOutput[] = [];
@@ -2155,7 +2166,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2155
2166
  for (const item of items) {
2156
2167
  try {
2157
2168
  const { schemaOutput, rsuInput } = await this.buildSchemaForConnector(
2158
- item.CompanyIntegrationID, item.Objects, validatedPlatform, user, skipGitCommit, skipRestart
2169
+ item.CompanyIntegrationID, item.Objects, validatedPlatform, user, skipGitCommit, skipRestart, provider
2159
2170
  );
2160
2171
  pipelineInputs.push(rsuInput);
2161
2172
  itemResults.push({
@@ -2218,10 +2229,11 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2218
2229
  ): Promise<ApplyAllOutput> {
2219
2230
  try {
2220
2231
  const user = this.getAuthenticatedUser(ctx);
2232
+ const provider = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
2221
2233
  const validatedPlatform = this.validatePlatform(platform);
2222
2234
 
2223
2235
  // Step 1: Resolve connector and derive schema name
2224
- const { connector, companyIntegration } = await this.resolveConnector(input.CompanyIntegrationID, user);
2236
+ const { connector, companyIntegration } = await this.resolveConnector(input.CompanyIntegrationID, user, provider);
2225
2237
  const schemaName = this.deriveSchemaName(companyIntegration.Integration);
2226
2238
 
2227
2239
  // Step 1b: Ensure IntegrationEngine cache is populated so IntrospectSchema's
@@ -2310,7 +2322,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2310
2322
 
2311
2323
  // Step 3: Build schema and RSU pipeline input
2312
2324
  const { schemaOutput, rsuInput } = await this.buildSchemaForConnector(
2313
- input.CompanyIntegrationID, objects, validatedPlatform, user, skipGitCommit, skipRestart
2325
+ input.CompanyIntegrationID, objects, validatedPlatform, user, skipGitCommit, skipRestart, provider
2314
2326
  );
2315
2327
 
2316
2328
  // Step 4: Inject integration post-restart payload into RSU input.
@@ -2368,9 +2380,9 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2368
2380
  // If restart happened, this code never executes (process died).
2369
2381
  // If skipRestart=true, we can do entity maps now.
2370
2382
  if (skipRestart) {
2371
- await Metadata.Provider.Refresh();
2383
+ await provider.Refresh();
2372
2384
  const entityMapsCreated = await this.createEntityAndFieldMaps(
2373
- input.CompanyIntegrationID, objects, connector, companyIntegration, schemaName, user,
2385
+ input.CompanyIntegrationID, objects, connector, companyIntegration, schemaName, user, provider,
2374
2386
  input.DefaultSyncDirection ?? 'Pull'
2375
2387
  );
2376
2388
  const createdMapIDs = entityMapsCreated.map(em => em.EntityMapID).filter(Boolean);
@@ -2394,7 +2406,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2394
2406
  companyIntegration.Integration,
2395
2407
  input.CronExpression,
2396
2408
  input.ScheduleTimezone,
2397
- user
2409
+ user,
2410
+ provider
2398
2411
  ) ?? undefined;
2399
2412
  } catch (schedErr) {
2400
2413
  console.warn(`[Integration] Schedule creation failed: ${schedErr}`);
@@ -2460,14 +2473,14 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2460
2473
  companyIntegration: MJCompanyIntegrationEntity,
2461
2474
  schemaName: string,
2462
2475
  user: UserInfo,
2476
+ provider: IMetadataProvider,
2463
2477
  defaultSyncDirection: string = 'Pull'
2464
2478
  ): Promise<ApplyAllEntityMapCreated[]> {
2465
- const md = new Metadata();
2466
2479
  const results: ApplyAllEntityMapCreated[] = [];
2467
2480
 
2468
2481
  for (const obj of objects) {
2469
2482
  const entityMapResult = await this.createSingleEntityMap(
2470
- companyIntegrationID, obj, connector, companyIntegration, schemaName, user, md, defaultSyncDirection
2483
+ companyIntegrationID, obj, connector, companyIntegration, schemaName, user, provider, defaultSyncDirection
2471
2484
  );
2472
2485
  if (entityMapResult) {
2473
2486
  results.push(entityMapResult);
@@ -2484,7 +2497,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2484
2497
  companyIntegration: MJCompanyIntegrationEntity,
2485
2498
  schemaName: string,
2486
2499
  user: UserInfo,
2487
- md: Metadata,
2500
+ md: IMetadataProvider,
2488
2501
  defaultSyncDirection: string = 'Pull'
2489
2502
  ): Promise<ApplyAllEntityMapCreated | null> {
2490
2503
  // Find the entity by schema + table name
@@ -2533,7 +2546,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2533
2546
  connector: BaseIntegrationConnector,
2534
2547
  companyIntegration: MJCompanyIntegrationEntity,
2535
2548
  user: UserInfo,
2536
- md: Metadata
2549
+ md: IMetadataProvider
2537
2550
  ): Promise<number> {
2538
2551
  let fieldCount = 0;
2539
2552
  try {
@@ -2609,9 +2622,10 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2609
2622
  user: UserInfo,
2610
2623
  skipGitCommit: boolean,
2611
2624
  skipRestart: boolean,
2625
+ provider: IMetadataProvider,
2612
2626
  prefetchedSourceSchema?: SourceSchemaInfo
2613
2627
  ): Promise<{ schemaOutput: SchemaBuilderOutput; rsuInput: RSUPipelineInput }> {
2614
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
2628
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
2615
2629
 
2616
2630
  // If the caller already ran IntrospectSchema (e.g. IntegrationApplyAllBatch),
2617
2631
  // reuse it. The legacy path was running introspect TWICE per apply — once
@@ -2652,7 +2666,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2652
2666
  AdditionalSchemaInfoPath: process.env.RSU_ADDITIONAL_SCHEMA_INFO_PATH ?? 'additionalSchemaInfo.json',
2653
2667
  MigrationsDir: process.env.RSU_MIGRATIONS_PATH ?? 'migrations/rsu',
2654
2668
  MetadataDir: process.env.RSU_METADATA_DIR ?? 'metadata',
2655
- ExistingTables: this.buildExistingTables(targetConfigs),
2669
+ ExistingTables: this.buildExistingTables(targetConfigs, provider),
2656
2670
  EntitySettingsForTargets: {}
2657
2671
  };
2658
2672
 
@@ -2866,7 +2880,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2866
2880
  ): Promise<CreateScheduleOutput> {
2867
2881
  try {
2868
2882
  const user = this.getAuthenticatedUser(ctx);
2869
- const md = new Metadata();
2883
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
2870
2884
  const rv = new RunView();
2871
2885
 
2872
2886
  // Find IntegrationSync job type
@@ -2926,7 +2940,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2926
2940
  ): Promise<MutationResultOutput> {
2927
2941
  try {
2928
2942
  const user = this.getAuthenticatedUser(ctx);
2929
- const md = new Metadata();
2943
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
2930
2944
  const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs', user);
2931
2945
  const loaded = await job.InnerLoad(CompositeKey.FromID(scheduledJobID));
2932
2946
  if (!loaded) return { Success: false, Message: 'ScheduledJob not found' };
@@ -2955,7 +2969,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2955
2969
  try {
2956
2970
  this.getAuthenticatedUser(ctx); // verify caller is authenticated
2957
2971
  const sysUser = this.getSystemUser();
2958
- const md = new Metadata();
2972
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
2959
2973
  const job = await md.GetEntityObject<MJScheduledJobEntity>('MJ: Scheduled Jobs', sysUser);
2960
2974
  const loaded = await job.InnerLoad(CompositeKey.FromID(scheduledJobID));
2961
2975
  if (!loaded) return { Success: false, Message: 'ScheduledJob not found' };
@@ -2980,7 +2994,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
2980
2994
  try {
2981
2995
  this.getAuthenticatedUser(ctx); // verify caller is authenticated
2982
2996
  const sysUser = this.getSystemUser(); // use system user for delete operations
2983
- const md = new Metadata();
2997
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
2984
2998
 
2985
2999
  // Unlink from CI if provided
2986
3000
  if (companyIntegrationID) {
@@ -3034,7 +3048,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3034
3048
  }
3035
3049
 
3036
3050
  // Now delete job runs + job in a transaction
3037
- const tg = await Metadata.Provider.CreateTransactionGroup();
3051
+ const tg = await md.CreateTransactionGroup();
3038
3052
  if (jobRunsResult.Success) {
3039
3053
  for (const run of jobRunsResult.Results) {
3040
3054
  run.TransactionGroup = tg;
@@ -3163,7 +3177,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3163
3177
  ): Promise<MutationResultOutput> {
3164
3178
  try {
3165
3179
  const user = this.getAuthenticatedUser(ctx);
3166
- const md = new Metadata();
3180
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
3167
3181
  const errors: string[] = [];
3168
3182
 
3169
3183
  for (const update of updates) {
@@ -3206,9 +3220,9 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3206
3220
  try {
3207
3221
  this.getAuthenticatedUser(ctx);
3208
3222
  const sysUser = this.getSystemUser();
3209
- const md = new Metadata();
3223
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
3210
3224
  const rv = new RunView();
3211
- const tg = await Metadata.Provider.CreateTransactionGroup();
3225
+ const tg = await md.CreateTransactionGroup();
3212
3226
  const errors: string[] = [];
3213
3227
 
3214
3228
  for (const entityMapID of entityMapIDs) {
@@ -3328,7 +3342,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3328
3342
  ): Promise<IntegrationStatusOutput> {
3329
3343
  try {
3330
3344
  const user = this.getAuthenticatedUser(ctx);
3331
- const md = new Metadata();
3345
+ const md = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true }) as unknown as IMetadataProvider;
3332
3346
  const ci = await md.GetEntityObject<MJCompanyIntegrationEntity>('MJ: Company Integrations', user);
3333
3347
  const loaded = await ci.InnerLoad(CompositeKey.FromID(companyIntegrationID));
3334
3348
  if (!loaded) return { Success: false, Message: 'Not found' };
@@ -3427,7 +3441,8 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3427
3441
  ): Promise<ConnectorCapabilitiesOutput> {
3428
3442
  try {
3429
3443
  const user = this.getAuthenticatedUser(ctx);
3430
- const { connector } = await this.resolveConnector(companyIntegrationID, user);
3444
+ const provider = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true }) as unknown as IMetadataProvider;
3445
+ const { connector } = await this.resolveConnector(companyIntegrationID, user, provider);
3431
3446
 
3432
3447
  return {
3433
3448
  Success: true,
@@ -3462,6 +3477,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3462
3477
  ): Promise<ApplyAllBatchOutput> {
3463
3478
  try {
3464
3479
  const user = this.getAuthenticatedUser(ctx);
3480
+ const provider = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
3465
3481
  const validatedPlatform = this.validatePlatform(platform);
3466
3482
 
3467
3483
  // Bust RunView caches for integration metadata BEFORE Config(true).
@@ -3478,7 +3494,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3478
3494
  // Phase 1: Build schema for each connector in parallel
3479
3495
  const buildResults = await Promise.allSettled(
3480
3496
  input.Connectors.map(async (connInput) => {
3481
- const { connector, companyIntegration } = await this.resolveConnector(connInput.CompanyIntegrationID, user);
3497
+ const { connector, companyIntegration } = await this.resolveConnector(connInput.CompanyIntegrationID, user, provider);
3482
3498
  const schemaName = this.deriveSchemaName(companyIntegration.Integration);
3483
3499
  console.log(
3484
3500
  `[IntegrationApplyAllBatch] connector=${companyIntegration.Integration} ` +
@@ -3601,7 +3617,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3601
3617
  });
3602
3618
 
3603
3619
  const { schemaOutput, rsuInput } = await this.buildSchemaForConnector(
3604
- connInput.CompanyIntegrationID, objects, validatedPlatform, user, skipGitCommit, skipRestart, sourceSchema
3620
+ connInput.CompanyIntegrationID, objects, validatedPlatform, user, skipGitCommit, skipRestart, provider, sourceSchema
3605
3621
  );
3606
3622
 
3607
3623
  // Build per-object field map for pending file
@@ -3719,10 +3735,10 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3719
3735
 
3720
3736
  if (skipRestart) {
3721
3737
  // Entity maps, field maps, sync
3722
- await Metadata.Provider.Refresh();
3738
+ await provider.Refresh();
3723
3739
  const entityMapsCreated = await this.createEntityAndFieldMaps(
3724
3740
  build.connInput.CompanyIntegrationID, build.objects, build.connector,
3725
- build.companyIntegration, build.schemaName, user,
3741
+ build.companyIntegration, build.schemaName, user, provider,
3726
3742
  build.connInput.DefaultSyncDirection ?? 'Pull'
3727
3743
  );
3728
3744
  connResult.EntityMapsCreated = entityMapsCreated;
@@ -3747,7 +3763,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3747
3763
  if (build.connInput.CronExpression) {
3748
3764
  const scheduleResult = await this.createScheduleForConnector(
3749
3765
  build.connInput.CompanyIntegrationID, integrationName,
3750
- build.connInput.CronExpression, build.connInput.ScheduleTimezone, user
3766
+ build.connInput.CronExpression, build.connInput.ScheduleTimezone, user, provider
3751
3767
  );
3752
3768
  if (scheduleResult) connResult.ScheduledJobID = scheduleResult;
3753
3769
  }
@@ -3793,10 +3809,11 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3793
3809
  integrationName: string,
3794
3810
  cronExpression: string,
3795
3811
  timezone: string | undefined,
3796
- user: UserInfo
3812
+ user: UserInfo,
3813
+ provider: IMetadataProvider
3797
3814
  ): Promise<string | null> {
3798
3815
  try {
3799
- const md = new Metadata();
3816
+ const md = provider;
3800
3817
  const rv = new RunView();
3801
3818
 
3802
3819
  // Find IntegrationSync job type
@@ -3857,7 +3874,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3857
3874
  try {
3858
3875
  this.getAuthenticatedUser(ctx); // verify caller is authenticated
3859
3876
  const sysUser = this.getSystemUser(); // use system user for cascade delete
3860
- const md = new Metadata();
3877
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
3861
3878
  const rv = new RunView();
3862
3879
 
3863
3880
  // Step 1: Load CompanyIntegration
@@ -3866,7 +3883,7 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
3866
3883
  if (!ciLoaded) return { Success: false, Message: 'CompanyIntegration not found' };
3867
3884
 
3868
3885
  // Cascade delete in FK-safe order using TransactionGroup
3869
- const tg = await Metadata.Provider.CreateTransactionGroup();
3886
+ const tg = await md.CreateTransactionGroup();
3870
3887
  let fieldMapsDeleted = 0;
3871
3888
  let entityMapsDeleted = 0;
3872
3889
  let schedulesDeleted = 0;
@@ -4046,9 +4063,9 @@ export class IntegrationDiscoveryResolver extends ResolverBase {
4046
4063
  try {
4047
4064
  const user = this.getAuthenticatedUser(ctx);
4048
4065
  const validatedPlatform = this.validatePlatform(platform);
4049
- const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user);
4066
+ const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
4067
+ const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, md);
4050
4068
  const schemaName = this.deriveSchemaName(companyIntegration.Integration);
4051
- const md = new Metadata();
4052
4069
  const rv = new RunView();
4053
4070
 
4054
4071
  // Step 1: Get existing entity maps for this CompanyIntegration
@@ -1,7 +1,8 @@
1
1
  import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType } from 'type-graphql';
2
2
  import { AppContext, UserPayload } from '../types.js';
3
- import { BaseEntity, CompositeKey, DatabaseProviderBase, EntityDeleteOptions, EntitySaveOptions, LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
3
+ import { BaseEntity, CompositeKey, DatabaseProviderBase, EntityDeleteOptions, EntitySaveOptions, IMetadataProvider, LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
4
4
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
5
+ import { GetReadWriteProvider } from '../util.js';
5
6
  import { CompositeKeyInputType, CompositeKeyOutputType } from '../generic/KeyInputOutputTypes.js';
6
7
  import { MJDatasetItemEntity } from '@memberjunction/core-entities';
7
8
 
@@ -110,9 +111,9 @@ export class SyncDataResolver {
110
111
  @Arg('items', () => [ActionItemInputType] ) items: ActionItemInputType[],
111
112
  @Ctx() context: AppContext
112
113
  ) {
113
- try {
114
- // iterate through the items
115
- const md = new Metadata();
114
+ try {
115
+ // iterate through the items
116
+ const md = (GetReadWriteProvider(context.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider) ?? (new Metadata() as unknown as IMetadataProvider);
116
117
  const results: ActionItemOutputType[] = [];
117
118
  for (const item of items) {
118
119
  results.push(await this.SyncSingleItem(item, context, md, context.userPayload));
@@ -160,7 +161,7 @@ export class SyncDataResolver {
160
161
  return false; // didn't find any
161
162
  }
162
163
 
163
- protected async SyncSingleItem(item: ActionItemInputType, context: AppContext, md: Metadata, userPayload: UserPayload): Promise<ActionItemOutputType> {
164
+ protected async SyncSingleItem(item: ActionItemInputType, context: AppContext, md: IMetadataProvider, userPayload: UserPayload): Promise<ActionItemOutputType> {
164
165
  const result = new ActionItemOutputType();
165
166
  result.AlternateKey = item.AlternateKey;
166
167
  result.PrimaryKey = item.PrimaryKey;
@@ -192,7 +193,7 @@ export class SyncDataResolver {
192
193
  await this.SyncSingleItemDelete(entityObject, pk, ak, result, userPayload);
193
194
  break;
194
195
  case SyncDataActionType.DeleteWithFilter:
195
- await this.SyncSingleItemDeleteWithFilter(item.EntityName, item.DeleteFilter, result, context.userPayload.userRecord, userPayload);
196
+ await this.SyncSingleItemDeleteWithFilter(item.EntityName, item.DeleteFilter, result, context.userPayload.userRecord, userPayload, md);
196
197
  break;
197
198
  default:
198
199
  throw new Error('Invalid SyncDataActionType');
@@ -211,7 +212,7 @@ export class SyncDataResolver {
211
212
  }
212
213
 
213
214
 
214
- protected async SyncSingleItemDeleteWithFilter(entityName: string, filter: string, result: ActionItemOutputType, user: UserInfo, userPayload: UserPayload) {
215
+ protected async SyncSingleItemDeleteWithFilter(entityName: string, filter: string, result: ActionItemOutputType, user: UserInfo, userPayload: UserPayload, providerOverride?: IMetadataProvider) {
215
216
  try {
216
217
  // Run the view to find matching records, then delete them all atomically —
217
218
  // any single failure rolls back the entire batch so the dataset stays consistent.
@@ -233,7 +234,7 @@ export class SyncDataResolver {
233
234
  return;
234
235
  }
235
236
 
236
- const provider = Metadata.Provider as DatabaseProviderBase;
237
+ const provider = (providerOverride ?? Metadata.Provider) as unknown as DatabaseProviderBase;
237
238
  await provider.BeginTransaction();
238
239
  try {
239
240
  for (const entityObject of data.Results) {
@@ -254,11 +255,11 @@ export class SyncDataResolver {
254
255
  }
255
256
  }
256
257
 
257
- protected async LoadFromAlternateKey(entityName: string, alternateKey: CompositeKey, user: UserInfo): Promise<BaseEntity> {
258
+ protected async LoadFromAlternateKey(entityName: string, alternateKey: CompositeKey, user: UserInfo, provider?: IMetadataProvider): Promise<BaseEntity> {
258
259
  try {
259
- // no primary key provided, attempt to look up the primary key based on the
260
+ // no primary key provided, attempt to look up the primary key based on the
260
261
  const rv = new RunView();
261
- const md = new Metadata();
262
+ const md = provider ?? new Metadata();
262
263
  const entity = md.EntityByName(entityName);
263
264
  const r = await rv.RunView<BaseEntity>({
264
265
  EntityName: entityName,