@memberjunction/actions 4.0.0 → 4.2.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 (3) hide show
  1. package/README.md +416 -0
  2. package/package.json +10 -10
  3. package/readme.md +321 -429
package/README.md ADDED
@@ -0,0 +1,416 @@
1
+ # @memberjunction/actions
2
+
3
+ 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.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @memberjunction/actions
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ 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.
14
+
15
+ The package contains two subsystems:
16
+
17
+ - **Generic Action Engine** — Executes standalone actions with validation, filtering, and logging
18
+ - **Entity Action Engine** — Executes actions bound to entity records, supporting CRUD lifecycle hooks, list/view batch operations, and record validation
19
+
20
+ ```mermaid
21
+ flowchart TD
22
+ subgraph Consumers["External Consumers"]
23
+ Agent["AI Agents"]
24
+ WF["Workflows"]
25
+ API["GraphQL API"]
26
+ end
27
+
28
+ subgraph Engine["@memberjunction/actions"]
29
+ AES["ActionEngineServer"]
30
+ EAES["EntityActionEngineServer"]
31
+ end
32
+
33
+ subgraph Pipeline["Execution Pipeline"]
34
+ Validate["Validate Inputs"]
35
+ Filter["Run Filters"]
36
+ Dispatch["ClassFactory Dispatch"]
37
+ Log["Execution Logging"]
38
+ end
39
+
40
+ subgraph Actions["Registered Actions"]
41
+ BA["BaseAction Subclasses"]
42
+ OAuth["BaseOAuthAction Subclasses"]
43
+ end
44
+
45
+ Consumers --> Engine
46
+ AES --> Validate --> Filter --> Dispatch --> Log
47
+ Dispatch --> BA
48
+ Dispatch --> OAuth
49
+ EAES --> AES
50
+
51
+ style Consumers fill:#2d6a9f,stroke:#1a4971,color:#fff
52
+ style Engine fill:#7c5295,stroke:#563a6b,color:#fff
53
+ style Pipeline fill:#2d8659,stroke:#1a5c3a,color:#fff
54
+ style Actions fill:#b8762f,stroke:#8a5722,color:#fff
55
+ ```
56
+
57
+ ## Key Features
58
+
59
+ - **Action Execution Pipeline** — Validates inputs, evaluates filters, dispatches via ClassFactory, and logs all executions
60
+ - **ClassFactory Dispatch** — Looks up `BaseAction` subclasses by `DriverClass` or action name at runtime
61
+ - **Pre-Execution Filters** — `BaseActionFilter` subclasses can gate whether an action should run
62
+ - **Execution Logging** — Automatic start/end logging to `Action Execution Logs` entity with params and result codes
63
+ - **OAuth Token Management** — `BaseOAuthAction` provides token lifecycle (refresh, retry on auth failure, persistence)
64
+ - **OAuth2Manager** — Standalone OAuth2 client supporting authorization code, client credentials, and refresh token flows
65
+ - **Entity Action Invocation** — Bind actions to entity CRUD lifecycle events (BeforeCreate, AfterUpdate, etc.)
66
+ - **Batch Entity Actions** — Run actions against Lists or Views of records with consolidated results
67
+ - **Script Evaluation** — Entity action params support runtime script evaluation with entity context
68
+
69
+ ## Usage
70
+
71
+ ### Running a Standalone Action
72
+
73
+ ```typescript
74
+ import { ActionEngineServer } from '@memberjunction/actions';
75
+
76
+ // Configure the engine (typically done once at startup)
77
+ await ActionEngineServer.Instance.Config(false, contextUser);
78
+
79
+ // Find the action by name
80
+ const action = ActionEngineServer.Instance.Actions.find(a => a.Name === 'Send Email');
81
+
82
+ // Execute it
83
+ const result = await ActionEngineServer.Instance.RunAction({
84
+ Action: action,
85
+ ContextUser: contextUser,
86
+ Params: [
87
+ { Name: 'to', Value: 'user@example.com', Type: 'Input' },
88
+ { Name: 'subject', Value: 'Hello', Type: 'Input' },
89
+ { Name: 'body', Value: 'Message content', Type: 'Input' }
90
+ ],
91
+ Filters: []
92
+ });
93
+
94
+ if (result.Success) {
95
+ console.log('Action completed:', result.Message);
96
+ } else {
97
+ console.error('Action failed:', result.Message);
98
+ }
99
+ ```
100
+
101
+ ### Creating a Custom Action
102
+
103
+ All actions extend `BaseAction` and implement `InternalRunAction`. Register them with `@RegisterClass` so the engine can discover them via ClassFactory:
104
+
105
+ ```typescript
106
+ import { RegisterClass } from '@memberjunction/global';
107
+ import { BaseAction } from '@memberjunction/actions';
108
+ import { RunActionParams, ActionResultSimple } from '@memberjunction/actions-base';
109
+
110
+ @RegisterClass(BaseAction, 'My Custom Action')
111
+ export class MyCustomAction extends BaseAction {
112
+ protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
113
+ const inputValue = params.Params.find(p => p.Name === 'input')?.Value;
114
+
115
+ // Your action logic here
116
+ const result = await this.doWork(inputValue);
117
+
118
+ return {
119
+ Success: true,
120
+ ResultCode: 'SUCCESS',
121
+ Message: `Processed: ${result}`
122
+ };
123
+ }
124
+
125
+ private async doWork(input: string): Promise<string> {
126
+ // Delegate to service classes for real logic
127
+ return `Done with ${input}`;
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### Creating an OAuth-Authenticated Action
133
+
134
+ For actions that need to call external APIs with OAuth2 credentials:
135
+
136
+ ```typescript
137
+ import { RegisterClass } from '@memberjunction/global';
138
+ import { BaseOAuthAction } from '@memberjunction/actions';
139
+ import { RunActionParams, ActionResultSimple } from '@memberjunction/actions-base';
140
+
141
+ @RegisterClass(BaseAction, 'Fetch External Data')
142
+ export class FetchExternalDataAction extends BaseOAuthAction {
143
+ protected async refreshAccessToken(): Promise<void> {
144
+ // Platform-specific token refresh logic
145
+ const response = await fetch('https://api.example.com/oauth/token', {
146
+ method: 'POST',
147
+ body: new URLSearchParams({
148
+ grant_type: 'refresh_token',
149
+ refresh_token: this.getRefreshToken(),
150
+ })
151
+ });
152
+ const data = await response.json();
153
+ await this.updateStoredTokens(data.access_token, data.refresh_token, data.expires_in);
154
+ }
155
+
156
+ protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
157
+ const companyIntegrationId = params.Params.find(
158
+ p => p.Name === 'CompanyIntegrationID'
159
+ )?.Value as string;
160
+
161
+ // Initialize OAuth (loads tokens, refreshes if expired)
162
+ if (!await this.initializeOAuth(companyIntegrationId)) {
163
+ return this.handleOAuthError(new Error('OAuth initialization failed'));
164
+ }
165
+
166
+ // Make authenticated request with automatic retry on 401
167
+ const data = await this.makeAuthenticatedRequest(async (token) => {
168
+ const res = await fetch('https://api.example.com/data', {
169
+ headers: { Authorization: `Bearer ${token}` }
170
+ });
171
+ return res.json();
172
+ });
173
+
174
+ return { Success: true, ResultCode: 'SUCCESS', Message: JSON.stringify(data) };
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### Using OAuth2Manager Directly
180
+
181
+ For standalone OAuth2 token management outside the action framework:
182
+
183
+ ```typescript
184
+ import { OAuth2Manager } from '@memberjunction/actions';
185
+
186
+ const oauth = new OAuth2Manager({
187
+ clientId: 'your-client-id',
188
+ clientSecret: 'your-client-secret',
189
+ tokenEndpoint: 'https://api.example.com/oauth/token',
190
+ scopes: ['read', 'write'],
191
+ onTokenUpdate: async (tokens) => {
192
+ // Persist tokens to your storage
193
+ await saveTokens(tokens);
194
+ }
195
+ });
196
+
197
+ // Get a valid token (auto-refreshes if expired)
198
+ const token = await oauth.getAccessToken();
199
+
200
+ // Or use client credentials flow
201
+ const tokenData = await oauth.getClientCredentialsToken();
202
+ ```
203
+
204
+ ## Architecture
205
+
206
+ ### Action Execution Pipeline
207
+
208
+ The `ActionEngineServer.RunAction()` method follows this sequence:
209
+
210
+ ```mermaid
211
+ sequenceDiagram
212
+ participant Caller
213
+ participant Engine as ActionEngineServer
214
+ participant Filter as BaseActionFilter
215
+ participant CF as ClassFactory
216
+ participant Action as BaseAction Subclass
217
+ participant Log as Execution Log
218
+
219
+ Caller->>Engine: RunAction(params)
220
+ Engine->>Engine: ValidateInputs(params)
221
+ alt Validation fails
222
+ Engine->>Log: StartAndEndActionLog()
223
+ Engine-->>Caller: {Success: false}
224
+ end
225
+ Engine->>Filter: RunFilters(params)
226
+ alt Filters block execution
227
+ Engine->>Log: StartAndEndActionLog()
228
+ Engine-->>Caller: {Success: true, "Filters blocked"}
229
+ end
230
+ Engine->>Log: StartActionLog()
231
+ Engine->>CF: CreateInstance(BaseAction, driverClass)
232
+ CF-->>Engine: action instance
233
+ Engine->>Action: Run(params)
234
+ Action->>Action: InternalRunAction(params)
235
+ Action-->>Engine: ActionResultSimple
236
+ Engine->>Log: EndActionLog()
237
+ Engine-->>Caller: ActionResult
238
+ ```
239
+
240
+ ### Entity Action Invocation
241
+
242
+ Entity actions are bound to entity lifecycle events. The `EntityActionEngineServer` delegates to invocation-type-specific handlers via ClassFactory:
243
+
244
+ ```mermaid
245
+ classDiagram
246
+ class EntityActionInvocationBase {
247
+ <<abstract>>
248
+ +InvokeAction(params) EntityActionResult
249
+ +MapParams(params, entityActionParams, entity) ActionParam[]
250
+ +SafeEvalScript(id, script, entity) any
251
+ }
252
+
253
+ class SingleRecord {
254
+ +InvokeAction(params) EntityActionResult
255
+ +ValidateParams(params) boolean
256
+ }
257
+
258
+ class MultipleRecords {
259
+ +InvokeAction(params) EntityActionResult
260
+ #GetRecordList() BaseEntity[]
261
+ }
262
+
263
+ class Validate {
264
+ +InvokeAction(params) EntityActionResult
265
+ }
266
+
267
+ EntityActionInvocationBase <|-- SingleRecord
268
+ EntityActionInvocationBase <|-- MultipleRecords
269
+ SingleRecord <|-- Validate
270
+
271
+ note for SingleRecord "Registered for: Read, BeforeCreate,\nBeforeUpdate, BeforeDelete, AfterCreate,\nAfterUpdate, AfterDelete, SingleRecord"
272
+ note for MultipleRecords "Registered for: List, View"
273
+ note for Validate "Registered for: Validate"
274
+ ```
275
+
276
+ ### Class Hierarchy
277
+
278
+ ```mermaid
279
+ classDiagram
280
+ class BaseAction {
281
+ <<abstract>>
282
+ +Run(params) ActionResultSimple
283
+ #InternalRunAction(params)* ActionResultSimple
284
+ }
285
+
286
+ class BaseOAuthAction {
287
+ <<abstract>>
288
+ #initializeOAuth(id) boolean
289
+ #getAccessToken() string
290
+ #makeAuthenticatedRequest(fn) T
291
+ #refreshAccessToken()* void
292
+ }
293
+
294
+ class BaseActionFilter {
295
+ <<abstract>>
296
+ +Run(params, filter) boolean
297
+ #InternalRun(params, filter)* boolean
298
+ }
299
+
300
+ class ActionEngineServer {
301
+ +RunAction(params) ActionResult
302
+ #ValidateInputs(params) boolean
303
+ #RunFilters(params) boolean
304
+ #InternalRunAction(params) ActionResult
305
+ }
306
+
307
+ class EntityActionEngineServer {
308
+ +RunEntityAction(params) EntityActionResult
309
+ }
310
+
311
+ class OAuth2Manager {
312
+ +getAccessToken() string
313
+ +getAuthorizationUrl() string
314
+ +exchangeAuthorizationCode(code) OAuth2TokenData
315
+ +getClientCredentialsToken() OAuth2TokenData
316
+ +refreshAccessToken() OAuth2TokenData
317
+ }
318
+
319
+ BaseAction <|-- BaseOAuthAction
320
+ ActionEngineServer --> BaseAction : dispatches to
321
+ ActionEngineServer --> BaseActionFilter : evaluates
322
+ EntityActionEngineServer --> ActionEngineServer : delegates to
323
+ BaseOAuthAction --> OAuth2Manager : can use
324
+ ```
325
+
326
+ ## API Reference
327
+
328
+ ### ActionEngineServer
329
+
330
+ Singleton engine that executes actions. Access via `ActionEngineServer.Instance`.
331
+
332
+ | Method | Description |
333
+ |--------|-------------|
334
+ | `Config(forceRefresh, contextUser)` | Initialize/refresh the engine's action and filter metadata |
335
+ | `RunAction(params)` | Execute an action through the full pipeline (validate, filter, dispatch, log) |
336
+
337
+ ### BaseAction
338
+
339
+ Abstract base class for all action implementations.
340
+
341
+ | Method | Description |
342
+ |--------|-------------|
343
+ | `Run(params)` | Public entry point — calls `InternalRunAction` |
344
+ | `InternalRunAction(params)` | **Abstract** — implement your action logic here |
345
+
346
+ ### BaseOAuthAction
347
+
348
+ Abstract base for actions requiring OAuth authentication. Extends `BaseAction`.
349
+
350
+ | Method | Description |
351
+ |--------|-------------|
352
+ | `initializeOAuth(companyIntegrationId)` | Load integration, check/refresh tokens |
353
+ | `getAccessToken()` | Get the current access token |
354
+ | `makeAuthenticatedRequest(fn)` | Execute a request with automatic retry on 401/403 |
355
+ | `refreshAccessToken()` | **Abstract** — implement platform-specific token refresh |
356
+ | `updateStoredTokens(access, refresh?, expiresIn?)` | Persist new tokens to the Company Integration entity |
357
+ | `handleOAuthError(error)` | Return a standardized error result for OAuth failures |
358
+
359
+ ### BaseActionFilter
360
+
361
+ Abstract base for pre-execution filters.
362
+
363
+ | Method | Description |
364
+ |--------|-------------|
365
+ | `Run(params, filter)` | Public entry point — calls `InternalRun` |
366
+ | `InternalRun(params, filter)` | **Abstract** — implement filter logic, return `true` to allow execution |
367
+
368
+ ### EntityActionEngineServer
369
+
370
+ Singleton engine for entity-bound actions. Access via `EntityActionEngineServer.Instance`.
371
+
372
+ | Method | Description |
373
+ |--------|-------------|
374
+ | `RunEntityAction(params)` | Execute an entity action, dispatching to the correct invocation type handler |
375
+
376
+ ### OAuth2Manager
377
+
378
+ Standalone OAuth2 token manager supporting multiple grant types.
379
+
380
+ | Method | Description |
381
+ |--------|-------------|
382
+ | `getAccessToken()` | Get a valid token, auto-refreshing if needed (thread-safe) |
383
+ | `getAuthorizationUrl(state?)` | Build the authorization URL for auth code flow |
384
+ | `exchangeAuthorizationCode(code)` | Exchange an auth code for tokens |
385
+ | `getClientCredentialsToken()` | Obtain tokens via client credentials flow |
386
+ | `refreshAccessToken()` | Refresh using the stored refresh token |
387
+ | `setTokens(access, refresh?, expiresIn?)` | Set tokens obtained externally |
388
+ | `isTokenValid()` | Check if current token is valid (with buffer) |
389
+
390
+ ## Dependencies
391
+
392
+ This package depends on:
393
+
394
+ - [@memberjunction/global](../../MJGlobal/README.md) — ClassFactory and `@RegisterClass` decorator
395
+ - [@memberjunction/core](../../MJCore/README.md) — `Metadata`, `RunView`, `BaseEntity`, logging utilities
396
+ - [@memberjunction/actions-base](../Base/README.md) — Shared types (`ActionEngineBase`, `RunActionParams`, `ActionResult`, etc.)
397
+ - [@memberjunction/core-entities](../../MJCoreEntities/README.md) — Generated entity classes (`ActionExecutionLogEntity`, `ActionFilterEntity`, etc.)
398
+ - [@memberjunction/ai](../../AI/Core/README.md) — AI model integration
399
+ - [@memberjunction/ai-core-plus](../../AI/CorePlus/README.md) — Extended AI utilities
400
+ - [@memberjunction/aiengine](../../AI/Engine/README.md) — AI engine orchestration
401
+ - [@memberjunction/ai-prompts](../../AI/Prompts/README.md) — AI prompt execution
402
+
403
+ ## Related Packages
404
+
405
+ - [@memberjunction/actions-base](../Base/README.md) — Shared types and base classes used by both client and server
406
+ - [CoreActions](../CoreActions/) — Built-in action implementations (Create Record, generated actions, etc.)
407
+ - [ScheduledActions](../ScheduledActions/) — Scheduled action execution support
408
+ - [ApolloEnrichment](../ApolloEnrichment/) — Apollo data enrichment actions
409
+ - [ContentAutotag](../ContentAutotag/) — Content auto-tagging actions
410
+ - [CodeExecution](../CodeExecution/) — Dynamic code execution actions
411
+
412
+ For the Actions system philosophy and development guide, see the [Actions CLAUDE.md](../CLAUDE.md).
413
+
414
+ ## Contributing
415
+
416
+ See the [MemberJunction Contributing Guide](../../../CONTRIBUTING.md) for development setup and guidelines.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@memberjunction/actions",
3
3
  "type": "module",
4
- "version": "4.0.0",
4
+ "version": "4.2.0",
5
5
  "description": "Main library for MemberJunction Actions. This library is only intended to be imported on the server side.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -20,15 +20,15 @@
20
20
  "typescript": "^5.9.3"
21
21
  },
22
22
  "dependencies": {
23
- "@memberjunction/global": "4.0.0",
24
- "@memberjunction/core": "4.0.0",
25
- "@memberjunction/actions-base": "4.0.0",
26
- "@memberjunction/core-entities": "4.0.0",
27
- "@memberjunction/ai": "4.0.0",
28
- "@memberjunction/ai-core-plus": "4.0.0",
29
- "@memberjunction/aiengine": "4.0.0",
30
- "@memberjunction/ai-prompts": "4.0.0",
31
- "@memberjunction/doc-utils": "4.0.0"
23
+ "@memberjunction/global": "4.2.0",
24
+ "@memberjunction/core": "4.2.0",
25
+ "@memberjunction/actions-base": "4.2.0",
26
+ "@memberjunction/core-entities": "4.2.0",
27
+ "@memberjunction/ai": "4.2.0",
28
+ "@memberjunction/ai-core-plus": "4.2.0",
29
+ "@memberjunction/aiengine": "4.2.0",
30
+ "@memberjunction/ai-prompts": "4.2.0",
31
+ "@memberjunction/doc-utils": "4.2.0"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
package/readme.md CHANGED
@@ -1,21 +1,6 @@
1
1
  # @memberjunction/actions
2
2
 
3
- 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.
4
-
5
- ## Overview
6
-
7
- 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.
8
-
9
- **IMPORTANT:** This library should only be imported on the server side.
10
-
11
- ## Key Features
12
-
13
- - **Action Engine**: Central execution engine for running actions with parameter validation, filtering, and logging
14
- - **Entity Actions**: Actions that can be triggered on entity lifecycle events (create, update, delete)
15
- - **Code Generation**: AI-powered automatic code generation for actions based on natural language prompts
16
- - **Action Filters**: Pre-execution filters to control when actions should run
17
- - **Transaction Support**: Built-in transaction management for complex multi-step operations
18
- - **Comprehensive Logging**: Automatic logging of all action executions with parameters and results
3
+ 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.
19
4
 
20
5
  ## Installation
21
6
 
@@ -23,502 +8,409 @@ The Actions Framework is a powerful system for creating reusable, parameterized
23
8
  npm install @memberjunction/actions
24
9
  ```
25
10
 
26
- ## Dependencies
27
-
28
- This package depends on several other MemberJunction packages:
29
- - `@memberjunction/global` - Global utilities and class factory
30
- - `@memberjunction/core` - Core MJ functionality and base classes
31
- - `@memberjunction/actions-base` - Base types and interfaces for actions
32
- - `@memberjunction/core-entities` - Entity definitions
33
- - `@memberjunction/ai` - AI integration capabilities
34
- - `@memberjunction/aiengine` - AI engine functionality
35
- - `@memberjunction/doc-utils` - Documentation utilities
36
-
37
- ## Usage
38
-
39
- ### Creating a Custom Action
11
+ ## Overview
40
12
 
41
- To create a custom action, extend the `BaseAction` class and implement the `InternalRunAction` method:
13
+ 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.
14
+
15
+ The package contains two subsystems:
16
+
17
+ - **Generic Action Engine** — Executes standalone actions with validation, filtering, and logging
18
+ - **Entity Action Engine** — Executes actions bound to entity records, supporting CRUD lifecycle hooks, list/view batch operations, and record validation
19
+
20
+ ```mermaid
21
+ flowchart TD
22
+ subgraph Consumers["External Consumers"]
23
+ Agent["AI Agents"]
24
+ WF["Workflows"]
25
+ API["GraphQL API"]
26
+ end
27
+
28
+ subgraph Engine["@memberjunction/actions"]
29
+ AES["ActionEngineServer"]
30
+ EAES["EntityActionEngineServer"]
31
+ end
32
+
33
+ subgraph Pipeline["Execution Pipeline"]
34
+ Validate["Validate Inputs"]
35
+ Filter["Run Filters"]
36
+ Dispatch["ClassFactory Dispatch"]
37
+ Log["Execution Logging"]
38
+ end
39
+
40
+ subgraph Actions["Registered Actions"]
41
+ BA["BaseAction Subclasses"]
42
+ OAuth["BaseOAuthAction Subclasses"]
43
+ end
44
+
45
+ Consumers --> Engine
46
+ AES --> Validate --> Filter --> Dispatch --> Log
47
+ Dispatch --> BA
48
+ Dispatch --> OAuth
49
+ EAES --> AES
50
+
51
+ style Consumers fill:#2d6a9f,stroke:#1a4971,color:#fff
52
+ style Engine fill:#7c5295,stroke:#563a6b,color:#fff
53
+ style Pipeline fill:#2d8659,stroke:#1a5c3a,color:#fff
54
+ style Actions fill:#b8762f,stroke:#8a5722,color:#fff
55
+ ```
42
56
 
43
- ```typescript
44
- import { BaseAction } from '@memberjunction/actions';
45
- import { ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
46
- import { RegisterClass } from '@memberjunction/global';
57
+ ## Key Features
47
58
 
48
- @RegisterClass(BaseAction, 'MyCustomAction')
49
- export class MyCustomAction extends BaseAction {
50
- protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
51
- // Access input parameters
52
- const inputParam = params.Params.find(p => p.Name === 'InputValue');
53
-
54
- // Perform your action logic
55
- const result = await this.performBusinessLogic(inputParam?.Value);
56
-
57
- // Return the result
58
- return {
59
- Success: true,
60
- ResultCode: 'SUCCESS',
61
- Message: 'Action completed successfully',
62
- Params: params.Params // Include any output parameters
63
- };
64
- }
65
-
66
- private async performBusinessLogic(value: any): Promise<any> {
67
- // Your custom logic here
68
- return value;
69
- }
70
- }
71
- ```
59
+ - **Action Execution Pipeline** — Validates inputs, evaluates filters, dispatches via ClassFactory, and logs all executions
60
+ - **ClassFactory Dispatch** — Looks up `BaseAction` subclasses by `DriverClass` or action name at runtime
61
+ - **Pre-Execution Filters** — `BaseActionFilter` subclasses can gate whether an action should run
62
+ - **Execution Logging** — Automatic start/end logging to `Action Execution Logs` entity with params and result codes
63
+ - **OAuth Token Management** — `BaseOAuthAction` provides token lifecycle (refresh, retry on auth failure, persistence)
64
+ - **OAuth2Manager** — Standalone OAuth2 client supporting authorization code, client credentials, and refresh token flows
65
+ - **Entity Action Invocation** — Bind actions to entity CRUD lifecycle events (BeforeCreate, AfterUpdate, etc.)
66
+ - **Batch Entity Actions** — Run actions against Lists or Views of records with consolidated results
67
+ - **Script Evaluation** — Entity action params support runtime script evaluation with entity context
72
68
 
73
- ### Running Actions with ActionEngine
69
+ ## Usage
74
70
 
75
- The `ActionEngineServer` class provides the main interface for executing actions:
71
+ ### Running a Standalone Action
76
72
 
77
73
  ```typescript
78
74
  import { ActionEngineServer } from '@memberjunction/actions';
79
- import { RunActionParams } from '@memberjunction/actions-base';
80
75
 
81
- // Get the singleton instance
82
- const engine = ActionEngineServer.Instance;
76
+ // Configure the engine (typically done once at startup)
77
+ await ActionEngineServer.Instance.Config(false, contextUser);
83
78
 
84
- // Configure the engine (only needs to be done once)
85
- await engine.Config(false, currentUser);
79
+ // Find the action by name
80
+ const action = ActionEngineServer.Instance.Actions.find(a => a.Name === 'Send Email');
86
81
 
87
- // Run an action by ID
88
- const result = await engine.RunActionByID({
89
- ActionID: 'your-action-id',
90
- ContextUser: currentUser,
82
+ // Execute it
83
+ const result = await ActionEngineServer.Instance.RunAction({
84
+ Action: action,
85
+ ContextUser: contextUser,
91
86
  Params: [
92
- { Name: 'InputParam', Value: 'some value', Type: 'string' }
93
- ]
87
+ { Name: 'to', Value: 'user@example.com', Type: 'Input' },
88
+ { Name: 'subject', Value: 'Hello', Type: 'Input' },
89
+ { Name: 'body', Value: 'Message content', Type: 'Input' }
90
+ ],
91
+ Filters: []
94
92
  });
95
93
 
96
- // Run an action with full parameters
97
- const params: RunActionParams = {
98
- Action: actionEntity, // ActionEntity instance
99
- ContextUser: currentUser,
100
- Filters: [], // Optional filters
101
- Params: [
102
- { Name: 'InputParam', Value: 'some value', Type: 'string' }
103
- ]
104
- };
105
-
106
- const result = await engine.RunAction(params);
94
+ if (result.Success) {
95
+ console.log('Action completed:', result.Message);
96
+ } else {
97
+ console.error('Action failed:', result.Message);
98
+ }
107
99
  ```
108
100
 
109
- ### Using Context in Actions (New in v2.51.0)
101
+ ### Creating a Custom Action
110
102
 
111
- Actions now support type-safe context propagation for runtime-specific information:
103
+ All actions extend `BaseAction` and implement `InternalRunAction`. Register them with `@RegisterClass` so the engine can discover them via ClassFactory:
112
104
 
113
105
  ```typescript
114
- import { BaseAction } from '@memberjunction/actions';
115
- import { ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
116
106
  import { RegisterClass } from '@memberjunction/global';
107
+ import { BaseAction } from '@memberjunction/actions';
108
+ import { RunActionParams, ActionResultSimple } from '@memberjunction/actions-base';
117
109
 
118
- // Define your context type
119
- interface APIContext {
120
- apiEndpoint: string;
121
- apiKey: string;
122
- timeout: number;
123
- retryCount: number;
124
- }
110
+ @RegisterClass(BaseAction, 'My Custom Action')
111
+ export class MyCustomAction extends BaseAction {
112
+ protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
113
+ const inputValue = params.Params.find(p => p.Name === 'input')?.Value;
125
114
 
126
- // Note: BaseAction does not have generics - context is typed through params
127
- @RegisterClass(BaseAction, 'APICallAction')
128
- export class APICallAction extends BaseAction {
129
- protected async InternalRunAction(params: RunActionParams<APIContext>): Promise<ActionResultSimple> {
130
- // Access typed context through params
131
- const endpoint = params.Context?.apiEndpoint;
132
- const apiKey = params.Context?.apiKey;
133
- const timeout = params.Context?.timeout || 30000;
134
-
135
- if (!endpoint || !apiKey) {
136
- return {
137
- Success: false,
138
- ResultCode: 'MISSING_CONTEXT',
139
- Message: 'API endpoint and key are required in context'
140
- };
141
- }
142
-
143
- // Use context for API call
144
- const requestData = params.Params.find(p => p.Name === 'RequestData')?.Value;
145
-
146
- try {
147
- const response = await this.callAPI(endpoint, apiKey, requestData, timeout);
148
-
149
- // Set output parameter
150
- const outputParam = params.Params.find(p => p.Name === 'ResponseData');
151
- if (outputParam) {
152
- outputParam.Value = response;
153
- }
154
-
155
- return {
156
- Success: true,
157
- ResultCode: 'SUCCESS',
158
- Message: 'API call completed successfully',
159
- Params: params.Params
160
- };
161
- } catch (error) {
162
- return {
163
- Success: false,
164
- ResultCode: 'API_ERROR',
165
- Message: error.message
166
- };
167
- }
168
- }
169
-
170
- private async callAPI(endpoint: string, apiKey: string, data: any, timeout: number): Promise<any> {
171
- // Implementation details
172
- const controller = new AbortController();
173
- const timeoutId = setTimeout(() => controller.abort(), timeout);
174
-
175
- try {
176
- const response = await fetch(endpoint, {
177
- method: 'POST',
178
- headers: {
179
- 'Authorization': `Bearer ${apiKey}`,
180
- 'Content-Type': 'application/json'
181
- },
182
- body: JSON.stringify(data),
183
- signal: controller.signal
184
- });
185
-
186
- clearTimeout(timeoutId);
187
- return await response.json();
188
- } catch (error) {
189
- clearTimeout(timeoutId);
190
- throw error;
191
- }
192
- }
193
- }
194
- ```
115
+ // Your action logic here
116
+ const result = await this.doWork(inputValue);
195
117
 
196
- #### Running Actions with Context
118
+ return {
119
+ Success: true,
120
+ ResultCode: 'SUCCESS',
121
+ Message: `Processed: ${result}`
122
+ };
123
+ }
197
124
 
198
- ```typescript
199
- import { ActionEngineServer } from '@memberjunction/actions';
200
- import { RunActionParams } from '@memberjunction/actions-base';
201
-
202
- // Define context type
203
- interface APIContext {
204
- apiEndpoint: string;
205
- apiKey: string;
206
- timeout: number;
207
- retryCount: number;
125
+ private async doWork(input: string): Promise<string> {
126
+ // Delegate to service classes for real logic
127
+ return `Done with ${input}`;
128
+ }
208
129
  }
209
-
210
- // Configure parameters with context
211
- const params = new RunActionParams<APIContext>();
212
- params.Action = apiCallAction;
213
- params.ContextUser = currentUser;
214
- params.Params = [
215
- { Name: 'RequestData', Value: { orderId: '12345' }, Type: 'Input' },
216
- { Name: 'ResponseData', Value: null, Type: 'Output' }
217
- ];
218
-
219
- // Set runtime context
220
- params.Context = {
221
- apiEndpoint: process.env.API_ENDPOINT,
222
- apiKey: process.env.API_KEY,
223
- timeout: 10000,
224
- retryCount: 3
225
- };
226
-
227
- // Execute with typed context
228
- const result = await ActionEngineServer.Instance.RunAction(params);
229
130
  ```
230
131
 
231
- #### Context vs Parameters
232
-
233
- **Use Context for:**
234
- - Environment-specific configuration (API endpoints, service URLs)
235
- - Runtime credentials (API keys, tokens)
236
- - Session information (user preferences, correlation IDs)
237
- - Feature flags and toggles
238
- - Timeout and retry policies
239
-
240
- **Use Parameters for:**
241
- - Business data (customer ID, order details)
242
- - Action-specific inputs (email content, calculation values)
243
- - Data that should be logged and audited
244
- - Values that need to be stored in the database
245
- - Output values that other actions may depend on
246
-
247
- 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.
132
+ ### Creating an OAuth-Authenticated Action
248
133
 
249
- ### Entity Actions
250
-
251
- Entity Actions are triggered automatically during entity lifecycle events. To work with entity actions, use the `EntityActionEngineServer`:
134
+ For actions that need to call external APIs with OAuth2 credentials:
252
135
 
253
136
  ```typescript
254
- import { EntityActionEngineServer } from '@memberjunction/actions';
255
- import { EntityActionInvocationParams } from '@memberjunction/actions-base';
256
-
257
- const entityActionEngine = EntityActionEngineServer.Instance;
258
-
259
- // Run an entity action
260
- const params: EntityActionInvocationParams = {
261
- EntityAction: entityActionEntity,
262
- InvocationType: invocationTypeEntity, // e.g., 'BeforeCreate', 'AfterUpdate'
263
- EntityObject: entityInstance,
264
- ContextUser: currentUser
265
- };
266
-
267
- const result = await entityActionEngine.RunEntityAction(params);
268
- ```
137
+ import { RegisterClass } from '@memberjunction/global';
138
+ import { BaseOAuthAction } from '@memberjunction/actions';
139
+ import { RunActionParams, ActionResultSimple } from '@memberjunction/actions-base';
140
+
141
+ @RegisterClass(BaseAction, 'Fetch External Data')
142
+ export class FetchExternalDataAction extends BaseOAuthAction {
143
+ protected async refreshAccessToken(): Promise<void> {
144
+ // Platform-specific token refresh logic
145
+ const response = await fetch('https://api.example.com/oauth/token', {
146
+ method: 'POST',
147
+ body: new URLSearchParams({
148
+ grant_type: 'refresh_token',
149
+ refresh_token: this.getRefreshToken(),
150
+ })
151
+ });
152
+ const data = await response.json();
153
+ await this.updateStoredTokens(data.access_token, data.refresh_token, data.expires_in);
154
+ }
269
155
 
270
- ### Creating Custom Action Filters
156
+ protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
157
+ const companyIntegrationId = params.Params.find(
158
+ p => p.Name === 'CompanyIntegrationID'
159
+ )?.Value as string;
271
160
 
272
- Action filters determine whether an action should run. Create custom filters by extending `BaseActionFilter`:
161
+ // Initialize OAuth (loads tokens, refreshes if expired)
162
+ if (!await this.initializeOAuth(companyIntegrationId)) {
163
+ return this.handleOAuthError(new Error('OAuth initialization failed'));
164
+ }
273
165
 
274
- ```typescript
275
- import { BaseActionFilter } from '@memberjunction/actions';
276
- import { RunActionParams } from '@memberjunction/actions-base';
277
- import { ActionFilterEntity } from '@memberjunction/core-entities';
278
- import { RegisterClass } from '@memberjunction/global';
166
+ // Make authenticated request with automatic retry on 401
167
+ const data = await this.makeAuthenticatedRequest(async (token) => {
168
+ const res = await fetch('https://api.example.com/data', {
169
+ headers: { Authorization: `Bearer ${token}` }
170
+ });
171
+ return res.json();
172
+ });
279
173
 
280
- @RegisterClass(BaseActionFilter, 'MyCustomFilter')
281
- export class MyCustomFilter extends BaseActionFilter {
282
- protected async InternalRun(
283
- params: RunActionParams,
284
- filter: ActionFilterEntity
285
- ): Promise<boolean> {
286
- // Implement your filter logic
287
- // Return true to allow action execution, false to skip
288
- return params.ContextUser.IsActive === true;
174
+ return { Success: true, ResultCode: 'SUCCESS', Message: JSON.stringify(data) };
289
175
  }
290
176
  }
291
177
  ```
292
178
 
293
- ## API Reference
179
+ ### Using OAuth2Manager Directly
294
180
 
295
- ### Classes
181
+ For standalone OAuth2 token management outside the action framework:
296
182
 
297
- #### ActionEngineServer
183
+ ```typescript
184
+ import { OAuth2Manager } from '@memberjunction/actions';
298
185
 
299
- The main engine for executing actions.
186
+ const oauth = new OAuth2Manager({
187
+ clientId: 'your-client-id',
188
+ clientSecret: 'your-client-secret',
189
+ tokenEndpoint: 'https://api.example.com/oauth/token',
190
+ scopes: ['read', 'write'],
191
+ onTokenUpdate: async (tokens) => {
192
+ // Persist tokens to your storage
193
+ await saveTokens(tokens);
194
+ }
195
+ });
300
196
 
301
- **Methods:**
302
- - `RunAction(params: RunActionParams): Promise<ActionResult>` - Executes an action with full control over parameters
303
- - `RunActionByID(params: RunActionByNameParams): Promise<ActionResult>` - Convenience method to run an action by its ID
304
- - `Config(forceRefresh?: boolean, contextUser?: UserInfo): Promise<void>` - Configures the engine (inherited from base)
197
+ // Get a valid token (auto-refreshes if expired)
198
+ const token = await oauth.getAccessToken();
305
199
 
306
- #### BaseAction
200
+ // Or use client credentials flow
201
+ const tokenData = await oauth.getClientCredentialsToken();
202
+ ```
307
203
 
308
- Abstract base class for all actions. Note that BaseAction does not use generics - context typing is achieved through the RunActionParams parameter.
204
+ ## Architecture
205
+
206
+ ### Action Execution Pipeline
207
+
208
+ The `ActionEngineServer.RunAction()` method follows this sequence:
209
+
210
+ ```mermaid
211
+ sequenceDiagram
212
+ participant Caller
213
+ participant Engine as ActionEngineServer
214
+ participant Filter as BaseActionFilter
215
+ participant CF as ClassFactory
216
+ participant Action as BaseAction Subclass
217
+ participant Log as Execution Log
218
+
219
+ Caller->>Engine: RunAction(params)
220
+ Engine->>Engine: ValidateInputs(params)
221
+ alt Validation fails
222
+ Engine->>Log: StartAndEndActionLog()
223
+ Engine-->>Caller: {Success: false}
224
+ end
225
+ Engine->>Filter: RunFilters(params)
226
+ alt Filters block execution
227
+ Engine->>Log: StartAndEndActionLog()
228
+ Engine-->>Caller: {Success: true, "Filters blocked"}
229
+ end
230
+ Engine->>Log: StartActionLog()
231
+ Engine->>CF: CreateInstance(BaseAction, driverClass)
232
+ CF-->>Engine: action instance
233
+ Engine->>Action: Run(params)
234
+ Action->>Action: InternalRunAction(params)
235
+ Action-->>Engine: ActionResultSimple
236
+ Engine->>Log: EndActionLog()
237
+ Engine-->>Caller: ActionResult
238
+ ```
309
239
 
310
- **Methods:**
311
- - `Run(params: RunActionParams): Promise<ActionResultSimple>` - Public method called by the engine
312
- - `InternalRunAction(params: RunActionParams): Promise<ActionResultSimple>` - Abstract method to implement action logic
240
+ ### Entity Action Invocation
313
241
 
314
- #### EntityActionEngineServer
242
+ Entity actions are bound to entity lifecycle events. The `EntityActionEngineServer` delegates to invocation-type-specific handlers via ClassFactory:
315
243
 
316
- Engine specifically for entity-related actions.
244
+ ```mermaid
245
+ classDiagram
246
+ class EntityActionInvocationBase {
247
+ <<abstract>>
248
+ +InvokeAction(params) EntityActionResult
249
+ +MapParams(params, entityActionParams, entity) ActionParam[]
250
+ +SafeEvalScript(id, script, entity) any
251
+ }
317
252
 
318
- **Methods:**
319
- - `RunEntityAction(params: EntityActionInvocationParams): Promise<EntityActionResult>` - Executes an entity action
253
+ class SingleRecord {
254
+ +InvokeAction(params) EntityActionResult
255
+ +ValidateParams(params) boolean
256
+ }
320
257
 
321
- #### BaseActionFilter
258
+ class MultipleRecords {
259
+ +InvokeAction(params) EntityActionResult
260
+ #GetRecordList() BaseEntity[]
261
+ }
322
262
 
323
- Abstract base class for action filters.
263
+ class Validate {
264
+ +InvokeAction(params) EntityActionResult
265
+ }
324
266
 
325
- **Methods:**
326
- - `Run(params: RunActionParams, filter: ActionFilterEntity): Promise<boolean>` - Public method called by the engine
327
- - `InternalRun(params: RunActionParams, filter: ActionFilterEntity): Promise<boolean>` - Abstract method to implement filter logic
267
+ EntityActionInvocationBase <|-- SingleRecord
268
+ EntityActionInvocationBase <|-- MultipleRecords
269
+ SingleRecord <|-- Validate
328
270
 
329
- #### ActionEntityServerEntity
271
+ note for SingleRecord "Registered for: Read, BeforeCreate,\nBeforeUpdate, BeforeDelete, AfterCreate,\nAfterUpdate, AfterDelete, SingleRecord"
272
+ note for MultipleRecords "Registered for: List, View"
273
+ note for Validate "Registered for: Validate"
274
+ ```
330
275
 
331
- Server-side entity class for Actions with AI-powered code generation. This class is located in the `@memberjunction/core-entities-server` package.
276
+ ### Class Hierarchy
332
277
 
333
- **Key Features:**
334
- - Automatic code generation from natural language prompts
335
- - Code validation and improvement through AI
336
- - Library dependency management
337
- - Transaction-safe save operations
338
- - Automatic creation of action parameters and result codes from AI-generated definitions
278
+ ```mermaid
279
+ classDiagram
280
+ class BaseAction {
281
+ <<abstract>>
282
+ +Run(params) ActionResultSimple
283
+ #InternalRunAction(params)* ActionResultSimple
284
+ }
339
285
 
340
- ### Types and Interfaces
286
+ class BaseOAuthAction {
287
+ <<abstract>>
288
+ #initializeOAuth(id) boolean
289
+ #getAccessToken() string
290
+ #makeAuthenticatedRequest(fn) T
291
+ #refreshAccessToken()* void
292
+ }
341
293
 
342
- The package exports all types from `@memberjunction/actions-base`, including:
294
+ class BaseActionFilter {
295
+ <<abstract>>
296
+ +Run(params, filter) boolean
297
+ #InternalRun(params, filter)* boolean
298
+ }
343
299
 
344
- - `RunActionParams` - Parameters for running an action
345
- - `ActionResult` - Detailed result of action execution
346
- - `ActionResultSimple` - Simplified action result
347
- - `ActionParam` - Parameter definition for actions
348
- - `EntityActionInvocationParams` - Parameters for entity actions
349
- - `EntityActionResult` - Result of entity action execution
300
+ class ActionEngineServer {
301
+ +RunAction(params) ActionResult
302
+ #ValidateInputs(params) boolean
303
+ #RunFilters(params) boolean
304
+ #InternalRunAction(params) ActionResult
305
+ }
350
306
 
351
- ## Entity Action Invocation Types
307
+ class EntityActionEngineServer {
308
+ +RunEntityAction(params) EntityActionResult
309
+ }
352
310
 
353
- The framework supports various invocation types for entity actions:
311
+ class OAuth2Manager {
312
+ +getAccessToken() string
313
+ +getAuthorizationUrl() string
314
+ +exchangeAuthorizationCode(code) OAuth2TokenData
315
+ +getClientCredentialsToken() OAuth2TokenData
316
+ +refreshAccessToken() OAuth2TokenData
317
+ }
354
318
 
355
- ### Single Record Operations
356
- - `Read` - Triggered when reading an entity
357
- - `BeforeCreate` - Before creating a new record
358
- - `AfterCreate` - After creating a new record
359
- - `BeforeUpdate` - Before updating a record
360
- - `AfterUpdate` - After updating a record
361
- - `BeforeDelete` - Before deleting a record
362
- - `AfterDelete` - After deleting a record
319
+ BaseAction <|-- BaseOAuthAction
320
+ ActionEngineServer --> BaseAction : dispatches to
321
+ ActionEngineServer --> BaseActionFilter : evaluates
322
+ EntityActionEngineServer --> ActionEngineServer : delegates to
323
+ BaseOAuthAction --> OAuth2Manager : can use
324
+ ```
363
325
 
364
- ### Multiple Record Operations
365
- - `List` - Actions operating on a list of records
366
- - `View` - Actions operating on records from a view
326
+ ## API Reference
367
327
 
368
- ### Validation
369
- - `Validate` - Special invocation type for validation logic
328
+ ### ActionEngineServer
370
329
 
371
- ## Code Generation
330
+ Singleton engine that executes actions. Access via `ActionEngineServer.Instance`.
372
331
 
373
- The framework includes sophisticated AI-powered code generation capabilities:
332
+ | Method | Description |
333
+ |--------|-------------|
334
+ | `Config(forceRefresh, contextUser)` | Initialize/refresh the engine's action and filter metadata |
335
+ | `RunAction(params)` | Execute an action through the full pipeline (validate, filter, dispatch, log) |
374
336
 
375
- 1. **Natural Language Input**: Define action behavior using plain English in the `UserPrompt` field
376
- 2. **Automatic Code Generation**: The system generates TypeScript code based on your prompt
377
- 3. **Code Validation**: Generated code is automatically validated and improved
378
- 4. **Library Management**: Automatic tracking and importing of required libraries
337
+ ### BaseAction
379
338
 
380
- Example workflow:
381
- ```typescript
382
- // In your database, create an Action record with:
383
- // Name: "SendWelcomeEmail"
384
- // Type: "Generated"
385
- // UserPrompt: "Send a welcome email to a new user with their name and registration date"
339
+ Abstract base class for all action implementations.
386
340
 
387
- // The system will automatically generate the implementation code
388
- ```
341
+ | Method | Description |
342
+ |--------|-------------|
343
+ | `Run(params)` | Public entry point — calls `InternalRunAction` |
344
+ | `InternalRunAction(params)` | **Abstract** — implement your action logic here |
389
345
 
390
- ## Best Practices
391
-
392
- 1. **Action Naming**: Use clear, descriptive names for actions (e.g., `SendInvoiceEmail`, `CalculateOrderTotal`)
393
-
394
- 2. **Parameter Design**: Design action parameters to be reusable and flexible:
395
- ```typescript
396
- params: [
397
- { Name: 'EmailTemplate', Type: 'string', ValueType: 'Scalar' },
398
- { Name: 'RecipientUser', Type: 'User', ValueType: 'BaseEntity Sub-Class' },
399
- { Name: 'EmailSent', Type: 'boolean', ValueType: 'Scalar', IsInput: false }
400
- ]
401
- ```
402
-
403
- 3. **Error Handling**: Always include proper error handling in your actions:
404
- ```typescript
405
- try {
406
- // Action logic
407
- return { Success: true, ResultCode: 'SUCCESS', Message: 'Completed' };
408
- } catch (error) {
409
- return { Success: false, ResultCode: 'ERROR', Message: error.message };
410
- }
411
- ```
412
-
413
- 4. **Logging**: The framework automatically logs action executions, but include additional logging for debugging:
414
- ```typescript
415
- import { LogError, LogStatus } from '@memberjunction/core';
416
-
417
- LogStatus('Starting custom action processing...');
418
- ```
419
-
420
- 5. **Transaction Management**: Use transaction groups for multi-step operations:
421
- ```typescript
422
- const tg = await metadata.CreateTransactionGroup();
423
- try {
424
- // Multiple operations
425
- await tg.Submit();
426
- } catch (error) {
427
- // Automatic rollback on error
428
- }
429
- ```
430
-
431
- ## Integration with Other MJ Packages
432
-
433
- This package integrates seamlessly with:
434
-
435
- - **@memberjunction/core**: Provides base entity functionality and metadata access
436
- - **@memberjunction/ai**: Enables AI-powered code generation
437
- - **@memberjunction/core-entities**: Provides strongly-typed entity classes
438
- - **@memberjunction/global**: Manages class registration and instantiation
439
-
440
- ## OAuth2Manager (Server-Side Only)
441
-
442
- The package includes a generic OAuth2 token manager for server-side integrations:
346
+ ### BaseOAuthAction
443
347
 
444
- ```typescript
445
- import { OAuth2Manager } from '@memberjunction/actions';
348
+ Abstract base for actions requiring OAuth authentication. Extends `BaseAction`.
446
349
 
447
- // Initialize OAuth2 manager
448
- const oauth = new OAuth2Manager({
449
- clientId: process.env.OAUTH_CLIENT_ID,
450
- clientSecret: process.env.OAUTH_CLIENT_SECRET,
451
- tokenEndpoint: 'https://api.example.com/oauth/token',
452
- authorizationEndpoint: 'https://api.example.com/oauth/authorize',
453
- scopes: ['read', 'write'],
454
- onTokenUpdate: async (tokens) => {
455
- // Persist updated tokens to database
456
- await saveTokens(tokens);
457
- }
458
- });
350
+ | Method | Description |
351
+ |--------|-------------|
352
+ | `initializeOAuth(companyIntegrationId)` | Load integration, check/refresh tokens |
353
+ | `getAccessToken()` | Get the current access token |
354
+ | `makeAuthenticatedRequest(fn)` | Execute a request with automatic retry on 401/403 |
355
+ | `refreshAccessToken()` | **Abstract** — implement platform-specific token refresh |
356
+ | `updateStoredTokens(access, refresh?, expiresIn?)` | Persist new tokens to the Company Integration entity |
357
+ | `handleOAuthError(error)` | Return a standardized error result for OAuth failures |
459
358
 
460
- // Get authorization URL for user to visit
461
- const authUrl = oauth.getAuthorizationUrl('random-state-string');
359
+ ### BaseActionFilter
462
360
 
463
- // Exchange authorization code for tokens
464
- const tokens = await oauth.exchangeAuthorizationCode(code);
361
+ Abstract base for pre-execution filters.
465
362
 
466
- // Get valid access token (auto-refreshes if needed)
467
- const accessToken = await oauth.getAccessToken();
468
- ```
363
+ | Method | Description |
364
+ |--------|-------------|
365
+ | `Run(params, filter)` | Public entry point — calls `InternalRun` |
366
+ | `InternalRun(params, filter)` | **Abstract** — implement filter logic, return `true` to allow execution |
469
367
 
470
- **Features:**
471
- - Multiple grant type support (authorization_code, client_credentials, refresh_token)
472
- - Automatic token refresh before expiration
473
- - Thread-safe token refresh (prevents concurrent requests)
474
- - Token persistence callbacks
475
- - Provider customization hooks for non-standard OAuth2 implementations
368
+ ### EntityActionEngineServer
476
369
 
477
- **⚠️ Server-Side Only**: OAuth2Manager requires `process.env` and should only be used in Node.js server environments, not in browser/client code.
370
+ Singleton engine for entity-bound actions. Access via `EntityActionEngineServer.Instance`.
478
371
 
479
- ## Advanced Topics
372
+ | Method | Description |
373
+ |--------|-------------|
374
+ | `RunEntityAction(params)` | Execute an entity action, dispatching to the correct invocation type handler |
480
375
 
481
- ### Custom Action Engines
376
+ ### OAuth2Manager
482
377
 
483
- You can create custom action engines by extending `ActionEngineServer`:
378
+ Standalone OAuth2 token manager supporting multiple grant types.
484
379
 
485
- ```typescript
486
- @RegisterClass(BaseEngine, 'ActionEngineBase', 1) // Higher priority
487
- export class CustomActionEngine extends ActionEngineServer {
488
- protected async ValidateInputs(params: RunActionParams): Promise<boolean> {
489
- // Custom validation logic
490
- return super.ValidateInputs(params);
491
- }
492
-
493
- protected async RunFilters(params: RunActionParams): Promise<boolean> {
494
- // Custom filter logic
495
- return super.RunFilters(params);
496
- }
497
- }
498
- ```
380
+ | Method | Description |
381
+ |--------|-------------|
382
+ | `getAccessToken()` | Get a valid token, auto-refreshing if needed (thread-safe) |
383
+ | `getAuthorizationUrl(state?)` | Build the authorization URL for auth code flow |
384
+ | `exchangeAuthorizationCode(code)` | Exchange an auth code for tokens |
385
+ | `getClientCredentialsToken()` | Obtain tokens via client credentials flow |
386
+ | `refreshAccessToken()` | Refresh using the stored refresh token |
387
+ | `setTokens(access, refresh?, expiresIn?)` | Set tokens obtained externally |
388
+ | `isTokenValid()` | Check if current token is valid (with buffer) |
499
389
 
500
- ### Script Evaluation in Entity Actions
390
+ ## Dependencies
501
391
 
502
- Entity actions support dynamic script evaluation for parameter mapping:
392
+ This package depends on:
503
393
 
504
- ```typescript
505
- // In EntityActionParam configuration:
506
- {
507
- ValueType: 'Script',
508
- Value: `
509
- const user = EntityActionContext.entityObject;
510
- EntityActionContext.result = user.Email.toUpperCase();
511
- `
512
- }
513
- ```
394
+ - [@memberjunction/global](../../MJGlobal/README.md) — ClassFactory and `@RegisterClass` decorator
395
+ - [@memberjunction/core](../../MJCore/README.md) — `Metadata`, `RunView`, `BaseEntity`, logging utilities
396
+ - [@memberjunction/actions-base](../Base/README.md) — Shared types (`ActionEngineBase`, `RunActionParams`, `ActionResult`, etc.)
397
+ - [@memberjunction/core-entities](../../MJCoreEntities/README.md) — Generated entity classes (`ActionExecutionLogEntity`, `ActionFilterEntity`, etc.)
398
+ - [@memberjunction/ai](../../AI/Core/README.md) — AI model integration
399
+ - [@memberjunction/ai-core-plus](../../AI/CorePlus/README.md) — Extended AI utilities
400
+ - [@memberjunction/aiengine](../../AI/Engine/README.md) — AI engine orchestration
401
+ - [@memberjunction/ai-prompts](../../AI/Prompts/README.md) — AI prompt execution
402
+
403
+ ## Related Packages
514
404
 
515
- ## Troubleshooting
405
+ - [@memberjunction/actions-base](../Base/README.md) — Shared types and base classes used by both client and server
406
+ - [CoreActions](../CoreActions/) — Built-in action implementations (Create Record, generated actions, etc.)
407
+ - [ScheduledActions](../ScheduledActions/) — Scheduled action execution support
408
+ - [ApolloEnrichment](../ApolloEnrichment/) — Apollo data enrichment actions
409
+ - [ContentAutotag](../ContentAutotag/) — Content auto-tagging actions
410
+ - [CodeExecution](../CodeExecution/) — Dynamic code execution actions
516
411
 
517
- 1. **Action Not Found**: Ensure your action class is properly registered with `@RegisterClass`
518
- 2. **Code Generation Fails**: Check that AI models are configured and API keys are set
519
- 3. **Filter Not Running**: Verify filter is associated with the action in metadata
520
- 4. **Entity Action Not Triggering**: Confirm invocation type matches the entity operation
412
+ For the Actions system philosophy and development guide, see the [Actions CLAUDE.md](../CLAUDE.md).
521
413
 
522
- ## License
414
+ ## Contributing
523
415
 
524
- This package is part of the MemberJunction ecosystem. See the main repository for license information.
416
+ See the [MemberJunction Contributing Guide](../../../CONTRIBUTING.md) for development setup and guidelines.