@memberjunction/server 5.11.0 → 5.13.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 (67) hide show
  1. package/dist/agents/skip-sdk.d.ts.map +1 -1
  2. package/dist/agents/skip-sdk.js +1 -0
  3. package/dist/agents/skip-sdk.js.map +1 -1
  4. package/dist/generated/generated.d.ts +163 -0
  5. package/dist/generated/generated.d.ts.map +1 -1
  6. package/dist/generated/generated.js +897 -0
  7. package/dist/generated/generated.js.map +1 -1
  8. package/dist/generic/ResolverBase.js +3 -3
  9. package/dist/generic/ResolverBase.js.map +1 -1
  10. package/dist/index.d.ts +2 -3
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +121 -90
  13. package/dist/index.js.map +1 -1
  14. package/dist/middleware/BaseServerMiddleware.d.ts +103 -0
  15. package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -0
  16. package/dist/middleware/BaseServerMiddleware.js +104 -0
  17. package/dist/middleware/BaseServerMiddleware.js.map +1 -0
  18. package/dist/middleware/MJTenantFilterMiddleware.d.ts +22 -0
  19. package/dist/middleware/MJTenantFilterMiddleware.d.ts.map +1 -0
  20. package/dist/middleware/MJTenantFilterMiddleware.js +41 -0
  21. package/dist/middleware/MJTenantFilterMiddleware.js.map +1 -0
  22. package/dist/middleware/index.d.ts +3 -0
  23. package/dist/middleware/index.d.ts.map +1 -0
  24. package/dist/middleware/index.js +3 -0
  25. package/dist/middleware/index.js.map +1 -0
  26. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  27. package/dist/resolvers/AdhocQueryResolver.js +8 -0
  28. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  29. package/dist/resolvers/CreateQueryResolver.d.ts +2 -0
  30. package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
  31. package/dist/resolvers/CreateQueryResolver.js +11 -0
  32. package/dist/resolvers/CreateQueryResolver.js.map +1 -1
  33. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  34. package/dist/resolvers/GetDataResolver.js +16 -2
  35. package/dist/resolvers/GetDataResolver.js.map +1 -1
  36. package/dist/resolvers/QueryResolver.d.ts +2 -0
  37. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  38. package/dist/resolvers/QueryResolver.js +20 -0
  39. package/dist/resolvers/QueryResolver.js.map +1 -1
  40. package/dist/resolvers/RunAIAgentResolver.d.ts +24 -0
  41. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  42. package/dist/resolvers/RunAIAgentResolver.js +264 -1
  43. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  44. package/package.json +59 -59
  45. package/src/__tests__/bcsaas-integration.test.ts +1 -1
  46. package/src/agents/skip-sdk.ts +1 -0
  47. package/src/generated/generated.ts +623 -1
  48. package/src/generic/ResolverBase.ts +3 -3
  49. package/src/index.ts +139 -96
  50. package/src/middleware/BaseServerMiddleware.ts +141 -0
  51. package/src/middleware/MJTenantFilterMiddleware.ts +39 -0
  52. package/src/middleware/index.ts +2 -0
  53. package/src/resolvers/AdhocQueryResolver.ts +8 -0
  54. package/src/resolvers/CreateQueryResolver.ts +9 -0
  55. package/src/resolvers/GetDataResolver.ts +18 -2
  56. package/src/resolvers/QueryResolver.ts +18 -0
  57. package/src/resolvers/RunAIAgentResolver.ts +301 -2
  58. package/dist/hooks.d.ts +0 -65
  59. package/dist/hooks.d.ts.map +0 -1
  60. package/dist/hooks.js +0 -14
  61. package/dist/hooks.js.map +0 -1
  62. package/dist/test-dynamic-plugin.d.ts +0 -6
  63. package/dist/test-dynamic-plugin.d.ts.map +0 -1
  64. package/dist/test-dynamic-plugin.js +0 -18
  65. package/dist/test-dynamic-plugin.js.map +0 -1
  66. package/src/hooks.ts +0 -77
  67. package/src/test-dynamic-plugin.ts +0 -36
@@ -61,6 +61,12 @@ export class RunQueryResultType {
61
61
  @Field()
62
62
  TotalRowCount: number;
63
63
 
64
+ @Field(() => Int, { nullable: true })
65
+ PageNumber?: number;
66
+
67
+ @Field(() => Int, { nullable: true })
68
+ PageSize?: number;
69
+
64
70
  @Field()
65
71
  ExecutionTime: number;
66
72
 
@@ -233,6 +239,8 @@ export class RunQueryResolver extends ResolverBase {
233
239
  ExecutionTime: result.ExecutionTime ?? 0,
234
240
  ErrorMessage: result.ErrorMessage || '',
235
241
  AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined,
242
+ PageNumber: result.PageNumber,
243
+ PageSize: result.PageSize,
236
244
  CacheHit: (result as any).CacheHit,
237
245
  CacheTTLRemaining: (result as any).CacheTTLRemaining
238
246
  };
@@ -276,6 +284,8 @@ export class RunQueryResolver extends ResolverBase {
276
284
  ExecutionTime: result.ExecutionTime ?? 0,
277
285
  ErrorMessage: result.ErrorMessage || '',
278
286
  AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined,
287
+ PageNumber: result.PageNumber,
288
+ PageSize: result.PageSize,
279
289
  CacheHit: (result as any).CacheHit,
280
290
  CacheTTLRemaining: (result as any).CacheTTLRemaining
281
291
  };
@@ -332,6 +342,8 @@ export class RunQueryResolver extends ResolverBase {
332
342
  ExecutionTime: result.ExecutionTime ?? 0,
333
343
  ErrorMessage: result.ErrorMessage || '',
334
344
  AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined,
345
+ PageNumber: result.PageNumber,
346
+ PageSize: result.PageSize,
335
347
  CacheHit: (result as any).CacheHit,
336
348
  CacheTTLRemaining: (result as any).CacheTTLRemaining
337
349
  };
@@ -374,6 +386,8 @@ export class RunQueryResolver extends ResolverBase {
374
386
  ExecutionTime: result.ExecutionTime ?? 0,
375
387
  ErrorMessage: result.ErrorMessage || '',
376
388
  AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined,
389
+ PageNumber: result.PageNumber,
390
+ PageSize: result.PageSize,
377
391
  CacheHit: (result as any).CacheHit,
378
392
  CacheTTLRemaining: (result as any).CacheTTLRemaining
379
393
  };
@@ -424,6 +438,8 @@ export class RunQueryResolver extends ResolverBase {
424
438
  ExecutionTime: result.ExecutionTime ?? 0,
425
439
  ErrorMessage: result.ErrorMessage || '',
426
440
  AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined,
441
+ PageNumber: result.PageNumber,
442
+ PageSize: result.PageSize,
427
443
  CacheHit: (result as Record<string, unknown>).CacheHit as boolean | undefined,
428
444
  CacheTTLRemaining: (result as Record<string, unknown>).CacheTTLRemaining as number | undefined
429
445
  };
@@ -471,6 +487,8 @@ export class RunQueryResolver extends ResolverBase {
471
487
  ExecutionTime: result.ExecutionTime ?? 0,
472
488
  ErrorMessage: result.ErrorMessage || '',
473
489
  AppliedParameters: result.AppliedParameters ? JSON.stringify(result.AppliedParameters) : undefined,
490
+ PageNumber: result.PageNumber,
491
+ PageSize: result.PageSize,
474
492
  CacheHit: (result as Record<string, unknown>).CacheHit as boolean | undefined,
475
493
  CacheTTLRemaining: (result as Record<string, unknown>).CacheTTLRemaining as number | undefined
476
494
  };
@@ -1,7 +1,7 @@
1
1
  import { Resolver, Mutation, Query, Arg, Ctx, ObjectType, Field, PubSub, PubSubEngine, Subscription, Root, ResolverFilterData, ID, Int } from 'type-graphql';
2
2
  import { AppContext, UserPayload } from '../types.js';
3
3
  import { DatabaseProviderBase, LogError, LogStatus, Metadata, RunView, UserInfo } from '@memberjunction/core';
4
- import { MJConversationDetailEntity, MJConversationDetailAttachmentEntity } from '@memberjunction/core-entities';
4
+ import { MJConversationDetailEntity, MJConversationDetailAttachmentEntity, MJAIAgentRequestEntity } from '@memberjunction/core-entities';
5
5
  import { AgentRunner } from '@memberjunction/ai-agents';
6
6
  import { MJAIAgentEntityExtended, MJAIAgentRunEntityExtended, ExecuteAgentResult, ConversationUtility, AttachmentData } from '@memberjunction/ai-core-plus';
7
7
  import { AIEngine } from '@memberjunction/aiengine';
@@ -154,7 +154,8 @@ export class RunAIAgentResolver extends ResolverBase {
154
154
  errorMessage: result.agentRun?.ErrorMessage,
155
155
  finalStep: result.agentRun?.FinalStep,
156
156
  cancelled: result.agentRun?.Status === 'Cancelled',
157
- cancellationReason: result.agentRun?.CancellationReason
157
+ cancellationReason: result.agentRun?.CancellationReason,
158
+ feedbackRequestId: result.feedbackRequestId
158
159
  };
159
160
 
160
161
  // Safely extract agent run data using GetAll() for proper serialization
@@ -433,6 +434,21 @@ export class RunAIAgentResolver extends ResolverBase {
433
434
 
434
435
  const executionTime = Date.now() - startTime;
435
436
 
437
+ // Sync feedback request if this is a continuation run (user responded via conversation)
438
+ if (lastRunId && result.agentRun?.ID) {
439
+ await this.syncFeedbackRequestFromConversation(
440
+ lastRunId,
441
+ result.agentRun.ID,
442
+ userMessage,
443
+ currentUser
444
+ );
445
+ }
446
+
447
+ // Send notification if agent created a feedback request (Chat step)
448
+ if (result.feedbackRequestId) {
449
+ await this.sendFeedbackRequestNotification(result, currentUser, pubSub, userPayload);
450
+ }
451
+
436
452
  // Create notification if enabled and artifact was created successfully
437
453
  if (createNotification && result.success && artifactInfo && artifactInfo.artifactId && artifactInfo.versionId && artifactInfo.versionNumber) {
438
454
  await this.createCompletionNotification(
@@ -752,6 +768,112 @@ export class RunAIAgentResolver extends ResolverBase {
752
768
  }
753
769
  }
754
770
 
771
+ /**
772
+ * When a continuation run completes (lastRunId was provided), sync the corresponding
773
+ * AIAgentRequest by marking it as responded. This keeps the dashboard accurate when
774
+ * users respond to Chat steps via the conversation UI.
775
+ *
776
+ * Called server-side in the resolver so the conversation UI doesn't need any changes.
777
+ */
778
+ private async syncFeedbackRequestFromConversation(
779
+ lastRunId: string,
780
+ newRunId: string,
781
+ userMessage: string | undefined,
782
+ contextUser: UserInfo
783
+ ): Promise<void> {
784
+ try {
785
+ const rv = new RunView();
786
+ const result = await rv.RunView<MJAIAgentRequestEntity>({
787
+ EntityName: 'MJ: AI Agent Requests',
788
+ ExtraFilter: `OriginatingAgentRunID='${lastRunId}' AND Status='Requested'`,
789
+ MaxRows: 1,
790
+ ResultType: 'entity_object'
791
+ }, contextUser);
792
+
793
+ if (!result.Success || !result.Results || result.Results.length === 0) {
794
+ return; // No pending request for this run — normal for non-Chat continuations
795
+ }
796
+
797
+ const request = result.Results[0];
798
+ request.Status = 'Responded';
799
+ request.RespondedAt = new Date();
800
+ request.ResponseByUserID = contextUser.ID;
801
+ request.ResumingAgentRunID = newRunId;
802
+ if (userMessage) {
803
+ request.Response = userMessage;
804
+ }
805
+
806
+ const saved = await request.Save();
807
+ if (saved) {
808
+ LogStatus(`📋 Synced feedback request ${request.ID} → Responded (via conversation)`);
809
+ } else {
810
+ LogError(`Failed to save feedback request sync for ${request.ID}`);
811
+ }
812
+ } catch (error) {
813
+ // Don't let sync failure break the agent execution
814
+ LogError(`Error syncing feedback request: ${(error as Error).message}`);
815
+ }
816
+ }
817
+
818
+ /**
819
+ * Sends a notification when an agent creates a feedback request (Chat step).
820
+ * Called after execution completes if the result contains a feedbackRequestId.
821
+ */
822
+ private async sendFeedbackRequestNotification(
823
+ result: ExecuteAgentResult,
824
+ contextUser: UserInfo,
825
+ pubSub: PubSubEngine,
826
+ userPayload: UserPayload
827
+ ): Promise<void> {
828
+ if (!result.feedbackRequestId) {
829
+ return;
830
+ }
831
+
832
+ try {
833
+ // Get agent name
834
+ await AIEngine.Instance.Config(false, contextUser);
835
+ const agent = AIEngine.Instance.Agents.find(a => UUIDsEqual(a.ID, result.agentRun?.AgentID));
836
+ const agentName = agent?.Name || 'Agent';
837
+
838
+ // Truncate message for notification
839
+ const message = result.agentRun?.Message || 'Agent needs your input';
840
+ const truncatedMessage = message.length > 200 ? message.substring(0, 197) + '...' : message;
841
+
842
+ const notificationEngine = NotificationEngine.Instance;
843
+ await notificationEngine.Config(false, contextUser);
844
+ const notifResult = await notificationEngine.SendNotification({
845
+ userId: contextUser.ID,
846
+ typeNameOrId: 'Agent Feedback Request',
847
+ title: `${agentName} needs your input`,
848
+ message: truncatedMessage,
849
+ resourceConfiguration: {
850
+ type: 'agent-request',
851
+ requestId: result.feedbackRequestId
852
+ }
853
+ }, contextUser);
854
+
855
+ if (notifResult.success && notifResult.inAppNotificationId) {
856
+ LogStatus(`📬 Feedback request notification sent (ID: ${notifResult.inAppNotificationId})`);
857
+
858
+ // Publish real-time notification event
859
+ pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
860
+ userPayload: JSON.stringify(userPayload),
861
+ message: JSON.stringify({
862
+ type: 'notification',
863
+ notificationId: notifResult.inAppNotificationId,
864
+ action: 'create',
865
+ title: `${agentName} needs your input`,
866
+ message: truncatedMessage
867
+ })
868
+ });
869
+ } else if (!notifResult.success) {
870
+ LogError(`Feedback request notification failed: ${notifResult.errors?.join(', ')}`);
871
+ }
872
+ } catch (error) {
873
+ LogError(`Error sending feedback request notification: ${(error as Error).message}`);
874
+ }
875
+ }
876
+
755
877
  /**
756
878
  * Optimized mutation that loads conversation history server-side.
757
879
  * This avoids sending large attachment data from client to server.
@@ -854,6 +976,183 @@ export class RunAIAgentResolver extends ResolverBase {
854
976
  }
855
977
  }
856
978
 
979
+ /**
980
+ * Respond to a pending AIAgentRequest from the dashboard or API.
981
+ * Updates the request record with the response and optionally spawns a
982
+ * new agent run to resume execution with the human's input.
983
+ */
984
+ @Mutation(() => AIAgentRunResult)
985
+ async RespondToAgentRequest(
986
+ @Arg('requestId') requestId: string,
987
+ @Arg('status') status: string,
988
+ @Ctx() { userPayload, providers, dataSource }: AppContext,
989
+ @Arg('response', { nullable: true }) response?: string,
990
+ @Arg('responseData', { nullable: true }) responseData?: string,
991
+ @Arg('resumeAgent', { nullable: true }) resumeAgent?: boolean
992
+ ): Promise<AIAgentRunResult> {
993
+ const startTime = Date.now();
994
+ try {
995
+ const currentUser = this.GetUserFromPayload(userPayload);
996
+ if (!currentUser) {
997
+ throw new Error('Unable to determine current user');
998
+ }
999
+
1000
+ const md = new Metadata();
1001
+ const request = await md.GetEntityObject<MJAIAgentRequestEntity>(
1002
+ 'MJ: AI Agent Requests',
1003
+ currentUser
1004
+ );
1005
+ if (!(await request.Load(requestId))) {
1006
+ throw new Error(`Agent request ${requestId} not found`);
1007
+ }
1008
+
1009
+ if (request.Status !== 'Requested') {
1010
+ throw new Error(`Request ${requestId} is already ${request.Status}, cannot respond`);
1011
+ }
1012
+
1013
+ // Validate status
1014
+ const validStatuses = ['Approved', 'Rejected', 'Responded'];
1015
+ if (!validStatuses.includes(status)) {
1016
+ throw new Error(`Invalid status "${status}". Must be one of: ${validStatuses.join(', ')}`);
1017
+ }
1018
+
1019
+ // Update the request
1020
+ request.Status = status as 'Approved' | 'Rejected' | 'Responded';
1021
+ request.Response = response || null;
1022
+ request.ResponseData = responseData || null;
1023
+ request.RespondedAt = new Date();
1024
+ request.ResponseByUserID = currentUser.ID;
1025
+
1026
+ const saved = await request.Save();
1027
+ if (!saved) {
1028
+ throw new Error(`Failed to save response for request ${requestId}`);
1029
+ }
1030
+
1031
+ LogStatus(`📋 Agent request ${requestId} → ${status} by ${currentUser.Email || currentUser.ID}`);
1032
+
1033
+ const executionTime = Date.now() - startTime;
1034
+ return {
1035
+ success: true,
1036
+ executionTimeMs: executionTime,
1037
+ result: JSON.stringify({
1038
+ success: true,
1039
+ requestId: requestId,
1040
+ status: status,
1041
+ resumed: false
1042
+ })
1043
+ };
1044
+ } catch (error) {
1045
+ const executionTime = Date.now() - startTime;
1046
+ const errorMessage = (error as Error).message || 'Unknown error';
1047
+ LogError(`RespondToAgentRequest failed: ${errorMessage}`, undefined, error);
1048
+ return {
1049
+ success: false,
1050
+ errorMessage,
1051
+ executionTimeMs: executionTime,
1052
+ result: JSON.stringify({ success: false, errorMessage })
1053
+ };
1054
+ }
1055
+ }
1056
+
1057
+ /**
1058
+ * Reassign an agent request to a different user.
1059
+ * Updates RequestForUserID and sends a notification to the new assignee.
1060
+ */
1061
+ @Mutation(() => AIAgentRunResult)
1062
+ async ReassignAgentRequest(
1063
+ @Arg('requestId') requestId: string,
1064
+ @Arg('newUserID') newUserID: string,
1065
+ @Ctx() { userPayload }: AppContext,
1066
+ @Arg('note', { nullable: true }) note?: string
1067
+ ): Promise<AIAgentRunResult> {
1068
+ const startTime = Date.now();
1069
+ try {
1070
+ const currentUser = this.GetUserFromPayload(userPayload);
1071
+ if (!currentUser) {
1072
+ throw new Error('Unable to determine current user');
1073
+ }
1074
+
1075
+ const md = new Metadata();
1076
+ const request = await md.GetEntityObject<MJAIAgentRequestEntity>(
1077
+ 'MJ: AI Agent Requests',
1078
+ currentUser
1079
+ );
1080
+ if (!(await request.Load(requestId))) {
1081
+ throw new Error(`Agent request ${requestId} not found`);
1082
+ }
1083
+
1084
+ if (request.Status !== 'Requested') {
1085
+ throw new Error(`Request ${requestId} is ${request.Status} and cannot be reassigned`);
1086
+ }
1087
+
1088
+ const previousUserID = request.RequestForUserID;
1089
+ request.RequestForUserID = newUserID;
1090
+
1091
+ // Append reassignment note to Comments
1092
+ if (note || previousUserID) {
1093
+ const timestamp = new Date().toISOString();
1094
+ const reassignEntry = `[${timestamp} by ${currentUser.Email || currentUser.ID}] Reassigned from ${previousUserID || '(unassigned)'} to ${newUserID}${note ? ` — "${note}"` : ''}`;
1095
+ request.Comments = request.Comments
1096
+ ? `${request.Comments}\n${reassignEntry}`
1097
+ : reassignEntry;
1098
+ }
1099
+
1100
+ const saved = await request.Save();
1101
+ if (!saved) {
1102
+ throw new Error(`Failed to save reassignment for request ${requestId}`);
1103
+ }
1104
+
1105
+ // Send notification to new assignee
1106
+ try {
1107
+ await AIEngine.Instance.Config(false, currentUser);
1108
+ const agent = AIEngine.Instance.Agents.find(a => UUIDsEqual(a.ID, request.AgentID));
1109
+ const agentName = agent?.Name || 'Agent';
1110
+ const truncatedRequest = request.Request.length > 200
1111
+ ? request.Request.substring(0, 197) + '...'
1112
+ : request.Request;
1113
+
1114
+ const notificationEngine = NotificationEngine.Instance;
1115
+ await notificationEngine.Config(false, currentUser);
1116
+ await notificationEngine.SendNotification({
1117
+ userId: newUserID,
1118
+ typeNameOrId: 'Agent Feedback Request',
1119
+ title: `${agentName} request assigned to you`,
1120
+ message: truncatedRequest,
1121
+ resourceConfiguration: {
1122
+ type: 'agent-request',
1123
+ requestId: requestId
1124
+ }
1125
+ }, currentUser);
1126
+ } catch (notifError) {
1127
+ LogError(`Failed to send reassignment notification: ${(notifError as Error).message}`);
1128
+ }
1129
+
1130
+ LogStatus(`📋 Agent request ${requestId} reassigned to ${newUserID}`);
1131
+
1132
+ const executionTime = Date.now() - startTime;
1133
+ return {
1134
+ success: true,
1135
+ executionTimeMs: executionTime,
1136
+ result: JSON.stringify({
1137
+ success: true,
1138
+ requestId,
1139
+ newUserID,
1140
+ reassigned: true
1141
+ })
1142
+ };
1143
+ } catch (error) {
1144
+ const executionTime = Date.now() - startTime;
1145
+ const errorMessage = (error as Error).message || 'Unknown error';
1146
+ LogError(`ReassignAgentRequest failed: ${errorMessage}`, undefined, error);
1147
+ return {
1148
+ success: false,
1149
+ errorMessage,
1150
+ executionTimeMs: executionTime,
1151
+ result: JSON.stringify({ success: false, errorMessage })
1152
+ };
1153
+ }
1154
+ }
1155
+
857
1156
  /**
858
1157
  * Execute agent in background (fire-and-forget).
859
1158
  * Handles errors by publishing error completion events via PubSub,
package/dist/hooks.d.ts DELETED
@@ -1,65 +0,0 @@
1
- /**
2
- * Server-level extensibility types for MJServer.
3
- *
4
- * Provider-level hook types (PreRunViewHook, PostRunViewHook, PreSaveHook)
5
- * are defined and exported from @memberjunction/core (hookRegistry.ts) so
6
- * that ProviderBase and BaseEntity can reference them without depending
7
- * on @memberjunction/server.
8
- *
9
- * This file re-exports those types for convenience and adds the
10
- * server-specific extensibility options (Express middleware, Apollo plugins,
11
- * schema transformers).
12
- */
13
- export type { PreRunViewHook, PostRunViewHook, PreSaveHook, HookName, HookRegistrationOptions } from '@memberjunction/core';
14
- import type { PreRunViewHook, PostRunViewHook, PreSaveHook } from '@memberjunction/core';
15
- import type { RequestHandler, ErrorRequestHandler, Application } from 'express';
16
- import type { ApolloServerPlugin } from '@apollo/server';
17
- import type { GraphQLSchema } from 'graphql';
18
- /**
19
- * A hook entry that includes the hook function plus optional registration metadata.
20
- * Dynamic packages use this format to declare hooks with priority and namespace.
21
- */
22
- export interface HookWithOptions<T> {
23
- hook: T;
24
- Priority?: number;
25
- Namespace?: string;
26
- }
27
- /** A hook value is either a plain function or a function with registration options */
28
- export type HookOrEntry<T> = T | HookWithOptions<T>;
29
- /**
30
- * Extensibility options that can be passed to `serve()` (via MJServerOptions)
31
- * or to `createMJServer()` (via MJServerConfig).
32
- *
33
- * All properties are optional — when omitted, zero behavior change (backward compatible).
34
- */
35
- export interface ServerExtensibilityOptions {
36
- /** Express middleware applied after compression but before OAuth/REST/GraphQL routes */
37
- ExpressMiddlewareBefore?: RequestHandler[];
38
- /** Express middleware/error handlers applied after all routes (catch-alls, error handlers) */
39
- ExpressMiddlewareAfter?: (RequestHandler | ErrorRequestHandler)[];
40
- /** Escape hatch for advanced Express app configuration (CORS, trust proxy, etc.) */
41
- ConfigureExpressApp?: (app: Application) => void | Promise<void>;
42
- /** Additional Apollo Server plugins merged with the built-in drain/cleanup plugins */
43
- ApolloPlugins?: ApolloServerPlugin[];
44
- /** Schema transformers applied after built-in directive transformers */
45
- SchemaTransformers?: ((schema: GraphQLSchema) => GraphQLSchema)[];
46
- /**
47
- * Express middleware that runs AFTER authentication has resolved UserInfo
48
- * onto the request. Use this for middleware that needs the authenticated
49
- * user (e.g., tenant context resolution, org membership loading).
50
- *
51
- * The authenticated user payload is available at `req.userPayload`.
52
- * Middleware in this array runs in registration order.
53
- */
54
- ExpressMiddlewarePostAuth?: RequestHandler[];
55
- /** Hooks that modify RunViewParams before query execution (e.g., tenant filter injection).
56
- * Each entry can be a plain hook function or a `{ hook, Priority, Namespace }` object. */
57
- PreRunViewHooks?: HookOrEntry<PreRunViewHook>[];
58
- /** Hooks that modify RunViewResult after query execution (e.g., data masking).
59
- * Each entry can be a plain hook function or a `{ hook, Priority, Namespace }` object. */
60
- PostRunViewHooks?: HookOrEntry<PostRunViewHook>[];
61
- /** Hooks that validate/reject Save operations before they hit the database.
62
- * Each entry can be a plain hook function or a `{ hook, Priority, Namespace }` object. */
63
- PreSaveHooks?: HookOrEntry<PreSaveHook>[];
64
- }
65
- //# sourceMappingURL=hooks.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE5H,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAA2B,MAAM,sBAAsB,CAAC;AAClH,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;GAGG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sFAAsF;AACtF,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,wFAAwF;IACxF,uBAAuB,CAAC,EAAE,cAAc,EAAE,CAAC;IAE3C,8FAA8F;IAC9F,sBAAsB,CAAC,EAAE,CAAC,cAAc,GAAG,mBAAmB,CAAC,EAAE,CAAC;IAElE,oFAAoF;IACpF,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,sFAAsF;IACtF,aAAa,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAErC,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,aAAa,KAAK,aAAa,CAAC,EAAE,CAAC;IAElE;;;;;;;OAOG;IACH,yBAAyB,CAAC,EAAE,cAAc,EAAE,CAAC;IAE7C;8FAC0F;IAC1F,eAAe,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,EAAE,CAAC;IAEhD;8FAC0F;IAC1F,gBAAgB,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC;IAElD;8FAC0F;IAC1F,YAAY,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;CAC3C"}
package/dist/hooks.js DELETED
@@ -1,14 +0,0 @@
1
- /**
2
- * Server-level extensibility types for MJServer.
3
- *
4
- * Provider-level hook types (PreRunViewHook, PostRunViewHook, PreSaveHook)
5
- * are defined and exported from @memberjunction/core (hookRegistry.ts) so
6
- * that ProviderBase and BaseEntity can reference them without depending
7
- * on @memberjunction/server.
8
- *
9
- * This file re-exports those types for convenience and adds the
10
- * server-specific extensibility options (Express middleware, Apollo plugins,
11
- * schema transformers).
12
- */
13
- export {};
14
- //# sourceMappingURL=hooks.js.map
package/dist/hooks.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
@@ -1,6 +0,0 @@
1
- /**
2
- * Startup function called by DynamicPackageLoader.
3
- * Returns extensibility config that gets merged into server options.
4
- */
5
- export declare function LoadTestPlugin(): Record<string, unknown>;
6
- //# sourceMappingURL=test-dynamic-plugin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"test-dynamic-plugin.d.ts","sourceRoot":"","sources":["../src/test-dynamic-plugin.ts"],"names":[],"mappings":"AA0BA;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKxD"}
@@ -1,18 +0,0 @@
1
- /** Post-auth middleware that logs authenticated requests */
2
- const testPostAuthMiddleware = (req, _res, next) => {
3
- const userPayload = req.userPayload;
4
- const email = userPayload?.email ?? 'unknown';
5
- console.log(`[TestPlugin] Post-auth middleware: ${req.method} ${req.path} (user: ${email})`);
6
- next();
7
- };
8
- /**
9
- * Startup function called by DynamicPackageLoader.
10
- * Returns extensibility config that gets merged into server options.
11
- */
12
- export function LoadTestPlugin() {
13
- console.log('[TestPlugin] Startup function called');
14
- return {
15
- ExpressMiddlewarePostAuth: [testPostAuthMiddleware],
16
- };
17
- }
18
- //# sourceMappingURL=test-dynamic-plugin.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"test-dynamic-plugin.js","sourceRoot":"","sources":["../src/test-dynamic-plugin.ts"],"names":[],"mappings":"AAkBA,4DAA4D;AAC5D,MAAM,sBAAsB,GAAmB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;IACjE,MAAM,WAAW,GAAI,GAAqC,CAAC,WAAW,CAAC;IACvE,MAAM,KAAK,GAAG,WAAW,EAAE,KAAK,IAAI,SAAS,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,sCAAsC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,WAAW,KAAK,GAAG,CAAC,CAAC;IAC7F,IAAI,EAAE,CAAC;AACT,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;IACpD,OAAO;QACL,yBAAyB,EAAE,CAAC,sBAAsB,CAAC;KACpD,CAAC;AACJ,CAAC"}
package/src/hooks.ts DELETED
@@ -1,77 +0,0 @@
1
- /**
2
- * Server-level extensibility types for MJServer.
3
- *
4
- * Provider-level hook types (PreRunViewHook, PostRunViewHook, PreSaveHook)
5
- * are defined and exported from @memberjunction/core (hookRegistry.ts) so
6
- * that ProviderBase and BaseEntity can reference them without depending
7
- * on @memberjunction/server.
8
- *
9
- * This file re-exports those types for convenience and adds the
10
- * server-specific extensibility options (Express middleware, Apollo plugins,
11
- * schema transformers).
12
- */
13
-
14
- export type { PreRunViewHook, PostRunViewHook, PreSaveHook, HookName, HookRegistrationOptions } from '@memberjunction/core';
15
-
16
- import type { PreRunViewHook, PostRunViewHook, PreSaveHook, HookRegistrationOptions } from '@memberjunction/core';
17
- import type { RequestHandler, ErrorRequestHandler, Application } from 'express';
18
- import type { ApolloServerPlugin } from '@apollo/server';
19
- import type { GraphQLSchema } from 'graphql';
20
-
21
- /**
22
- * A hook entry that includes the hook function plus optional registration metadata.
23
- * Dynamic packages use this format to declare hooks with priority and namespace.
24
- */
25
- export interface HookWithOptions<T> {
26
- hook: T;
27
- Priority?: number;
28
- Namespace?: string;
29
- }
30
-
31
- /** A hook value is either a plain function or a function with registration options */
32
- export type HookOrEntry<T> = T | HookWithOptions<T>;
33
-
34
- /**
35
- * Extensibility options that can be passed to `serve()` (via MJServerOptions)
36
- * or to `createMJServer()` (via MJServerConfig).
37
- *
38
- * All properties are optional — when omitted, zero behavior change (backward compatible).
39
- */
40
- export interface ServerExtensibilityOptions {
41
- /** Express middleware applied after compression but before OAuth/REST/GraphQL routes */
42
- ExpressMiddlewareBefore?: RequestHandler[];
43
-
44
- /** Express middleware/error handlers applied after all routes (catch-alls, error handlers) */
45
- ExpressMiddlewareAfter?: (RequestHandler | ErrorRequestHandler)[];
46
-
47
- /** Escape hatch for advanced Express app configuration (CORS, trust proxy, etc.) */
48
- ConfigureExpressApp?: (app: Application) => void | Promise<void>;
49
-
50
- /** Additional Apollo Server plugins merged with the built-in drain/cleanup plugins */
51
- ApolloPlugins?: ApolloServerPlugin[];
52
-
53
- /** Schema transformers applied after built-in directive transformers */
54
- SchemaTransformers?: ((schema: GraphQLSchema) => GraphQLSchema)[];
55
-
56
- /**
57
- * Express middleware that runs AFTER authentication has resolved UserInfo
58
- * onto the request. Use this for middleware that needs the authenticated
59
- * user (e.g., tenant context resolution, org membership loading).
60
- *
61
- * The authenticated user payload is available at `req.userPayload`.
62
- * Middleware in this array runs in registration order.
63
- */
64
- ExpressMiddlewarePostAuth?: RequestHandler[];
65
-
66
- /** Hooks that modify RunViewParams before query execution (e.g., tenant filter injection).
67
- * Each entry can be a plain hook function or a `{ hook, Priority, Namespace }` object. */
68
- PreRunViewHooks?: HookOrEntry<PreRunViewHook>[];
69
-
70
- /** Hooks that modify RunViewResult after query execution (e.g., data masking).
71
- * Each entry can be a plain hook function or a `{ hook, Priority, Namespace }` object. */
72
- PostRunViewHooks?: HookOrEntry<PostRunViewHook>[];
73
-
74
- /** Hooks that validate/reject Save operations before they hit the database.
75
- * Each entry can be a plain hook function or a `{ hook, Priority, Namespace }` object. */
76
- PreSaveHooks?: HookOrEntry<PreSaveHook>[];
77
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * Test Dynamic Plugin for Phase 2 DynamicPackageLoader testing.
3
- *
4
- * This simple plugin validates the DynamicPackageLoader → MJServer pipeline:
5
- * - Startup function is called and returns extensibility config
6
- * - Post-auth middleware is injected and runs on each authenticated request
7
- * - Middleware has access to the authenticated user via req.userPayload
8
- *
9
- * To enable: add to dynamicPackages.server in packages/MJAPI/mj.config.cjs
10
- * To disable: set Enabled: false or remove the entry
11
- */
12
- import type { RequestHandler } from 'express';
13
-
14
- interface UserPayload {
15
- email?: string;
16
- userRecord?: { ID?: string };
17
- }
18
-
19
- /** Post-auth middleware that logs authenticated requests */
20
- const testPostAuthMiddleware: RequestHandler = (req, _res, next) => {
21
- const userPayload = (req as { userPayload?: UserPayload }).userPayload;
22
- const email = userPayload?.email ?? 'unknown';
23
- console.log(`[TestPlugin] Post-auth middleware: ${req.method} ${req.path} (user: ${email})`);
24
- next();
25
- };
26
-
27
- /**
28
- * Startup function called by DynamicPackageLoader.
29
- * Returns extensibility config that gets merged into server options.
30
- */
31
- export function LoadTestPlugin(): Record<string, unknown> {
32
- console.log('[TestPlugin] Startup function called');
33
- return {
34
- ExpressMiddlewarePostAuth: [testPostAuthMiddleware],
35
- };
36
- }