@memberjunction/server 6.1.0-edge.1 → 6.1.0-edge.2
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/auth/exampleNewUserSubClass.js +1 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +9 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +1 -1
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.js +1 -1
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/context.js +2 -2
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +45 -4
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +229 -21
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +19 -3
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +51 -17
- 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 +85 -41
- package/dist/index.js.map +1 -1
- package/dist/logging/secretRedactor.d.ts +9 -0
- package/dist/logging/secretRedactor.d.ts.map +1 -1
- package/dist/logging/secretRedactor.js +39 -0
- package/dist/logging/secretRedactor.js.map +1 -1
- package/dist/middleware/BaseServerMiddleware.d.ts +5 -0
- package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
- package/dist/middleware/BaseServerMiddleware.js +5 -0
- package/dist/middleware/BaseServerMiddleware.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +12 -0
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/EntityPermissionResolver.js +1 -1
- package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +9 -0
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +3 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +5 -0
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/SearchEntitiesResolver.js +1 -1
- package/dist/resolvers/SearchEntitiesResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +2 -2
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +1 -1
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +1 -1
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/SignatureWebhookHandler.js +1 -1
- package/dist/rest/SignatureWebhookHandler.js.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts +26 -0
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -0
- package/dist/services/DurableEntityActionTaskSubmitter.js +55 -0
- package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -0
- package/dist/services/ScheduledJobsService.js +1 -1
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.d.ts.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.js +16 -1
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +34 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -0
- package/dist/services/TaskGraphActionRunner.js +95 -0
- package/dist/services/TaskGraphActionRunner.js.map +1 -0
- package/dist/services/TaskGraphAgentRunner.d.ts +13 -0
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +30 -0
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphPromptRunner.d.ts +31 -0
- package/dist/services/TaskGraphPromptRunner.d.ts.map +1 -0
- package/dist/services/TaskGraphPromptRunner.js +134 -0
- package/dist/services/TaskGraphPromptRunner.js.map +1 -0
- package/dist/telephony/RingCentralTelephonyService.js +1 -1
- package/dist/telephony/RingCentralTelephonyService.js.map +1 -1
- package/dist/telephony/TwilioTelephonyRouter.js +1 -1
- package/dist/telephony/TwilioTelephonyRouter.js.map +1 -1
- package/dist/telephony/VonageTelephonyRouter.js +1 -1
- package/dist/telephony/VonageTelephonyRouter.js.map +1 -1
- package/package.json +90 -90
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +1 -1
- package/src/__tests__/ResolverBase.frozenRecordMapping.test.ts +133 -0
- package/src/__tests__/ResolverBase.transportMapping.test.ts +289 -0
- package/src/__tests__/SessionManager.test.ts +1 -1
- package/src/__tests__/WidgetSessionService.test.ts +1 -1
- package/src/__tests__/databaseAbstraction.test.ts +1 -1
- package/src/__tests__/magicLinkService.provisionLog.test.ts +1 -1
- package/src/__tests__/resolverBase.rls.test.ts +3 -0
- package/src/__tests__/secretRedactor.failOpen.test.ts +135 -0
- package/src/__tests__/widgetGuestElevation.test.ts +1 -1
- package/src/auth/exampleNewUserSubClass.ts +1 -1
- package/src/auth/index.ts +10 -6
- package/src/auth/magicLink/MagicLinkService.ts +1 -1
- package/src/auth/newUsers.ts +1 -1
- package/src/context.ts +2 -2
- package/src/generated/generated.ts +166 -17
- package/src/generic/ResolverBase.ts +52 -17
- package/src/index.ts +92 -42
- package/src/logging/secretRedactor.ts +43 -0
- package/src/middleware/BaseServerMiddleware.ts +5 -0
- package/src/realtimeWidget/WidgetSessionService.ts +1 -1
- package/src/realtimeWidget/widgetGuestElevation.ts +1 -1
- package/src/resolvers/ActionResolver.ts +13 -0
- package/src/resolvers/ComponentRegistryResolver.ts +1 -1
- package/src/resolvers/EntityPermissionResolver.ts +1 -1
- package/src/resolvers/IntegrationDiscoveryResolver.ts +12 -2
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
- package/src/resolvers/RealtimeClientSessionResolver.ts +13 -0
- package/src/resolvers/SearchEntitiesResolver.ts +1 -1
- package/src/resolvers/SqlLoggingConfigResolver.ts +1 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +2 -2
- package/src/resolvers/UserFavoriteResolver.ts +1 -1
- package/src/rest/OAuthCallbackHandler.ts +1 -1
- package/src/rest/SignatureWebhookHandler.ts +1 -1
- package/src/services/DurableEntityActionTaskSubmitter.ts +78 -0
- package/src/services/ScheduledJobsService.ts +1 -1
- package/src/services/StartTaskGraphDispatcher.ts +17 -0
- package/src/services/TaskGraphActionRunner.ts +99 -0
- package/src/services/TaskGraphAgentRunner.ts +35 -1
- package/src/services/TaskGraphPromptRunner.ts +144 -0
- package/src/telephony/RingCentralTelephonyService.ts +1 -1
- package/src/telephony/TwilioTelephonyRouter.ts +1 -1
- package/src/telephony/VonageTelephonyRouter.ts +1 -1
package/src/index.ts
CHANGED
|
@@ -5,9 +5,9 @@ dotenv.config({ quiet: true });
|
|
|
5
5
|
import { expressMiddleware } from '@as-integrations/express5';
|
|
6
6
|
import { mergeSchemas } from '@graphql-tools/schema';
|
|
7
7
|
import { Metadata, DatabasePlatform, SetProvider, StartupManager as StartupManagerImport, BaseEntity, BaseEntityEvent, RunView, DatabaseProviderBase, ResolveStartupMode } from '@memberjunction/core';
|
|
8
|
-
import { resolveDbPlatformFromEnv } from '@memberjunction/generic-database-provider';
|
|
8
|
+
import { UserCache, resolveDbPlatformFromEnv } from '@memberjunction/generic-database-provider';
|
|
9
9
|
import { MJGlobal, MJEventType, UUIDsEqual, ShutdownRegistry } from '@memberjunction/global';
|
|
10
|
-
import { setupSQLServerClient, SQLServerDataProvider, SQLServerProviderConfigData
|
|
10
|
+
import { setupSQLServerClient, SQLServerDataProvider, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
|
|
11
11
|
import { extendConnectionPoolWithQuery } from './util.js';
|
|
12
12
|
import { registerIntegrationCustomColumnPromoter } from './integration/CustomColumnPromoter.js';
|
|
13
13
|
import { DisableUnselectedEntityMaps, ReenableFieldMapsForEntityMap } from './integration/EntityMapLifecycle.js';
|
|
@@ -19,11 +19,12 @@ import { default as fg } from 'fast-glob';
|
|
|
19
19
|
import { useServer } from 'graphql-ws/lib/use/ws';
|
|
20
20
|
import { createServer } from 'node:http';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
|
-
import { readFileSync } from 'node:fs';
|
|
22
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { Session as InspectorSession } from 'node:inspector';
|
|
23
24
|
import { sep } from 'node:path';
|
|
24
25
|
import 'reflect-metadata';
|
|
25
26
|
import { ReplaySubject } from 'rxjs';
|
|
26
|
-
import { BuildSchemaOptions, buildSchemaSync, GraphQLTimestamp, PubSubEngine } from 'type-graphql';
|
|
27
|
+
import { BuildSchemaOptions, buildSchemaSync, getMetadataStorage, GraphQLTimestamp, PubSubEngine } from 'type-graphql';
|
|
27
28
|
import { PubSub } from 'graphql-subscriptions';
|
|
28
29
|
import sql from 'mssql';
|
|
29
30
|
import { WebSocketServer } from 'ws';
|
|
@@ -153,6 +154,7 @@ export * from './resolvers/QueryResolver.js';
|
|
|
153
154
|
export * from './services/TaskGraphProviderFactory.js';
|
|
154
155
|
export * from './services/TaskGraphAgentRunner.js';
|
|
155
156
|
export * from './services/StartTaskGraphDispatcher.js';
|
|
157
|
+
export * from './services/TaskGraphPromptRunner.js';
|
|
156
158
|
export * from './services/TaskGraphContinuationDeliverer.js';
|
|
157
159
|
export * from './resolvers/TaskGraphFrameResolver.js';
|
|
158
160
|
export * from './resolvers/TestQuerySQLResolver.js';
|
|
@@ -328,8 +330,8 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
328
330
|
await provider.Config(pgConfigData);
|
|
329
331
|
SetProvider(provider);
|
|
330
332
|
|
|
331
|
-
//
|
|
332
|
-
await
|
|
333
|
+
// Warm the user cache — dialect-neutral, same call the SQL Server path makes
|
|
334
|
+
await UserCache.Instance.Refresh(provider);
|
|
333
335
|
|
|
334
336
|
// Run startup actions — same 'full' entry-point default as the SQL Server path
|
|
335
337
|
const sysUser = UserCache.Instance.GetSystemUser();
|
|
@@ -858,23 +860,73 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
858
860
|
lap('Resolver + Middleware Discovery', tServe);
|
|
859
861
|
tServe = startupLog.BeginPhase('Building GraphQL schema');
|
|
860
862
|
|
|
863
|
+
// ── Scale-runtime instrumentation (fix #1 profiling) ─────────────────────────
|
|
864
|
+
// Split the previously-monolithic "Schema Build" phase into its three real
|
|
865
|
+
// sub-steps so we can see which one dominates at high entity counts, and capture
|
|
866
|
+
// the type-graphql metadata cardinality (the O(entities×fields) signal). Guarded
|
|
867
|
+
// by MJ_SCHEMA_PROFILE, and emits ONE explicit console line (independent of the
|
|
868
|
+
// startup logger's level, and NOT via EndPhase, so the "schema" summary token and
|
|
869
|
+
// its total math stay clean). No-op in normal deployments.
|
|
870
|
+
const schemaProfile = process.env.MJ_SCHEMA_PROFILE === '1';
|
|
871
|
+
let objectTypeCount = -1, fieldCount = -1;
|
|
872
|
+
if (schemaProfile) {
|
|
873
|
+
try {
|
|
874
|
+
const meta = getMetadataStorage();
|
|
875
|
+
// Read BEFORE buildSchemaSync — buildSchemaSync's internal build() resets these arrays.
|
|
876
|
+
objectTypeCount = meta.objectTypes.length;
|
|
877
|
+
fieldCount = meta.fields.length;
|
|
878
|
+
} catch {
|
|
879
|
+
/* metadata cardinality is best-effort; leave sentinels */
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Surgical CPU profile of ONLY buildSchemaSync (behind MJ_SCHEMA_CPUPROF=1), so we can see
|
|
884
|
+
// whether the ~66s at scale is uniform per-field work (cache is the only fix) or a patchable
|
|
885
|
+
// hotspot in type-graphql's generator. Deterministic flush (writes before serve continues) —
|
|
886
|
+
// no dependency on --cpu-prof surviving a process kill.
|
|
887
|
+
const cpuProf = process.env.MJ_SCHEMA_CPUPROF === '1';
|
|
888
|
+
let profSession: InspectorSession | undefined;
|
|
889
|
+
if (cpuProf) {
|
|
890
|
+
profSession = new InspectorSession();
|
|
891
|
+
profSession.connect();
|
|
892
|
+
await new Promise<void>((res, rej) => profSession!.post('Profiler.enable', (e) => (e ? rej(e) : res())));
|
|
893
|
+
await new Promise<void>((res, rej) => profSession!.post('Profiler.start', (e) => (e ? rej(e) : res())));
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const tBuild = performance.now();
|
|
897
|
+
const builtSchema = buildSchemaSync({
|
|
898
|
+
resolvers: allResolvers,
|
|
899
|
+
validate: false,
|
|
900
|
+
scalarsMap: [{ type: Date, scalar: GraphQLTimestamp }],
|
|
901
|
+
emitSchemaFile: websiteRunFromPackage !== 1,
|
|
902
|
+
pubSub,
|
|
903
|
+
globalMiddlewares: [variablesLoggingMiddleware],
|
|
904
|
+
});
|
|
905
|
+
const buildMs = performance.now() - tBuild;
|
|
906
|
+
|
|
907
|
+
if (cpuProf && profSession) {
|
|
908
|
+
const profile = await new Promise<unknown>((res, rej) =>
|
|
909
|
+
profSession!.post('Profiler.stop', (e, r) => (e ? rej(e) : res((r as { profile: unknown }).profile)))
|
|
910
|
+
);
|
|
911
|
+
const outPath = `/tmp/mjapi_schema_build_${process.pid}.cpuprofile`;
|
|
912
|
+
writeFileSync(outPath, JSON.stringify(profile));
|
|
913
|
+
profSession.disconnect();
|
|
914
|
+
// eslint-disable-next-line no-console
|
|
915
|
+
console.log(`[SCHEMA-CPUPROF] wrote ${outPath} (buildSchemaSync=${buildMs.toFixed(0)}ms)`);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const tMerge = performance.now();
|
|
861
919
|
let schema = mergeSchemas({
|
|
862
|
-
schemas: [
|
|
863
|
-
buildSchemaSync({
|
|
864
|
-
resolvers: allResolvers,
|
|
865
|
-
validate: false,
|
|
866
|
-
scalarsMap: [{ type: Date, scalar: GraphQLTimestamp }],
|
|
867
|
-
emitSchemaFile: websiteRunFromPackage !== 1,
|
|
868
|
-
pubSub,
|
|
869
|
-
globalMiddlewares: [variablesLoggingMiddleware],
|
|
870
|
-
}),
|
|
871
|
-
],
|
|
920
|
+
schemas: [builtSchema],
|
|
872
921
|
typeDefs: [requireSystemUserDirective.typeDefs, publicDirective.typeDefs],
|
|
873
922
|
});
|
|
923
|
+
const mergeMs = performance.now() - tMerge;
|
|
874
924
|
|
|
875
925
|
// Verbose-mode-only diagnostic: name custom-resolver args that aren't metadata-bound
|
|
876
926
|
// and aren't @NoLog-marked. No-op in default config (logVariables=false).
|
|
877
927
|
auditResolversForUndecoratedArgs();
|
|
928
|
+
|
|
929
|
+
const tTransform = performance.now();
|
|
878
930
|
schema = requireSystemUserDirective.transformer(schema);
|
|
879
931
|
schema = publicDirective.transformer(schema);
|
|
880
932
|
|
|
@@ -882,6 +934,17 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
882
934
|
for (const transformer of mwSchemaTransformers) {
|
|
883
935
|
schema = transformer(schema);
|
|
884
936
|
}
|
|
937
|
+
const transformMs = performance.now() - tTransform;
|
|
938
|
+
|
|
939
|
+
if (schemaProfile) {
|
|
940
|
+
const typeMapSize = Object.keys(schema.getTypeMap()).length;
|
|
941
|
+
// eslint-disable-next-line no-console
|
|
942
|
+
console.log(
|
|
943
|
+
`[SCHEMA-PROFILE] resolvers=${allResolvers.length} objectTypes=${objectTypeCount} fields=${fieldCount} ` +
|
|
944
|
+
`typeMap=${typeMapSize} | buildSchemaSync=${buildMs.toFixed(0)}ms mergeSchemas=${mergeMs.toFixed(0)}ms ` +
|
|
945
|
+
`transformers=${transformMs.toFixed(0)}ms`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
885
948
|
|
|
886
949
|
lap('Schema Build', tServe);
|
|
887
950
|
tServe = startupLog.BeginPhase('Starting HTTP server');
|
|
@@ -1350,8 +1413,20 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
1350
1413
|
// submitted graph — submission would be durable and inert, which is strictly worse than the old
|
|
1351
1414
|
// client-driven path it replaced. Gated on SQL Server because the provider factory mints
|
|
1352
1415
|
// SQLServerDataProvider; the PG branch lands with PG parity. Self-registers with ShutdownRegistry.
|
|
1416
|
+
//
|
|
1417
|
+
// `MJ_DISABLE_TASK_GRAPH_DISPATCHER=1` suppresses it, for the one case where a second dispatcher
|
|
1418
|
+
// is actively harmful: the integration suite's task-graph bundle drives its OWN dispatcher against
|
|
1419
|
+
// a stub runner and asserts exactly-once execution. A dispatcher claims from the whole table, not
|
|
1420
|
+
// from "its own" graphs, so a server sharing that database races the suite for every claim and
|
|
1421
|
+
// executes the suite's tasks with the real agent runner. The bundle then reports tasks that never
|
|
1422
|
+
// ran and graphs that settled to the wrong status — symptoms that read as engine defects and cost
|
|
1423
|
+
// a release cycle to trace back to here. The suite still needs MJAPI up for its client-transport
|
|
1424
|
+
// members, so "stop the server" is not the remedy; this is.
|
|
1353
1425
|
const taskGraphPool = dataSources[0]?.dataSource;
|
|
1354
|
-
|
|
1426
|
+
const taskGraphDispatcherDisabled = process.env.MJ_DISABLE_TASK_GRAPH_DISPATCHER === '1';
|
|
1427
|
+
if (taskGraphDispatcherDisabled) {
|
|
1428
|
+
LogStatus('[TaskGraphDispatcher] Disabled by MJ_DISABLE_TASK_GRAPH_DISPATCHER=1 — submitted graphs will not be executed by this process.');
|
|
1429
|
+
} else if (resumeUser && taskGraphPool instanceof sql.ConnectionPool) {
|
|
1355
1430
|
StartTaskGraphDispatcher(taskGraphPool, resumeUser)
|
|
1356
1431
|
.catch(err => console.warn(`[TaskGraphDispatcher] Startup failed: ${err}`));
|
|
1357
1432
|
}
|
|
@@ -1745,31 +1820,6 @@ function createMSSQLCompatPool(pgPool: import('pg').Pool): sql.ConnectionPool {
|
|
|
1745
1820
|
return wrapper as unknown as sql.ConnectionPool;
|
|
1746
1821
|
}
|
|
1747
1822
|
|
|
1748
|
-
/**
|
|
1749
|
-
* Refreshes the UserCache using PostgreSQL queries instead of MSSQL.
|
|
1750
|
-
* This mirrors the logic in UserCache.Refresh() but uses pg.Pool.
|
|
1751
|
-
*/
|
|
1752
|
-
async function refreshUserCacheFromPG(pgPool: import('pg').Pool, coreSchema: string): Promise<void> {
|
|
1753
|
-
const { UserInfo } = await import('@memberjunction/core');
|
|
1754
|
-
const uResult = await pgPool.query(`SELECT * FROM ${coreSchema}."vwUsers"`);
|
|
1755
|
-
const rResult = await pgPool.query(`SELECT * FROM ${coreSchema}."vwUserRoles"`);
|
|
1756
|
-
const users = uResult.rows;
|
|
1757
|
-
const roles = rResult.rows;
|
|
1758
|
-
|
|
1759
|
-
if (users) {
|
|
1760
|
-
const userInfos = users.map((user: Record<string, unknown>) => {
|
|
1761
|
-
const userWithRoles = {
|
|
1762
|
-
...user,
|
|
1763
|
-
UserRoles: roles.filter((role: Record<string, unknown>) => UUIDsEqual(role.UserID as string, user.ID as string)),
|
|
1764
|
-
};
|
|
1765
|
-
return new UserInfo(Metadata.Provider, userWithRoles); // global-provider-ok: bootstrap (UserCache initialization)
|
|
1766
|
-
});
|
|
1767
|
-
// Access the UserCache internals to set users
|
|
1768
|
-
const cache = UserCache.Instance;
|
|
1769
|
-
(cache as unknown as Record<string, unknown>)['_users'] = userInfos;
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
1823
|
/**
|
|
1774
1824
|
* Translates SQL Server bracket-quoted identifiers to PostgreSQL double-quoted identifiers.
|
|
1775
1825
|
* Converts [schema].[table] to "schema"."table" and handles common T-SQL patterns.
|
|
@@ -51,6 +51,20 @@ export function redactArg(ctx: RedactionContext): unknown {
|
|
|
51
51
|
const entity = ctx.provider.Entities.find((e) => e.ClassName === match.groups!.name);
|
|
52
52
|
if (entity) {
|
|
53
53
|
encryptedFieldNames = new Set(entity.EncryptedFields.map((f) => f.Name));
|
|
54
|
+
} else {
|
|
55
|
+
// The name matched the CRUD convention but resolves to no entity — so the
|
|
56
|
+
// metadata half of the redactor contributes nothing and, absent any @NoLog
|
|
57
|
+
// field, this arg falls through to shortenForLog with its values intact.
|
|
58
|
+
//
|
|
59
|
+
// This is the silent case: the boot audit tests only the NAME pattern, so an
|
|
60
|
+
// input like `CreateConnectionInput` is classified as metadata-bound and never
|
|
61
|
+
// warned about, while the lookup here quietly fails. Every hand-written
|
|
62
|
+
// `Create*Input` in the resolvers hits this — codegen inputs embed the entity
|
|
63
|
+
// ClassName (`CreateMJCredentialInput`), hand-written ones do not.
|
|
64
|
+
//
|
|
65
|
+
// Report it once per input type so the operator who enabled variables logging
|
|
66
|
+
// learns which args are unprotected, rather than discovering it in a log file.
|
|
67
|
+
WarnUnboundCrudInput(ctx.inputTypeName);
|
|
54
68
|
}
|
|
55
69
|
}
|
|
56
70
|
|
|
@@ -80,3 +94,32 @@ export function redactArg(ctx: RedactionContext): unknown {
|
|
|
80
94
|
}
|
|
81
95
|
|
|
82
96
|
const EMPTY_SET: ReadonlySet<string> = new Set<string>();
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Input type names already reported by {@link WarnUnboundCrudInput}. Warning once
|
|
100
|
+
* per type keeps a hot mutation from flooding the log with the same diagnostic.
|
|
101
|
+
*/
|
|
102
|
+
const warnedUnboundInputTypes = new Set<string>();
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reports, once per input type, that an arg named like a CRUD input resolves to no
|
|
106
|
+
* entity and is therefore logged unredacted unless its fields carry `@NoLog`.
|
|
107
|
+
*
|
|
108
|
+
* Exported for tests; not part of the redaction path's contract.
|
|
109
|
+
*/
|
|
110
|
+
export function WarnUnboundCrudInput(inputTypeName: string): void {
|
|
111
|
+
if (warnedUnboundInputTypes.has(inputTypeName)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
warnedUnboundInputTypes.add(inputTypeName);
|
|
115
|
+
console.warn(
|
|
116
|
+
`[MJServer] Variables logging: '${inputTypeName}' matches the Create/Update/Delete input ` +
|
|
117
|
+
`naming convention but maps to no entity, so encrypted-field redaction cannot apply to it. ` +
|
|
118
|
+
`Its values are being logged. Mark any sensitive @Field() on this input with @NoLog.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Test seam — clears the once-per-type warning state. */
|
|
123
|
+
export function ResetUnboundCrudInputWarnings(): void {
|
|
124
|
+
warnedUnboundInputTypes.clear();
|
|
125
|
+
}
|
|
@@ -126,6 +126,11 @@ export abstract class BaseServerMiddleware {
|
|
|
126
126
|
/**
|
|
127
127
|
* Hook that runs after a RunView operation completes. Can modify the result
|
|
128
128
|
* (e.g., filtering or augmenting data) before it is returned to the caller.
|
|
129
|
+
*
|
|
130
|
+
* ⚠️ `results.Results` and its rows may be FROZEN shared cache state (LocalCacheManager
|
|
131
|
+
* freeze-on-write) — writing into a row throws `TypeError`. To modify, build copies and
|
|
132
|
+
* either reassign `results.Results = results.Results.map(r => ({ ...r, ... }))` or return
|
|
133
|
+
* a new result; never mutate rows in place.
|
|
129
134
|
*/
|
|
130
135
|
PostRunView(params: RunViewParams, results: RunViewResult, contextUser: UserInfo | undefined): RunViewResult | Promise<RunViewResult> {
|
|
131
136
|
return results;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { Metadata, RunView, UserInfo, LogError, LogStatus } from '@memberjunction/core';
|
|
18
18
|
import { MJLruCache, UUIDsEqual } from '@memberjunction/global';
|
|
19
19
|
import type { MJConversationWidgetInstanceEntity, MJConversationEntity } from '@memberjunction/core-entities';
|
|
20
|
-
import { UserCache } from '@memberjunction/
|
|
20
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
21
21
|
import { MagicLinkKeyManager } from '../auth/magicLink/MagicLinkKeys.js';
|
|
22
22
|
import { generateSessionId } from '../auth/magicLink/magicLinkCore.js';
|
|
23
23
|
import { MagicLinkService } from '../auth/magicLink/MagicLinkService.js';
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { RunView, LogError, type UserInfo, type DatabaseProviderBase } from '@memberjunction/core';
|
|
17
17
|
import type { MJConversationWidgetInstanceEntity } from '@memberjunction/core-entities';
|
|
18
|
-
import { UserCache } from '@memberjunction/
|
|
18
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
19
19
|
import type { UserPayload } from '../types.js';
|
|
20
20
|
|
|
21
21
|
const WIDGET_ENTITY = 'MJ: Conversation Widget Instances';
|
|
@@ -366,6 +366,19 @@ export class ActionResolver extends ResolverBase {
|
|
|
366
366
|
// Run the entity action
|
|
367
367
|
const result = await EntityActionEngineServer.Instance.RunEntityAction(params);
|
|
368
368
|
|
|
369
|
+
if (!result) {
|
|
370
|
+
// The binding is scoped (ScopeEntityID/ScopeRecordID) and this record falls outside it, so
|
|
371
|
+
// the action never ran. That is a legitimate answer, not a failure — dereferencing the null
|
|
372
|
+
// here turned "this binding does not apply to that record" into a server error for the
|
|
373
|
+
// caller. Reported as a successful no-op with an explicit message, so a client can tell it
|
|
374
|
+
// apart from an action that ran and did nothing.
|
|
375
|
+
return {
|
|
376
|
+
Success: true,
|
|
377
|
+
Message: 'The entity action did not run: its binding is scoped to different records.',
|
|
378
|
+
ResultData: JSON.stringify({ Ran: false, Reason: 'OutOfScope' })
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
369
382
|
// Return the result
|
|
370
383
|
return {
|
|
371
384
|
Success: result.Success,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, ObjectType, Query, Mutation, Resolver } from 'type-graphql';
|
|
2
2
|
import { UserInfo, LogError, LogStatus } from '@memberjunction/core';
|
|
3
3
|
import { UUIDsEqual, MJLruCache } from '@memberjunction/global';
|
|
4
|
-
import { UserCache } from '@memberjunction/
|
|
4
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
5
5
|
import { MJComponentRegistryEntity, ComponentMetadataEngine } from '@memberjunction/core-entities';
|
|
6
6
|
import { CredentialEngine } from '@memberjunction/credentials';
|
|
7
7
|
import { ComponentSpec } from '@memberjunction/interactive-component-types';
|
|
@@ -3,7 +3,7 @@ import { LogError } from '@memberjunction/core';
|
|
|
3
3
|
import { AppContext } from '../types.js';
|
|
4
4
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
5
5
|
import { GetReadOnlyProvider } from '../util.js';
|
|
6
|
-
import { UserCache } from '@memberjunction/
|
|
6
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
7
7
|
|
|
8
8
|
@ObjectType()
|
|
9
9
|
class EntityPermissionResult {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Resolver, Query, Mutation, Arg, Ctx, ObjectType, Field, InputType, Int, Float } from "type-graphql";
|
|
2
2
|
import { CompositeKey, DatabaseProviderBase, EntityInfo, LocalCacheManager, Metadata, RunView, UserInfo, LogError, LogStatus, IMetadataProvider, TransactionGroupBase } from "@memberjunction/core";
|
|
3
3
|
import { GetReadOnlyProvider, GetReadWriteProvider } from "../util.js";
|
|
4
|
+
import { NoLog } from "../logging/NoLog.js";
|
|
4
5
|
import { UUIDsEqual } from "@memberjunction/global";
|
|
5
6
|
import { CronExpressionHelper } from "@memberjunction/scheduling-engine";
|
|
6
7
|
import {
|
|
@@ -56,7 +57,7 @@ import { IntegrationCustomColumnPromoter } from "../integration/CustomColumnProm
|
|
|
56
57
|
import { ComputeCascadeRemovalSet, ComputeRemovedDependencyWarnings, DisableUnselectedEntityMaps, ReenableFieldMapsForEntityMap, ResetPullWatermarks, SetEntityMapEnabled } from "../integration/EntityMapLifecycle.js";
|
|
57
58
|
import { AppContext } from "../types.js";
|
|
58
59
|
import { RequireSystemUser } from "../directives/RequireSystemUser.js";
|
|
59
|
-
import { UserCache } from "@memberjunction/
|
|
60
|
+
import { UserCache } from "@memberjunction/generic-database-provider";
|
|
60
61
|
|
|
61
62
|
// ─── RSU Pipeline Output Types ──────────────────────────────────────────────
|
|
62
63
|
|
|
@@ -551,7 +552,16 @@ class CreateConnectionInput {
|
|
|
551
552
|
@Field() CompanyID: string;
|
|
552
553
|
@Field() CredentialTypeID: string;
|
|
553
554
|
@Field() CredentialName: string;
|
|
554
|
-
|
|
555
|
+
/**
|
|
556
|
+
* The credential payload being connected, as a JSON string.
|
|
557
|
+
*
|
|
558
|
+
* `@NoLog` is load-bearing here, not decorative. This field does NOT map to an
|
|
559
|
+
* entity column — the resolver assigns it onto `MJ: Credentials`.`Values`
|
|
560
|
+
* (which IS `Encrypt=true`) in procedural code further down. The metadata-driven
|
|
561
|
+
* half of the redactor keys off entity columns, so it cannot see this field, and
|
|
562
|
+
* without the mark the variables log emits the credential in plaintext.
|
|
563
|
+
*/
|
|
564
|
+
@Field() @NoLog CredentialValues: string;
|
|
555
565
|
@Field({ nullable: true }) ExternalSystemID?: string;
|
|
556
566
|
@Field({ nullable: true }) Configuration?: string;
|
|
557
567
|
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
PotentialDuplicateResult,
|
|
9
9
|
} from '@memberjunction/core';
|
|
10
10
|
import { AppContext } from '../types.js';
|
|
11
|
-
import { UserCache } from '@memberjunction/
|
|
11
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
12
12
|
|
|
13
13
|
import { CompositeKeyInputType, CompositeKeyOutputType, KeyValuePairOutputType } from '../generic/KeyInputOutputTypes.js';
|
|
14
14
|
import { GetReadOnlyProvider } from '../util.js';
|
|
@@ -227,6 +227,18 @@ export class StartRealtimeClientSessionResult {
|
|
|
227
227
|
@Field(() => String, { nullable: true })
|
|
228
228
|
ModelName?: string;
|
|
229
229
|
|
|
230
|
+
/**
|
|
231
|
+
* `DriverClass` of the vendor that actually runs the session (e.g. `OpenAIRealtime`,
|
|
232
|
+
* `ElevenLabsRealtime`). Null when unknown.
|
|
233
|
+
*
|
|
234
|
+
* Surfaced to the BROWSER because on the default-model path the framework picks the vendor itself:
|
|
235
|
+
* without this the caller cannot tell which vendor spoke, and so cannot diagnose a voice that did
|
|
236
|
+
* not land (issue #3530). `ModelName` alone is not enough — one model can resolve to different
|
|
237
|
+
* vendors depending on which API keys a deployment configures.
|
|
238
|
+
*/
|
|
239
|
+
@Field(() => String, { nullable: true })
|
|
240
|
+
DriverClass?: string;
|
|
241
|
+
|
|
230
242
|
/**
|
|
231
243
|
* DB-driven progress-narration instruction template (contains a `{{ progressMessage }}`
|
|
232
244
|
* placeholder). Null when the narration prompt is not present in this deployment's metadata —
|
|
@@ -1677,6 +1689,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1677
1689
|
ExpiresAt: cfg.ExpiresAt,
|
|
1678
1690
|
SessionConfigJson: JSON.stringify(cfg.SessionConfig),
|
|
1679
1691
|
ModelName: prep.ModelName,
|
|
1692
|
+
DriverClass: prep.DriverClass,
|
|
1680
1693
|
NarrationInstructionsTemplate: prep.NarrationInstructionsTemplate,
|
|
1681
1694
|
NarrationPaceMs: prep.NarrationPaceMs,
|
|
1682
1695
|
EffectiveConfigJson: prep.EffectiveConfig ? JSON.stringify(prep.EffectiveConfig) : undefined,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, Float, InputType, Int, ObjectType, Query, Resolver } from 'type-graphql';
|
|
2
2
|
import { EntitySearchResult, SearchEntityParams } from '@memberjunction/core';
|
|
3
3
|
import { AppContext } from '../types.js';
|
|
4
|
-
import { UserCache } from '@memberjunction/
|
|
4
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
5
5
|
import { GetReadOnlyProvider } from '../util.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -2,14 +2,13 @@ import { Arg, Ctx, Field, InputType, Mutation, ObjectType, Query, Int, Resolver
|
|
|
2
2
|
import { AppContext } from '../types.js';
|
|
3
3
|
import { Metadata, UserInfo } from '@memberjunction/core';
|
|
4
4
|
import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
|
|
5
|
-
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
6
5
|
import { v4 as uuidv4 } from 'uuid';
|
|
7
6
|
import * as path from 'path';
|
|
8
7
|
import * as fs from 'fs/promises';
|
|
9
8
|
import { loadConfig } from '../config.js';
|
|
10
9
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
11
10
|
import { GetReadOnlyProvider } from '../util.js';
|
|
12
|
-
import { SqlLoggingOptions as ProviderSqlLoggingOptions } from '@memberjunction/generic-database-provider';
|
|
11
|
+
import { UserCache, SqlLoggingOptions as ProviderSqlLoggingOptions } from '@memberjunction/generic-database-provider';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Configuration options for SQL logging sessions.
|
|
@@ -4,7 +4,7 @@ import { DatabaseProviderBase, EntityDeleteOptions, EntitySaveOptions, IMetadata
|
|
|
4
4
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
5
5
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
6
6
|
import { MJRoleEntity, MJUserEntity, MJUserRoleEntity } from '@memberjunction/core-entities';
|
|
7
|
-
import { UserCache } from '@memberjunction/
|
|
7
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
8
8
|
import { GetReadWriteProvider } from '../util.js';
|
|
9
9
|
|
|
10
10
|
@ObjectType()
|
|
@@ -112,7 +112,7 @@ export class SyncRolesAndUsersResolver {
|
|
|
112
112
|
await provider.CommitTransaction();
|
|
113
113
|
|
|
114
114
|
// refresh the user cache — one-time, since the normal auto-refresh is already scheduled
|
|
115
|
-
await UserCache.Instance.Refresh(
|
|
115
|
+
await UserCache.Instance.Refresh(provider);
|
|
116
116
|
return usersResult;
|
|
117
117
|
} catch (txErr) {
|
|
118
118
|
await provider.RollbackTransaction();
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
Query,
|
|
15
15
|
Resolver,
|
|
16
16
|
} from '@memberjunction/server';
|
|
17
|
-
import { UserCache } from '@memberjunction/
|
|
17
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
18
18
|
|
|
19
19
|
import { MJUserFavorite_, MJUserFavoriteResolverBase } from '../generated/generated.js';
|
|
20
20
|
import { GetReadOnlyProvider } from '../util.js';
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import express from 'express';
|
|
16
16
|
import { LogError, LogStatus, RunView, UserInfo } from '@memberjunction/core';
|
|
17
17
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
18
|
-
import { UserCache } from '@memberjunction/
|
|
18
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
19
19
|
import { OAuthManager, MCPClientManager } from '@memberjunction/ai-mcp-client';
|
|
20
20
|
import type { MCPServerOAuthConfig } from '@memberjunction/ai-mcp-client';
|
|
21
21
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import express from 'express';
|
|
15
15
|
import BodyParser from 'body-parser';
|
|
16
16
|
import { LogError, LogStatus, UserInfo } from '@memberjunction/core';
|
|
17
|
-
import { UserCache } from '@memberjunction/
|
|
17
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
18
18
|
import { SignatureEngine } from '@memberjunction/esignature/server';
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MJServer's implementation of the `DurableEntityActionSubmitter` seam.
|
|
3
|
+
*
|
|
4
|
+
* Turns one After* entity-action dispatch into a single-node durable task graph, which is D14's
|
|
5
|
+
* whole answer to "run this durably": not a new queue, the substrate that already has a claim
|
|
6
|
+
* protocol, restart recovery and orphan reclaim.
|
|
7
|
+
*
|
|
8
|
+
* Registered at boot beside the dispatcher, because the two are one capability — a submitter with
|
|
9
|
+
* no dispatcher writes rows nobody picks up, which is worse than staying inline.
|
|
10
|
+
*
|
|
11
|
+
* @module @memberjunction/server
|
|
12
|
+
*/
|
|
13
|
+
import type {
|
|
14
|
+
DurableEntityActionRequest,
|
|
15
|
+
DurableEntityActionSubmission,
|
|
16
|
+
DurableEntityActionSubmitter,
|
|
17
|
+
} from '@memberjunction/actions-base';
|
|
18
|
+
import { LogError } from '@memberjunction/core';
|
|
19
|
+
import { TaskNode, type TaskGraphSpec } from '@memberjunction/ai-core-plus';
|
|
20
|
+
import { MJEnvironmentEntityExtended } from '@memberjunction/core-entities';
|
|
21
|
+
import { TaskGraphService, type ProviderFactory } from '@memberjunction/task-graph';
|
|
22
|
+
|
|
23
|
+
export class DurableEntityActionTaskSubmitter implements DurableEntityActionSubmitter {
|
|
24
|
+
/**
|
|
25
|
+
* @param environmentID which environment the tasks belong to. Defaults to the platform default
|
|
26
|
+
* because a save has no environment of its own to inherit — the record's entity is global, and
|
|
27
|
+
* inventing a per-save environment would put a durable dispatch somewhere nobody looks.
|
|
28
|
+
*/
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly providerFactory: ProviderFactory,
|
|
31
|
+
private readonly environmentID: string = MJEnvironmentEntityExtended.DefaultEnvironmentID,
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
public async Submit(request: DurableEntityActionRequest): Promise<DurableEntityActionSubmission> {
|
|
35
|
+
try {
|
|
36
|
+
const spec: TaskGraphSpec = {
|
|
37
|
+
workflowName: `${request.ActionName} — ${request.EntityName}`,
|
|
38
|
+
reasoning:
|
|
39
|
+
`Durable dispatch of entity action binding ${request.EntityActionID} on ` +
|
|
40
|
+
`${request.EntityName} (${request.RecordID}), fired by ${request.InvocationType}.`,
|
|
41
|
+
tasks: [
|
|
42
|
+
TaskNode.Action(
|
|
43
|
+
{
|
|
44
|
+
tempId: 'action',
|
|
45
|
+
name: request.ActionName,
|
|
46
|
+
description: `Run ${request.ActionName} for ${request.EntityName} ${request.RecordID}.`,
|
|
47
|
+
dependsOn: [],
|
|
48
|
+
inputPayload: request.RedactedParams,
|
|
49
|
+
},
|
|
50
|
+
{ actionName: request.ActionName },
|
|
51
|
+
),
|
|
52
|
+
],
|
|
53
|
+
// Nothing to say to anyone: this graph exists because a record was saved, not
|
|
54
|
+
// because a person asked a question. Defaulting to 'message' would post an
|
|
55
|
+
// orphaned result into whichever conversation happened to be nearby.
|
|
56
|
+
continuation: 'none',
|
|
57
|
+
// The point of the exercise. A one-node graph constant-folds to in-run execution by
|
|
58
|
+
// default (D9), which is exactly the fire-and-forget behaviour being escaped.
|
|
59
|
+
durable: true,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const provider = await this.providerFactory.CreateProvider();
|
|
63
|
+
const result = await new TaskGraphService().Submit(spec, {
|
|
64
|
+
EnvironmentID: this.environmentID,
|
|
65
|
+
ContextUser: request.ContextUser,
|
|
66
|
+
Provider: provider,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return result.Success
|
|
70
|
+
? { Success: true, ParentTaskID: result.ParentTaskID }
|
|
71
|
+
: { Success: false, ErrorMessage: result.ErrorMessage };
|
|
72
|
+
} catch (e) {
|
|
73
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
74
|
+
LogError(`[DurableEntityActionTaskSubmitter] Submission failed for binding ${request.EntityActionID}: ${message}`);
|
|
75
|
+
return { Success: false, ErrorMessage: message };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { LogError, LogStatus, LogStatusEx, UserInfo } from '@memberjunction/core';
|
|
7
|
-
import { UserCache } from '@memberjunction/
|
|
7
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
8
8
|
import { SchedulingEngine } from '@memberjunction/scheduling-engine';
|
|
9
9
|
import { ScheduledJobsConfig } from '../config.js';
|
|
10
10
|
|
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { UserInfo } from '@memberjunction/core';
|
|
20
20
|
import { LoadTaskGraphOperations, TaskGraphDispatcher } from '@memberjunction/task-graph';
|
|
21
|
+
import { TaskGraphPromptRunner } from './TaskGraphPromptRunner.js';
|
|
21
22
|
import sql from 'mssql';
|
|
23
|
+
import { DurableEntityActionRegistry } from '@memberjunction/actions-base';
|
|
22
24
|
import { CreateTaskGraphProviderFactory } from './TaskGraphProviderFactory.js';
|
|
25
|
+
import { TaskGraphActionRunner } from './TaskGraphActionRunner.js';
|
|
23
26
|
import { TaskGraphAgentRunner } from './TaskGraphAgentRunner.js';
|
|
27
|
+
import { DurableEntityActionTaskSubmitter } from './DurableEntityActionTaskSubmitter.js';
|
|
24
28
|
import { TaskGraphContinuationDeliverer } from './TaskGraphContinuationDeliverer.js';
|
|
25
29
|
import { PubSubManager } from '../generic/PubSubManager.js';
|
|
26
30
|
import { TaskGraphFrameBroadcaster } from '../resolvers/TaskGraphFrameResolver.js';
|
|
@@ -55,7 +59,20 @@ export async function StartTaskGraphDispatcher(
|
|
|
55
59
|
// Live signal. Supplied only when a PubSub engine exists — in a worker or a test there is
|
|
56
60
|
// nobody to publish to, and the dispatcher runs identically without one.
|
|
57
61
|
pubSub ? new TaskGraphFrameBroadcaster(pubSub) : undefined,
|
|
62
|
+
// Action nodes — the third execution shape, beside agents and people. Durable After* entity
|
|
63
|
+
// actions land as these.
|
|
64
|
+
new TaskGraphActionRunner(),
|
|
65
|
+
// Prompt nodes. Without this a Flow agent with a Prompt step submits and then
|
|
66
|
+
// stalls on a task nothing can execute — which is why the shipped User Onboarding
|
|
67
|
+
// Flow Agent could not run at all before this seam existed.
|
|
68
|
+
new TaskGraphPromptRunner(),
|
|
58
69
|
);
|
|
59
70
|
await dispatcher.Start();
|
|
71
|
+
|
|
72
|
+
// Registered only AFTER the dispatcher is running, and only here. A submitter without a
|
|
73
|
+
// dispatcher writes Task rows nobody picks up — strictly worse than staying inline, because the
|
|
74
|
+
// work is now both undone and invisible in the place it used to be logged.
|
|
75
|
+
DurableEntityActionRegistry.Instance.Register(new DurableEntityActionTaskSubmitter(providerFactory));
|
|
76
|
+
|
|
60
77
|
return dispatcher;
|
|
61
78
|
}
|