Package Exports
- @memberjunction/actions
- @memberjunction/actions/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@memberjunction/actions) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@memberjunction/actions
Server-side action execution engine for MemberJunction. This package provides the runtime infrastructure for executing actions — including input validation, filter evaluation, ClassFactory-based action dispatch, execution logging, OAuth token management, and entity-bound action invocation. It is intended for server-side use only.
Installation
npm install @memberjunction/actionsOverview
The Actions Engine sits between external consumers (AI agents, workflows, APIs) and the actual action implementations registered via @RegisterClass. It handles the full execution lifecycle: validating inputs, running pre-execution filters, dispatching to the correct BaseAction subclass via ClassFactory, and logging results.
The package contains two subsystems:
- Generic Action Engine — Executes standalone actions with validation, filtering, and logging
- Entity Action Engine — Executes actions bound to entity records, supporting CRUD lifecycle hooks, list/view batch operations, and record validation
flowchart TD
subgraph Consumers["External Consumers"]
Agent["AI Agents"]
WF["Workflows"]
API["GraphQL API"]
end
subgraph Engine["@memberjunction/actions"]
AES["ActionEngineServer"]
EAES["EntityActionEngineServer"]
end
subgraph Pipeline["Execution Pipeline"]
Validate["Validate Inputs"]
Filter["Run Filters"]
Dispatch["ClassFactory Dispatch"]
Log["Execution Logging"]
end
subgraph Actions["Registered Actions"]
BA["BaseAction Subclasses"]
OAuth["BaseOAuthAction Subclasses"]
end
Consumers --> Engine
AES --> Validate --> Filter --> Dispatch --> Log
Dispatch --> BA
Dispatch --> OAuth
EAES --> AES
style Consumers fill:#2d6a9f,stroke:#1a4971,color:#fff
style Engine fill:#7c5295,stroke:#563a6b,color:#fff
style Pipeline fill:#2d8659,stroke:#1a5c3a,color:#fff
style Actions fill:#b8762f,stroke:#8a5722,color:#fffKey Features
- Action Execution Pipeline — Validates inputs, evaluates filters, dispatches via ClassFactory, and logs all executions
- ClassFactory Dispatch — Looks up
BaseActionsubclasses byDriverClassor action name at runtime - Pre-Execution Filters —
BaseActionFiltersubclasses can gate whether an action should run - Execution Logging — Automatic start/end logging to
Action Execution Logsentity with params and result codes - OAuth Token Management —
BaseOAuthActionprovides token lifecycle (refresh, retry on auth failure, persistence) - OAuth2Manager — Standalone OAuth2 client supporting authorization code, client credentials, and refresh token flows
- Entity Action Invocation — Bind actions to entity CRUD lifecycle events (BeforeCreate, AfterUpdate, etc.)
- Batch Entity Actions — Run actions against Lists or Views of records with consolidated results
- Script Evaluation — Entity action params support runtime script evaluation with entity context
Usage
Running a Standalone Action
import { ActionEngineServer } from '@memberjunction/actions';
// Configure the engine (typically done once at startup)
await ActionEngineServer.Instance.Config(false, contextUser);
// Find the action by name
const action = ActionEngineServer.Instance.Actions.find(a => a.Name === 'Send Email');
// Execute it
const result = await ActionEngineServer.Instance.RunAction({
Action: action,
ContextUser: contextUser,
Params: [
{ Name: 'to', Value: 'user@example.com', Type: 'Input' },
{ Name: 'subject', Value: 'Hello', Type: 'Input' },
{ Name: 'body', Value: 'Message content', Type: 'Input' }
],
Filters: []
});
if (result.Success) {
console.log('Action completed:', result.Message);
} else {
console.error('Action failed:', result.Message);
}Creating a Custom Action
All actions extend BaseAction and implement InternalRunAction. Register them with @RegisterClass so the engine can discover them via ClassFactory:
import { RegisterClass } from '@memberjunction/global';
import { BaseAction } from '@memberjunction/actions';
import { RunActionParams, ActionResultSimple } from '@memberjunction/actions-base';
@RegisterClass(BaseAction, 'My Custom Action')
export class MyCustomAction extends BaseAction {
protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
const inputValue = params.Params.find(p => p.Name === 'input')?.Value;
// Your action logic here
const result = await this.doWork(inputValue);
return {
Success: true,
ResultCode: 'SUCCESS',
Message: `Processed: ${result}`
};
}
private async doWork(input: string): Promise<string> {
// Delegate to service classes for real logic
return `Done with ${input}`;
}
}Creating an OAuth-Authenticated Action
For actions that need to call external APIs with OAuth2 credentials:
import { RegisterClass } from '@memberjunction/global';
import { BaseOAuthAction } from '@memberjunction/actions';
import { RunActionParams, ActionResultSimple } from '@memberjunction/actions-base';
@RegisterClass(BaseAction, 'Fetch External Data')
export class FetchExternalDataAction extends BaseOAuthAction {
protected async refreshAccessToken(): Promise<void> {
// Platform-specific token refresh logic
const response = await fetch('https://api.example.com/oauth/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.getRefreshToken(),
})
});
const data = await response.json();
await this.updateStoredTokens(data.access_token, data.refresh_token, data.expires_in);
}
protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
const companyIntegrationId = params.Params.find(
p => p.Name === 'CompanyIntegrationID'
)?.Value as string;
// Initialize OAuth (loads tokens, refreshes if expired)
if (!await this.initializeOAuth(companyIntegrationId)) {
return this.handleOAuthError(new Error('OAuth initialization failed'));
}
// Make authenticated request with automatic retry on 401
const data = await this.makeAuthenticatedRequest(async (token) => {
const res = await fetch('https://api.example.com/data', {
headers: { Authorization: `Bearer ${token}` }
});
return res.json();
});
return { Success: true, ResultCode: 'SUCCESS', Message: JSON.stringify(data) };
}
}Using OAuth2Manager Directly
For standalone OAuth2 token management outside the action framework:
import { OAuth2Manager } from '@memberjunction/actions';
const oauth = new OAuth2Manager({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
tokenEndpoint: 'https://api.example.com/oauth/token',
scopes: ['read', 'write'],
onTokenUpdate: async (tokens) => {
// Persist tokens to your storage
await saveTokens(tokens);
}
});
// Get a valid token (auto-refreshes if expired)
const token = await oauth.getAccessToken();
// Or use client credentials flow
const tokenData = await oauth.getClientCredentialsToken();Architecture
Action Execution Pipeline
The ActionEngineServer.RunAction() method follows this sequence:
sequenceDiagram
participant Caller
participant Engine as ActionEngineServer
participant Filter as BaseActionFilter
participant CF as ClassFactory
participant Action as BaseAction Subclass
participant Log as Execution Log
Caller->>Engine: RunAction(params)
Engine->>Engine: ValidateInputs(params)
alt Validation fails
Engine->>Log: StartAndEndActionLog()
Engine-->>Caller: {Success: false}
end
Engine->>Filter: RunFilters(params)
alt Filters block execution
Engine->>Log: StartAndEndActionLog()
Engine-->>Caller: {Success: true, "Filters blocked"}
end
Engine->>Log: StartActionLog()
Engine->>CF: CreateInstance(BaseAction, driverClass)
CF-->>Engine: action instance
Engine->>Action: Run(params)
Action->>Action: InternalRunAction(params)
Action-->>Engine: ActionResultSimple
Engine->>Log: EndActionLog()
Engine-->>Caller: ActionResultEntity Action Invocation
Entity actions are bound to entity lifecycle events. The EntityActionEngineServer delegates to invocation-type-specific handlers via ClassFactory:
classDiagram
class EntityActionInvocationBase {
<<abstract>>
+InvokeAction(params) EntityActionResult
+MapParams(params, entityActionParams, entity) ActionParam[]
+SafeEvalScript(id, script, entity) any
}
class SingleRecord {
+InvokeAction(params) EntityActionResult
+ValidateParams(params) boolean
}
class MultipleRecords {
+InvokeAction(params) EntityActionResult
#GetRecordList() BaseEntity[]
}
class Validate {
+InvokeAction(params) EntityActionResult
}
EntityActionInvocationBase <|-- SingleRecord
EntityActionInvocationBase <|-- MultipleRecords
SingleRecord <|-- Validate
note for SingleRecord "Registered for: Read, BeforeCreate,\nBeforeUpdate, BeforeDelete, AfterCreate,\nAfterUpdate, AfterDelete, SingleRecord"
note for MultipleRecords "Registered for: List, View"
note for Validate "Registered for: Validate"Class Hierarchy
classDiagram
class BaseAction {
<<abstract>>
+Run(params) ActionResultSimple
#InternalRunAction(params)* ActionResultSimple
}
class BaseOAuthAction {
<<abstract>>
#initializeOAuth(id) boolean
#getAccessToken() string
#makeAuthenticatedRequest(fn) T
#refreshAccessToken()* void
}
class BaseActionFilter {
<<abstract>>
+Run(params, filter) boolean
#InternalRun(params, filter)* boolean
}
class ActionEngineServer {
+RunAction(params) ActionResult
#ValidateInputs(params) boolean
#RunFilters(params) boolean
#InternalRunAction(params) ActionResult
}
class EntityActionEngineServer {
+RunEntityAction(params) EntityActionResult
}
class OAuth2Manager {
+getAccessToken() string
+getAuthorizationUrl() string
+exchangeAuthorizationCode(code) OAuth2TokenData
+getClientCredentialsToken() OAuth2TokenData
+refreshAccessToken() OAuth2TokenData
}
BaseAction <|-- BaseOAuthAction
ActionEngineServer --> BaseAction : dispatches to
ActionEngineServer --> BaseActionFilter : evaluates
EntityActionEngineServer --> ActionEngineServer : delegates to
BaseOAuthAction --> OAuth2Manager : can useAPI Reference
ActionEngineServer
Singleton engine that executes actions. Access via ActionEngineServer.Instance.
| Method | Description |
|---|---|
Config(forceRefresh, contextUser) |
Initialize/refresh the engine's action and filter metadata |
RunAction(params) |
Execute an action through the full pipeline (validate, filter, dispatch, log) |
BaseAction
Abstract base class for all action implementations.
| Method | Description |
|---|---|
Run(params) |
Public entry point — calls InternalRunAction |
InternalRunAction(params) |
Abstract — implement your action logic here |
BaseOAuthAction
Abstract base for actions requiring OAuth authentication. Extends BaseAction.
| Method | Description |
|---|---|
initializeOAuth(companyIntegrationId) |
Load integration, check/refresh tokens |
getAccessToken() |
Get the current access token |
makeAuthenticatedRequest(fn) |
Execute a request with automatic retry on 401/403 |
refreshAccessToken() |
Abstract — implement platform-specific token refresh |
updateStoredTokens(access, refresh?, expiresIn?) |
Persist new tokens to the Company Integration entity |
handleOAuthError(error) |
Return a standardized error result for OAuth failures |
BaseActionFilter
Abstract base for pre-execution filters.
| Method | Description |
|---|---|
Run(params, filter) |
Public entry point — calls InternalRun |
InternalRun(params, filter) |
Abstract — implement filter logic, return true to allow execution |
EntityActionEngineServer
Singleton engine for entity-bound actions. Access via EntityActionEngineServer.Instance.
| Method | Description |
|---|---|
RunEntityAction(params) |
Execute an entity action, dispatching to the correct invocation type handler |
OAuth2Manager
Standalone OAuth2 token manager supporting multiple grant types.
| Method | Description |
|---|---|
getAccessToken() |
Get a valid token, auto-refreshing if needed (thread-safe) |
getAuthorizationUrl(state?) |
Build the authorization URL for auth code flow |
exchangeAuthorizationCode(code) |
Exchange an auth code for tokens |
getClientCredentialsToken() |
Obtain tokens via client credentials flow |
refreshAccessToken() |
Refresh using the stored refresh token |
setTokens(access, refresh?, expiresIn?) |
Set tokens obtained externally |
isTokenValid() |
Check if current token is valid (with buffer) |
Dependencies
This package depends on:
- @memberjunction/global — ClassFactory and
@RegisterClassdecorator - @memberjunction/core —
Metadata,RunView,BaseEntity, logging utilities - @memberjunction/actions-base — Shared types (
ActionEngineBase,RunActionParams,ActionResult, etc.) - @memberjunction/core-entities — Generated entity classes (
ActionExecutionLogEntity,ActionFilterEntity, etc.) - @memberjunction/ai — AI model integration
- @memberjunction/ai-core-plus — Extended AI utilities
- @memberjunction/aiengine — AI engine orchestration
- @memberjunction/ai-prompts — AI prompt execution
Related Packages
- @memberjunction/actions-base — Shared types and base classes used by both client and server
- CoreActions — Built-in action implementations (Create Record, generated actions, etc.)
- ScheduledActions — Scheduled action execution support
- ApolloEnrichment — Apollo data enrichment actions
- ContentAutotag — Content auto-tagging actions
- CodeExecution — Dynamic code execution actions
For the Actions system philosophy and development guide, see the Actions CLAUDE.md.
Contributing
See the MemberJunction Contributing Guide for development setup and guidelines.