The @memberjunction/actions library provides the core server-side infrastructure for the MemberJunction Actions Framework. It includes base classes for actions and filters, the action execution engine, and support for entity-specific actions.
The Actions Framework is a powerful system for creating reusable, parameterized business logic that can be executed on demand. Actions are "verbs" in the MemberJunction ecosystem - they perform specific tasks and can be triggered through various mechanisms including entity events, API calls, or scheduled jobs.
IMPORTANT: This library should only be imported on the server side.
npm install @memberjunction/actions
This package depends on several other MemberJunction packages:
@memberjunction/global - Global utilities and class factory@memberjunction/core - Core MJ functionality and base classes@memberjunction/actions-base - Base types and interfaces for actions@memberjunction/core-entities - Entity definitions@memberjunction/ai - AI integration capabilities@memberjunction/aiengine - AI engine functionality@memberjunction/doc-utils - Documentation utilitiesTo create a custom action, extend the BaseAction class and implement the InternalRunAction method:
import { BaseAction } from '@memberjunction/actions';
import { ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
import { RegisterClass } from '@memberjunction/global';
@RegisterClass(BaseAction, 'MyCustomAction')
export class MyCustomAction extends BaseAction {
protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
// Access input parameters
const inputParam = params.Params.find(p => p.Name === 'InputValue');
// Perform your action logic
const result = await this.performBusinessLogic(inputParam?.Value);
// Return the result
return {
Success: true,
ResultCode: 'SUCCESS',
Message: 'Action completed successfully',
Params: params.Params // Include any output parameters
};
}
private async performBusinessLogic(value: any): Promise<any> {
// Your custom logic here
return value;
}
}
The ActionEngineServer class provides the main interface for executing actions:
import { ActionEngineServer } from '@memberjunction/actions';
import { RunActionParams } from '@memberjunction/actions-base';
// Get the singleton instance
const engine = ActionEngineServer.Instance;
// Configure the engine (only needs to be done once)
await engine.Config(false, currentUser);
// Run an action by ID
const result = await engine.RunActionByID({
ActionID: 'your-action-id',
ContextUser: currentUser,
Params: [
{ Name: 'InputParam', Value: 'some value', Type: 'string' }
]
});
// Run an action with full parameters
const params: RunActionParams = {
Action: actionEntity, // ActionEntity instance
ContextUser: currentUser,
Filters: [], // Optional filters
Params: [
{ Name: 'InputParam', Value: 'some value', Type: 'string' }
]
};
const result = await engine.RunAction(params);
Actions now support type-safe context propagation for runtime-specific information:
import { BaseAction } from '@memberjunction/actions';
import { ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
import { RegisterClass } from '@memberjunction/global';
// Define your context type
interface APIContext {
apiEndpoint: string;
apiKey: string;
timeout: number;
retryCount: number;
}
// Note: BaseAction does not have generics - context is typed through params
@RegisterClass(BaseAction, 'APICallAction')
export class APICallAction extends BaseAction {
protected async InternalRunAction(params: RunActionParams<APIContext>): Promise<ActionResultSimple> {
// Access typed context through params
const endpoint = params.Context?.apiEndpoint;
const apiKey = params.Context?.apiKey;
const timeout = params.Context?.timeout || 30000;
if (!endpoint || !apiKey) {
return {
Success: false,
ResultCode: 'MISSING_CONTEXT',
Message: 'API endpoint and key are required in context'
};
}
// Use context for API call
const requestData = params.Params.find(p => p.Name === 'RequestData')?.Value;
try {
const response = await this.callAPI(endpoint, apiKey, requestData, timeout);
// Set output parameter
const outputParam = params.Params.find(p => p.Name === 'ResponseData');
if (outputParam) {
outputParam.Value = response;
}
return {
Success: true,
ResultCode: 'SUCCESS',
Message: 'API call completed successfully',
Params: params.Params
};
} catch (error) {
return {
Success: false,
ResultCode: 'API_ERROR',
Message: error.message
};
}
}
private async callAPI(endpoint: string, apiKey: string, data: any, timeout: number): Promise<any> {
// Implementation details
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data),
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
}
import { ActionEngineServer } from '@memberjunction/actions';
import { RunActionParams } from '@memberjunction/actions-base';
// Define context type
interface APIContext {
apiEndpoint: string;
apiKey: string;
timeout: number;
retryCount: number;
}
// Configure parameters with context
const params = new RunActionParams<APIContext>();
params.Action = apiCallAction;
params.ContextUser = currentUser;
params.Params = [
{ Name: 'RequestData', Value: { orderId: '12345' }, Type: 'Input' },
{ Name: 'ResponseData', Value: null, Type: 'Output' }
];
// Set runtime context
params.Context = {
apiEndpoint: process.env.API_ENDPOINT,
apiKey: process.env.API_KEY,
timeout: 10000,
retryCount: 3
};
// Execute with typed context
const result = await ActionEngineServer.Instance.RunAction(params);
Use Context for:
Use Parameters for:
The context is particularly useful when actions are executed from AI agents, as it allows the agent to pass runtime information down through the entire execution hierarchy without modifying the action's formal parameter structure.
Entity Actions are triggered automatically during entity lifecycle events. To work with entity actions, use the EntityActionEngineServer:
import { EntityActionEngineServer } from '@memberjunction/actions';
import { EntityActionInvocationParams } from '@memberjunction/actions-base';
const entityActionEngine = EntityActionEngineServer.Instance;
// Run an entity action
const params: EntityActionInvocationParams = {
EntityAction: entityActionEntity,
InvocationType: invocationTypeEntity, // e.g., 'BeforeCreate', 'AfterUpdate'
EntityObject: entityInstance,
ContextUser: currentUser
};
const result = await entityActionEngine.RunEntityAction(params);
Action filters determine whether an action should run. Create custom filters by extending BaseActionFilter:
import { BaseActionFilter } from '@memberjunction/actions';
import { RunActionParams } from '@memberjunction/actions-base';
import { ActionFilterEntity } from '@memberjunction/core-entities';
import { RegisterClass } from '@memberjunction/global';
@RegisterClass(BaseActionFilter, 'MyCustomFilter')
export class MyCustomFilter extends BaseActionFilter {
protected async InternalRun(
params: RunActionParams,
filter: ActionFilterEntity
): Promise<boolean> {
// Implement your filter logic
// Return true to allow action execution, false to skip
return params.ContextUser.IsActive === true;
}
}
The main engine for executing actions.
Methods:
RunAction(params: RunActionParams): Promise<ActionResult> - Executes an action with full control over parametersRunActionByID(params: RunActionByNameParams): Promise<ActionResult> - Convenience method to run an action by its IDConfig(forceRefresh?: boolean, contextUser?: UserInfo): Promise<void> - Configures the engine (inherited from base)Abstract base class for all actions. Note that BaseAction does not use generics - context typing is achieved through the RunActionParams parameter.
Methods:
Run(params: RunActionParams): Promise<ActionResultSimple> - Public method called by the engineInternalRunAction(params: RunActionParams): Promise<ActionResultSimple> - Abstract method to implement action logicEngine specifically for entity-related actions.
Methods:
RunEntityAction(params: EntityActionInvocationParams): Promise<EntityActionResult> - Executes an entity actionAbstract base class for action filters.
Methods:
Run(params: RunActionParams, filter: ActionFilterEntity): Promise<boolean> - Public method called by the engineInternalRun(params: RunActionParams, filter: ActionFilterEntity): Promise<boolean> - Abstract method to implement filter logicServer-side entity class for Actions with AI-powered code generation. This class is located in the @memberjunction/core-entities-server package.
Key Features:
The package exports all types from @memberjunction/actions-base, including:
RunActionParams - Parameters for running an actionActionResult - Detailed result of action executionActionResultSimple - Simplified action resultActionParam - Parameter definition for actionsEntityActionInvocationParams - Parameters for entity actionsEntityActionResult - Result of entity action executionThe framework supports various invocation types for entity actions:
Read - Triggered when reading an entityBeforeCreate - Before creating a new recordAfterCreate - After creating a new recordBeforeUpdate - Before updating a recordAfterUpdate - After updating a recordBeforeDelete - Before deleting a recordAfterDelete - After deleting a recordList - Actions operating on a list of recordsView - Actions operating on records from a viewValidate - Special invocation type for validation logicThe framework includes sophisticated AI-powered code generation capabilities:
UserPrompt fieldExample workflow:
// In your database, create an Action record with:
// Name: "SendWelcomeEmail"
// Type: "Generated"
// UserPrompt: "Send a welcome email to a new user with their name and registration date"
// The system will automatically generate the implementation code
Action Naming: Use clear, descriptive names for actions (e.g., SendInvoiceEmail, CalculateOrderTotal)
Parameter Design: Design action parameters to be reusable and flexible:
params: [
{ Name: 'EmailTemplate', Type: 'string', ValueType: 'Scalar' },
{ Name: 'RecipientUser', Type: 'User', ValueType: 'BaseEntity Sub-Class' },
{ Name: 'EmailSent', Type: 'boolean', ValueType: 'Scalar', IsInput: false }
]
Error Handling: Always include proper error handling in your actions:
try {
// Action logic
return { Success: true, ResultCode: 'SUCCESS', Message: 'Completed' };
} catch (error) {
return { Success: false, ResultCode: 'ERROR', Message: error.message };
}
Logging: The framework automatically logs action executions, but include additional logging for debugging:
import { LogError, LogStatus } from '@memberjunction/core';
LogStatus('Starting custom action processing...');
Transaction Management: Use transaction groups for multi-step operations:
const tg = await metadata.CreateTransactionGroup();
try {
// Multiple operations
await tg.Submit();
} catch (error) {
// Automatic rollback on error
}
This package integrates seamlessly with:
The package includes a generic OAuth2 token manager for server-side integrations:
import { OAuth2Manager } from '@memberjunction/actions';
// Initialize OAuth2 manager
const oauth = new OAuth2Manager({
clientId: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
tokenEndpoint: 'https://api.example.com/oauth/token',
authorizationEndpoint: 'https://api.example.com/oauth/authorize',
scopes: ['read', 'write'],
onTokenUpdate: async (tokens) => {
// Persist updated tokens to database
await saveTokens(tokens);
}
});
// Get authorization URL for user to visit
const authUrl = oauth.getAuthorizationUrl('random-state-string');
// Exchange authorization code for tokens
const tokens = await oauth.exchangeAuthorizationCode(code);
// Get valid access token (auto-refreshes if needed)
const accessToken = await oauth.getAccessToken();
Features:
⚠️ Server-Side Only: OAuth2Manager requires process.env and should only be used in Node.js server environments, not in browser/client code.
You can create custom action engines by extending ActionEngineServer:
@RegisterClass(BaseEngine, 'ActionEngineBase', 1) // Higher priority
export class CustomActionEngine extends ActionEngineServer {
protected async ValidateInputs(params: RunActionParams): Promise<boolean> {
// Custom validation logic
return super.ValidateInputs(params);
}
protected async RunFilters(params: RunActionParams): Promise<boolean> {
// Custom filter logic
return super.RunFilters(params);
}
}
Entity actions support dynamic script evaluation for parameter mapping:
// In EntityActionParam configuration:
{
ValueType: 'Script',
Value: `
const user = EntityActionContext.entityObject;
EntityActionContext.result = user.Email.toUpperCase();
`
}
@RegisterClassThis package is part of the MemberJunction ecosystem. See the main repository for license information.