@memberjunction/server 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +689 -513
  2. package/dist/auth/newUsers.d.ts.map +1 -1
  3. package/dist/auth/newUsers.js +4 -2
  4. package/dist/auth/newUsers.js.map +1 -1
  5. package/dist/generated/generated.d.ts +20 -6
  6. package/dist/generated/generated.d.ts.map +1 -1
  7. package/dist/generated/generated.js +107 -51
  8. package/dist/generated/generated.js.map +1 -1
  9. package/dist/index.d.ts +12 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +12 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/resolvers/FileCategoryResolver.d.ts +1 -1
  14. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  15. package/dist/resolvers/FileCategoryResolver.js +2 -2
  16. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  17. package/dist/resolvers/InfoResolver.d.ts +2 -2
  18. package/dist/resolvers/InfoResolver.d.ts.map +1 -1
  19. package/dist/resolvers/InfoResolver.js +13 -13
  20. package/dist/resolvers/InfoResolver.js.map +1 -1
  21. package/package.json +52 -48
  22. package/src/auth/newUsers.ts +4 -2
  23. package/src/generated/generated.ts +82 -40
  24. package/src/index.ts +12 -0
  25. package/src/resolvers/FileCategoryResolver.ts +1 -1
  26. package/src/resolvers/InfoResolver.ts +5 -5
  27. package/dist/apolloServer/TransactionPlugin.d.ts +0 -4
  28. package/dist/apolloServer/TransactionPlugin.d.ts.map +0 -1
  29. package/dist/apolloServer/TransactionPlugin.js +0 -46
  30. package/dist/apolloServer/TransactionPlugin.js.map +0 -1
  31. package/dist/auth/__tests__/backward-compatibility.test.d.ts +0 -2
  32. package/dist/auth/__tests__/backward-compatibility.test.d.ts.map +0 -1
  33. package/dist/auth/__tests__/backward-compatibility.test.js +0 -135
  34. package/dist/auth/__tests__/backward-compatibility.test.js.map +0 -1
  35. package/dist/resolvers/AskSkipResolver.d.ts +0 -123
  36. package/dist/resolvers/AskSkipResolver.d.ts.map +0 -1
  37. package/dist/resolvers/AskSkipResolver.js +0 -1788
  38. package/dist/resolvers/AskSkipResolver.js.map +0 -1
  39. package/dist/scheduler/LearningCycleScheduler.d.ts +0 -4
  40. package/dist/scheduler/LearningCycleScheduler.d.ts.map +0 -1
  41. package/dist/scheduler/LearningCycleScheduler.js +0 -4
  42. package/dist/scheduler/LearningCycleScheduler.js.map +0 -1
package/README.md CHANGED
@@ -1,84 +1,180 @@
1
1
  # @memberjunction/server
2
2
 
3
- The `@memberjunction/server` library provides a comprehensive API server for MemberJunction, featuring both GraphQL and REST APIs. It includes all the functions required to start up the server, manage authentication, handle database connections, and provide a robust interface for accessing and managing metadata within MemberJunction.
4
-
5
- ## Key Features
6
-
7
- - **Dual API Support**: Both GraphQL and REST APIs with consistent authentication
8
- - **Multi-Database Support**: Read-write and optional read-only database connections
9
- - **Advanced Authentication**: Support for MSAL (Azure AD) and Auth0 authentication providers
10
- - **Transaction Management**: Automatic transaction wrapper for GraphQL mutations
11
- - **Type-Safe Resolvers**: Full TypeScript support with type-graphql integration
12
- - **Entity Management**: Comprehensive CRUD operations with change tracking
13
- - **Real-time Support**: WebSocket subscriptions for GraphQL
14
- - **Compression**: Built-in response compression for better performance
15
- - **Extensible Architecture**: Support for custom resolvers and entity subclasses
16
- - **AI Integration**: Built-in support for AI operations, prompts, agents, and embeddings
17
- - **Security Features**: Entity-level and schema-level access control for REST API
18
- - **SQL Logging**: Runtime SQL logging configuration and session management for debugging
3
+ The MemberJunction server package provides the complete API server infrastructure for MemberJunction applications. It delivers both GraphQL and REST APIs with a unified authentication layer, per-request transaction isolation, pluggable authentication providers, scope-based API key authorization, scheduled job orchestration, SQL logging, and built-in AI operation endpoints. This package is the primary integration point between client applications and the MemberJunction data layer.
19
4
 
20
5
  ## Installation
21
6
 
22
- ```shell
7
+ ```bash
23
8
  npm install @memberjunction/server
24
9
  ```
25
10
 
26
- ## Dependencies
11
+ ## Overview
12
+
13
+ MJServer acts as the central API gateway for MemberJunction, sitting between client applications (MJExplorer, external integrations, AI agents) and the SQL Server database via the `@memberjunction/sqlserver-dataprovider`. It initializes database connections, loads entity metadata, builds a GraphQL schema from dynamically discovered resolver modules, optionally exposes a REST API, and manages the full request lifecycle including authentication, per-request provider isolation, and transaction management.
14
+
15
+ ```mermaid
16
+ flowchart TD
17
+ subgraph Clients["Client Applications"]
18
+ EX[MJ Explorer]
19
+ EXT[External Apps]
20
+ AI[AI Agents / MCP]
21
+ end
22
+
23
+ subgraph MJServer["@memberjunction/server"]
24
+ direction TB
25
+ AUTH[Authentication Layer]
26
+ GQL[GraphQL API - Apollo Server]
27
+ REST[REST API - Express Routes]
28
+ CTX[Request Context & Provider Factory]
29
+ RES[Resolver Layer]
30
+ SCHED[Scheduled Jobs Service]
31
+ SQLLOG[SQL Logging Manager]
32
+ end
33
+
34
+ subgraph Data["Data Layer"]
35
+ SQLP[SQL Server Data Provider]
36
+ DB[(SQL Server Database)]
37
+ end
38
+
39
+ Clients --> AUTH
40
+ AUTH --> GQL
41
+ AUTH --> REST
42
+ GQL --> CTX
43
+ REST --> CTX
44
+ CTX --> RES
45
+ RES --> SQLP
46
+ SQLP --> DB
47
+ SCHED --> SQLP
48
+
49
+ style Clients fill:#2d6a9f,stroke:#1a4971,color:#fff
50
+ style MJServer fill:#7c5295,stroke:#563a6b,color:#fff
51
+ style Data fill:#2d8659,stroke:#1a5c3a,color:#fff
52
+ ```
27
53
 
28
- This package depends on several core MemberJunction packages:
29
- - `@memberjunction/core`: Core functionality and metadata management
30
- - `@memberjunction/sqlserver-dataprovider`: SQL Server data provider
31
- - `@memberjunction/graphql-dataprovider`: GraphQL data provider
32
- - `@memberjunction/ai`: AI engine integration
33
- - Various other MJ packages for specific functionality
54
+ ### Key Features
55
+
56
+ - **Dual API Support**: GraphQL (Apollo Server) and REST APIs with consistent authentication and authorization
57
+ - **Pluggable Authentication**: Support for Azure AD/Entra ID (MSAL), Auth0, Okta, AWS Cognito, and Google via the `IAuthProvider` interface
58
+ - **API Key Authorization**: User-level (`X-API-Key`) and system-level (`x-mj-api-key`) API keys with scope-based access control
59
+ - **Per-Request Provider Isolation**: Each GraphQL request receives its own `SQLServerDataProvider` instance for transaction safety
60
+ - **Multi-Database Support**: Separate read-write and read-only database connection pools
61
+ - **Transaction Management**: Automatic transaction wrapping for GraphQL mutations with savepoint support
62
+ - **Scheduled Jobs**: Built-in job scheduler with configurable polling, concurrency limits, and lock management
63
+ - **SQL Logging**: Real-time SQL statement capture with session management, user filtering, and migration-format output
64
+ - **AI Integration**: Resolvers for AI prompt execution, agent orchestration, text embeddings, and Skip AI
65
+ - **Real-time Support**: WebSocket subscriptions via `graphql-ws`
66
+ - **Response Compression**: Built-in gzip compression with configurable thresholds
67
+ - **Encryption Handling**: Transparent field-level encryption/decryption with configurable API exposure policies
68
+ - **CloudEvents**: Optional CloudEvent emission for entity lifecycle events
69
+ - **Telemetry**: Configurable server-side telemetry with multiple verbosity levels
70
+ - **Extensible Architecture**: Custom resolvers, entity subclasses, and new user handling via `@RegisterClass`
34
71
 
35
72
  ## Configuration
36
73
 
37
- The server uses configuration from its environment
38
-
39
- | Env variable | Description |
40
- | ------------------------ | ------------------------------------------------------------ |
41
- | DB_HOST | The hostname for the common data store database |
42
- | DB_PORT | The port for the common data store database (default 1433) |
43
- | DB_USERNAME | The username used to authenticate with the common data store |
44
- | DB_PASSWORD | The password used to authenticate with the common data store |
45
- | DB_DATABASE | The common data store database name |
46
- | PORT | The port used by the server (default 4000) |
47
- | ROOT_PATH | The GraphQL root path (default /) |
48
- | WEB_CLIENT_ID | The client ID used for MSAL authentication |
49
- | TENANT_ID | The tenant ID used for MSAL authentication |
50
- | ENABLE_INTROSPECTION | A flag to allow GraphQL introspection (default false) |
51
- | WEBSITE_RUN_FROM_PACKAGE | An Azure flag to indicate a read-only file system |
52
- | AUTH0_DOMAIN | The Auth0 domain |
53
- | AUTH0_CLIENT_ID | The Auth0 Client ID |
54
- | AUTH0_CLIENT_SECRET | The Auth0 Client secret |
55
- | MJ_CORE_SCHEMA | The core schema to use for the data provider |
56
- | CONFIG_FILE | An absolute path to the config file json |
57
- | DB_READ_ONLY_USERNAME | Username for read-only database connection (optional) |
58
- | DB_READ_ONLY_PASSWORD | Password for read-only database connection (optional) |
59
-
60
- ### REST API
61
-
62
- In addition to the GraphQL API, MemberJunction provides a REST API for applications that prefer RESTful architecture. By default, the REST API is enabled but can be disabled.
63
-
64
- For comprehensive documentation on the REST API, including configuration options, security controls, and available endpoints, see [REST_API.md](./REST_API.md).
65
-
66
- The REST API supports:
67
- - Standard CRUD operations for entities
68
- - View operations for data retrieval
69
- - Metadata exploration
70
- - Wildcard pattern matching for entity filtering
71
- - Schema-level access control
72
- - Comprehensive security configuration
74
+ MJServer uses a layered configuration system with the following priority (highest to lowest):
75
+
76
+ 1. Environment variables
77
+ 2. `mj.config.cjs` file (discovered via [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig))
78
+ 3. `DEFAULT_SERVER_CONFIG` hardcoded defaults
79
+
80
+ ### Environment Variables
81
+
82
+ | Variable | Description | Default |
83
+ |----------|-------------|---------|
84
+ | `DB_HOST` | Database server hostname | `localhost` |
85
+ | `DB_PORT` | Database server port | `1433` |
86
+ | `DB_DATABASE` | Database name | (required) |
87
+ | `DB_USERNAME` | Database username | (required) |
88
+ | `DB_PASSWORD` | Database password | (required) |
89
+ | `DB_READ_ONLY_USERNAME` | Read-only connection username | (optional) |
90
+ | `DB_READ_ONLY_PASSWORD` | Read-only connection password | (optional) |
91
+ | `DB_TRUST_SERVER_CERTIFICATE` | Trust self-signed certs | `false` |
92
+ | `DB_INSTANCE_NAME` | Named SQL Server instance | (optional) |
93
+ | `MJ_CORE_SCHEMA` | MJ metadata schema name | `__mj` |
94
+ | `GRAPHQL_PORT` | Server listen port | `4000` |
95
+ | `GRAPHQL_ROOT_PATH` | GraphQL endpoint path | `/` |
96
+ | `GRAPHQL_BASE_URL` | Server base URL | `http://localhost` |
97
+ | `MJAPI_PUBLIC_URL` | Public callback URL (e.g. ngrok) | (optional) |
98
+ | `ENABLE_INTROSPECTION` | Allow GraphQL introspection | `false` |
99
+ | `MJ_API_KEY` | System-level API key | (optional) |
100
+ | `TENANT_ID` | Azure AD tenant ID | (optional) |
101
+ | `WEB_CLIENT_ID` | Azure AD client ID | (optional) |
102
+ | `AUTH0_DOMAIN` | Auth0 domain | (optional) |
103
+ | `AUTH0_CLIENT_ID` | Auth0 client ID | (optional) |
104
+ | `AUTH0_CLIENT_SECRET` | Auth0 client secret | (optional) |
105
+ | `WEBSITE_RUN_FROM_PACKAGE` | Azure read-only filesystem flag | (optional) |
106
+ | `MJ_REST_API_ENABLED` | Enable/disable REST API | (from config) |
107
+ | `MJ_REST_API_INCLUDE_ENTITIES` | Comma-separated entity include list | (optional) |
108
+ | `MJ_REST_API_EXCLUDE_ENTITIES` | Comma-separated entity exclude list | (optional) |
109
+ | `MJ_TELEMETRY_ENABLED` | Enable server telemetry | `true` |
110
+ | `METADATA_CACHE_REFRESH_INTERVAL` | Metadata refresh interval (ms) | `180000` |
111
+
112
+ ### Configuration File (`mj.config.cjs`)
113
+
114
+ ```javascript
115
+ module.exports = {
116
+ dbHost: 'myserver.database.windows.net',
117
+ dbDatabase: 'MemberJunction',
118
+ dbUsername: 'mj_user',
119
+ dbPassword: 'secret',
120
+ mjCoreSchema: '__mj',
121
+ graphqlPort: 4000,
122
+
123
+ userHandling: {
124
+ autoCreateNewUsers: true,
125
+ newUserLimitedToAuthorizedDomains: false,
126
+ newUserRoles: ['UI', 'Developer'],
127
+ contextUserForNewUserCreation: 'admin@example.com',
128
+ CreateUserApplicationRecords: true,
129
+ },
130
+
131
+ databaseSettings: {
132
+ connectionTimeout: 45000,
133
+ requestTimeout: 30000,
134
+ metadataCacheRefreshInterval: 180000,
135
+ connectionPool: {
136
+ max: 50,
137
+ min: 5,
138
+ idleTimeoutMillis: 30000,
139
+ acquireTimeoutMillis: 30000,
140
+ },
141
+ },
142
+
143
+ restApiOptions: {
144
+ enabled: true,
145
+ includeEntities: ['User*', 'Entity*'],
146
+ excludeEntities: ['Password', 'APIKey*'],
147
+ includeSchemas: ['public'],
148
+ excludeSchemas: ['internal'],
149
+ },
150
+
151
+ scheduledJobs: {
152
+ enabled: true,
153
+ systemUserEmail: 'system@example.com',
154
+ maxConcurrentJobs: 5,
155
+ },
156
+
157
+ sqlLogging: {
158
+ enabled: true,
159
+ allowedLogDirectory: './logs/sql',
160
+ maxActiveSessions: 5,
161
+ sessionTimeout: 3600000,
162
+ },
73
163
 
164
+ telemetry: {
165
+ enabled: true,
166
+ level: 'standard', // 'minimal' | 'standard' | 'verbose' | 'debug'
167
+ },
168
+ };
169
+ ```
74
170
 
75
171
  ## Usage
76
172
 
77
173
  ### Basic Server Setup
78
174
 
79
- Import the `serve` function from the package and run it as part of the server's main function. The function accepts an array of absolute paths to the resolver code.
175
+ Import the `serve` function and provide paths to your custom resolver modules. The server automatically includes its own built-in resolvers.
80
176
 
81
- ```ts
177
+ ```typescript
82
178
  import { serve } from '@memberjunction/server';
83
179
  import { resolve } from 'node:path';
84
180
 
@@ -88,357 +184,338 @@ const resolverPaths = [
88
184
  'resolvers/**/*Resolver.{js,ts}',
89
185
  'generic/*Resolver.{js,ts}',
90
186
  'generated/generated.ts',
91
- ]
187
+ ];
92
188
 
93
189
  serve(resolverPaths.map(localPath));
94
190
  ```
95
191
 
96
192
  ### Advanced Server Options
97
193
 
98
- The `serve` function accepts an optional `MJServerOptions` object:
194
+ The `serve` function accepts an optional `MJServerOptions` object for lifecycle hooks and REST API overrides.
99
195
 
100
196
  ```typescript
101
- import { serve, MJServerOptions } from '@memberjunction/server';
197
+ import { serve, createApp, MJServerOptions } from '@memberjunction/server';
102
198
 
103
199
  const options: MJServerOptions = {
104
200
  onBeforeServe: async () => {
105
- // Custom initialization logic
201
+ // Custom initialization after schema is built, before HTTP listen
106
202
  console.log('Server is about to start...');
107
203
  },
108
204
  restApiOptions: {
109
205
  enabled: true,
110
206
  includeEntities: ['User*', 'Entity*'],
111
207
  excludeEntities: ['Password', 'APIKey*'],
112
- includeSchemas: ['public'],
113
- excludeSchemas: ['internal']
114
- }
208
+ },
115
209
  };
116
210
 
117
211
  serve(resolverPaths.map(localPath), createApp(), options);
118
212
  ```
119
213
 
120
- ### Transaction Management
214
+ ### Custom New User Handling
121
215
 
122
- MJServer provides automatic transaction management for all GraphQL mutations with full support for multi-user environments through per-request provider instances.
123
-
124
- #### Per-Request Provider Architecture
125
-
126
- Each GraphQL request receives its own SQLServerDataProvider instance, ensuring complete isolation between concurrent requests:
216
+ Override the default new user creation behavior by subclassing `NewUserBase` and registering it with a higher priority.
127
217
 
128
218
  ```typescript
129
- // Each request automatically:
130
- // 1. Creates a new SQLServerDataProvider instance
131
- // 2. Reuses cached metadata for fast initialization
132
- // 3. Has isolated transaction state within the provider
133
- // 4. Gets garbage collected after request completion
134
-
135
- mutation {
136
- CreateUser(input: { FirstName: "John", LastName: "Doe" }) {
137
- ID
138
- }
139
- CreateUserRole(input: { UserID: "...", RoleID: "..." }) {
140
- ID
219
+ import { RegisterClass } from '@memberjunction/global';
220
+ import { NewUserBase } from '@memberjunction/server';
221
+
222
+ @RegisterClass(NewUserBase, undefined, 1)
223
+ export class CustomNewUserHandler extends NewUserBase {
224
+ public override async createNewUser(
225
+ firstName: string,
226
+ lastName: string,
227
+ email: string
228
+ ) {
229
+ // Custom logic: create linked records, assign roles, etc.
230
+ return super.createNewUser(firstName, lastName, email, 'Other');
141
231
  }
142
232
  }
143
- // Both operations execute within the same provider's transaction
144
- // Success: Both committed together
145
- // Error: Both rolled back together
146
233
  ```
147
234
 
148
- #### Key Transaction Features
235
+ Import the file before calling `serve` to ensure registration:
149
236
 
150
- - **Provider Isolation**: Each request has its own data provider instance
151
- - **Automatic Management**: Transactions are automatically started, committed, or rolled back
152
- - **Multi-User Safety**: Concurrent requests have completely isolated provider instances
153
- - **Zero Configuration**: No transaction IDs or scope management needed
154
- - **Efficient**: Metadata caching makes provider creation lightweight
155
- - **Nested Support**: Providers use SQL Server savepoints for nested transaction logic
237
+ ```typescript
238
+ import './auth/customNewUserHandler';
239
+ import { serve } from '@memberjunction/server';
240
+ // ...
241
+ serve(resolverPaths);
242
+ ```
156
243
 
157
- ### Provider Configuration
244
+ ## Architecture
245
+
246
+ ### Request Lifecycle
247
+
248
+ ```mermaid
249
+ sequenceDiagram
250
+ participant C as Client
251
+ participant A as Auth Layer
252
+ participant CF as Context Factory
253
+ participant R as Resolver
254
+ participant P as SQLServerDataProvider
255
+ participant DB as SQL Server
256
+
257
+ C->>A: Request (Bearer token / API key)
258
+ A->>A: Validate JWT or API key
259
+ A->>A: Resolve UserInfo from UserCache
260
+ A->>CF: Create request context
261
+ CF->>P: New per-request provider instance
262
+ P->>P: Reuse cached metadata
263
+ CF->>R: Pass AppContext with providers
264
+ R->>P: Execute operations
265
+ P->>DB: SQL queries within transaction
266
+ DB-->>P: Results
267
+ P-->>R: Entity objects / data
268
+ R-->>C: GraphQL / REST response
269
+ Note over P,DB: Transaction auto-committed on success, rolled back on error
270
+ ```
158
271
 
159
- MJServer automatically creates per-request SQLServerDataProvider instances for optimal isolation and performance:
272
+ ### Per-Request Provider Isolation
273
+
274
+ Each GraphQL request receives its own `SQLServerDataProvider` instance, ensuring complete transaction isolation between concurrent requests. Metadata is cached and reused across providers for efficiency.
160
275
 
161
276
  ```typescript
162
- // In each GraphQL request context:
277
+ // Automatically created per request in context.ts:
163
278
  const provider = new SQLServerDataProvider();
164
279
  await provider.Config({
165
280
  connectionPool: pool,
166
281
  MJCoreSchemaName: '__mj',
167
- ignoreExistingMetadata: false // Reuse cached metadata
282
+ ignoreExistingMetadata: false, // Reuse cached metadata
168
283
  });
169
284
 
170
- // Provider is included in AppContext
171
- context.providers = [{
172
- provider: provider,
173
- type: 'Read-Write'
174
- }];
285
+ // Included in AppContext for resolvers:
286
+ context.providers = [
287
+ { provider: readWriteProvider, type: 'Read-Write' },
288
+ { provider: readOnlyProvider, type: 'Read-Only' }, // if configured
289
+ ];
175
290
  ```
176
291
 
177
- #### Provider Types
178
-
179
- The AppContext supports multiple providers with different access levels:
180
-
181
- ```typescript
182
- export type AppContext = {
183
- dataSource: sql.ConnectionPool; // Legacy, for backward compatibility
184
- userPayload: UserPayload;
185
- dataSources: DataSourceInfo[];
186
- providers: Array<ProviderInfo>; // Per-request provider instances
187
- };
188
-
189
- export class ProviderInfo {
190
- provider: DatabaseProviderBase;
191
- type: 'Admin' | 'Read-Write' | 'Read-Only' | 'Other';
192
- }
292
+ ### Authentication Architecture
293
+
294
+ MJServer uses a pluggable authentication provider system built on the `IAuthProvider` interface and `AuthProviderFactory`.
295
+
296
+ ```mermaid
297
+ flowchart LR
298
+ subgraph Providers["Auth Providers"]
299
+ MSAL[MSAL / Azure AD]
300
+ A0[Auth0]
301
+ OK[Okta]
302
+ COG[AWS Cognito]
303
+ GOOG[Google]
304
+ end
305
+
306
+ subgraph Factory["AuthProviderFactory"]
307
+ REG[Provider Registry]
308
+ CACHE[Issuer Cache]
309
+ end
310
+
311
+ subgraph Auth["Authentication Flow"]
312
+ JWT[JWT Token]
313
+ APIKEY[API Key]
314
+ SYS[System API Key]
315
+ end
316
+
317
+ JWT --> REG
318
+ REG --> Providers
319
+ APIKEY --> VAL[API Key Engine]
320
+ SYS --> SYSUSER[System User Lookup]
321
+
322
+ style Providers fill:#2d6a9f,stroke:#1a4971,color:#fff
323
+ style Factory fill:#7c5295,stroke:#563a6b,color:#fff
324
+ style Auth fill:#b8762f,stroke:#8a5722,color:#fff
193
325
  ```
194
326
 
195
- ### Custom New User Behavior
327
+ Providers are registered via `@RegisterClass(BaseAuthProvider, 'type-key')` and automatically discovered at startup. Each provider implements:
196
328
 
197
- The behavior to handle new users can be customized by subclassing the `NewUserBase` class. The subclass can pre-process, post-process or entirely override the base class behavior as needed. Import the class before calling `serve` to ensure the class is registered.
329
+ - `validateConfig()` -- Validates required configuration
330
+ - `getSigningKey()` -- Retrieves JWKS signing keys with retry logic
331
+ - `extractUserInfo()` -- Extracts email, name from provider-specific JWT claims
332
+ - `matchesIssuer()` -- Matches JWT `iss` claim to the provider
198
333
 
199
- `index.ts`
334
+ Configure providers in `mj.config.cjs` under `authProviders` or via environment variables (see Configuration section).
200
335
 
201
- ```ts
202
- import { serve } from '@memberjunction/server';
203
- import { resolve } from 'node:path';
204
-
205
- import './auth/exampleNewUserSubClass'; // make sure this new class gets registered
206
-
207
- // ...
208
- ```
209
-
210
-
211
- `auth/exampleNewUserSubClass.ts`
212
-
213
- ```ts
214
- import { LogError, Metadata, RunView } from "@memberjunction/core";
215
- import { RegisterClass } from "@memberjunction/global";
216
- import { NewUserBase, configInfo } from '@memberjunction/server';
217
- import { UserCache } from "@memberjunction/sqlserver-dataprovider";
218
-
219
- /**
220
- * This example class subclasses the @NewUserBase class and overrides the createNewUser method to create a new person record and then call the base class to create the user record. In this example there is an entity
221
- * called "Persons" that is mapped to the User table in the core MemberJunction schema. You can sub-class the NewUserBase to do whatever behavior you want and pre-process, post-process or entirely override the base
222
- * class behavior.
223
- */
224
- @RegisterClass(NewUserBase, undefined, 1) /*by putting 1 into the priority setting, MJGlobal ClassFactory will use this instead of the base class as that registration had no priority*/
225
- export class ExampleNewUserSubClass extends NewUserBase {
226
- public override async createNewUser(firstName: string, lastName: string, email: string) {
227
- try {
228
- const md = new Metadata();
229
- const contextUser = UserCache.Instance.Users.find(u => u.Email.trim().toLowerCase() === configInfo?.userHandling?.contextUserForNewUserCreation?.trim().toLowerCase())
230
- if(!contextUser) {
231
- LogError(`Failed to load context user ${configInfo?.userHandling?.contextUserForNewUserCreation}, if you've not specified this on your config.json you must do so. This is the user that is contextually used for creating a new user record dynamically.`);
232
- return undefined;
233
- }
234
-
235
- const pEntity = md.Entities.find(e => e.Name === 'Persons'); // look up the entity info for the Persons entity
236
- if (!pEntity) {
237
- LogError('Failed to find Persons entity');
238
- return undefined;
239
- }
240
-
241
- let personId;
242
- // this block of code only executes if we have an entity called Persons
243
- const rv = new RunView();
244
- const viewResults = await rv.RunView({
245
- EntityName: 'Persons',
246
- ExtraFilter: `Email = '${email}'`
247
- }, contextUser)
248
-
249
- if (viewResults && viewResults.Success && Array.isArray(viewResults.Results) && viewResults.Results.length > 0) {
250
- // we have a match so use it
251
- const row = (viewResults.Results as { ID: number }[])[0]; // we know the rows will have an ID number
252
- personId = row['ID'];
253
- }
254
-
255
- if (!personId) {
256
- // we don't have a match so create a new person record
257
- const p = await md.GetEntityObject('Persons', contextUser);
258
- p.NewRecord(); // assumes we have an entity called Persons that has FirstName/LastName/Email fields
259
- p.FirstName = firstName;
260
- p.LastName = lastName;
261
- p.Email = email;
262
- p.Status = 'active';
263
- if (await p.Save()) {
264
- personId = p.ID;
265
- }
266
- else {
267
- LogError(`Failed to create new person ${firstName} ${lastName} ${email}`)
268
- }
269
- }
270
-
271
- // now call the base class to create the user, and pass in our LinkedRecordType and ID
272
- return super.createNewUser(firstName, lastName, email, 'Other', pEntity?.ID, personId);
273
- }
274
- catch (e) {
275
- LogError(`Error creating new user ${email} ${e}`);
276
- return undefined;
277
- }
278
- }
279
- }
280
- ```
281
-
282
- ## API Documentation
283
-
284
- ### Core Exports
285
-
286
- The package exports numerous utilities and types:
287
-
288
- #### Server Functions
289
- - `serve(resolverPaths: string[], app?: Express, options?: MJServerOptions)`: Main server initialization
290
- - `createApp()`: Creates an Express application instance
291
-
292
- #### Authentication
293
- - `NewUserBase`: Base class for custom new user handling
294
- - `TokenExpiredError`: Token expiration error class
295
- - `getSystemUser(dataSource?: DataSource)`: Get system user for operations
296
-
297
- #### Resolvers and Base Classes
298
- - `ResolverBase`: Base class for custom resolvers
299
- - `RunViewResolver`: Base resolver for view operations
300
- - `PushStatusResolver`: Status update resolver base
301
-
302
- #### Type Definitions
303
- - `AppContext`: GraphQL context type
304
- - `DataSourceInfo`: Database connection information
305
- - `KeyValuePairInput`: Generic key-value input type
306
- - `DeleteOptionsInput`: Delete operation options
336
+ ### API Key Authentication
307
337
 
308
- #### Utility Functions
309
- - `GetReadOnlyDataSource(dataSources: DataSourceInfo[])`: Get read-only data source
310
- - `GetReadWriteDataSource(dataSources: DataSourceInfo[])`: Get read-write data source
338
+ The server supports two types of API key authentication:
311
339
 
312
- ### Entity Subclasses
340
+ **User API Keys** (`X-API-Key` header, `mj_sk_*` format):
341
+ - Authenticate as a specific user
342
+ - Support expiration dates and individual revocation
343
+ - Subject to scope-based authorization
344
+ - Usage is logged for audit
313
345
 
314
- The server includes specialized entity subclasses:
315
- - `UserViewEntityServer`: Server-side user view handling
316
- - `EntityPermissionsEntityServer`: Entity permission management
317
- - `DuplicateRunEntityServer`: Duplicate detection operations
318
- - `ReportEntityServer`: Report generation and management
346
+ **System API Key** (`x-mj-api-key` header):
347
+ - Single shared key set via `MJ_API_KEY` environment variable
348
+ - Authenticates as the system user with elevated privileges
349
+ - Used for server-to-server communication
319
350
 
320
- ## AI Integration
351
+ ### Scope-Based Authorization
321
352
 
322
- The server includes built-in AI capabilities:
353
+ API keys are subject to a two-level scope evaluation: the application ceiling (maximum allowed scopes for MJAPI, MCP Server, etc.) and the individual key's assigned scopes.
323
354
 
324
- ### Learning Cycle Scheduler
355
+ | Scope | Description |
356
+ |-------|-------------|
357
+ | `full_access` | Bypass all scope checks |
358
+ | `entity:read` | Read entity records |
359
+ | `entity:create` | Create records |
360
+ | `entity:update` | Update records |
361
+ | `entity:delete` | Delete records |
362
+ | `view:run` | Execute RunView queries |
363
+ | `agent:execute` | Execute AI agents |
364
+ | `agent:monitor` | Check agent run status |
365
+ | `action:execute` | Execute MJ Actions |
366
+ | `prompt:execute` | Execute AI prompts |
367
+ | `query:run` | Execute queries |
368
+ | `metadata:entities:read` | Read entity metadata |
369
+ | `metadata:agents:read` | Read agent metadata |
370
+ | `communication:send` | Send emails/messages |
325
371
 
326
- The server can automatically run AI learning cycles:
372
+ Add scope checks to custom resolvers:
327
373
 
328
374
  ```typescript
329
- import { LearningCycleScheduler } from '@memberjunction/server/scheduler';
375
+ import { ResolverBase } from '@memberjunction/server';
330
376
 
331
- // In your server initialization
332
- const scheduler = LearningCycleScheduler.Instance;
333
- scheduler.setDataSources(dataSources);
334
- scheduler.start(60); // Run every 60 minutes
377
+ @Resolver()
378
+ export class MyResolver extends ResolverBase {
379
+ @Mutation(() => MyResult)
380
+ async myOperation(@Ctx() ctx: AppContext): Promise<MyResult> {
381
+ // Checks scope for API key auth; no-op for JWT auth
382
+ await this.CheckAPIKeyScopeAuthorization('my:scope', 'resource-name', ctx.userPayload);
383
+ // Proceed with operation...
384
+ }
385
+ }
335
386
  ```
336
387
 
337
- ### AI Resolvers
338
-
339
- The server includes comprehensive AI resolvers for various AI operations:
340
-
341
- #### RunAIPromptResolver
342
- Handles AI prompt execution with multiple methods:
388
+ Alternatively, use the standalone utility functions:
343
389
 
344
- - `RunAIPrompt`: Execute stored AI prompts with full parameter support
345
- - Supports temperature, topP, topK, and other model parameters
346
- - Handles conversation messages and system prompt overrides
347
- - Returns parsed results, token usage, and execution metadata
348
-
349
- - `ExecuteSimplePrompt`: Execute ad-hoc prompts without stored configuration
350
- - Direct system prompt and message execution
351
- - Smart model selection based on preferences or power level
352
- - Automatic JSON extraction from responses
353
- - Available for both regular users and system users
354
-
355
- - `EmbedText`: Generate text embeddings using local models
356
- - Batch processing support for multiple texts
357
- - Model size selection (small/medium)
358
- - Returns vector dimensions and model information
359
- - Available for both regular users and system users
360
-
361
- #### RunAIAgentResolver
362
- - `RunAIAgent`: Execute AI agents for conversational interactions
363
- - Session management for conversation context
364
- - Streaming support for real-time responses
365
- - Progress tracking and partial results
366
- - Available for both regular users and system users
367
-
368
- #### System User Variants
369
- All AI operations have system user variants (queries) that use the `@RequireSystemUser` decorator:
370
- - `RunAIPromptSystemUser`
371
- - `ExecuteSimplePromptSystemUser`
372
- - `EmbedTextSystemUser`
373
- - `RunAIAgentSystemUser`
374
-
375
- These allow server-to-server operations with elevated privileges.
376
-
377
- #### AskSkipResolver
378
- - `AskSkipResolver`: Handle Skip AI queries
379
-
380
- ### SQL Logging Resolver
390
+ ```typescript
391
+ import { CheckAPIKeyScope, RequireScope } from '@memberjunction/server';
381
392
 
382
- - `SqlLoggingConfigResolver`: Manage SQL logging configuration and sessions
393
+ // Standalone function
394
+ await CheckAPIKeyScope(ctx.userPayload.apiKeyId, 'view:run', ctx.userPayload.userRecord, {
395
+ resource: 'Users',
396
+ });
383
397
 
384
- ## SQL Logging Management
398
+ // Pre-built scope checker
399
+ const requireViewRun = RequireScope('view:run');
400
+ await requireViewRun(ctx);
401
+ ```
385
402
 
386
- The server includes a comprehensive SQL logging management system that allows Owner-level users to control SQL statement capture in real-time.
403
+ ## GraphQL API
387
404
 
388
- ### Key Features
405
+ ### Custom Directives
389
406
 
390
- - **Owner-only Access**: SQL logging requires `Type = 'Owner'` privileges
391
- - **Session Management**: Create, monitor, and stop multiple concurrent logging sessions
392
- - **User Filtering**: Capture SQL statements from specific users only
393
- - **Multiple Formats**: Standard SQL logs or migration-ready files
394
- - **Real-time Control**: Start/stop sessions via GraphQL API
395
- - **Automatic Cleanup**: Sessions auto-expire and clean up empty files
407
+ - **`@RequireSystemUser`**: Restricts a field or mutation to system-user-only access. Applied at the schema level; non-system users receive an `AuthorizationError`.
408
+ - **`@Public`**: Marks a field as publicly accessible without authentication. All other fields require an active, authenticated user by default.
409
+
410
+ ### Built-in Resolvers
411
+
412
+ The server includes resolvers for the following domains:
413
+
414
+ | Resolver | Operations |
415
+ |----------|------------|
416
+ | `EntityResolver` | CRUD operations for all entities |
417
+ | `RunViewResolver` | RunView by ID, name, or dynamic entity |
418
+ | `RunAIPromptResolver` | Execute AI prompts, simple prompts, text embeddings |
419
+ | `RunAIAgentResolver` | Execute AI agents with session and streaming support |
420
+ | `ActionResolver` | Execute MJ Actions |
421
+ | `QueryResolver` | Execute and create queries |
422
+ | `ReportResolver` | Run and manage reports |
423
+ | `DatasetResolver` | Dataset operations |
424
+ | `UserViewResolver` | User view management |
425
+ | `UserResolver` | User profile operations |
426
+ | `UserFavoriteResolver` | Favorite record management |
427
+ | `MergeRecordsResolver` | Record merge operations |
428
+ | `SyncDataResolver` / `SyncRolesUsersResolver` | Data synchronization |
429
+ | `FileResolver` / `FileCategoryResolver` | File and category management |
430
+ | `EntityCommunicationsResolver` | Entity-level communications |
431
+ | `EntityRecordNameResolver` | Record name resolution |
432
+ | `TransactionGroupResolver` | Transaction group management |
433
+ | `ComponentRegistryResolver` | Component registry queries |
434
+ | `MCPResolver` | MCP server operations |
435
+ | `APIKeyResolver` | API key management |
436
+ | `SqlLoggingConfigResolver` | SQL logging session management |
437
+ | `TelemetryResolver` | Server telemetry queries |
438
+ | `ColorResolver` | Color palette operations |
439
+ | `InfoResolver` | Server info queries |
440
+ | `PotentialDuplicateRecordResolver` | Duplicate detection |
441
+ | `RunTestResolver` | Test execution |
442
+ | `RunTemplateResolver` | Template execution |
443
+ | `TaskResolver` | Task orchestration |
396
444
 
397
- ### GraphQL Operations
445
+ ### Transaction Management
398
446
 
399
- #### Query Configuration
447
+ GraphQL mutations are automatically wrapped in transactions through the per-request provider:
400
448
 
401
449
  ```graphql
402
- query {
403
- sqlLoggingConfig {
404
- enabled
405
- activeSessionCount
406
- maxActiveSessions
407
- allowedLogDirectory
408
- sessionTimeout
409
- defaultOptions {
410
- prettyPrint
411
- statementTypes
412
- formatAsMigration
413
- logRecordChangeMetadata
414
- }
415
- }
450
+ mutation {
451
+ CreateUser(input: { FirstName: "John", LastName: "Doe" }) { ID }
452
+ CreateUserRole(input: { UserID: "...", RoleID: "..." }) { ID }
416
453
  }
454
+ # Both operations execute within the same provider's transaction scope.
455
+ # Success: both committed together. Error: both rolled back.
417
456
  ```
418
457
 
419
- #### List Active Sessions
458
+ ### WebSocket Subscriptions
420
459
 
421
- ```graphql
422
- query {
423
- activeSqlLoggingSessions {
424
- id
425
- sessionName
426
- filePath
427
- startTime
428
- statementCount
429
- filterByUserId
430
- options {
431
- prettyPrint
432
- statementTypes
433
- formatAsMigration
434
- }
435
- }
436
- }
460
+ Real-time updates are supported via WebSocket at the same path as the GraphQL endpoint:
461
+
462
+ ```typescript
463
+ // Server-side: already configured
464
+ const webSocketServer = new WebSocketServer({
465
+ server: httpServer,
466
+ path: graphqlRootPath,
467
+ });
437
468
  ```
438
469
 
439
- #### Start a New Session
470
+ ### ResolverBase
471
+
472
+ All built-in resolvers extend `ResolverBase`, which provides:
473
+
474
+ | Method | Description |
475
+ |--------|-------------|
476
+ | `CreateRecord()` | Create entity with before/after hooks |
477
+ | `UpdateRecord()` | Update with optimistic concurrency detection |
478
+ | `DeleteRecord()` | Delete with before/after hooks |
479
+ | `RunViewByIDGeneric()` | Execute a saved view by ID |
480
+ | `RunViewByNameGeneric()` | Execute a saved view by name |
481
+ | `RunDynamicViewGeneric()` | Execute an ad-hoc view on an entity |
482
+ | `RunViewsGeneric()` | Batch-execute multiple views |
483
+ | `CheckUserReadPermissions()` | Validate entity-level read access |
484
+ | `CheckAPIKeyScopeAuthorization()` | Validate API key scope |
485
+ | `MapFieldNamesToCodeNames()` | Map field names for GraphQL transport |
486
+ | `FilterEncryptedFieldsForAPI()` | Handle encryption policy for API responses |
487
+ | `EmitCloudEvent()` | Emit CloudEvents for entity lifecycle |
488
+ | `ListenForEntityMessages()` | Subscribe to entity event messages |
489
+ | `BeforeCreate()` / `AfterCreate()` | Lifecycle hooks for create |
490
+ | `BeforeUpdate()` / `AfterUpdate()` | Lifecycle hooks for update |
491
+ | `BeforeDelete()` / `AfterDelete()` | Lifecycle hooks for delete |
492
+
493
+ ## REST API
494
+
495
+ In addition to GraphQL, MJServer provides a REST API at `/api/v1/`. By default it is disabled and can be enabled via configuration.
496
+
497
+ For comprehensive REST API documentation including endpoints, security configuration, wildcard filtering, and examples, see [REST_API.md](./REST_API.md).
498
+
499
+ Key endpoints:
500
+
501
+ | Endpoint | Method | Description |
502
+ |----------|--------|-------------|
503
+ | `/api/v1/entities/:entityName` | GET | List entity records |
504
+ | `/api/v1/entities/:entityName` | POST | Create a record |
505
+ | `/api/v1/entities/:entityName/:id` | GET | Get a record by ID |
506
+ | `/api/v1/entities/:entityName/:id` | PUT | Update a record |
507
+ | `/api/v1/entities/:entityName/:id` | DELETE | Delete a record |
508
+ | `/api/v1/views/:entityName` | POST | Run a view |
509
+ | `/api/v1/views/batch` | POST | Batch view execution |
510
+ | `/api/v1/metadata/entities` | GET | List entity metadata |
511
+ | `/api/v1/users/current` | GET | Get current user |
512
+
513
+ ## SQL Logging
514
+
515
+ The server includes a runtime SQL logging system for debugging and migration generation. Requires `Owner`-level user privileges.
440
516
 
441
517
  ```graphql
518
+ # Start a logging session
442
519
  mutation {
443
520
  startSqlLogging(input: {
444
521
  fileName: "debug-session.sql"
@@ -453,264 +530,363 @@ mutation {
453
530
  id
454
531
  filePath
455
532
  sessionName
456
- startTime
457
533
  }
458
534
  }
459
- ```
460
535
 
461
- #### Stop Sessions
462
-
463
- ```graphql
464
- # Stop specific session
465
- mutation {
466
- stopSqlLogging(sessionId: "session-id-here")
536
+ # Query active sessions
537
+ query {
538
+ activeSqlLoggingSessions {
539
+ id
540
+ sessionName
541
+ statementCount
542
+ }
467
543
  }
468
544
 
469
- # Stop all sessions
545
+ # Stop a session
470
546
  mutation {
471
- stopAllSqlLogging
547
+ stopSqlLogging(sessionId: "session-id")
472
548
  }
473
549
  ```
474
550
 
475
- ### Security Requirements
476
-
477
- All SQL logging operations require:
478
- 1. **Authentication**: Valid user session or API key
479
- 2. **Authorization**: User must have `Type = 'Owner'` in the Users table
480
- 3. **Configuration**: SQL logging must be enabled in server config
481
-
482
- ### Configuration
483
-
484
- SQL logging is configured in `mj.config.cjs`:
551
+ Configuration in `mj.config.cjs`:
485
552
 
486
553
  ```javascript
487
554
  sqlLogging: {
488
- enabled: true, // Master switch
555
+ enabled: true,
489
556
  allowedLogDirectory: './logs/sql',
490
557
  maxActiveSessions: 5,
491
- sessionTimeout: 3600000, // 1 hour
558
+ sessionTimeout: 3600000,
492
559
  autoCleanupEmptyFiles: true,
493
560
  defaultOptions: {
494
561
  formatAsMigration: false,
495
- statementTypes: 'both',
562
+ statementTypes: 'both', // 'queries' | 'mutations' | 'both'
496
563
  prettyPrint: true,
497
564
  logRecordChangeMetadata: false,
498
- retainEmptyLogFiles: false
499
- }
565
+ },
500
566
  }
501
567
  ```
502
568
 
503
- ## Security Configuration
504
-
505
- ### Authentication Providers
506
-
507
- The server supports multiple authentication providers:
508
-
509
- 1. **Azure AD (MSAL)**:
510
- - Set `TENANT_ID` and `WEB_CLIENT_ID` environment variables
511
- - Supports Microsoft identity platform
569
+ ## Scheduled Jobs
570
+
571
+ MJServer integrates with the `@memberjunction/scheduling-engine` to execute scheduled jobs defined in MemberJunction metadata.
572
+
573
+ ```mermaid
574
+ flowchart LR
575
+ subgraph Config["Configuration"]
576
+ CFG[mj.config.cjs]
577
+ end
578
+
579
+ subgraph Service["ScheduledJobsService"]
580
+ INIT[Initialize]
581
+ POLL[Start Polling]
582
+ STOP[Stop Polling]
583
+ end
584
+
585
+ subgraph Engine["SchedulingEngine"]
586
+ JOBS[Active Jobs]
587
+ EXEC[Job Execution]
588
+ LOCK[Lock Management]
589
+ end
590
+
591
+ CFG --> INIT
592
+ INIT --> POLL
593
+ POLL --> JOBS
594
+ JOBS --> EXEC
595
+ EXEC --> LOCK
596
+
597
+ style Config fill:#b8762f,stroke:#8a5722,color:#fff
598
+ style Service fill:#2d6a9f,stroke:#1a4971,color:#fff
599
+ style Engine fill:#2d8659,stroke:#1a5c3a,color:#fff
600
+ ```
512
601
 
513
- 2. **Auth0**:
514
- - Set `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET`
515
- - Supports Auth0 authentication
602
+ Configuration:
516
603
 
517
- ### API Key Authentication
604
+ ```javascript
605
+ scheduledJobs: {
606
+ enabled: true,
607
+ systemUserEmail: 'system@example.com',
608
+ maxConcurrentJobs: 5,
609
+ defaultLockTimeout: 600000, // 10 minutes
610
+ staleLockCleanupInterval: 300000, // 5 minutes
611
+ }
612
+ ```
518
613
 
519
- The server supports two types of API key authentication:
614
+ The service starts automatically during server initialization and shuts down gracefully on SIGTERM/SIGINT.
520
615
 
521
- #### User API Keys (`X-API-Key` header)
616
+ ## AI Integration
522
617
 
523
- Per-user API keys that authenticate as a specific user. These follow the `mj_sk_*` format and are created via the EncryptionEngine:
618
+ ### AI Prompt Execution
524
619
 
525
- ```typescript
526
- import { EncryptionEngine } from '@memberjunction/encryption';
527
-
528
- // Create a new API key for a user
529
- const result = await EncryptionEngine.Instance.CreateAPIKey({
530
- userId: 'user-guid-here',
531
- label: 'My Integration',
532
- description: 'API key for external integration',
533
- expiresAt: new Date('2025-12-31') // Optional
534
- }, contextUser);
535
-
536
- if (result.success) {
537
- console.log('API Key:', result.rawKey); // Save this - cannot be recovered!
620
+ ```graphql
621
+ mutation {
622
+ RunAIPrompt(input: {
623
+ PromptName: "Summarize Content"
624
+ ModelID: "model-guid"
625
+ Temperature: 0.7
626
+ Messages: [{ role: "user", content: "Summarize this article..." }]
627
+ }) {
628
+ Success
629
+ Result
630
+ TokenUsage { InputTokens OutputTokens }
631
+ }
538
632
  }
539
633
  ```
540
634
 
541
- Usage:
542
- ```bash
543
- curl -H "X-API-Key: mj_sk_abc123..." https://api.example.com/graphql
635
+ ### Simple Prompt Execution
636
+
637
+ ```graphql
638
+ mutation {
639
+ ExecuteSimplePrompt(input: {
640
+ SystemPrompt: "You are a helpful assistant."
641
+ UserMessage: "What is MemberJunction?"
642
+ ModelPowerLevel: "Standard"
643
+ }) {
644
+ Success
645
+ Result
646
+ }
647
+ }
544
648
  ```
545
649
 
546
- Features:
547
- - Authenticates as the specific user who owns the key
548
- - Supports expiration dates
549
- - Can be revoked individually
550
- - Usage is logged for audit purposes
551
- - `apiKeyId` is included in the request context
650
+ ### Text Embeddings
552
651
 
553
- #### System API Key (`x-mj-api-key` header)
652
+ ```graphql
653
+ mutation {
654
+ EmbedText(input: {
655
+ Texts: ["Hello world", "MemberJunction framework"]
656
+ ModelSize: "small"
657
+ }) {
658
+ Success
659
+ Embeddings
660
+ Dimensions
661
+ Model
662
+ }
663
+ }
664
+ ```
554
665
 
555
- A single shared API key for system-level operations, configured via the `MJ_API_KEY` environment variable:
666
+ ### AI Agent Execution
556
667
 
557
- ```bash
558
- curl -H "x-mj-api-key: your-system-key" https://api.example.com/graphql
668
+ ```graphql
669
+ mutation {
670
+ RunAIAgent(input: {
671
+ AgentID: "agent-guid"
672
+ SessionID: "session-guid"
673
+ UserMessage: "Find all active users"
674
+ }) {
675
+ Success
676
+ Result
677
+ SessionID
678
+ }
679
+ }
559
680
  ```
560
681
 
561
- Features:
562
- - Authenticates as the system user
563
- - Used for server-to-server communication
564
- - Has elevated privileges for system operations
565
- - `isSystemUser: true` is set in the request context
566
-
567
- ### Scope-Based Authorization
682
+ All AI operations have system-user variants (e.g., `RunAIPromptSystemUser`) that use the `@RequireSystemUser` directive for server-to-server operations.
568
683
 
569
- API keys are subject to scope-based authorization, which controls what operations each key can perform. This is implemented in `ResolverBase.CheckAPIKeyScopeAuthorization()`.
684
+ ## API Reference
570
685
 
571
- #### How It Works
686
+ ### Core Exports
572
687
 
573
- 1. **Application Ceiling**: Each API application (GraphQL API, MCP Server, A2A Server) defines maximum allowed scopes
574
- 2. **API Key Scopes**: Each API key has assigned scopes from the `MJ: API Scopes` entity
575
- 3. **Two-Level Evaluation**: Authorization succeeds only if both application ceiling AND API key scopes permit the operation
688
+ #### Server Functions
576
689
 
577
- #### Available Scopes
690
+ | Export | Description |
691
+ |--------|-------------|
692
+ | `serve(resolverPaths, app?, options?)` | Main server initialization function |
693
+ | `createApp()` | Creates a new Express application instance |
578
694
 
579
- | Scope | Description |
580
- |-------|-------------|
581
- | `full_access` | Bypass all scope checks ("god mode") |
582
- | `entity:read` | Read entity records |
583
- | `entity:create` | Create new records |
584
- | `entity:update` | Update existing records |
585
- | `entity:delete` | Delete records |
586
- | `view:run` | Execute RunView queries |
587
- | `agent:execute` | Execute AI agents |
588
- | `agent:monitor` | Check agent run status |
589
- | `action:execute` | Execute MJ Actions |
590
- | `prompt:execute` | Execute AI prompts |
591
- | `query:run` | Execute queries |
592
- | `metadata:entities:read` | Read entity metadata |
593
- | `metadata:agents:read` | Read agent metadata |
594
- | `communication:send` | Send emails/messages |
695
+ #### Types
595
696
 
596
- #### Default Behavior
697
+ | Export | Description |
698
+ |--------|-------------|
699
+ | `AppContext` | GraphQL resolver context type |
700
+ | `UserPayload` | Authenticated user payload |
701
+ | `DataSourceInfo` | Database connection descriptor |
702
+ | `ProviderInfo` | Per-request provider descriptor |
703
+ | `MJServerOptions` | Options for `serve()` |
704
+ | `ConfigInfo` | Full server configuration type |
705
+ | `MJServerEvent` | Server lifecycle event type |
597
706
 
598
- **Important**: API keys with **no scopes assigned** will have **no permissions** and all requests will be denied (except for OAuth/JWT authenticated users which bypass scope checks).
707
+ #### Authentication
599
708
 
600
- #### Adding Scope Checks to Custom Resolvers
709
+ | Export | Description |
710
+ |--------|-------------|
711
+ | `IAuthProvider` | Authentication provider interface |
712
+ | `AuthProviderFactory` | Provider registry and factory |
713
+ | `NewUserBase` | Base class for custom new user handling |
714
+ | `TokenExpiredError` | Token expiration error class |
715
+ | `getSystemUser(dataSource?)` | Retrieve the system user |
716
+ | `getSigningKeys(issuer)` | Get JWT signing keys for an issuer |
717
+ | `extractUserInfoFromPayload(payload)` | Extract user info from JWT claims |
718
+ | `verifyUserRecord(email, ...)` | Verify and optionally create a user record |
719
+
720
+ #### Scope Authorization
721
+
722
+ | Export | Description |
723
+ |--------|-------------|
724
+ | `CheckAPIKeyScope(apiKeyId, scopePath, contextUser, options?)` | Check API key scope |
725
+ | `CheckAPIKeyScopeAndLog(apiKeyId, scopePath, contextUser, usageDetails, options?)` | Check scope with usage logging |
726
+ | `RequireScope(scopePath, options?)` | Create a reusable scope checker |
727
+ | `RequireViewRun` | Pre-built scope checker for `view:run` |
728
+ | `RequireQueryRun` | Pre-built scope checker for `query:run` |
729
+ | `RequireAgentExecute` | Pre-built scope checker for `agent:execute` |
730
+
731
+ #### Resolver Base Classes
732
+
733
+ | Export | Description |
734
+ |--------|-------------|
735
+ | `ResolverBase` | Base class for all resolvers |
736
+ | `RunViewResolver` | Base resolver for view operations |
737
+ | `PushStatusResolver` | Status update resolver with pub/sub |
601
738
 
602
- Custom resolvers should extend `ResolverBase` and call `CheckAPIKeyScopeAuthorization()`:
739
+ #### Utility Functions
603
740
 
604
- ```typescript
605
- import { ResolverBase } from '@memberjunction/server';
741
+ | Export | Description |
742
+ |--------|-------------|
743
+ | `GetReadOnlyDataSource(dataSources, options?)` | Get read-only connection pool |
744
+ | `GetReadWriteDataSource(dataSources)` | Get read-write connection pool |
745
+ | `GetReadOnlyProvider(providers, options?)` | Get read-only provider instance |
746
+ | `GetReadWriteProvider(providers, options?)` | Get read-write provider instance |
606
747
 
607
- @Resolver()
608
- export class MyResolver extends ResolverBase {
609
- @Mutation(() => MyResult)
610
- async myOperation(
611
- @Ctx() ctx: AppContext
612
- ): Promise<MyResult> {
613
- // Check scope authorization for API keys
614
- await this.CheckAPIKeyScopeAuthorization('my:scope', 'resource-name', ctx.userPayload);
615
-
616
- // Proceed with operation...
617
- }
618
- }
619
- ```
748
+ #### GraphQL Inputs
620
749
 
621
- The method:
622
- - Returns immediately for OAuth/JWT users (no scope restrictions)
623
- - Validates API key scopes against application ceiling and key scopes
624
- - Throws `AuthorizationError` with detailed message on failure
750
+ | Export | Description |
751
+ |--------|-------------|
752
+ | `KeyValuePairInput` | Generic key-value input type |
753
+ | `DeleteOptionsInput` | Delete operation options |
625
754
 
626
- ### Access Control
755
+ #### Directives
627
756
 
628
- For REST API access control, see the comprehensive documentation in [REST_API.md](./REST_API.md).
757
+ | Export | Description |
758
+ |--------|-------------|
759
+ | `RequireSystemUser` | Decorator restricting access to system users |
760
+ | `Public` | Decorator marking endpoints as publicly accessible |
761
+ | `configInfo` | Parsed server configuration singleton |
762
+ | `DEFAULT_SERVER_CONFIG` | Default configuration values |
629
763
 
630
- ## Performance Optimization
764
+ ## Performance
631
765
 
632
- ### Compression
766
+ ### Response Compression
633
767
 
634
- The server includes built-in compression middleware:
635
- - Responses larger than 1KB are compressed
636
- - Binary files are excluded from compression
637
- - Uses compression level 6 for optimal balance
768
+ Responses larger than 1KB are automatically compressed using gzip at compression level 6. Binary content types (images, video, audio) are excluded.
638
769
 
639
770
  ### Connection Pooling
640
771
 
641
- Database connections are managed with TypeORM's connection pooling. Configure pool size in your TypeORM configuration.
772
+ Database connections are managed via `mssql` connection pools. Configure pool size in `databaseSettings.connectionPool`:
642
773
 
643
- ### Caching
774
+ ```javascript
775
+ connectionPool: {
776
+ max: 50, // Maximum connections
777
+ min: 5, // Minimum connections
778
+ idleTimeoutMillis: 30000,
779
+ acquireTimeoutMillis: 30000,
780
+ }
781
+ ```
644
782
 
645
- The server uses LRU caching for frequently accessed data. Configure cache settings in your `mj.config.cjs`:
783
+ **Recommended settings:**
784
+ - Development: `max: 10, min: 2`
785
+ - Production: `max: 50, min: 5`
786
+ - High load: `max: 100, min: 10`
787
+
788
+ ### Metadata Caching
789
+
790
+ Entity metadata is loaded once at startup and shared across all per-request provider instances. The cache refresh interval is configurable:
646
791
 
647
792
  ```javascript
648
793
  databaseSettings: {
649
- metadataCacheRefreshInterval: 300000 // 5 minutes
794
+ metadataCacheRefreshInterval: 180000, // 3 minutes
650
795
  }
651
796
  ```
652
797
 
653
- ## Advanced Configuration
798
+ ### Authentication Token Caching
654
799
 
655
- ### Custom Directives
800
+ Validated JWT tokens are cached using an LRU cache, avoiding repeated cryptographic verification for the same token within its lifetime.
656
801
 
657
- The server includes custom GraphQL directives:
658
- - `@RequireSystemUser`: Requires system user permissions
659
- - `@Public`: Makes endpoints publicly accessible
802
+ ## Graceful Shutdown
660
803
 
661
- ### SQL Logging Integration
804
+ The server registers handlers for `SIGTERM` and `SIGINT` to:
662
805
 
663
- The server integrates with the SQLServerDataProvider's logging capabilities:
664
- - Session management through GraphQL resolvers
665
- - Owner-level access control validation
666
- - Real-time session monitoring and control
667
- - Integration with MemberJunction Explorer UI
806
+ 1. Stop the scheduled jobs service
807
+ 2. Close the HTTP server
808
+ 3. Force-exit after a 10-second timeout if graceful shutdown stalls
668
809
 
669
- ### WebSocket Configuration
810
+ Unhandled promise rejections are caught and logged without crashing the server.
670
811
 
671
- For real-time subscriptions:
812
+ ## Dependencies
672
813
 
673
- ```typescript
674
- const webSocketServer = new WebSocketServer({
675
- server: httpServer,
676
- path: graphqlRootPath
677
- });
678
- ```
814
+ ### Core MemberJunction Packages
815
+
816
+ | Package | Purpose |
817
+ |---------|---------|
818
+ | [@memberjunction/core](../MJCore/README.md) | Core metadata, entities, RunView |
819
+ | [@memberjunction/core-entities](../MJCoreEntities/README.md) | Generated entity classes |
820
+ | [@memberjunction/global](../MJGlobal/README.md) | ClassFactory, event system |
821
+ | [@memberjunction/sqlserver-dataprovider](../SQLServerDataProvider/README.md) | SQL Server data provider |
822
+ | [@memberjunction/graphql-dataprovider](../GraphQLDataProvider/README.md) | GraphQL field mapping |
823
+ | [@memberjunction/config](../Config/README.md) | Configuration utilities |
824
+ | [@memberjunction/api-keys](../APIKeys/README.md) | API key engine and scope evaluation |
825
+ | [@memberjunction/encryption](../Encryption/README.md) | Field-level encryption engine |
826
+
827
+ ### AI Packages
828
+
829
+ | Package | Purpose |
830
+ |---------|---------|
831
+ | [@memberjunction/ai](../AI/README.md) | AI engine abstraction |
832
+ | [@memberjunction/ai-prompts](../AI/Prompts/README.md) | AI prompt execution |
833
+ | [@memberjunction/ai-agents](../AI/Agents/README.md) | AI agent framework |
834
+ | [@memberjunction/ai-core-plus](../AI/CorePlus/README.md) | AI prompt parameters |
835
+ | [@memberjunction/aiengine](../AIEngine/README.md) | AI engine orchestration |
836
+ | [@memberjunction/ai-provider-bundle](../AI/ProviderBundle/README.md) | Bundled AI providers |
837
+
838
+ ### Infrastructure Packages
839
+
840
+ | Package | Purpose |
841
+ |---------|---------|
842
+ | [@memberjunction/scheduling-engine](../SchedulingEngine/README.md) | Scheduled job execution |
843
+ | [@memberjunction/actions](../Actions/README.md) | Action framework |
844
+ | [@memberjunction/templates](../Templates/README.md) | Template engine |
845
+ | [@memberjunction/notifications](../Notifications/README.md) | Notification system |
846
+ | [@memberjunction/storage](../Storage/README.md) | File storage |
847
+ | [@memberjunction/communication-ms-graph](../Communication/providers/MSGraphProvider/README.md) | MS Graph communications |
848
+ | [@memberjunction/communication-sendgrid](../Communication/providers/SendGridProvider/README.md) | SendGrid communications |
849
+
850
+ ### Third-Party Dependencies
851
+
852
+ | Package | Purpose |
853
+ |---------|---------|
854
+ | `@apollo/server` | GraphQL server |
855
+ | `express` | HTTP framework |
856
+ | `type-graphql` | TypeScript GraphQL decorators |
857
+ | `mssql` | SQL Server client |
858
+ | `jsonwebtoken` / `jwks-rsa` | JWT verification |
859
+ | `graphql-ws` / `ws` | WebSocket subscriptions |
860
+ | `compression` | Response compression |
861
+ | `cosmiconfig` | Configuration file discovery |
862
+ | `cloudevents` | CloudEvent emission |
863
+ | `zod` | Configuration schema validation |
864
+
865
+ ## Related Packages
866
+
867
+ - [@memberjunction/server-bootstrap](../ServerBootstrap/README.md) -- Pre-built class registration manifest for tree-shaking prevention
868
+ - [MJAPI](../../MJAPI/README.md) -- Reference server application that consumes this package
869
+ - [@memberjunction/core](../MJCore/README.md) -- Core framework that MJServer exposes via API
679
870
 
680
871
  ## Troubleshooting
681
872
 
682
- ### Common Issues
873
+ | Issue | Solution |
874
+ |-------|----------|
875
+ | Authentication errors | Verify environment variables for your auth provider are set |
876
+ | Database connection failures | Check `DB_HOST`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD` |
877
+ | No resolvers found | Verify resolver paths passed to `serve()` are absolute and use correct glob patterns |
878
+ | Transaction errors | Review mutation logic; check that entity operations are within the per-request provider |
879
+ | SQL logging access denied | Ensure user has `Type = 'Owner'` in the Users table |
880
+ | Metadata not loading | Check `MJ_CORE_SCHEMA` matches your database schema name |
881
+ | Token expired errors | Expected behavior for long-lived sessions; client should refresh tokens |
683
882
 
684
- 1. **Authentication Errors**: Ensure all required environment variables are set
685
- 2. **Database Connection**: Verify connection string and credentials
686
- 3. **Module Loading**: Check resolver paths are correct and accessible
687
- 4. **Transaction Errors**: Review mutation logic for proper error handling
688
- 5. **SQL Logging Access**: Ensure user has Owner privileges and logging is enabled in config
883
+ Enable verbose logging:
689
884
 
690
- ### Debug Mode
691
-
692
- Enable detailed logging by setting environment variables:
693
- ```shell
885
+ ```bash
694
886
  DEBUG=mj:*
695
887
  NODE_ENV=development
696
888
  ```
697
889
 
698
- ## Best Practices
699
-
700
- 1. **Use Read-Only Connections**: Configure read-only database connections for query operations
701
- 2. **Implement Custom User Handling**: Extend `NewUserBase` for organization-specific user creation
702
- 3. **Monitor Performance**: Use the built-in timing logs for transaction monitoring
703
- 4. **Secure Your REST API**: Always configure entity and schema filters for production
704
- 5. **Handle Errors Gracefully**: Implement proper error handling in custom resolvers
705
-
706
890
  ## Contributing
707
891
 
708
- When contributing to this package:
709
- 1. Follow the existing code style and patterns
710
- 2. Add appropriate TypeScript types
711
- 3. Include tests for new functionality
712
- 4. Update documentation as needed
713
-
714
- ## License
715
-
716
- ISC
892
+ See the [MemberJunction Contributing Guide](../../CONTRIBUTING.md) for development setup and guidelines.