@memberjunction/server 5.30.1 → 5.32.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/dist/agents/skip-sdk.d.ts +17 -1
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +18 -5
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +1 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.js +2 -2
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/newUsers.js +2 -2
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/context.js +3 -3
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +217 -4
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +1251 -24
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +5 -5
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +21 -18
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/multiTenancy/index.js +1 -1
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/resolvers/APIKeyResolver.d.ts.map +1 -1
- package/dist/resolvers/APIKeyResolver.js +5 -3
- package/dist/resolvers/APIKeyResolver.js.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts +3 -3
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +18 -12
- package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +6 -4
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.js +2 -2
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +1 -2
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/ISAEntityResolver.d.ts.map +1 -1
- package/dist/resolvers/ISAEntityResolver.js +2 -5
- package/dist/resolvers/ISAEntityResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +75 -66
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts +4 -4
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +9 -8
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +6 -6
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +22 -18
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.d.ts +43 -0
- package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -0
- package/dist/resolvers/TagGovernanceResolver.js +245 -0
- package/dist/resolvers/TagGovernanceResolver.js.map +1 -0
- package/dist/resolvers/TaskResolver.d.ts +1 -1
- package/dist/resolvers/TaskResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskResolver.js +4 -2
- package/dist/resolvers/TaskResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +2 -1
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +4 -4
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +9 -9
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +4 -4
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskOrchestrator.d.ts +4 -2
- package/dist/services/TaskOrchestrator.d.ts.map +1 -1
- package/dist/services/TaskOrchestrator.js +16 -12
- package/dist/services/TaskOrchestrator.js.map +1 -1
- package/package.json +68 -66
- package/src/__tests__/TagGovernanceResolver.test.ts +255 -0
- package/src/agents/skip-sdk.ts +30 -7
- package/src/auth/exampleNewUserSubClass.ts +1 -1
- package/src/auth/index.ts +2 -2
- package/src/auth/newUsers.ts +2 -2
- package/src/context.ts +3 -3
- package/src/generated/generated.ts +861 -21
- package/src/generic/ResolverBase.ts +28 -21
- package/src/index.ts +9 -9
- package/src/multiTenancy/index.ts +1 -1
- package/src/resolvers/APIKeyResolver.ts +7 -4
- package/src/resolvers/AutotagPipelineResolver.ts +20 -11
- package/src/resolvers/ComponentRegistryResolver.ts +8 -5
- package/src/resolvers/FileResolver.ts +2 -2
- package/src/resolvers/GetDataContextDataResolver.ts +1 -2
- package/src/resolvers/ISAEntityResolver.ts +3 -5
- package/src/resolvers/IntegrationDiscoveryResolver.ts +83 -66
- package/src/resolvers/SyncDataResolver.ts +12 -11
- package/src/resolvers/SyncRolesUsersResolver.ts +23 -19
- package/src/resolvers/TagGovernanceResolver.ts +189 -0
- package/src/resolvers/TaskResolver.ts +5 -3
- package/src/resolvers/TransactionGroupResolver.ts +3 -2
- package/src/rest/EntityCRUDHandler.ts +4 -4
- package/src/rest/RESTEndpointHandler.ts +9 -9
- package/src/rest/ViewOperationsHandler.ts +4 -4
- package/src/services/TaskOrchestrator.ts +18 -13
|
@@ -5,8 +5,10 @@ import {
|
|
|
5
5
|
CompositeKey,
|
|
6
6
|
DatabaseProviderBase,
|
|
7
7
|
EntityFieldTSType,
|
|
8
|
+
EntityInfo,
|
|
8
9
|
EntityPermissionType,
|
|
9
10
|
EntitySaveOptions,
|
|
11
|
+
IMetadataProvider,
|
|
10
12
|
IRunViewProvider,
|
|
11
13
|
LogDebug,
|
|
12
14
|
LogError,
|
|
@@ -65,7 +67,7 @@ export class ResolverBase {
|
|
|
65
67
|
* @param contextUser - Optional user context for decryption (required for encrypted fields)
|
|
66
68
|
* @returns The processed data object
|
|
67
69
|
*/
|
|
68
|
-
protected async MapFieldNamesToCodeNames(entityName: string, dataObject: any, contextUser?: UserInfo): Promise<any> {
|
|
70
|
+
protected async MapFieldNamesToCodeNames(entityName: string, dataObject: any, contextUser?: UserInfo, provider?: IMetadataProvider): Promise<any> {
|
|
69
71
|
// Return null for empty objects (e.g. when no rows found due to RLS filtering)
|
|
70
72
|
if (!dataObject || Object.keys(dataObject).length === 0) {
|
|
71
73
|
return null;
|
|
@@ -77,8 +79,8 @@ export class ResolverBase {
|
|
|
77
79
|
// with the CodeName, because we can't transfer those via GraphQL as they are not
|
|
78
80
|
// valid property names in GraphQL
|
|
79
81
|
{
|
|
80
|
-
const md = new Metadata();
|
|
81
|
-
const entityInfo = md.
|
|
82
|
+
const md = provider ?? new Metadata();
|
|
83
|
+
const entityInfo = md.EntityByName(entityName);
|
|
82
84
|
if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
|
|
83
85
|
// const fields = entityInfo.Fields.filter((f) => f.Name !== f.CodeName || f.Name.startsWith('__mj_'));
|
|
84
86
|
const mapper = new FieldMapper();
|
|
@@ -209,12 +211,13 @@ export class ResolverBase {
|
|
|
209
211
|
protected async FilterEncryptedFieldsForAPI(
|
|
210
212
|
entityName: string,
|
|
211
213
|
dataObject: Record<string, unknown>,
|
|
212
|
-
contextUser: UserInfo
|
|
214
|
+
contextUser: UserInfo,
|
|
215
|
+
provider?: IMetadataProvider
|
|
213
216
|
): Promise<Record<string, unknown>> {
|
|
214
217
|
if (!dataObject) return dataObject;
|
|
215
218
|
|
|
216
|
-
const md = new Metadata();
|
|
217
|
-
const entityInfo = md.
|
|
219
|
+
const md = provider ?? new Metadata();
|
|
220
|
+
const entityInfo = md.EntityByName(entityName);
|
|
218
221
|
if (!entityInfo) return dataObject;
|
|
219
222
|
|
|
220
223
|
// Find all encrypted fields that need filtering
|
|
@@ -269,13 +272,14 @@ export class ResolverBase {
|
|
|
269
272
|
protected async ArrayFilterEncryptedFieldsForAPI(
|
|
270
273
|
entityName: string,
|
|
271
274
|
dataObjectArray: Record<string, unknown>[],
|
|
272
|
-
contextUser: UserInfo
|
|
275
|
+
contextUser: UserInfo,
|
|
276
|
+
provider?: IMetadataProvider
|
|
273
277
|
): Promise<Record<string, unknown>[]> {
|
|
274
278
|
if (!dataObjectArray || dataObjectArray.length === 0) return dataObjectArray;
|
|
275
279
|
|
|
276
280
|
// Check if entity has any encrypted fields first to avoid unnecessary processing
|
|
277
|
-
const md = new Metadata();
|
|
278
|
-
const entityInfo = md.
|
|
281
|
+
const md = provider ?? new Metadata();
|
|
282
|
+
const entityInfo = md.EntityByName(entityName);
|
|
279
283
|
if (!entityInfo) return dataObjectArray;
|
|
280
284
|
|
|
281
285
|
const encryptedFields = entityInfo.Fields.filter(f => f.Encrypt && !f.AllowDecryptInAPI);
|
|
@@ -283,7 +287,7 @@ export class ResolverBase {
|
|
|
283
287
|
|
|
284
288
|
// Process each element
|
|
285
289
|
for (const element of dataObjectArray) {
|
|
286
|
-
await this.FilterEncryptedFieldsForAPI(entityName, element, contextUser);
|
|
290
|
+
await this.FilterEncryptedFieldsForAPI(entityName, element, contextUser, provider);
|
|
287
291
|
}
|
|
288
292
|
|
|
289
293
|
return dataObjectArray;
|
|
@@ -562,9 +566,9 @@ export class ResolverBase {
|
|
|
562
566
|
}
|
|
563
567
|
}
|
|
564
568
|
|
|
565
|
-
protected CheckUserReadPermissions(entityName: string, userPayload: UserPayload | null) {
|
|
566
|
-
const md = new Metadata();
|
|
567
|
-
const entityInfo = md.
|
|
569
|
+
protected CheckUserReadPermissions(entityName: string, userPayload: UserPayload | null, provider?: IMetadataProvider) {
|
|
570
|
+
const md = provider ?? new Metadata();
|
|
571
|
+
const entityInfo = md.EntityByName(entityName);
|
|
568
572
|
if (!userPayload) {
|
|
569
573
|
throw new Error(`userPayload is null`);
|
|
570
574
|
}
|
|
@@ -796,10 +800,10 @@ export class ResolverBase {
|
|
|
796
800
|
// Skip processing if no params
|
|
797
801
|
if (!params.length) return [];
|
|
798
802
|
|
|
799
|
-
let md:
|
|
803
|
+
let md: IMetadataProvider | null = null;
|
|
800
804
|
const rv = params[0].provider as any as IRunViewProvider;
|
|
801
805
|
let runViewParams: RunViewParams[] = [];
|
|
802
|
-
|
|
806
|
+
|
|
803
807
|
// Fix #1: Get user info only once for all queries
|
|
804
808
|
let contextUser: UserInfo | null = null;
|
|
805
809
|
if (params[0]?.userPayload?.email) {
|
|
@@ -810,10 +814,12 @@ export class ResolverBase {
|
|
|
810
814
|
}
|
|
811
815
|
contextUser = user;
|
|
812
816
|
}
|
|
813
|
-
|
|
817
|
+
|
|
814
818
|
// Create a map of entities to validate only once per entity
|
|
815
819
|
const validatedEntities = new Set<string>();
|
|
816
|
-
|
|
820
|
+
// Use the per-request provider that came in on params instead of `new Metadata()` so
|
|
821
|
+
// multi-tenant servers resolve metadata against the request's own connection.
|
|
822
|
+
md = params[0].provider as unknown as IMetadataProvider;
|
|
817
823
|
|
|
818
824
|
// Transform parameters
|
|
819
825
|
for (const param of params) {
|
|
@@ -821,7 +827,7 @@ export class ResolverBase {
|
|
|
821
827
|
// Validate entity only once per entity type
|
|
822
828
|
const entityName = param.viewInfo.Entity;
|
|
823
829
|
if (!validatedEntities.has(entityName)) {
|
|
824
|
-
const entityInfo = md.
|
|
830
|
+
const entityInfo = md.EntityByName(entityName);
|
|
825
831
|
if (!entityInfo) {
|
|
826
832
|
throw new Error(`Entity ${entityName} not found in metadata`);
|
|
827
833
|
}
|
|
@@ -998,7 +1004,7 @@ export class ResolverBase {
|
|
|
998
1004
|
}
|
|
999
1005
|
|
|
1000
1006
|
public get MJCoreSchema(): string {
|
|
1001
|
-
return Metadata.Provider.ConfigData.MJCoreSchemaName;
|
|
1007
|
+
return Metadata.Provider.ConfigData.MJCoreSchemaName; // global-provider-ok: process-wide config (schema name) read once at module level
|
|
1002
1008
|
}
|
|
1003
1009
|
|
|
1004
1010
|
/**
|
|
@@ -1356,9 +1362,10 @@ export class ResolverBase {
|
|
|
1356
1362
|
}
|
|
1357
1363
|
});
|
|
1358
1364
|
|
|
1359
|
-
// Create ErrorLog record in the database
|
|
1365
|
+
// Create ErrorLog record in the database — use the entity's bound provider so the
|
|
1366
|
+
// ErrorLog write goes to the same connection as the entity that triggered it.
|
|
1360
1367
|
try {
|
|
1361
|
-
const md =
|
|
1368
|
+
const md = entityObject.ProviderToUse as unknown as IMetadataProvider;
|
|
1362
1369
|
const errorLogEntity = await md.GetEntityObject<MJErrorLogEntity>('MJ: Error Logs', contextUser);
|
|
1363
1370
|
errorLogEntity.Code = 'ENTITY_SAVE_INCONSISTENCY';
|
|
1364
1371
|
errorLogEntity.Message = `Entity save inconsistency detected for ${entityObject.EntityInfo.Name}: ${JSON.stringify(msg)}`;
|
package/src/index.ts
CHANGED
|
@@ -100,6 +100,7 @@ export * from './resolvers/FetchEntityVectorsResolver.js';
|
|
|
100
100
|
export * from './resolvers/PipelineProgressResolver.js';
|
|
101
101
|
export * from './resolvers/ClientToolRequestResolver.js';
|
|
102
102
|
export * from './resolvers/AutotagPipelineResolver.js';
|
|
103
|
+
export * from './resolvers/TagGovernanceResolver.js';
|
|
103
104
|
export * from './resolvers/TaskResolver.js';
|
|
104
105
|
export * from './generic/KeyValuePairInput.js';
|
|
105
106
|
export * from './generic/KeyInputOutputTypes.js';
|
|
@@ -284,7 +285,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
284
285
|
return origExecuteSQLWithPool.call(this, pool, query, parameters, contextUser);
|
|
285
286
|
};
|
|
286
287
|
|
|
287
|
-
const md = new Metadata();
|
|
288
|
+
const md = new Metadata(); // global-provider-ok: bootstrap
|
|
288
289
|
console.log(`Data Source has been initialized. ${md?.Entities ? md.Entities.length : 0} entities loaded.`);
|
|
289
290
|
} else {
|
|
290
291
|
// ─── SQL Server Path (existing behavior) ───────────────────────
|
|
@@ -326,7 +327,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
326
327
|
const config = new SQLServerProviderConfigData(pool, mj_core_schema, cacheRefreshInterval);
|
|
327
328
|
await setupSQLServerClient(config);
|
|
328
329
|
tPhase = lap('Metadata + Provider Setup', tPhase);
|
|
329
|
-
const md = new Metadata();
|
|
330
|
+
const md = new Metadata(); // global-provider-ok: bootstrap
|
|
330
331
|
console.log(`Data Source has been initialized. ${md?.Entities ? md.Entities.length : 0} entities loaded.`);
|
|
331
332
|
|
|
332
333
|
// Set up CodeGen-credentialed provider for RSU DDL operations (CREATE TABLE, CREATE SCHEMA, etc.)
|
|
@@ -418,7 +419,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
418
419
|
enablePubSub: true,
|
|
419
420
|
enableLogging: true,
|
|
420
421
|
});
|
|
421
|
-
(Metadata.Provider as GenericDatabaseProvider).SetLocalStorageProvider(redisProvider);
|
|
422
|
+
(Metadata.Provider as GenericDatabaseProvider).SetLocalStorageProvider(redisProvider); // global-provider-ok: bootstrap (Redis cache wiring)
|
|
422
423
|
await redisProvider.StartListening();
|
|
423
424
|
|
|
424
425
|
// Connect Redis pub/sub events to LocalCacheManager callback dispatch
|
|
@@ -449,7 +450,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
449
450
|
// LocalCacheManager may have already been initialized (with in-memory provider)
|
|
450
451
|
// during engine loading. SetStorageProvider migrates cached data to Redis.
|
|
451
452
|
if (process.env.REDIS_URL) {
|
|
452
|
-
await LocalCacheManager.Instance.SetStorageProvider(Metadata.Provider.LocalStorageProvider);
|
|
453
|
+
await LocalCacheManager.Instance.SetStorageProvider(Metadata.Provider.LocalStorageProvider); // global-provider-ok: bootstrap
|
|
453
454
|
console.log('LocalCacheManager: storage provider swapped to Redis');
|
|
454
455
|
}
|
|
455
456
|
// Ensure LocalCacheManager is initialized (no-op if already done during engine loading)
|
|
@@ -463,7 +464,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
463
464
|
evictionSweepIntervalMs: (cs.evictionSweepIntervalSeconds ?? 300) * 1000,
|
|
464
465
|
verboseLogging: cs.verboseLogging ?? false,
|
|
465
466
|
};
|
|
466
|
-
await LocalCacheManager.Instance.Initialize(Metadata.Provider.LocalStorageProvider, cacheConfig);
|
|
467
|
+
await LocalCacheManager.Instance.Initialize(Metadata.Provider.LocalStorageProvider, cacheConfig); // global-provider-ok: bootstrap
|
|
467
468
|
console.log('LocalCacheManager initialized with cache config:', JSON.stringify({
|
|
468
469
|
maxMemoryMB: cs.maxMemoryMB ?? 150,
|
|
469
470
|
maxPercentOfCachePerEntity: cs.maxPercentOfCachePerEntity ?? 50,
|
|
@@ -943,8 +944,7 @@ async function processRSUPendingWork(): Promise<void> {
|
|
|
943
944
|
|
|
944
945
|
for (const item of pendingItems) {
|
|
945
946
|
try {
|
|
946
|
-
const md = new Metadata();
|
|
947
|
-
|
|
947
|
+
const md = new Metadata(); // global-provider-ok: server startup recovery — runs once before any per-request context exists
|
|
948
948
|
// Get system user for server-side operations
|
|
949
949
|
const systemUser = UserCache.Instance.Users.find(u => u.Type?.trim().toLowerCase() === 'owner') ?? UserCache.Instance.Users[0];
|
|
950
950
|
if (!systemUser) {
|
|
@@ -952,7 +952,7 @@ async function processRSUPendingWork(): Promise<void> {
|
|
|
952
952
|
continue;
|
|
953
953
|
}
|
|
954
954
|
|
|
955
|
-
await Metadata.Provider.Refresh();
|
|
955
|
+
await Metadata.Provider.Refresh(); // global-provider-ok: server startup recovery — one-shot global cache refresh
|
|
956
956
|
|
|
957
957
|
// Resolve connector
|
|
958
958
|
const rv = new RunView();
|
|
@@ -1221,7 +1221,7 @@ async function refreshUserCacheFromPG(pgPool: import('pg').Pool, coreSchema: str
|
|
|
1221
1221
|
...user,
|
|
1222
1222
|
UserRoles: roles.filter((role: Record<string, unknown>) => UUIDsEqual(role.UserID as string, user.ID as string)),
|
|
1223
1223
|
};
|
|
1224
|
-
return new UserInfo(Metadata.Provider, userWithRoles);
|
|
1224
|
+
return new UserInfo(Metadata.Provider, userWithRoles); // global-provider-ok: bootstrap (UserCache initialization)
|
|
1225
1225
|
});
|
|
1226
1226
|
// Access the UserCache internals to set users
|
|
1227
1227
|
const cache = UserCache.Instance;
|
|
@@ -67,7 +67,7 @@ export function attachTenantContext(
|
|
|
67
67
|
function isEntityScoped(entityName: string, config: MultiTenancyConfig): boolean {
|
|
68
68
|
// Auto-exclude core MJ entities (entities in the __mj schema)
|
|
69
69
|
if (config.autoExcludeCoreEntities) {
|
|
70
|
-
const md = new Metadata();
|
|
70
|
+
const md = new Metadata(); // global-provider-ok: read-only schema check. Hooks (PreRunViewHook/PreSaveHook) don't carry per-request provider; reading the constant `__mj` schema flag is identical across every provider's catalog.
|
|
71
71
|
const entity = md.Entities.find(
|
|
72
72
|
e => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase()
|
|
73
73
|
);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Resolver, Mutation, Arg, Ctx } from "type-graphql";
|
|
2
2
|
import { Field, InputType, ObjectType } from "type-graphql";
|
|
3
|
-
import { LogError, Metadata } from "@memberjunction/core";
|
|
3
|
+
import { IMetadataProvider, LogError, Metadata } from "@memberjunction/core";
|
|
4
4
|
import { MJAPIKeyScopeEntity } from "@memberjunction/core-entities";
|
|
5
5
|
import { GetAPIKeyEngine } from "@memberjunction/api-keys";
|
|
6
6
|
import { AppContext } from "../types.js";
|
|
7
7
|
import { ResolverBase } from "../generic/ResolverBase.js";
|
|
8
|
+
import { GetReadWriteProvider } from "../util.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Input type for creating a new API key
|
|
@@ -144,7 +145,8 @@ export class APIKeyResolver extends ResolverBase {
|
|
|
144
145
|
|
|
145
146
|
// Save scope associations if provided
|
|
146
147
|
if (input.ScopeIDs && input.ScopeIDs.length > 0 && result.APIKeyId) {
|
|
147
|
-
|
|
148
|
+
const writeProvider = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
|
|
149
|
+
await this.saveScopeAssociations(result.APIKeyId, input.ScopeIDs, user, writeProvider);
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
return {
|
|
@@ -219,9 +221,10 @@ export class APIKeyResolver extends ResolverBase {
|
|
|
219
221
|
private async saveScopeAssociations(
|
|
220
222
|
apiKeyId: string,
|
|
221
223
|
scopeIds: string[],
|
|
222
|
-
user: any
|
|
224
|
+
user: any,
|
|
225
|
+
provider?: IMetadataProvider
|
|
223
226
|
): Promise<void> {
|
|
224
|
-
const md = new Metadata();
|
|
227
|
+
const md = provider ?? new Metadata();
|
|
225
228
|
|
|
226
229
|
for (const scopeId of scopeIds) {
|
|
227
230
|
try {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Resolver, Mutation, Ctx, Arg, ObjectType, Field } from 'type-graphql';
|
|
2
2
|
import { AppContext } from '../types.js';
|
|
3
|
-
import { LogError, LogStatus, Metadata, RunView, UserInfo } from '@memberjunction/core';
|
|
3
|
+
import { IMetadataProvider, LogError, LogStatus, Metadata, RunView, UserInfo } from '@memberjunction/core';
|
|
4
4
|
import { MJContentProcessRunEntity } from '@memberjunction/core-entities';
|
|
5
5
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
6
6
|
import { ActionEngineServer } from '@memberjunction/actions';
|
|
7
7
|
import { PubSubManager } from '../generic/PubSubManager.js';
|
|
8
8
|
import { PipelineProgressNotification } from './PipelineProgressResolver.js';
|
|
9
9
|
import { v4 as uuidv4 } from 'uuid';
|
|
10
|
+
import { GetReadWriteProvider } from '../util.js';
|
|
10
11
|
|
|
11
12
|
const PIPELINE_PROGRESS_TOPIC = 'PIPELINE_PROGRESS';
|
|
12
13
|
|
|
@@ -31,7 +32,7 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
31
32
|
async RunAutotagPipeline(
|
|
32
33
|
@Arg('contentSourceIDs', () => [String], { nullable: true }) contentSourceIDs: string[] | undefined,
|
|
33
34
|
@Arg('forceReprocess', { nullable: true }) forceReprocess: boolean | undefined,
|
|
34
|
-
@Ctx() { userPayload }: AppContext = {} as AppContext
|
|
35
|
+
@Ctx() { userPayload, providers }: AppContext = {} as AppContext
|
|
35
36
|
): Promise<AutotagPipelineResult> {
|
|
36
37
|
try {
|
|
37
38
|
const currentUser = this.GetUserFromPayload(userPayload);
|
|
@@ -42,8 +43,14 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
42
43
|
const pipelineRunID = uuidv4();
|
|
43
44
|
LogStatus(`RunAutotagPipeline: starting pipeline ${pipelineRunID}`);
|
|
44
45
|
|
|
46
|
+
// Capture the per-request provider snapshot before returning so the fire-and-forget
|
|
47
|
+
// background job binds to the same connection the caller used. Without this, the
|
|
48
|
+
// background job would silently use the global default — wrong for multi-tenant.
|
|
49
|
+
const provider = (GetReadWriteProvider(providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider)
|
|
50
|
+
?? (new Metadata() as unknown as IMetadataProvider);
|
|
51
|
+
|
|
45
52
|
// Fire-and-forget: start the pipeline in the background and return immediately
|
|
46
|
-
this.runPipelineInBackground(pipelineRunID, currentUser, contentSourceIDs, forceReprocess);
|
|
53
|
+
this.runPipelineInBackground(pipelineRunID, currentUser, provider, contentSourceIDs, forceReprocess);
|
|
47
54
|
|
|
48
55
|
return {
|
|
49
56
|
Success: true,
|
|
@@ -68,11 +75,12 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
68
75
|
private async runPipelineInBackground(
|
|
69
76
|
pipelineRunID: string,
|
|
70
77
|
currentUser: UserInfo,
|
|
78
|
+
provider: IMetadataProvider,
|
|
71
79
|
contentSourceIDs?: string[],
|
|
72
80
|
forceReprocess?: boolean
|
|
73
81
|
): Promise<void> {
|
|
74
82
|
const startTime = Date.now();
|
|
75
|
-
const processRun = await this.createProcessRun(pipelineRunID, currentUser, contentSourceIDs);
|
|
83
|
+
const processRun = await this.createProcessRun(pipelineRunID, currentUser, provider, contentSourceIDs);
|
|
76
84
|
|
|
77
85
|
try {
|
|
78
86
|
this.publishProgress(pipelineRunID, 'autotag', 0, 0, startTime, 'Initializing pipeline...');
|
|
@@ -147,6 +155,7 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
147
155
|
private async createProcessRun(
|
|
148
156
|
pipelineRunID: string,
|
|
149
157
|
currentUser: UserInfo,
|
|
158
|
+
provider: IMetadataProvider,
|
|
150
159
|
contentSourceIDs?: string[]
|
|
151
160
|
): Promise<MJContentProcessRunEntity | null> {
|
|
152
161
|
try {
|
|
@@ -156,7 +165,7 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
156
165
|
sourceID = contentSourceIDs[0];
|
|
157
166
|
} else {
|
|
158
167
|
// Load content sources to get any available source ID (SourceID is NOT NULL)
|
|
159
|
-
const rv =
|
|
168
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
160
169
|
const result = await rv.RunView<{ ID: string }>({
|
|
161
170
|
EntityName: 'MJ: Content Sources',
|
|
162
171
|
Fields: ['ID'],
|
|
@@ -173,7 +182,7 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
173
182
|
return null;
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
const md =
|
|
185
|
+
const md = provider;
|
|
177
186
|
const run = await md.GetEntityObject<MJContentProcessRunEntity>('MJ: Content Process Runs', currentUser);
|
|
178
187
|
run.NewRecord();
|
|
179
188
|
run.ID = pipelineRunID;
|
|
@@ -252,7 +261,7 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
252
261
|
@Mutation(() => AutotagPipelineResult)
|
|
253
262
|
async PauseClassificationPipeline(
|
|
254
263
|
@Arg('processRunID') processRunID: string,
|
|
255
|
-
@Ctx() { userPayload }: AppContext = {} as AppContext
|
|
264
|
+
@Ctx() { userPayload, providers }: AppContext = {} as AppContext
|
|
256
265
|
): Promise<AutotagPipelineResult> {
|
|
257
266
|
try {
|
|
258
267
|
const currentUser = this.GetUserFromPayload(userPayload);
|
|
@@ -260,7 +269,7 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
260
269
|
return { Success: false, Status: 'Error', ErrorMessage: 'Unable to determine current user' };
|
|
261
270
|
}
|
|
262
271
|
|
|
263
|
-
const md = new Metadata();
|
|
272
|
+
const md = (GetReadWriteProvider(providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider) ?? new Metadata();
|
|
264
273
|
const run = await md.GetEntityObject<MJContentProcessRunEntity>('MJ: Content Process Runs', currentUser);
|
|
265
274
|
const loaded = await run.Load(processRunID);
|
|
266
275
|
if (!loaded) {
|
|
@@ -284,7 +293,7 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
284
293
|
@Mutation(() => AutotagPipelineResult)
|
|
285
294
|
async ResumeClassificationPipeline(
|
|
286
295
|
@Arg('processRunID') processRunID: string,
|
|
287
|
-
@Ctx() { userPayload }: AppContext = {} as AppContext
|
|
296
|
+
@Ctx() { userPayload, providers }: AppContext = {} as AppContext
|
|
288
297
|
): Promise<AutotagPipelineResult> {
|
|
289
298
|
try {
|
|
290
299
|
const currentUser = this.GetUserFromPayload(userPayload);
|
|
@@ -292,7 +301,7 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
292
301
|
return { Success: false, Status: 'Error', ErrorMessage: 'Unable to determine current user' };
|
|
293
302
|
}
|
|
294
303
|
|
|
295
|
-
const md = new Metadata();
|
|
304
|
+
const md = (GetReadWriteProvider(providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider) ?? new Metadata();
|
|
296
305
|
const run = await md.GetEntityObject<MJContentProcessRunEntity>('MJ: Content Process Runs', currentUser);
|
|
297
306
|
const loaded = await run.Load(processRunID);
|
|
298
307
|
if (!loaded) {
|
|
@@ -312,7 +321,7 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
312
321
|
const pipelineRunID = uuidv4();
|
|
313
322
|
LogStatus(`ResumeClassificationPipeline: Resuming run ${processRunID} from offset ${run.LastProcessedOffset}`);
|
|
314
323
|
|
|
315
|
-
this.runPipelineInBackground(pipelineRunID, currentUser, undefined, undefined);
|
|
324
|
+
this.runPipelineInBackground(pipelineRunID, currentUser, md as unknown as IMetadataProvider, undefined, undefined);
|
|
316
325
|
|
|
317
326
|
return { Success: true, Status: 'Resumed', PipelineRunID: pipelineRunID };
|
|
318
327
|
} catch (error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, ObjectType, Query, Mutation, Resolver } from 'type-graphql';
|
|
2
|
-
import { UserInfo, Metadata, LogError, LogStatus } from '@memberjunction/core';
|
|
2
|
+
import { UserInfo, IMetadataProvider, Metadata, LogError, LogStatus } from '@memberjunction/core';
|
|
3
3
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
4
4
|
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
5
5
|
import { MJComponentEntity, MJComponentRegistryEntity, ComponentMetadataEngine } from '@memberjunction/core-entities';
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from '@memberjunction/component-registry-client-sdk';
|
|
17
17
|
import { AppContext } from '../types.js';
|
|
18
18
|
import { configInfo } from '../config.js';
|
|
19
|
+
import { GetReadWriteProvider } from '../util.js';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* GraphQL types for Component Registry operations
|
|
@@ -181,7 +182,7 @@ export class ComponentRegistryExtendedResolver {
|
|
|
181
182
|
@Arg('registryName') registryName: string,
|
|
182
183
|
@Arg('namespace') namespace: string,
|
|
183
184
|
@Arg('name') name: string,
|
|
184
|
-
@Ctx() { userPayload }: AppContext,
|
|
185
|
+
@Ctx() { userPayload, providers }: AppContext,
|
|
185
186
|
@Arg('version', { nullable: true }) version?: string,
|
|
186
187
|
@Arg('hash', { nullable: true }) hash?: string
|
|
187
188
|
): Promise<ComponentSpecWithHashType> {
|
|
@@ -234,7 +235,8 @@ export class ComponentRegistryExtendedResolver {
|
|
|
234
235
|
|
|
235
236
|
// Optional: Cache in database if configured
|
|
236
237
|
if (this.shouldCache(registry)) {
|
|
237
|
-
|
|
238
|
+
const writeProvider = GetReadWriteProvider(providers, { allowFallbackToReadOnly: true }) as unknown as IMetadataProvider;
|
|
239
|
+
await this.cacheComponent(component, registry.ID, user, writeProvider);
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
// Return the ComponentSpec as a JSON string
|
|
@@ -505,11 +507,12 @@ export class ComponentRegistryExtendedResolver {
|
|
|
505
507
|
private async cacheComponent(
|
|
506
508
|
component: ComponentSpec,
|
|
507
509
|
registryId: string,
|
|
508
|
-
userInfo: UserInfo
|
|
510
|
+
userInfo: UserInfo,
|
|
511
|
+
provider?: IMetadataProvider
|
|
509
512
|
): Promise<void> {
|
|
510
513
|
try {
|
|
511
514
|
// Find or create component entity
|
|
512
|
-
const md = new Metadata();
|
|
515
|
+
const md = provider ?? new Metadata();
|
|
513
516
|
const componentEntity = await md.GetEntityObject<MJComponentEntity>('MJ: Components', userInfo);
|
|
514
517
|
|
|
515
518
|
// Check if component already exists
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityPermissionType,
|
|
1
|
+
import { EntityPermissionType, FieldValueCollection, EntitySaveOptions } from '@memberjunction/core';
|
|
2
2
|
import { NormalizeUUID } from '@memberjunction/global';
|
|
3
3
|
import { MJFileEntity, MJFileStorageProviderEntity, MJFileStorageAccountEntity } from '@memberjunction/core-entities';
|
|
4
4
|
import {
|
|
@@ -417,7 +417,7 @@ export class FileResolver extends FileResolverBase {
|
|
|
417
417
|
|
|
418
418
|
@FieldResolver(() => String)
|
|
419
419
|
async DownloadUrl(@Root() file: MJFile_, @Ctx() context: AppContext) {
|
|
420
|
-
const md =
|
|
420
|
+
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
421
421
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
422
422
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
423
423
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
@@ -2,7 +2,6 @@ import { Arg, Ctx, Field, ObjectType, Query, Resolver } from "type-graphql";
|
|
|
2
2
|
import { AppContext } from "../types.js";
|
|
3
3
|
import { DataContext } from "@memberjunction/data-context";
|
|
4
4
|
import { GetReadOnlyDataSource, GetReadOnlyProvider } from "../util.js";
|
|
5
|
-
import { Metadata } from "@memberjunction/core";
|
|
6
5
|
import { MJDataContextItemEntity } from "@memberjunction/core-entities";
|
|
7
6
|
import { ResolverBase } from "../generic/ResolverBase.js";
|
|
8
7
|
|
|
@@ -62,7 +61,7 @@ export class GetDataContextDataResolver extends ResolverBase {
|
|
|
62
61
|
const dciData = await md.GetEntityObject<MJDataContextItemEntity>("MJ: Data Context Items", appCtx.userPayload.userRecord);
|
|
63
62
|
if (await dciData.Load(DataContextItemID)) {
|
|
64
63
|
const dci = DataContext.CreateDataContextItem(); // use class factory to get whatever lowest level sub-class is registered
|
|
65
|
-
await dci.LoadMetadataFromEntityRecord(dciData,
|
|
64
|
+
await dci.LoadMetadataFromEntityRecord(dciData, md, appCtx.userPayload.userRecord);
|
|
66
65
|
// now the metadata is loaded so we can call the regular load function
|
|
67
66
|
if (await dci.LoadData(ds)) {
|
|
68
67
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IEntityDataProvider } from '@memberjunction/core';
|
|
2
2
|
import { Arg, Ctx, Field, ObjectType, Query, Resolver } from 'type-graphql';
|
|
3
3
|
import { AppContext } from '../types.js';
|
|
4
4
|
import { GetReadOnlyProvider } from '../util.js';
|
|
@@ -63,8 +63,7 @@ export class ISAEntityResolver {
|
|
|
63
63
|
): Promise<ISAChildEntityResult> {
|
|
64
64
|
try {
|
|
65
65
|
const provider = GetReadOnlyProvider(providers, { allowFallbackToReadWrite: true });
|
|
66
|
-
const
|
|
67
|
-
const entityInfo = md.Entities.find(e => e.Name === EntityName);
|
|
66
|
+
const entityInfo = provider.Entities.find(e => e.Name === EntityName);
|
|
68
67
|
|
|
69
68
|
if (!entityInfo) {
|
|
70
69
|
return {
|
|
@@ -127,8 +126,7 @@ export class ISAEntityResolver {
|
|
|
127
126
|
): Promise<ISAChildEntitiesResult> {
|
|
128
127
|
try {
|
|
129
128
|
const provider = GetReadOnlyProvider(providers, { allowFallbackToReadWrite: true });
|
|
130
|
-
const
|
|
131
|
-
const entityInfo = md.Entities.find(e => e.Name === EntityName);
|
|
129
|
+
const entityInfo = provider.Entities.find(e => e.Name === EntityName);
|
|
132
130
|
|
|
133
131
|
if (!entityInfo) {
|
|
134
132
|
return {
|