@memberjunction/server 5.40.2 → 5.42.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/agentSessions/HostInstance.d.ts +19 -0
- package/dist/agentSessions/HostInstance.d.ts.map +1 -0
- package/dist/agentSessions/HostInstance.js +48 -0
- package/dist/agentSessions/HostInstance.js.map +1 -0
- package/dist/agentSessions/SessionJanitor.d.ts +97 -0
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -0
- package/dist/agentSessions/SessionJanitor.js +222 -0
- package/dist/agentSessions/SessionJanitor.js.map +1 -0
- package/dist/agentSessions/SessionManager.d.ts +142 -0
- package/dist/agentSessions/SessionManager.d.ts.map +1 -0
- package/dist/agentSessions/SessionManager.js +308 -0
- package/dist/agentSessions/SessionManager.js.map +1 -0
- package/dist/agentSessions/index.d.ts +5 -0
- package/dist/agentSessions/index.d.ts.map +1 -0
- package/dist/agentSessions/index.js +31 -0
- package/dist/agentSessions/index.js.map +1 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts +102 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts.map +1 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.js +164 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.js.map +1 -0
- package/dist/agentSessions/remoteBrowserGoalRegistry.d.ts +66 -0
- package/dist/agentSessions/remoteBrowserGoalRegistry.d.ts.map +1 -0
- package/dist/agentSessions/remoteBrowserGoalRegistry.js +86 -0
- package/dist/agentSessions/remoteBrowserGoalRegistry.js.map +1 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +4 -2
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -2
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +41 -7
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +1296 -49
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +50343 -43421
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.js +1 -1
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.js +9 -10
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +127 -54
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts +108 -0
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -0
- package/dist/integration/CustomColumnPromoter.js +508 -0
- package/dist/integration/CustomColumnPromoter.js.map +1 -0
- package/dist/logging/StartupLogger.d.ts +177 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -0
- package/dist/logging/StartupLogger.js +354 -0
- package/dist/logging/StartupLogger.js.map +1 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +21 -2
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts +42 -0
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -0
- package/dist/resolvers/AgentSessionResolver.js +152 -0
- package/dist/resolvers/AgentSessionResolver.js.map +1 -0
- package/dist/resolvers/EntityPermissionResolver.d.ts +16 -0
- package/dist/resolvers/EntityPermissionResolver.d.ts.map +1 -0
- package/dist/resolvers/EntityPermissionResolver.js +95 -0
- package/dist/resolvers/EntityPermissionResolver.js.map +1 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +27 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +136 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -0
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +101 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +544 -42
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +121 -0
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.js +533 -0
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +688 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeClientSessionResolver.js +1774 -0
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +432 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.js +1108 -0
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +0 -7
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +5 -3
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/services/ScheduledJobsService.d.ts.map +1 -1
- package/dist/services/ScheduledJobsService.js +6 -5
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/package.json +83 -74
- package/src/__tests__/RealtimeBridgeResolver.test.ts +100 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +2605 -0
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +181 -0
- package/src/__tests__/RemoteBrowserGoalEngine.test.ts +144 -0
- package/src/__tests__/SessionJanitor.test.ts +234 -0
- package/src/__tests__/SessionManager.test.ts +465 -0
- package/src/__tests__/remoteBrowserGoalRegistry.test.ts +81 -0
- package/src/__tests__/subscriptionRedaction.test.ts +5 -0
- package/src/agentSessions/HostInstance.ts +53 -0
- package/src/agentSessions/SessionJanitor.ts +267 -0
- package/src/agentSessions/SessionManager.ts +446 -0
- package/src/agentSessions/index.ts +32 -0
- package/src/agentSessions/remoteBrowserGoalEngine.ts +191 -0
- package/src/agentSessions/remoteBrowserGoalRegistry.ts +122 -0
- package/src/auth/initializeProviders.ts +4 -2
- package/src/config.ts +13 -2
- package/src/context.ts +42 -7
- package/src/generated/generated.ts +33808 -29025
- package/src/generic/ResolverBase.ts +1 -1
- package/src/generic/RunViewResolver.ts +9 -9
- package/src/index.ts +128 -54
- package/src/integration/CustomColumnPromoter.ts +597 -0
- package/src/logging/StartupLogger.ts +441 -0
- package/src/logging/variablesLoggingMiddleware.ts +25 -5
- package/src/resolvers/AgentSessionResolver.ts +138 -0
- package/src/resolvers/EntityPermissionResolver.ts +73 -0
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +108 -0
- package/src/resolvers/IntegrationDiscoveryResolver.ts +396 -37
- package/src/resolvers/RealtimeBridgeResolver.ts +450 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +2162 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +1122 -0
- package/src/resolvers/RunAIAgentResolver.ts +1 -9
- package/src/rest/setupRESTEndpoints.ts +5 -3
- package/src/services/ScheduledJobsService.ts +6 -5
|
@@ -638,7 +638,7 @@ export class ResolverBase {
|
|
|
638
638
|
// user has. The API key's associated user (in userPayload.userRecord) is
|
|
639
639
|
// used later when the actual operation executes - their permissions are
|
|
640
640
|
// the ultimate ceiling that scopes can only narrow, never expand.
|
|
641
|
-
const systemUser = UserCache.Instance.
|
|
641
|
+
const systemUser = UserCache.Instance.GetSystemUser();
|
|
642
642
|
if (!systemUser) {
|
|
643
643
|
throw new Error('System user not found');
|
|
644
644
|
}
|
|
@@ -721,7 +721,7 @@ export class RunViewResolver extends ResolverBase {
|
|
|
721
721
|
return null;
|
|
722
722
|
|
|
723
723
|
const viewInfo = super.safeFirstArrayElement<MJUserViewEntityExtended>(await super.findBy<MJUserViewEntityExtended>(provider, "MJ: User Views", { Name: input.ViewName }, userPayload.userRecord));
|
|
724
|
-
const entity = provider.
|
|
724
|
+
const entity = provider.EntityByID(viewInfo.EntityID);
|
|
725
725
|
const returnData = this.processRawData(rawData.Results, viewInfo.EntityID, entity);
|
|
726
726
|
return {
|
|
727
727
|
Results: returnData,
|
|
@@ -752,7 +752,7 @@ export class RunViewResolver extends ResolverBase {
|
|
|
752
752
|
return null;
|
|
753
753
|
|
|
754
754
|
const viewInfo = super.safeFirstArrayElement<MJUserViewEntityExtended>(await super.findBy<MJUserViewEntityExtended>(provider, "MJ: User Views", { ID: input.ViewID }, userPayload.userRecord));
|
|
755
|
-
const entity = provider.
|
|
755
|
+
const entity = provider.EntityByID(viewInfo.EntityID);
|
|
756
756
|
const returnData = this.processRawData(rawData.Results, viewInfo.EntityID, entity);
|
|
757
757
|
return {
|
|
758
758
|
Results: returnData,
|
|
@@ -781,7 +781,7 @@ export class RunViewResolver extends ResolverBase {
|
|
|
781
781
|
const rawData = await super.RunDynamicViewGeneric(input, provider, userPayload, pubSub);
|
|
782
782
|
if (rawData === null) return null;
|
|
783
783
|
|
|
784
|
-
const entity = provider.
|
|
784
|
+
const entity = provider.EntityByName(input.EntityName);
|
|
785
785
|
const returnData = this.processRawData(rawData.Results, entity.ID, entity);
|
|
786
786
|
return {
|
|
787
787
|
Results: returnData,
|
|
@@ -816,7 +816,7 @@ export class RunViewResolver extends ResolverBase {
|
|
|
816
816
|
let results: RunViewGenericResult[] = [];
|
|
817
817
|
for (const [index, data] of rawData.entries()) {
|
|
818
818
|
// EntityName is backfilled by RunViewsGeneric when ViewID/ViewName was used
|
|
819
|
-
const entity = input[index].EntityName ? provider.
|
|
819
|
+
const entity = input[index].EntityName ? provider.EntityByName(input[index].EntityName) : null;
|
|
820
820
|
|
|
821
821
|
const returnData: any[] = this.processRawData(data.Results, entity ? entity.ID : null, entity);
|
|
822
822
|
|
|
@@ -860,7 +860,7 @@ export class RunViewResolver extends ResolverBase {
|
|
|
860
860
|
};
|
|
861
861
|
}
|
|
862
862
|
|
|
863
|
-
const entity = provider.
|
|
863
|
+
const entity = provider.EntityByName(input.ViewName);
|
|
864
864
|
const entityId = entity ? entity.ID : null;
|
|
865
865
|
const returnData = this.processRawData(rawData.Results, entityId, entity);
|
|
866
866
|
return {
|
|
@@ -908,7 +908,7 @@ export class RunViewResolver extends ResolverBase {
|
|
|
908
908
|
}
|
|
909
909
|
|
|
910
910
|
const viewInfo = super.safeFirstArrayElement<MJUserViewEntityExtended>(await super.findBy<MJUserViewEntityExtended>(provider, "MJ: User Views", { ID: input.ViewID }, userPayload.userRecord));
|
|
911
|
-
const entity = provider.
|
|
911
|
+
const entity = provider.EntityByID(viewInfo.EntityID);
|
|
912
912
|
const returnData = this.processRawData(rawData.Results, viewInfo.EntityID, entity);
|
|
913
913
|
return {
|
|
914
914
|
Results: returnData,
|
|
@@ -954,7 +954,7 @@ export class RunViewResolver extends ResolverBase {
|
|
|
954
954
|
};
|
|
955
955
|
}
|
|
956
956
|
|
|
957
|
-
const entity = provider.
|
|
957
|
+
const entity = provider.EntityByName(input.EntityName);
|
|
958
958
|
if (!entity) {
|
|
959
959
|
const errorMsg = `Entity ${input.EntityName} not found in metadata`;
|
|
960
960
|
LogError(new Error(errorMsg));
|
|
@@ -1007,7 +1007,7 @@ export class RunViewResolver extends ResolverBase {
|
|
|
1007
1007
|
|
|
1008
1008
|
let results: RunViewGenericResult[] = [];
|
|
1009
1009
|
for (const [index, data] of rawData.entries()) {
|
|
1010
|
-
const entity = provider.
|
|
1010
|
+
const entity = provider.EntityByName(input[index].EntityName);
|
|
1011
1011
|
if (!entity) {
|
|
1012
1012
|
LogError(new Error(`Entity with name ${input[index].EntityName} not found`));
|
|
1013
1013
|
continue;
|
|
@@ -1084,7 +1084,7 @@ export class RunViewResolver extends ResolverBase {
|
|
|
1084
1084
|
// Transform results to include processed data rows
|
|
1085
1085
|
const transformedResults: RunViewWithCacheCheckResultOutput[] = response.results.map((result, index) => {
|
|
1086
1086
|
const inputItem = input[index];
|
|
1087
|
-
const entity =
|
|
1087
|
+
const entity = inputItem.params.EntityName ? provider.EntityByName(inputItem.params.EntityName) : undefined;
|
|
1088
1088
|
|
|
1089
1089
|
// If we have differential data but no entity, that's a configuration error
|
|
1090
1090
|
if (result.status === 'differential' && result.differentialData && !entity) {
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { resolveDbPlatformFromEnv } from '@memberjunction/generic-database-provi
|
|
|
9
9
|
import { MJGlobal, MJEventType, UUIDsEqual, ShutdownRegistry } from '@memberjunction/global';
|
|
10
10
|
import { setupSQLServerClient, SQLServerDataProvider, SQLServerProviderConfigData, UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
11
11
|
import { extendConnectionPoolWithQuery } from './util.js';
|
|
12
|
+
import { registerIntegrationCustomColumnPromoter } from './integration/CustomColumnPromoter.js';
|
|
12
13
|
import { default as BodyParser } from 'body-parser';
|
|
13
14
|
import compression from 'compression'; // Add compression middleware
|
|
14
15
|
import cors from 'cors';
|
|
@@ -17,6 +18,7 @@ import { default as fg } from 'fast-glob';
|
|
|
17
18
|
import { useServer } from 'graphql-ws/lib/use/ws';
|
|
18
19
|
import { createServer } from 'node:http';
|
|
19
20
|
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { readFileSync } from 'node:fs';
|
|
20
22
|
import { sep } from 'node:path';
|
|
21
23
|
import 'reflect-metadata';
|
|
22
24
|
import { ReplaySubject } from 'rxjs';
|
|
@@ -25,13 +27,15 @@ import { PubSub } from 'graphql-subscriptions';
|
|
|
25
27
|
import sql from 'mssql';
|
|
26
28
|
import { WebSocketServer } from 'ws';
|
|
27
29
|
import buildApolloServer from './apolloServer/index.js';
|
|
28
|
-
import { configInfo, dbDatabase, dbHost, dbPort, dbUsername, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage, RESTApiOptions } from './config.js';
|
|
30
|
+
import { configInfo, configFilePath, dbDatabase, dbHost, dbPort, dbUsername, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage, RESTApiOptions } from './config.js';
|
|
29
31
|
import { default as jwt } from 'jsonwebtoken';
|
|
30
32
|
import { contextFunction, createUnifiedAuthMiddleware, getUserPayload } from './context.js';
|
|
31
33
|
import { UserPayload } from './types.js';
|
|
32
34
|
import { requireSystemUserDirective, publicDirective } from './directives/index.js';
|
|
33
35
|
import { variablesLoggingMiddleware } from './logging/variablesLoggingMiddleware.js';
|
|
34
36
|
import { auditResolversForUndecoratedArgs } from './logging/bootAudit.js';
|
|
37
|
+
import { StartupLogger } from './logging/StartupLogger.js';
|
|
38
|
+
import { AuthProviderFactory } from '@memberjunction/auth-providers';
|
|
35
39
|
import createMSSQLConfig from './orm.js';
|
|
36
40
|
import { setupRESTEndpoints } from './rest/setupRESTEndpoints.js';
|
|
37
41
|
import { createOAuthCallbackHandler } from './rest/OAuthCallbackHandler.js';
|
|
@@ -43,7 +47,7 @@ import { DataSourceInfo, raiseEvent } from './types.js';
|
|
|
43
47
|
|
|
44
48
|
import { ExternalChangeDetectorEngine } from '@memberjunction/external-change-detection';
|
|
45
49
|
import { ScheduledJobsService } from './services/ScheduledJobsService.js';
|
|
46
|
-
import { LocalCacheManager, StartupManager, TelemetryManager, TelemetryLevel } from '@memberjunction/core';
|
|
50
|
+
import { LocalCacheManager, StartupManager, TelemetryManager, TelemetryLevel, LogStatus, SetVerboseLogging } from '@memberjunction/core';
|
|
47
51
|
import { getSystemUser } from './auth/index.js';
|
|
48
52
|
import { GetAPIKeyEngine } from '@memberjunction/api-keys';
|
|
49
53
|
import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
|
|
@@ -52,6 +56,7 @@ import { PubSubManager } from './generic/PubSubManager.js';
|
|
|
52
56
|
import { IntegrationProgressEmitter } from '@memberjunction/integration-progress-artifacts';
|
|
53
57
|
import { PublishIntegrationProgress } from './resolvers/IntegrationProgressResolver.js';
|
|
54
58
|
import { ClientToolRequestManager, AgentRunWatchdog } from '@memberjunction/ai-agents';
|
|
59
|
+
import { SessionJanitor } from './agentSessions/index.js';
|
|
55
60
|
import { CACHE_INVALIDATION_TOPIC } from './generic/CacheInvalidationResolver.js';
|
|
56
61
|
import { ConnectorFactory, IntegrationEngine, IntegrationSyncOptions } from '@memberjunction/integration-engine';
|
|
57
62
|
import { CronExpressionHelper } from '@memberjunction/scheduling-engine';
|
|
@@ -167,6 +172,10 @@ export * from './resolvers/UserViewResolver.js';
|
|
|
167
172
|
export * from './resolvers/VersionHistoryResolver.js';
|
|
168
173
|
export * from './resolvers/CurrentUserContextResolver.js';
|
|
169
174
|
export * from './resolvers/RSUResolver.js';
|
|
175
|
+
export * from './resolvers/AgentSessionResolver.js';
|
|
176
|
+
export * from './resolvers/RealtimeClientSessionResolver.js';
|
|
177
|
+
export * from './resolvers/RemoteBrowserActionResolver.js';
|
|
178
|
+
export * from './agentSessions/index.js';
|
|
170
179
|
export { GetReadOnlyDataSource, GetReadWriteDataSource, GetReadWriteProvider, GetReadOnlyProvider } from './util.js';
|
|
171
180
|
|
|
172
181
|
export * from './generated/generated.js';
|
|
@@ -194,13 +203,37 @@ const localPath = (p: string) => {
|
|
|
194
203
|
|
|
195
204
|
export const createApp = (): Application => express();
|
|
196
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Resolves the MJServer package version for the startup summary header.
|
|
208
|
+
*
|
|
209
|
+
* Reads `@memberjunction/server`'s own `package.json` relative to this module.
|
|
210
|
+
* Returns `undefined` (and the header omits the version) if it can't be read,
|
|
211
|
+
* so version display is best-effort and never blocks startup.
|
|
212
|
+
*/
|
|
213
|
+
function resolveServerVersion(): string | undefined {
|
|
214
|
+
try {
|
|
215
|
+
// package.json sits at the package root, two levels up from dist/ (or src/).
|
|
216
|
+
const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
|
|
217
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version?: string };
|
|
218
|
+
return pkg.version;
|
|
219
|
+
} catch {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
197
224
|
export const serve = async (resolverPaths: Array<string>, app: Application = createApp(), options?: MJServerOptions): Promise<void> => {
|
|
198
225
|
const t0 = performance.now();
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
226
|
+
// Level-gated startup logger. Resolves verbosity from telemetry.level (single
|
|
227
|
+
// operator knob). At `standard` (default), per-phase timings are collapsed into
|
|
228
|
+
// the one-line summary; at `verbose`+ each phase prints inline as before.
|
|
229
|
+
const startupLog = new StartupLogger();
|
|
230
|
+
// Wire the GLOBAL verbose-logging gate to the resolved server level, so cross-package
|
|
231
|
+
// LogStatusEx({ verboseOnly: true }) lines (cache, scheduling, integration, MCP, …) honor the
|
|
232
|
+
// configured level instead of defaulting to hidden. Without this, "verbose-only" is "always hidden".
|
|
233
|
+
SetVerboseLogging(startupLog.IsAtLeast('verbose'));
|
|
234
|
+
// Transient boot spinner (standard level + TTY only); cleared by PrintSummary.
|
|
235
|
+
startupLog.StartBoot();
|
|
236
|
+
const lap = (label: string, since: number) => startupLog.EndPhase(label, since);
|
|
204
237
|
|
|
205
238
|
const localResolverPaths = ['resolvers/**/*Resolver.{js,ts}', 'generic/*Resolver.{js,ts}', 'generated/generated.{js,ts}'].map(localPath);
|
|
206
239
|
|
|
@@ -220,7 +253,8 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
220
253
|
|
|
221
254
|
if (dbType === 'postgresql') {
|
|
222
255
|
// ─── PostgreSQL Path ───────────────────────────────────────────
|
|
223
|
-
|
|
256
|
+
startupLog.BeginPhase('Connecting to database');
|
|
257
|
+
startupLog.LogIf('verbose', 'Database type: PostgreSQL');
|
|
224
258
|
const pg = await import('pg');
|
|
225
259
|
const { PostgreSQLDataProvider, PostgreSQLProviderConfigData } = await import('@memberjunction/postgresql-dataprovider');
|
|
226
260
|
|
|
@@ -246,7 +280,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
246
280
|
const testClient = await pgPool.connect();
|
|
247
281
|
await testClient.query('SELECT 1');
|
|
248
282
|
testClient.release();
|
|
249
|
-
|
|
283
|
+
startupLog.LogIf('verbose', `PostgreSQL pool connected to ${pgHost}:${pgPort}/${pgDatabase}`);
|
|
250
284
|
|
|
251
285
|
// Create a DataSourceInfo with a MSSQL-compatible wrapper around pg.Pool
|
|
252
286
|
// This allows existing code (types, util, context) to work without changes
|
|
@@ -338,12 +372,11 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
338
372
|
const codegenPgProvider = new PostgreSQLDataProvider();
|
|
339
373
|
await codegenPgProvider.Config(codegenPgConfigData); // separate pool (per-instance manager); does NOT touch the global API provider
|
|
340
374
|
RuntimeSchemaManager.Instance.SetDDLProvider(codegenPgProvider);
|
|
341
|
-
|
|
375
|
+
startupLog.LogIf('verbose', 'RSU DDL provider initialized with CodeGen credentials (PostgreSQL).');
|
|
342
376
|
|
|
343
377
|
// Set up in-process CodeGen runner for RSU
|
|
344
378
|
try {
|
|
345
|
-
const { RunCodeGenBase } = await import('@memberjunction/codegen-lib');
|
|
346
|
-
const { PostgreSQLCodeGenConnection } = await import('@memberjunction/codegen-lib/dist/Database/providers/postgresql/PostgreSQLCodeGenConnection.js');
|
|
379
|
+
const { RunCodeGenBase, PostgreSQLCodeGenConnection } = await import('@memberjunction/codegen-lib');
|
|
347
380
|
|
|
348
381
|
const codegenConnection = new PostgreSQLCodeGenConnection(codegenPgPool);
|
|
349
382
|
const codegenCurrentUser = UserCache.Instance.Users.find(u => u.Type?.trim().toLowerCase() === 'owner') ?? UserCache.Instance.Users[0];
|
|
@@ -361,21 +394,21 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
361
394
|
RuntimeSchemaManager.Instance.SetCodeGenRunner({
|
|
362
395
|
RunInProcess: (skipDB) => runObject.RunInProcess(codegenDataSource, skipDB, rsuWorkDir),
|
|
363
396
|
});
|
|
364
|
-
|
|
397
|
+
startupLog.LogIf('verbose', 'RSU in-process CodeGen runner initialized (PostgreSQL).');
|
|
365
398
|
|
|
366
399
|
// Inject CodeGen output paths for targeted git staging
|
|
367
400
|
const { initializeConfig } = await import('@memberjunction/codegen-lib');
|
|
368
401
|
const cgConfig = initializeConfig(rsuWorkDir);
|
|
369
402
|
const outputPaths = (cgConfig.output ?? []).map((o: { directory: string }) => o.directory);
|
|
370
403
|
RuntimeSchemaManager.Instance.SetCodeGenOutputPaths(outputPaths);
|
|
371
|
-
|
|
404
|
+
startupLog.LogIf('verbose', `RSU CodeGen output paths: ${outputPaths.length} directories configured.`);
|
|
372
405
|
|
|
373
406
|
// Point RSU's soft PK/FK writer at the SAME file CodeGen reads (mj.config.cjs
|
|
374
407
|
// `additionalSchemaInfo`), or RSU writes soft PKs to its own default path while
|
|
375
408
|
// CodeGen reads a different one and skips integration tables with "No primary key found".
|
|
376
409
|
if (cgConfig.additionalSchemaInfo) {
|
|
377
410
|
RuntimeSchemaManager.Instance.SetAdditionalSchemaInfoPath(cgConfig.additionalSchemaInfo);
|
|
378
|
-
|
|
411
|
+
startupLog.LogIf('verbose', `RSU additionalSchemaInfo path: ${cgConfig.additionalSchemaInfo}`);
|
|
379
412
|
}
|
|
380
413
|
} catch (codegenErr) {
|
|
381
414
|
console.warn(`RSU in-process CodeGen runner setup failed (will fall back to child process): ${(codegenErr as Error).message}`);
|
|
@@ -386,11 +419,13 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
386
419
|
}
|
|
387
420
|
|
|
388
421
|
const md = new Metadata(); // global-provider-ok: bootstrap
|
|
389
|
-
|
|
422
|
+
const pgEntityCount = md?.Entities ? md.Entities.length : 0;
|
|
423
|
+
startupLog.LogIf('verbose', `Data Source has been initialized. ${pgEntityCount} entities loaded.`);
|
|
424
|
+
startupLog.SetDatabaseInfo('PostgreSQL', `${pgHost}:${pgPort}/${pgDatabase}`, pgEntityCount);
|
|
390
425
|
} else {
|
|
391
426
|
// ─── SQL Server Path (existing behavior) ───────────────────────
|
|
392
|
-
|
|
393
|
-
let tPhase =
|
|
427
|
+
startupLog.LogIf('verbose', 'Database type: SQL Server');
|
|
428
|
+
let tPhase = startupLog.BeginPhase('Connecting to database');
|
|
394
429
|
const pool = new sql.ConnectionPool(createMSSQLConfig());
|
|
395
430
|
|
|
396
431
|
// Handle connection-level errors from dead/stale connections in the pool.
|
|
@@ -401,7 +436,8 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
401
436
|
});
|
|
402
437
|
|
|
403
438
|
await pool.connect();
|
|
404
|
-
|
|
439
|
+
lap('DB Pool Connect', tPhase);
|
|
440
|
+
tPhase = startupLog.BeginPhase('Loading metadata + providers');
|
|
405
441
|
|
|
406
442
|
dataSources.push(new DataSourceInfo({dataSource: pool, type: 'Read-Write', host: dbHost, port: dbPort, database: dbDatabase, userName: dbUsername}));
|
|
407
443
|
|
|
@@ -421,14 +457,17 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
421
457
|
await readOnlyPool.connect();
|
|
422
458
|
|
|
423
459
|
dataSources.push(new DataSourceInfo({dataSource: readOnlyPool, type: 'Read-Only', host: dbHost, port: dbPort, database: dbDatabase, userName: configInfo.dbReadOnlyUsername}));
|
|
424
|
-
|
|
460
|
+
startupLog.LogIf('verbose', 'Read-only Connection Pool has been initialized.');
|
|
425
461
|
}
|
|
426
462
|
|
|
427
463
|
const config = new SQLServerProviderConfigData(pool, mj_core_schema, cacheRefreshInterval);
|
|
428
464
|
await setupSQLServerClient(config);
|
|
429
|
-
|
|
465
|
+
lap('Metadata + Provider Setup', tPhase);
|
|
466
|
+
startupLog.BeginPhase('Initializing data provider');
|
|
430
467
|
const md = new Metadata(); // global-provider-ok: bootstrap
|
|
431
|
-
|
|
468
|
+
const entityCount = md?.Entities ? md.Entities.length : 0;
|
|
469
|
+
startupLog.LogIf('verbose', `Data Source has been initialized. ${entityCount} entities loaded.`);
|
|
470
|
+
startupLog.SetDatabaseInfo('SQL Server', `${dbHost}:${dbPort}/${dbDatabase}`, entityCount);
|
|
432
471
|
|
|
433
472
|
// Set up CodeGen-credentialed provider for RSU DDL operations (CREATE TABLE, CREATE SCHEMA, etc.)
|
|
434
473
|
const codegenUser = process.env.CODEGEN_DB_USERNAME;
|
|
@@ -457,12 +496,11 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
457
496
|
const codegenProvider = new SQLServerDataProvider();
|
|
458
497
|
await codegenProvider.Config(codegenConfig);
|
|
459
498
|
RuntimeSchemaManager.Instance.SetDDLProvider(codegenProvider);
|
|
460
|
-
|
|
499
|
+
startupLog.LogIf('verbose', 'RSU DDL provider initialized with CodeGen credentials.');
|
|
461
500
|
|
|
462
501
|
// Set up in-process CodeGen runner for RSU
|
|
463
502
|
try {
|
|
464
|
-
const { RunCodeGenBase } = await import('@memberjunction/codegen-lib');
|
|
465
|
-
const { SQLServerCodeGenConnection } = await import('@memberjunction/codegen-lib/dist/Database/providers/sqlserver/SQLServerCodeGenConnection.js');
|
|
503
|
+
const { RunCodeGenBase, SQLServerCodeGenConnection } = await import('@memberjunction/codegen-lib');
|
|
466
504
|
|
|
467
505
|
const codegenConnection = new SQLServerCodeGenConnection(codegenPool);
|
|
468
506
|
const codegenCurrentUser = UserCache.Instance.Users.find(u => u.Type?.trim().toLowerCase() === 'owner') ?? UserCache.Instance.Users[0];
|
|
@@ -480,14 +518,14 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
480
518
|
RuntimeSchemaManager.Instance.SetCodeGenRunner({
|
|
481
519
|
RunInProcess: (skipDB) => runObject.RunInProcess(codegenDataSource, skipDB, rsuWorkDir),
|
|
482
520
|
});
|
|
483
|
-
|
|
521
|
+
startupLog.LogIf('verbose', 'RSU in-process CodeGen runner initialized.');
|
|
484
522
|
|
|
485
523
|
// Inject CodeGen output paths for targeted git staging
|
|
486
524
|
const { initializeConfig } = await import('@memberjunction/codegen-lib');
|
|
487
525
|
const codegenConfig = initializeConfig(rsuWorkDir);
|
|
488
526
|
const outputPaths = (codegenConfig.output ?? []).map((o: { directory: string }) => o.directory);
|
|
489
527
|
RuntimeSchemaManager.Instance.SetCodeGenOutputPaths(outputPaths);
|
|
490
|
-
|
|
528
|
+
startupLog.LogIf('verbose', `RSU CodeGen output paths: ${outputPaths.length} directories configured.`);
|
|
491
529
|
|
|
492
530
|
// Point RSU's soft PK/FK writer at the SAME file CodeGen reads (mj.config.cjs
|
|
493
531
|
// `additionalSchemaInfo`). Without this, RSU writes soft PKs to its own default
|
|
@@ -495,7 +533,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
495
533
|
// with "No primary key found".
|
|
496
534
|
if (codegenConfig.additionalSchemaInfo) {
|
|
497
535
|
RuntimeSchemaManager.Instance.SetAdditionalSchemaInfoPath(codegenConfig.additionalSchemaInfo);
|
|
498
|
-
|
|
536
|
+
startupLog.LogIf('verbose', `RSU additionalSchemaInfo path: ${codegenConfig.additionalSchemaInfo}`);
|
|
499
537
|
}
|
|
500
538
|
} catch (codegenErr) {
|
|
501
539
|
console.warn(`RSU in-process CodeGen runner setup failed (will fall back to child process): ${(codegenErr as Error).message}`);
|
|
@@ -506,7 +544,15 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
506
544
|
}
|
|
507
545
|
}
|
|
508
546
|
|
|
509
|
-
|
|
547
|
+
// Register the post-sync custom-column promotion hook (gaps.md §2). Safe to call regardless of
|
|
548
|
+
// RSU config: the hook self-gates on captured overflow data and uses RSU only at fire time.
|
|
549
|
+
try {
|
|
550
|
+
registerIntegrationCustomColumnPromoter();
|
|
551
|
+
} catch (err) {
|
|
552
|
+
console.warn(`Custom-column promoter registration failed (post-sync promotion disabled): ${(err as Error).message}`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
let tServe = startupLog.BeginPhase('Initializing services');
|
|
510
556
|
|
|
511
557
|
// Store queryDialects config in GlobalObjectStore so MJQueryEntityServer can
|
|
512
558
|
// read it without a circular dependency on MJServer
|
|
@@ -521,10 +567,10 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
521
567
|
if (configInfo.telemetry?.level) {
|
|
522
568
|
tm.UpdateSettings({ level: configInfo.telemetry.level as TelemetryLevel });
|
|
523
569
|
}
|
|
524
|
-
|
|
570
|
+
startupLog.LogIf('verbose', `Server telemetry enabled with level: ${configInfo.telemetry.level || 'standard'}`);
|
|
525
571
|
} else {
|
|
526
572
|
tm.SetEnabled(false);
|
|
527
|
-
|
|
573
|
+
startupLog.LogIf('verbose', 'Server telemetry disabled');
|
|
528
574
|
}
|
|
529
575
|
|
|
530
576
|
// Optionally inject Redis as the shared storage provider for cross-server cache invalidation
|
|
@@ -559,7 +605,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
559
605
|
}
|
|
560
606
|
});
|
|
561
607
|
|
|
562
|
-
|
|
608
|
+
startupLog.LogIf('verbose', `Redis cache provider connected: ${process.env.REDIS_URL}`);
|
|
563
609
|
}
|
|
564
610
|
|
|
565
611
|
// If Redis is available, swap LocalCacheManager's storage provider to Redis.
|
|
@@ -567,7 +613,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
567
613
|
// during engine loading. SetStorageProvider migrates cached data to Redis.
|
|
568
614
|
if (process.env.REDIS_URL) {
|
|
569
615
|
await LocalCacheManager.Instance.SetStorageProvider(Metadata.Provider.LocalStorageProvider); // global-provider-ok: bootstrap
|
|
570
|
-
|
|
616
|
+
startupLog.LogIf('verbose', 'LocalCacheManager: storage provider swapped to Redis');
|
|
571
617
|
}
|
|
572
618
|
// Ensure LocalCacheManager is initialized (no-op if already done during engine loading)
|
|
573
619
|
if (!LocalCacheManager.Instance.IsInitialized) {
|
|
@@ -581,11 +627,14 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
581
627
|
verboseLogging: cs.verboseLogging ?? false,
|
|
582
628
|
};
|
|
583
629
|
await LocalCacheManager.Instance.Initialize(Metadata.Provider.LocalStorageProvider, cacheConfig); // global-provider-ok: bootstrap
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
630
|
+
if (startupLog.IsAtLeast('verbose')) {
|
|
631
|
+
// eslint-disable-next-line no-console
|
|
632
|
+
console.log('LocalCacheManager initialized with cache config:', JSON.stringify({
|
|
633
|
+
maxMemoryMB: cs.maxMemoryMB ?? 150,
|
|
634
|
+
maxPercentOfCachePerEntity: cs.maxPercentOfCachePerEntity ?? 50,
|
|
635
|
+
evictionSweepIntervalSeconds: cs.evictionSweepIntervalSeconds ?? 300,
|
|
636
|
+
}));
|
|
637
|
+
}
|
|
589
638
|
}
|
|
590
639
|
|
|
591
640
|
// Initialize APIKeyEngine singleton — reads apiKeyGeneration from mj.config.cjs automatically
|
|
@@ -610,7 +659,8 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
610
659
|
/******TEST HARNESS FOR CHANGE DETECTION */
|
|
611
660
|
/******TEST HARNESS FOR CHANGE DETECTION */
|
|
612
661
|
|
|
613
|
-
|
|
662
|
+
lap('Telemetry + Cache + APIKey Init', tServe);
|
|
663
|
+
tServe = startupLog.BeginPhase('Discovering resolvers');
|
|
614
664
|
|
|
615
665
|
const dynamicModules = await Promise.all(
|
|
616
666
|
paths.map((modulePath) => {
|
|
@@ -657,7 +707,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
657
707
|
// Initialize all middleware
|
|
658
708
|
for (const mw of middlewares) {
|
|
659
709
|
await mw.Initialize();
|
|
660
|
-
|
|
710
|
+
startupLog.LogIf('verbose', ` [Middleware] ${mw.Label}`);
|
|
661
711
|
}
|
|
662
712
|
|
|
663
713
|
// Collect middleware contributions for each pipeline stage
|
|
@@ -708,7 +758,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
708
758
|
Object.values(module).filter((value) => typeof value === 'function')
|
|
709
759
|
);
|
|
710
760
|
allResolvers = [...resolvers, ...mwResolvers] as BuildSchemaOptions['resolvers'];
|
|
711
|
-
|
|
761
|
+
startupLog.LogIf('verbose', ` [Middleware Resolvers] Loaded ${mwResolverFiles.length} resolver file(s) from middleware`);
|
|
712
762
|
}
|
|
713
763
|
}
|
|
714
764
|
|
|
@@ -767,7 +817,8 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
767
817
|
}
|
|
768
818
|
});
|
|
769
819
|
|
|
770
|
-
|
|
820
|
+
lap('Resolver + Middleware Discovery', tServe);
|
|
821
|
+
tServe = startupLog.BeginPhase('Building GraphQL schema');
|
|
771
822
|
|
|
772
823
|
let schema = mergeSchemas({
|
|
773
824
|
schemas: [
|
|
@@ -794,7 +845,8 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
794
845
|
schema = transformer(schema);
|
|
795
846
|
}
|
|
796
847
|
|
|
797
|
-
|
|
848
|
+
lap('Schema Build', tServe);
|
|
849
|
+
tServe = startupLog.BeginPhase('Starting HTTP server');
|
|
798
850
|
|
|
799
851
|
const httpServer = createServer(app);
|
|
800
852
|
|
|
@@ -911,7 +963,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
911
963
|
|
|
912
964
|
// ─── OAuth callback routes (unauthenticated, registered BEFORE auth) ─────
|
|
913
965
|
const oauthPublicUrl = configInfo.publicUrl || `${configInfo.baseUrl}:${configInfo.graphqlPort}${configInfo.graphqlRootPath || ''}`;
|
|
914
|
-
|
|
966
|
+
startupLog.LogIf('verbose', `[OAuth] publicUrl: ${oauthPublicUrl}`);
|
|
915
967
|
|
|
916
968
|
let oauthAuthenticatedRouter: ReturnType<typeof createOAuthCallbackHandler>['authenticatedRouter'] | undefined;
|
|
917
969
|
if (oauthPublicUrl) {
|
|
@@ -926,14 +978,14 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
926
978
|
|
|
927
979
|
// OAuth callback is unauthenticated (called by external auth server)
|
|
928
980
|
app.use('/oauth', oauthCors, callbackRouter);
|
|
929
|
-
|
|
981
|
+
startupLog.LogIf('verbose', '[OAuth] Callback route registered at /oauth/callback');
|
|
930
982
|
}
|
|
931
983
|
|
|
932
984
|
// ─── eSignature webhook (unauthenticated, registered BEFORE auth) ─────
|
|
933
985
|
// Called by external signature providers (DocuSign Connect, etc.) without an MJ bearer token.
|
|
934
986
|
// The provider DRIVER verifies the payload signature/HMAC; MJ auth does not apply here.
|
|
935
987
|
app.use('/esignature', cors<cors.CorsRequest>(), createSignatureWebhookHandler());
|
|
936
|
-
|
|
988
|
+
startupLog.LogIf('verbose', '[eSignature] Webhook route registered at /esignature/webhook/:driverKey');
|
|
937
989
|
|
|
938
990
|
// ─── Magic-link routes (MJ-issued, app-scoped external access) ───────────
|
|
939
991
|
// Public router (JWKS + redeem) mounts BEFORE the auth middleware; the
|
|
@@ -944,7 +996,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
944
996
|
magicLinkAuthenticatedRouter = authenticatedRouter;
|
|
945
997
|
registerMagicLinkAuthProvider(oauthPublicUrl, configInfo.magicLink);
|
|
946
998
|
app.use(MAGIC_LINK_MOUNT_PATH, cors<cors.CorsRequest>(), publicRouter);
|
|
947
|
-
|
|
999
|
+
startupLog.LogIf('verbose', `[MagicLink] Public routes registered at ${MAGIC_LINK_MOUNT_PATH}/redeem and ${MAGIC_LINK_MOUNT_PATH}/jwks.json`);
|
|
948
1000
|
}
|
|
949
1001
|
|
|
950
1002
|
// ─── Global CORS (before auth so 401 responses include CORS headers) ─────
|
|
@@ -998,13 +1050,13 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
998
1050
|
if (oauthAuthenticatedRouter) {
|
|
999
1051
|
const oauthCors = cors<cors.CorsRequest>();
|
|
1000
1052
|
app.use('/oauth', oauthCors, BodyParser.json(), oauthAuthenticatedRouter);
|
|
1001
|
-
|
|
1053
|
+
startupLog.LogIf('verbose', '[OAuth] Authenticated routes registered at /oauth/status, /oauth/initiate, and /oauth/exchange');
|
|
1002
1054
|
}
|
|
1003
1055
|
|
|
1004
1056
|
// ─── Magic-link authenticated route (invite creation) ─────────────────────
|
|
1005
1057
|
if (magicLinkAuthenticatedRouter) {
|
|
1006
1058
|
app.use(MAGIC_LINK_MOUNT_PATH, cors<cors.CorsRequest>(), magicLinkAuthenticatedRouter);
|
|
1007
|
-
|
|
1059
|
+
startupLog.LogIf('verbose', `[MagicLink] Authenticated route registered at ${MAGIC_LINK_MOUNT_PATH}/create`);
|
|
1008
1060
|
}
|
|
1009
1061
|
|
|
1010
1062
|
// ─── REST API endpoints (auth already handled by unified middleware) ─────
|
|
@@ -1021,9 +1073,11 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
1021
1073
|
if (process.env.MJ_REST_API_ENABLED !== undefined) {
|
|
1022
1074
|
restApiConfig.enabled = process.env.MJ_REST_API_ENABLED === 'true';
|
|
1023
1075
|
if (restApiConfig.enabled) {
|
|
1024
|
-
|
|
1076
|
+
// Genuine config override — actionable, surfaced at all levels.
|
|
1077
|
+
LogStatus('REST API is enabled via environment variable');
|
|
1025
1078
|
}
|
|
1026
1079
|
}
|
|
1080
|
+
startupLog.SetRestEnabled(restApiConfig.enabled);
|
|
1027
1081
|
|
|
1028
1082
|
if (process.env.MJ_REST_API_INCLUDE_ENTITIES) {
|
|
1029
1083
|
restApiConfig.includeEntities = process.env.MJ_REST_API_INCLUDE_ENTITIES.split(',').map(e => e.trim());
|
|
@@ -1079,12 +1133,24 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
1079
1133
|
await Promise.resolve(options.onBeforeServe());
|
|
1080
1134
|
}
|
|
1081
1135
|
|
|
1082
|
-
|
|
1136
|
+
lap('Apollo + Express Setup', tServe);
|
|
1083
1137
|
|
|
1084
1138
|
await new Promise<void>((resolve) => httpServer.listen({ port: graphqlPort }, resolve));
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1139
|
+
// The summary headline reports TRUE wall-clock since process start (see
|
|
1140
|
+
// StartupLogger.buildStartupLine), so we don't push a synthetic "total" phase row
|
|
1141
|
+
// (that double-counted the headline). Keep a verbose-only line for the serve()-scoped span.
|
|
1142
|
+
startupLog.LogIf('verbose', `serve() startup: ${((performance.now() - t0) / 1000).toFixed(1)}s`);
|
|
1143
|
+
startupLog.SetReadyUrl(`http://localhost:${graphqlPort}/`);
|
|
1144
|
+
// Populate summary fields that are known only now, then print the one summary block.
|
|
1145
|
+
for (const provider of AuthProviderFactory.Instance.getAllProviders()) {
|
|
1146
|
+
startupLog.AddAuthProvider(provider.name);
|
|
1147
|
+
}
|
|
1148
|
+
startupLog.SetVersion(resolveServerVersion());
|
|
1149
|
+
startupLog.SetConfigPath(configFilePath);
|
|
1150
|
+
if (scheduledJobsService) {
|
|
1151
|
+
startupLog.SetScheduledJobCount(scheduledJobsService.GetStatus().activeJobs);
|
|
1152
|
+
}
|
|
1153
|
+
startupLog.PrintSummary();
|
|
1088
1154
|
|
|
1089
1155
|
// Process pending RSU work from pre-restart (entity maps, field maps, sync)
|
|
1090
1156
|
processRSUPendingWork().catch(err => console.warn(`RSU pending work processing failed: ${err}`));
|
|
@@ -1105,6 +1171,14 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
1105
1171
|
.catch(err => console.warn(`[AgentRunWatchdog] Startup sweep failed: ${err}`));
|
|
1106
1172
|
}
|
|
1107
1173
|
|
|
1174
|
+
// Launch the AI Agent Session janitor: run own-host orphan recovery once at boot, then keep a
|
|
1175
|
+
// periodic staleness sweep running. Self-registers with ShutdownRegistry, so its timer is cleared
|
|
1176
|
+
// by the gracefulShutdown drain below (no explicit Stop() wiring needed here).
|
|
1177
|
+
if (resumeUser && Metadata.Provider instanceof DatabaseProviderBase) { // global-provider-ok: server startup recovery — boot-time session janitor uses the server's own provider
|
|
1178
|
+
SessionJanitor.Instance.Start(Metadata.Provider, resumeUser) // global-provider-ok: server-owned background reconciler runs under the server's provider + system user
|
|
1179
|
+
.catch(err => console.warn(`[SessionJanitor] Startup failed: ${err}`));
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1108
1182
|
// Set up graceful shutdown handlers
|
|
1109
1183
|
const gracefulShutdown = async (signal: string) => {
|
|
1110
1184
|
console.log(`\n${signal} received, shutting down gracefully...`);
|