@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
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { resolveDbPlatformFromEnv } from '@memberjunction/generic-database-provi
|
|
|
7
7
|
import { MJGlobal, MJEventType, UUIDsEqual, ShutdownRegistry } from '@memberjunction/global';
|
|
8
8
|
import { setupSQLServerClient, SQLServerDataProvider, SQLServerProviderConfigData, UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
9
9
|
import { extendConnectionPoolWithQuery } from './util.js';
|
|
10
|
+
import { registerIntegrationCustomColumnPromoter } from './integration/CustomColumnPromoter.js';
|
|
10
11
|
import { default as BodyParser } from 'body-parser';
|
|
11
12
|
import compression from 'compression'; // Add compression middleware
|
|
12
13
|
import cors from 'cors';
|
|
@@ -15,6 +16,7 @@ import { default as fg } from 'fast-glob';
|
|
|
15
16
|
import { useServer } from 'graphql-ws/lib/use/ws';
|
|
16
17
|
import { createServer } from 'node:http';
|
|
17
18
|
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
18
20
|
import { sep } from 'node:path';
|
|
19
21
|
import 'reflect-metadata';
|
|
20
22
|
import { ReplaySubject } from 'rxjs';
|
|
@@ -23,12 +25,14 @@ import { PubSub } from 'graphql-subscriptions';
|
|
|
23
25
|
import sql from 'mssql';
|
|
24
26
|
import { WebSocketServer } from 'ws';
|
|
25
27
|
import buildApolloServer from './apolloServer/index.js';
|
|
26
|
-
import { configInfo, dbDatabase, dbHost, dbPort, dbUsername, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage } from './config.js';
|
|
28
|
+
import { configInfo, configFilePath, dbDatabase, dbHost, dbPort, dbUsername, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage } from './config.js';
|
|
27
29
|
import { default as jwt } from 'jsonwebtoken';
|
|
28
30
|
import { contextFunction, createUnifiedAuthMiddleware, getUserPayload } from './context.js';
|
|
29
31
|
import { requireSystemUserDirective, publicDirective } from './directives/index.js';
|
|
30
32
|
import { variablesLoggingMiddleware } from './logging/variablesLoggingMiddleware.js';
|
|
31
33
|
import { auditResolversForUndecoratedArgs } from './logging/bootAudit.js';
|
|
34
|
+
import { StartupLogger } from './logging/StartupLogger.js';
|
|
35
|
+
import { AuthProviderFactory } from '@memberjunction/auth-providers';
|
|
32
36
|
import createMSSQLConfig from './orm.js';
|
|
33
37
|
import { setupRESTEndpoints } from './rest/setupRESTEndpoints.js';
|
|
34
38
|
import { createOAuthCallbackHandler } from './rest/OAuthCallbackHandler.js';
|
|
@@ -37,13 +41,14 @@ import { createMagicLinkHandler, registerMagicLinkAuthProvider, MAGIC_LINK_MOUNT
|
|
|
37
41
|
import { resolve } from 'node:path';
|
|
38
42
|
import { DataSourceInfo, raiseEvent } from './types.js';
|
|
39
43
|
import { ScheduledJobsService } from './services/ScheduledJobsService.js';
|
|
40
|
-
import { LocalCacheManager, TelemetryManager } from '@memberjunction/core';
|
|
44
|
+
import { LocalCacheManager, TelemetryManager, LogStatus, SetVerboseLogging } from '@memberjunction/core';
|
|
41
45
|
import { GetAPIKeyEngine } from '@memberjunction/api-keys';
|
|
42
46
|
import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
|
|
43
47
|
import { PubSubManager } from './generic/PubSubManager.js';
|
|
44
48
|
import { IntegrationProgressEmitter } from '@memberjunction/integration-progress-artifacts';
|
|
45
49
|
import { PublishIntegrationProgress } from './resolvers/IntegrationProgressResolver.js';
|
|
46
50
|
import { ClientToolRequestManager, AgentRunWatchdog } from '@memberjunction/ai-agents';
|
|
51
|
+
import { SessionJanitor } from './agentSessions/index.js';
|
|
47
52
|
import { CACHE_INVALIDATION_TOPIC } from './generic/CacheInvalidationResolver.js';
|
|
48
53
|
import { ConnectorFactory, IntegrationEngine } from '@memberjunction/integration-engine';
|
|
49
54
|
import { CronExpressionHelper } from '@memberjunction/scheduling-engine';
|
|
@@ -140,6 +145,10 @@ export * from './resolvers/UserViewResolver.js';
|
|
|
140
145
|
export * from './resolvers/VersionHistoryResolver.js';
|
|
141
146
|
export * from './resolvers/CurrentUserContextResolver.js';
|
|
142
147
|
export * from './resolvers/RSUResolver.js';
|
|
148
|
+
export * from './resolvers/AgentSessionResolver.js';
|
|
149
|
+
export * from './resolvers/RealtimeClientSessionResolver.js';
|
|
150
|
+
export * from './resolvers/RemoteBrowserActionResolver.js';
|
|
151
|
+
export * from './agentSessions/index.js';
|
|
143
152
|
export { GetReadOnlyDataSource, GetReadWriteDataSource, GetReadWriteProvider, GetReadOnlyProvider } from './util.js';
|
|
144
153
|
export * from './generated/generated.js';
|
|
145
154
|
export * from './multiTenancy/index.js';
|
|
@@ -154,13 +163,37 @@ const localPath = (p) => {
|
|
|
154
163
|
return resolvedPath;
|
|
155
164
|
};
|
|
156
165
|
export const createApp = () => express();
|
|
166
|
+
/**
|
|
167
|
+
* Resolves the MJServer package version for the startup summary header.
|
|
168
|
+
*
|
|
169
|
+
* Reads `@memberjunction/server`'s own `package.json` relative to this module.
|
|
170
|
+
* Returns `undefined` (and the header omits the version) if it can't be read,
|
|
171
|
+
* so version display is best-effort and never blocks startup.
|
|
172
|
+
*/
|
|
173
|
+
function resolveServerVersion() {
|
|
174
|
+
try {
|
|
175
|
+
// package.json sits at the package root, two levels up from dist/ (or src/).
|
|
176
|
+
const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
|
|
177
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
178
|
+
return pkg.version;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
157
184
|
export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
158
185
|
const t0 = performance.now();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
186
|
+
// Level-gated startup logger. Resolves verbosity from telemetry.level (single
|
|
187
|
+
// operator knob). At `standard` (default), per-phase timings are collapsed into
|
|
188
|
+
// the one-line summary; at `verbose`+ each phase prints inline as before.
|
|
189
|
+
const startupLog = new StartupLogger();
|
|
190
|
+
// Wire the GLOBAL verbose-logging gate to the resolved server level, so cross-package
|
|
191
|
+
// LogStatusEx({ verboseOnly: true }) lines (cache, scheduling, integration, MCP, …) honor the
|
|
192
|
+
// configured level instead of defaulting to hidden. Without this, "verbose-only" is "always hidden".
|
|
193
|
+
SetVerboseLogging(startupLog.IsAtLeast('verbose'));
|
|
194
|
+
// Transient boot spinner (standard level + TTY only); cleared by PrintSummary.
|
|
195
|
+
startupLog.StartBoot();
|
|
196
|
+
const lap = (label, since) => startupLog.EndPhase(label, since);
|
|
164
197
|
const localResolverPaths = ['resolvers/**/*Resolver.{js,ts}', 'generic/*Resolver.{js,ts}', 'generated/generated.{js,ts}'].map(localPath);
|
|
165
198
|
const combinedResolverPaths = [...resolverPaths, ...localResolverPaths];
|
|
166
199
|
const isWindows = sep === '\\';
|
|
@@ -175,7 +208,8 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
175
208
|
const dataSources = [];
|
|
176
209
|
if (dbType === 'postgresql') {
|
|
177
210
|
// ─── PostgreSQL Path ───────────────────────────────────────────
|
|
178
|
-
|
|
211
|
+
startupLog.BeginPhase('Connecting to database');
|
|
212
|
+
startupLog.LogIf('verbose', 'Database type: PostgreSQL');
|
|
179
213
|
const pg = await import('pg');
|
|
180
214
|
const { PostgreSQLDataProvider, PostgreSQLProviderConfigData } = await import('@memberjunction/postgresql-dataprovider');
|
|
181
215
|
const pgHost = process.env.PG_HOST || process.env.DB_HOST || 'localhost';
|
|
@@ -198,7 +232,7 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
198
232
|
const testClient = await pgPool.connect();
|
|
199
233
|
await testClient.query('SELECT 1');
|
|
200
234
|
testClient.release();
|
|
201
|
-
|
|
235
|
+
startupLog.LogIf('verbose', `PostgreSQL pool connected to ${pgHost}:${pgPort}/${pgDatabase}`);
|
|
202
236
|
// Create a DataSourceInfo with a MSSQL-compatible wrapper around pg.Pool
|
|
203
237
|
// This allows existing code (types, util, context) to work without changes
|
|
204
238
|
const mssqlCompatPool = createMSSQLCompatPool(pgPool);
|
|
@@ -273,11 +307,10 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
273
307
|
const codegenPgProvider = new PostgreSQLDataProvider();
|
|
274
308
|
await codegenPgProvider.Config(codegenPgConfigData); // separate pool (per-instance manager); does NOT touch the global API provider
|
|
275
309
|
RuntimeSchemaManager.Instance.SetDDLProvider(codegenPgProvider);
|
|
276
|
-
|
|
310
|
+
startupLog.LogIf('verbose', 'RSU DDL provider initialized with CodeGen credentials (PostgreSQL).');
|
|
277
311
|
// Set up in-process CodeGen runner for RSU
|
|
278
312
|
try {
|
|
279
|
-
const { RunCodeGenBase } = await import('@memberjunction/codegen-lib');
|
|
280
|
-
const { PostgreSQLCodeGenConnection } = await import('@memberjunction/codegen-lib/dist/Database/providers/postgresql/PostgreSQLCodeGenConnection.js');
|
|
313
|
+
const { RunCodeGenBase, PostgreSQLCodeGenConnection } = await import('@memberjunction/codegen-lib');
|
|
281
314
|
const codegenConnection = new PostgreSQLCodeGenConnection(codegenPgPool);
|
|
282
315
|
const codegenCurrentUser = UserCache.Instance.Users.find(u => u.Type?.trim().toLowerCase() === 'owner') ?? UserCache.Instance.Users[0];
|
|
283
316
|
const codegenDataSource = {
|
|
@@ -291,19 +324,19 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
291
324
|
RuntimeSchemaManager.Instance.SetCodeGenRunner({
|
|
292
325
|
RunInProcess: (skipDB) => runObject.RunInProcess(codegenDataSource, skipDB, rsuWorkDir),
|
|
293
326
|
});
|
|
294
|
-
|
|
327
|
+
startupLog.LogIf('verbose', 'RSU in-process CodeGen runner initialized (PostgreSQL).');
|
|
295
328
|
// Inject CodeGen output paths for targeted git staging
|
|
296
329
|
const { initializeConfig } = await import('@memberjunction/codegen-lib');
|
|
297
330
|
const cgConfig = initializeConfig(rsuWorkDir);
|
|
298
331
|
const outputPaths = (cgConfig.output ?? []).map((o) => o.directory);
|
|
299
332
|
RuntimeSchemaManager.Instance.SetCodeGenOutputPaths(outputPaths);
|
|
300
|
-
|
|
333
|
+
startupLog.LogIf('verbose', `RSU CodeGen output paths: ${outputPaths.length} directories configured.`);
|
|
301
334
|
// Point RSU's soft PK/FK writer at the SAME file CodeGen reads (mj.config.cjs
|
|
302
335
|
// `additionalSchemaInfo`), or RSU writes soft PKs to its own default path while
|
|
303
336
|
// CodeGen reads a different one and skips integration tables with "No primary key found".
|
|
304
337
|
if (cgConfig.additionalSchemaInfo) {
|
|
305
338
|
RuntimeSchemaManager.Instance.SetAdditionalSchemaInfoPath(cgConfig.additionalSchemaInfo);
|
|
306
|
-
|
|
339
|
+
startupLog.LogIf('verbose', `RSU additionalSchemaInfo path: ${cgConfig.additionalSchemaInfo}`);
|
|
307
340
|
}
|
|
308
341
|
}
|
|
309
342
|
catch (codegenErr) {
|
|
@@ -315,12 +348,14 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
315
348
|
}
|
|
316
349
|
}
|
|
317
350
|
const md = new Metadata(); // global-provider-ok: bootstrap
|
|
318
|
-
|
|
351
|
+
const pgEntityCount = md?.Entities ? md.Entities.length : 0;
|
|
352
|
+
startupLog.LogIf('verbose', `Data Source has been initialized. ${pgEntityCount} entities loaded.`);
|
|
353
|
+
startupLog.SetDatabaseInfo('PostgreSQL', `${pgHost}:${pgPort}/${pgDatabase}`, pgEntityCount);
|
|
319
354
|
}
|
|
320
355
|
else {
|
|
321
356
|
// ─── SQL Server Path (existing behavior) ───────────────────────
|
|
322
|
-
|
|
323
|
-
let tPhase =
|
|
357
|
+
startupLog.LogIf('verbose', 'Database type: SQL Server');
|
|
358
|
+
let tPhase = startupLog.BeginPhase('Connecting to database');
|
|
324
359
|
const pool = new sql.ConnectionPool(createMSSQLConfig());
|
|
325
360
|
// Handle connection-level errors from dead/stale connections in the pool.
|
|
326
361
|
// Without this handler, when Azure drops idle TCP connections, the pool silently
|
|
@@ -329,7 +364,8 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
329
364
|
console.error('[ConnectionPool] Pool-level connection error (stale connection evicted):', err.message);
|
|
330
365
|
});
|
|
331
366
|
await pool.connect();
|
|
332
|
-
|
|
367
|
+
lap('DB Pool Connect', tPhase);
|
|
368
|
+
tPhase = startupLog.BeginPhase('Loading metadata + providers');
|
|
333
369
|
dataSources.push(new DataSourceInfo({ dataSource: pool, type: 'Read-Write', host: dbHost, port: dbPort, database: dbDatabase, userName: dbUsername }));
|
|
334
370
|
// Establish a second read-only connection to the database if dbReadOnlyUsername and dbReadOnlyPassword exist
|
|
335
371
|
if (configInfo.dbReadOnlyUsername && configInfo.dbReadOnlyPassword) {
|
|
@@ -344,13 +380,16 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
344
380
|
});
|
|
345
381
|
await readOnlyPool.connect();
|
|
346
382
|
dataSources.push(new DataSourceInfo({ dataSource: readOnlyPool, type: 'Read-Only', host: dbHost, port: dbPort, database: dbDatabase, userName: configInfo.dbReadOnlyUsername }));
|
|
347
|
-
|
|
383
|
+
startupLog.LogIf('verbose', 'Read-only Connection Pool has been initialized.');
|
|
348
384
|
}
|
|
349
385
|
const config = new SQLServerProviderConfigData(pool, mj_core_schema, cacheRefreshInterval);
|
|
350
386
|
await setupSQLServerClient(config);
|
|
351
|
-
|
|
387
|
+
lap('Metadata + Provider Setup', tPhase);
|
|
388
|
+
startupLog.BeginPhase('Initializing data provider');
|
|
352
389
|
const md = new Metadata(); // global-provider-ok: bootstrap
|
|
353
|
-
|
|
390
|
+
const entityCount = md?.Entities ? md.Entities.length : 0;
|
|
391
|
+
startupLog.LogIf('verbose', `Data Source has been initialized. ${entityCount} entities loaded.`);
|
|
392
|
+
startupLog.SetDatabaseInfo('SQL Server', `${dbHost}:${dbPort}/${dbDatabase}`, entityCount);
|
|
354
393
|
// Set up CodeGen-credentialed provider for RSU DDL operations (CREATE TABLE, CREATE SCHEMA, etc.)
|
|
355
394
|
const codegenUser = process.env.CODEGEN_DB_USERNAME;
|
|
356
395
|
const codegenPass = process.env.CODEGEN_DB_PASSWORD;
|
|
@@ -377,11 +416,10 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
377
416
|
const codegenProvider = new SQLServerDataProvider();
|
|
378
417
|
await codegenProvider.Config(codegenConfig);
|
|
379
418
|
RuntimeSchemaManager.Instance.SetDDLProvider(codegenProvider);
|
|
380
|
-
|
|
419
|
+
startupLog.LogIf('verbose', 'RSU DDL provider initialized with CodeGen credentials.');
|
|
381
420
|
// Set up in-process CodeGen runner for RSU
|
|
382
421
|
try {
|
|
383
|
-
const { RunCodeGenBase } = await import('@memberjunction/codegen-lib');
|
|
384
|
-
const { SQLServerCodeGenConnection } = await import('@memberjunction/codegen-lib/dist/Database/providers/sqlserver/SQLServerCodeGenConnection.js');
|
|
422
|
+
const { RunCodeGenBase, SQLServerCodeGenConnection } = await import('@memberjunction/codegen-lib');
|
|
385
423
|
const codegenConnection = new SQLServerCodeGenConnection(codegenPool);
|
|
386
424
|
const codegenCurrentUser = UserCache.Instance.Users.find(u => u.Type?.trim().toLowerCase() === 'owner') ?? UserCache.Instance.Users[0];
|
|
387
425
|
const codegenDataSource = {
|
|
@@ -395,20 +433,20 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
395
433
|
RuntimeSchemaManager.Instance.SetCodeGenRunner({
|
|
396
434
|
RunInProcess: (skipDB) => runObject.RunInProcess(codegenDataSource, skipDB, rsuWorkDir),
|
|
397
435
|
});
|
|
398
|
-
|
|
436
|
+
startupLog.LogIf('verbose', 'RSU in-process CodeGen runner initialized.');
|
|
399
437
|
// Inject CodeGen output paths for targeted git staging
|
|
400
438
|
const { initializeConfig } = await import('@memberjunction/codegen-lib');
|
|
401
439
|
const codegenConfig = initializeConfig(rsuWorkDir);
|
|
402
440
|
const outputPaths = (codegenConfig.output ?? []).map((o) => o.directory);
|
|
403
441
|
RuntimeSchemaManager.Instance.SetCodeGenOutputPaths(outputPaths);
|
|
404
|
-
|
|
442
|
+
startupLog.LogIf('verbose', `RSU CodeGen output paths: ${outputPaths.length} directories configured.`);
|
|
405
443
|
// Point RSU's soft PK/FK writer at the SAME file CodeGen reads (mj.config.cjs
|
|
406
444
|
// `additionalSchemaInfo`). Without this, RSU writes soft PKs to its own default
|
|
407
445
|
// path while CodeGen reads a different one and skips every integration table
|
|
408
446
|
// with "No primary key found".
|
|
409
447
|
if (codegenConfig.additionalSchemaInfo) {
|
|
410
448
|
RuntimeSchemaManager.Instance.SetAdditionalSchemaInfoPath(codegenConfig.additionalSchemaInfo);
|
|
411
|
-
|
|
449
|
+
startupLog.LogIf('verbose', `RSU additionalSchemaInfo path: ${codegenConfig.additionalSchemaInfo}`);
|
|
412
450
|
}
|
|
413
451
|
}
|
|
414
452
|
catch (codegenErr) {
|
|
@@ -420,7 +458,15 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
420
458
|
}
|
|
421
459
|
}
|
|
422
460
|
}
|
|
423
|
-
|
|
461
|
+
// Register the post-sync custom-column promotion hook (gaps.md §2). Safe to call regardless of
|
|
462
|
+
// RSU config: the hook self-gates on captured overflow data and uses RSU only at fire time.
|
|
463
|
+
try {
|
|
464
|
+
registerIntegrationCustomColumnPromoter();
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
console.warn(`Custom-column promoter registration failed (post-sync promotion disabled): ${err.message}`);
|
|
468
|
+
}
|
|
469
|
+
let tServe = startupLog.BeginPhase('Initializing services');
|
|
424
470
|
// Store queryDialects config in GlobalObjectStore so MJQueryEntityServer can
|
|
425
471
|
// read it without a circular dependency on MJServer
|
|
426
472
|
if (configInfo.queryDialects) {
|
|
@@ -433,11 +479,11 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
433
479
|
if (configInfo.telemetry?.level) {
|
|
434
480
|
tm.UpdateSettings({ level: configInfo.telemetry.level });
|
|
435
481
|
}
|
|
436
|
-
|
|
482
|
+
startupLog.LogIf('verbose', `Server telemetry enabled with level: ${configInfo.telemetry.level || 'standard'}`);
|
|
437
483
|
}
|
|
438
484
|
else {
|
|
439
485
|
tm.SetEnabled(false);
|
|
440
|
-
|
|
486
|
+
startupLog.LogIf('verbose', 'Server telemetry disabled');
|
|
441
487
|
}
|
|
442
488
|
// Optionally inject Redis as the shared storage provider for cross-server cache invalidation
|
|
443
489
|
if (process.env.REDIS_URL) {
|
|
@@ -468,14 +514,14 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
468
514
|
});
|
|
469
515
|
}
|
|
470
516
|
});
|
|
471
|
-
|
|
517
|
+
startupLog.LogIf('verbose', `Redis cache provider connected: ${process.env.REDIS_URL}`);
|
|
472
518
|
}
|
|
473
519
|
// If Redis is available, swap LocalCacheManager's storage provider to Redis.
|
|
474
520
|
// LocalCacheManager may have already been initialized (with in-memory provider)
|
|
475
521
|
// during engine loading. SetStorageProvider migrates cached data to Redis.
|
|
476
522
|
if (process.env.REDIS_URL) {
|
|
477
523
|
await LocalCacheManager.Instance.SetStorageProvider(Metadata.Provider.LocalStorageProvider); // global-provider-ok: bootstrap
|
|
478
|
-
|
|
524
|
+
startupLog.LogIf('verbose', 'LocalCacheManager: storage provider swapped to Redis');
|
|
479
525
|
}
|
|
480
526
|
// Ensure LocalCacheManager is initialized (no-op if already done during engine loading)
|
|
481
527
|
if (!LocalCacheManager.Instance.IsInitialized) {
|
|
@@ -489,11 +535,14 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
489
535
|
verboseLogging: cs.verboseLogging ?? false,
|
|
490
536
|
};
|
|
491
537
|
await LocalCacheManager.Instance.Initialize(Metadata.Provider.LocalStorageProvider, cacheConfig); // global-provider-ok: bootstrap
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
538
|
+
if (startupLog.IsAtLeast('verbose')) {
|
|
539
|
+
// eslint-disable-next-line no-console
|
|
540
|
+
console.log('LocalCacheManager initialized with cache config:', JSON.stringify({
|
|
541
|
+
maxMemoryMB: cs.maxMemoryMB ?? 150,
|
|
542
|
+
maxPercentOfCachePerEntity: cs.maxPercentOfCachePerEntity ?? 50,
|
|
543
|
+
evictionSweepIntervalSeconds: cs.evictionSweepIntervalSeconds ?? 300,
|
|
544
|
+
}));
|
|
545
|
+
}
|
|
497
546
|
}
|
|
498
547
|
// Initialize APIKeyEngine singleton — reads apiKeyGeneration from mj.config.cjs automatically
|
|
499
548
|
// This must happen before any request handler calls GetAPIKeyEngine()
|
|
@@ -513,7 +562,8 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
513
562
|
// });
|
|
514
563
|
/******TEST HARNESS FOR CHANGE DETECTION */
|
|
515
564
|
/******TEST HARNESS FOR CHANGE DETECTION */
|
|
516
|
-
|
|
565
|
+
lap('Telemetry + Cache + APIKey Init', tServe);
|
|
566
|
+
tServe = startupLog.BeginPhase('Discovering resolvers');
|
|
517
567
|
const dynamicModules = await Promise.all(paths.map((modulePath) => {
|
|
518
568
|
try {
|
|
519
569
|
const module = import(isWindows ? `file://${modulePath}` : modulePath);
|
|
@@ -550,7 +600,7 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
550
600
|
// Initialize all middleware
|
|
551
601
|
for (const mw of middlewares) {
|
|
552
602
|
await mw.Initialize();
|
|
553
|
-
|
|
603
|
+
startupLog.LogIf('verbose', ` [Middleware] ${mw.Label}`);
|
|
554
604
|
}
|
|
555
605
|
// Collect middleware contributions for each pipeline stage
|
|
556
606
|
const mwPreAuth = [];
|
|
@@ -593,7 +643,7 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
593
643
|
}));
|
|
594
644
|
const mwResolvers = mwModules.flatMap((module) => Object.values(module).filter((value) => typeof value === 'function'));
|
|
595
645
|
allResolvers = [...resolvers, ...mwResolvers];
|
|
596
|
-
|
|
646
|
+
startupLog.LogIf('verbose', ` [Middleware Resolvers] Loaded ${mwResolverFiles.length} resolver file(s) from middleware`);
|
|
597
647
|
}
|
|
598
648
|
}
|
|
599
649
|
// Create an explicit PubSub instance so we can reference it outside of resolvers
|
|
@@ -643,7 +693,8 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
643
693
|
}
|
|
644
694
|
}
|
|
645
695
|
});
|
|
646
|
-
|
|
696
|
+
lap('Resolver + Middleware Discovery', tServe);
|
|
697
|
+
tServe = startupLog.BeginPhase('Building GraphQL schema');
|
|
647
698
|
let schema = mergeSchemas({
|
|
648
699
|
schemas: [
|
|
649
700
|
buildSchemaSync({
|
|
@@ -666,7 +717,8 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
666
717
|
for (const transformer of mwSchemaTransformers) {
|
|
667
718
|
schema = transformer(schema);
|
|
668
719
|
}
|
|
669
|
-
|
|
720
|
+
lap('Schema Build', tServe);
|
|
721
|
+
tServe = startupLog.BeginPhase('Starting HTTP server');
|
|
670
722
|
const httpServer = createServer(app);
|
|
671
723
|
const webSocketServer = new WebSocketServer({ server: httpServer, path: graphqlRootPath });
|
|
672
724
|
// Track per-connection expiry timers so we can clean them up on close
|
|
@@ -766,7 +818,7 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
766
818
|
}
|
|
767
819
|
// ─── OAuth callback routes (unauthenticated, registered BEFORE auth) ─────
|
|
768
820
|
const oauthPublicUrl = configInfo.publicUrl || `${configInfo.baseUrl}:${configInfo.graphqlPort}${configInfo.graphqlRootPath || ''}`;
|
|
769
|
-
|
|
821
|
+
startupLog.LogIf('verbose', `[OAuth] publicUrl: ${oauthPublicUrl}`);
|
|
770
822
|
let oauthAuthenticatedRouter;
|
|
771
823
|
if (oauthPublicUrl) {
|
|
772
824
|
const { callbackRouter, authenticatedRouter } = createOAuthCallbackHandler({
|
|
@@ -778,13 +830,13 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
778
830
|
const oauthCors = cors();
|
|
779
831
|
// OAuth callback is unauthenticated (called by external auth server)
|
|
780
832
|
app.use('/oauth', oauthCors, callbackRouter);
|
|
781
|
-
|
|
833
|
+
startupLog.LogIf('verbose', '[OAuth] Callback route registered at /oauth/callback');
|
|
782
834
|
}
|
|
783
835
|
// ─── eSignature webhook (unauthenticated, registered BEFORE auth) ─────
|
|
784
836
|
// Called by external signature providers (DocuSign Connect, etc.) without an MJ bearer token.
|
|
785
837
|
// The provider DRIVER verifies the payload signature/HMAC; MJ auth does not apply here.
|
|
786
838
|
app.use('/esignature', cors(), createSignatureWebhookHandler());
|
|
787
|
-
|
|
839
|
+
startupLog.LogIf('verbose', '[eSignature] Webhook route registered at /esignature/webhook/:driverKey');
|
|
788
840
|
// ─── Magic-link routes (MJ-issued, app-scoped external access) ───────────
|
|
789
841
|
// Public router (JWKS + redeem) mounts BEFORE the auth middleware; the
|
|
790
842
|
// authenticated invite-creation router mounts AFTER it (see below).
|
|
@@ -794,7 +846,7 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
794
846
|
magicLinkAuthenticatedRouter = authenticatedRouter;
|
|
795
847
|
registerMagicLinkAuthProvider(oauthPublicUrl, configInfo.magicLink);
|
|
796
848
|
app.use(MAGIC_LINK_MOUNT_PATH, cors(), publicRouter);
|
|
797
|
-
|
|
849
|
+
startupLog.LogIf('verbose', `[MagicLink] Public routes registered at ${MAGIC_LINK_MOUNT_PATH}/redeem and ${MAGIC_LINK_MOUNT_PATH}/jwks.json`);
|
|
798
850
|
}
|
|
799
851
|
// ─── Global CORS (before auth so 401 responses include CORS headers) ─────
|
|
800
852
|
// Without this, the browser blocks 401 responses from the auth middleware
|
|
@@ -843,12 +895,12 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
843
895
|
if (oauthAuthenticatedRouter) {
|
|
844
896
|
const oauthCors = cors();
|
|
845
897
|
app.use('/oauth', oauthCors, BodyParser.json(), oauthAuthenticatedRouter);
|
|
846
|
-
|
|
898
|
+
startupLog.LogIf('verbose', '[OAuth] Authenticated routes registered at /oauth/status, /oauth/initiate, and /oauth/exchange');
|
|
847
899
|
}
|
|
848
900
|
// ─── Magic-link authenticated route (invite creation) ─────────────────────
|
|
849
901
|
if (magicLinkAuthenticatedRouter) {
|
|
850
902
|
app.use(MAGIC_LINK_MOUNT_PATH, cors(), magicLinkAuthenticatedRouter);
|
|
851
|
-
|
|
903
|
+
startupLog.LogIf('verbose', `[MagicLink] Authenticated route registered at ${MAGIC_LINK_MOUNT_PATH}/create`);
|
|
852
904
|
}
|
|
853
905
|
// ─── REST API endpoints (auth already handled by unified middleware) ─────
|
|
854
906
|
const restApiConfig = {
|
|
@@ -862,9 +914,11 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
862
914
|
if (process.env.MJ_REST_API_ENABLED !== undefined) {
|
|
863
915
|
restApiConfig.enabled = process.env.MJ_REST_API_ENABLED === 'true';
|
|
864
916
|
if (restApiConfig.enabled) {
|
|
865
|
-
|
|
917
|
+
// Genuine config override — actionable, surfaced at all levels.
|
|
918
|
+
LogStatus('REST API is enabled via environment variable');
|
|
866
919
|
}
|
|
867
920
|
}
|
|
921
|
+
startupLog.SetRestEnabled(restApiConfig.enabled);
|
|
868
922
|
if (process.env.MJ_REST_API_INCLUDE_ENTITIES) {
|
|
869
923
|
restApiConfig.includeEntities = process.env.MJ_REST_API_INCLUDE_ENTITIES.split(',').map(e => e.trim());
|
|
870
924
|
}
|
|
@@ -908,11 +962,23 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
908
962
|
if (options?.onBeforeServe) {
|
|
909
963
|
await Promise.resolve(options.onBeforeServe());
|
|
910
964
|
}
|
|
911
|
-
|
|
965
|
+
lap('Apollo + Express Setup', tServe);
|
|
912
966
|
await new Promise((resolve) => httpServer.listen({ port: graphqlPort }, resolve));
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
967
|
+
// The summary headline reports TRUE wall-clock since process start (see
|
|
968
|
+
// StartupLogger.buildStartupLine), so we don't push a synthetic "total" phase row
|
|
969
|
+
// (that double-counted the headline). Keep a verbose-only line for the serve()-scoped span.
|
|
970
|
+
startupLog.LogIf('verbose', `serve() startup: ${((performance.now() - t0) / 1000).toFixed(1)}s`);
|
|
971
|
+
startupLog.SetReadyUrl(`http://localhost:${graphqlPort}/`);
|
|
972
|
+
// Populate summary fields that are known only now, then print the one summary block.
|
|
973
|
+
for (const provider of AuthProviderFactory.Instance.getAllProviders()) {
|
|
974
|
+
startupLog.AddAuthProvider(provider.name);
|
|
975
|
+
}
|
|
976
|
+
startupLog.SetVersion(resolveServerVersion());
|
|
977
|
+
startupLog.SetConfigPath(configFilePath);
|
|
978
|
+
if (scheduledJobsService) {
|
|
979
|
+
startupLog.SetScheduledJobCount(scheduledJobsService.GetStatus().activeJobs);
|
|
980
|
+
}
|
|
981
|
+
startupLog.PrintSummary();
|
|
916
982
|
// Process pending RSU work from pre-restart (entity maps, field maps, sync)
|
|
917
983
|
processRSUPendingWork().catch(err => console.warn(`RSU pending work processing failed: ${err}`));
|
|
918
984
|
// Resume any integration syncs that were orphaned by the previous process restart
|
|
@@ -929,6 +995,13 @@ export const serve = async (resolverPaths, app = createApp(), options) => {
|
|
|
929
995
|
AgentRunWatchdog.SweepOrphanedRuns(Metadata.Provider, resumeUser) // global-provider-ok: server startup recovery — one-shot orphaned-run sweep at boot
|
|
930
996
|
.catch(err => console.warn(`[AgentRunWatchdog] Startup sweep failed: ${err}`));
|
|
931
997
|
}
|
|
998
|
+
// Launch the AI Agent Session janitor: run own-host orphan recovery once at boot, then keep a
|
|
999
|
+
// periodic staleness sweep running. Self-registers with ShutdownRegistry, so its timer is cleared
|
|
1000
|
+
// by the gracefulShutdown drain below (no explicit Stop() wiring needed here).
|
|
1001
|
+
if (resumeUser && Metadata.Provider instanceof DatabaseProviderBase) { // global-provider-ok: server startup recovery — boot-time session janitor uses the server's own provider
|
|
1002
|
+
SessionJanitor.Instance.Start(Metadata.Provider, resumeUser) // global-provider-ok: server-owned background reconciler runs under the server's provider + system user
|
|
1003
|
+
.catch(err => console.warn(`[SessionJanitor] Startup failed: ${err}`));
|
|
1004
|
+
}
|
|
932
1005
|
// Set up graceful shutdown handlers
|
|
933
1006
|
const gracefulShutdown = async (signal) => {
|
|
934
1007
|
console.log(`\n${signal} received, shutting down gracefully...`);
|