@memberjunction/queue 2.43.0 → 2.45.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.
- package/README.md +269 -187
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @memberjunction/queue
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A flexible queue management system for MemberJunction applications that enables background task processing, job scheduling, and asynchronous execution with database persistence.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
The `@memberjunction/queue` package
|
|
7
|
+
The `@memberjunction/queue` package provides a robust framework for implementing persistent queues in MemberJunction applications. It offers:
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
9
|
+
- Database-backed task persistence
|
|
10
|
+
- Automatic queue creation and management
|
|
11
|
+
- Concurrent task processing with configurable limits
|
|
12
|
+
- Heartbeat monitoring for process health
|
|
13
|
+
- Type-safe task definitions
|
|
14
|
+
- Extensible queue implementations
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
@@ -21,234 +21,316 @@ npm install @memberjunction/queue
|
|
|
21
21
|
|
|
22
22
|
## Dependencies
|
|
23
23
|
|
|
24
|
-
This package
|
|
25
|
-
|
|
26
|
-
- `@memberjunction/
|
|
27
|
-
- `@memberjunction/
|
|
24
|
+
This package requires the following MemberJunction packages:
|
|
25
|
+
|
|
26
|
+
- `@memberjunction/core` - Core functionality and entity management
|
|
27
|
+
- `@memberjunction/global` - Global utilities and class registration
|
|
28
|
+
- `@memberjunction/core-entities` - Entity type definitions
|
|
28
29
|
- `@memberjunction/ai` - AI functionality (for AI-related queues)
|
|
29
30
|
- `@memberjunction/aiengine` - AI Engine integration
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
Additional dependencies:
|
|
33
|
+
- `uuid` - For generating unique identifiers
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
## Core Components
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
### TaskBase
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
- Processing lifecycle management
|
|
39
|
-
- Error handling and retry logic
|
|
40
|
-
- Status reporting and logging
|
|
39
|
+
The `TaskBase` class represents an individual task in a queue:
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
```typescript
|
|
42
|
+
export class TaskBase {
|
|
43
|
+
constructor(
|
|
44
|
+
taskRecord: QueueTaskEntity,
|
|
45
|
+
data: any,
|
|
46
|
+
options: TaskOptions
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Properties
|
|
50
|
+
ID: string // Unique task identifier
|
|
51
|
+
Status: TaskStatus // Current task status
|
|
52
|
+
Data: any // Task payload data
|
|
53
|
+
Options: TaskOptions // Task configuration
|
|
54
|
+
TaskRecord: QueueTaskEntity // Database entity
|
|
55
|
+
}
|
|
56
|
+
```
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
### TaskStatus
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
- Schedules queue processing
|
|
48
|
-
- Provides centralized queue access
|
|
49
|
-
- Supports prioritization across queues
|
|
60
|
+
Available task statuses:
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
```typescript
|
|
63
|
+
export const TaskStatus = {
|
|
64
|
+
Pending: 'Pending',
|
|
65
|
+
InProgress: 'InProgress',
|
|
66
|
+
Complete: 'Complete',
|
|
67
|
+
Failed: 'Failed',
|
|
68
|
+
Cancelled: 'Cancelled',
|
|
69
|
+
} as const;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### QueueBase
|
|
52
73
|
|
|
53
|
-
The `
|
|
74
|
+
The abstract `QueueBase` class serves as the foundation for all queue implementations:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
export abstract class QueueBase {
|
|
78
|
+
constructor(
|
|
79
|
+
QueueRecord: QueueEntity,
|
|
80
|
+
QueueTypeID: string,
|
|
81
|
+
ContextUser: UserInfo
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// Public methods
|
|
85
|
+
AddTask(task: TaskBase): boolean
|
|
86
|
+
FindTask(ID: string): TaskBase
|
|
87
|
+
|
|
88
|
+
// Protected abstract method to implement
|
|
89
|
+
protected abstract ProcessTask(
|
|
90
|
+
task: TaskBase,
|
|
91
|
+
contextUser: UserInfo
|
|
92
|
+
): Promise<TaskResult>
|
|
93
|
+
}
|
|
94
|
+
```
|
|
54
95
|
|
|
55
|
-
|
|
56
|
-
- Execution status tracking
|
|
57
|
-
- Retry information
|
|
58
|
-
- Result storage
|
|
96
|
+
### QueueManager
|
|
59
97
|
|
|
60
|
-
|
|
98
|
+
The `QueueManager` is a singleton that manages all active queues:
|
|
61
99
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
100
|
+
```typescript
|
|
101
|
+
export class QueueManager {
|
|
102
|
+
// Singleton access
|
|
103
|
+
static get Instance(): QueueManager
|
|
104
|
+
|
|
105
|
+
// Static methods
|
|
106
|
+
static async Config(contextUser: UserInfo): Promise<void>
|
|
107
|
+
static async AddTask(
|
|
108
|
+
QueueType: string,
|
|
109
|
+
data: any,
|
|
110
|
+
options: any,
|
|
111
|
+
contextUser: UserInfo
|
|
112
|
+
): Promise<TaskBase | undefined>
|
|
113
|
+
|
|
114
|
+
// Instance methods
|
|
115
|
+
async AddTask(
|
|
116
|
+
QueueTypeID: string,
|
|
117
|
+
data: any,
|
|
118
|
+
options: any,
|
|
119
|
+
contextUser: UserInfo
|
|
120
|
+
): Promise<TaskBase | undefined>
|
|
121
|
+
}
|
|
122
|
+
```
|
|
66
123
|
|
|
67
|
-
|
|
124
|
+
### TaskResult
|
|
68
125
|
|
|
69
|
-
|
|
126
|
+
Structure returned by task processing:
|
|
70
127
|
|
|
71
128
|
```typescript
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
} from '@memberjunction/queue';
|
|
78
|
-
|
|
79
|
-
// Initialize the queue manager (typically done at application startup)
|
|
80
|
-
const queueManager = QueueManager.getInstance();
|
|
81
|
-
|
|
82
|
-
// Define a simple task
|
|
83
|
-
class EmailTask extends TaskBase {
|
|
84
|
-
recipient: string;
|
|
85
|
-
subject: string;
|
|
86
|
-
body: string;
|
|
87
|
-
|
|
88
|
-
constructor(recipient: string, subject: string, body: string) {
|
|
89
|
-
super();
|
|
90
|
-
this.recipient = recipient;
|
|
91
|
-
this.subject = subject;
|
|
92
|
-
this.body = body;
|
|
93
|
-
}
|
|
129
|
+
export class TaskResult {
|
|
130
|
+
success: boolean // Whether task completed successfully
|
|
131
|
+
userMessage: string // User-friendly message
|
|
132
|
+
output: any // Task output data
|
|
133
|
+
exception: any // Exception details if failed
|
|
94
134
|
}
|
|
135
|
+
```
|
|
95
136
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
super('EmailQueue', 'Handles email sending tasks');
|
|
100
|
-
}
|
|
137
|
+
## Usage Examples
|
|
138
|
+
|
|
139
|
+
### Basic Queue Implementation
|
|
101
140
|
|
|
102
|
-
|
|
103
|
-
|
|
141
|
+
Create a custom queue by extending `QueueBase`:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { QueueBase, TaskBase, TaskResult } from '@memberjunction/queue';
|
|
145
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
146
|
+
import { UserInfo } from '@memberjunction/core';
|
|
147
|
+
|
|
148
|
+
// Register your queue with a specific queue type name
|
|
149
|
+
@RegisterClass(QueueBase, 'Email Notification')
|
|
150
|
+
export class EmailNotificationQueue extends QueueBase {
|
|
151
|
+
protected async ProcessTask(
|
|
152
|
+
task: TaskBase,
|
|
153
|
+
contextUser: UserInfo
|
|
154
|
+
): Promise<TaskResult> {
|
|
104
155
|
try {
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
156
|
+
// Extract task data
|
|
157
|
+
const { recipient, subject, body } = task.Data;
|
|
158
|
+
|
|
159
|
+
// Implement your email sending logic here
|
|
160
|
+
console.log(`Sending email to ${recipient}`);
|
|
161
|
+
console.log(`Subject: ${subject}`);
|
|
109
162
|
|
|
110
|
-
//
|
|
111
|
-
|
|
163
|
+
// Simulate email sending
|
|
164
|
+
await this.sendEmail(recipient, subject, body);
|
|
165
|
+
|
|
166
|
+
// Return success result
|
|
167
|
+
return {
|
|
168
|
+
success: true,
|
|
169
|
+
userMessage: 'Email sent successfully',
|
|
170
|
+
output: { sentAt: new Date() },
|
|
171
|
+
exception: null
|
|
172
|
+
};
|
|
112
173
|
} catch (error) {
|
|
113
|
-
|
|
114
|
-
return
|
|
174
|
+
// Return failure result
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
userMessage: `Failed to send email: ${error.message}`,
|
|
178
|
+
output: null,
|
|
179
|
+
exception: error
|
|
180
|
+
};
|
|
115
181
|
}
|
|
116
182
|
}
|
|
183
|
+
|
|
184
|
+
private async sendEmail(to: string, subject: string, body: string) {
|
|
185
|
+
// Your email service integration here
|
|
186
|
+
}
|
|
117
187
|
}
|
|
188
|
+
```
|
|
118
189
|
|
|
119
|
-
|
|
120
|
-
queueManager.registerQueue(new EmailQueue());
|
|
190
|
+
### Adding Tasks to Queue
|
|
121
191
|
|
|
122
|
-
|
|
123
|
-
|
|
192
|
+
```typescript
|
|
193
|
+
import { QueueManager } from '@memberjunction/queue';
|
|
194
|
+
import { UserInfo } from '@memberjunction/core';
|
|
195
|
+
|
|
196
|
+
// Initialize queue manager (typically done once at app startup)
|
|
197
|
+
await QueueManager.Config(contextUser);
|
|
198
|
+
|
|
199
|
+
// Add a task using queue type name
|
|
200
|
+
const task = await QueueManager.AddTask(
|
|
201
|
+
'Email Notification', // Queue type name
|
|
202
|
+
{ // Task data
|
|
203
|
+
recipient: 'user@example.com',
|
|
204
|
+
subject: 'Welcome to MemberJunction',
|
|
205
|
+
body: 'Thank you for joining!'
|
|
206
|
+
},
|
|
207
|
+
{ // Task options
|
|
208
|
+
priority: 1
|
|
209
|
+
},
|
|
210
|
+
contextUser
|
|
211
|
+
);
|
|
124
212
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const task = new EmailTask(recipient, subject, body);
|
|
128
|
-
await queueManager.getQueue('EmailQueue').addTask(task);
|
|
213
|
+
if (task) {
|
|
214
|
+
console.log(`Task created with ID: ${task.ID}`);
|
|
129
215
|
}
|
|
130
|
-
|
|
131
|
-
// Usage
|
|
132
|
-
sendEmailLater(
|
|
133
|
-
'user@example.com',
|
|
134
|
-
'Welcome to MemberJunction',
|
|
135
|
-
'Thank you for registering with MemberJunction!'
|
|
136
|
-
);
|
|
137
216
|
```
|
|
138
217
|
|
|
139
|
-
###
|
|
218
|
+
### AI Action Queue Example
|
|
219
|
+
|
|
220
|
+
The package includes built-in queues for AI operations:
|
|
140
221
|
|
|
141
222
|
```typescript
|
|
142
|
-
import {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// Set task options
|
|
158
|
-
this.maxRetries = 3; // Allow 3 retries
|
|
159
|
-
this.priority = 2; // Higher priority (lower number = higher priority)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
223
|
+
import { AIActionQueue, EntityAIActionQueue } from '@memberjunction/queue';
|
|
224
|
+
|
|
225
|
+
// These queues are automatically registered and available for use
|
|
226
|
+
// Add an AI action task
|
|
227
|
+
const aiTask = await QueueManager.AddTask(
|
|
228
|
+
'AI Action',
|
|
229
|
+
{
|
|
230
|
+
actionName: 'GenerateText',
|
|
231
|
+
prompt: 'Write a product description',
|
|
232
|
+
parameters: { maxTokens: 100 }
|
|
233
|
+
},
|
|
234
|
+
{},
|
|
235
|
+
contextUser
|
|
236
|
+
);
|
|
162
237
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
protected async processTask(task: DataSyncTask): Promise<boolean> {
|
|
175
|
-
// Validate task
|
|
176
|
-
if (!task.entityName || !task.recordId || !task.sourceSystem) {
|
|
177
|
-
this.logError(`Invalid task parameters: ${JSON.stringify(task)}`);
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
// Get entity metadata and create instance
|
|
183
|
-
const md = BaseEntity.getEntityMetadata(task.entityName);
|
|
184
|
-
const entity = BaseEntity.createByEntityName(task.entityName);
|
|
185
|
-
|
|
186
|
-
// Load the entity
|
|
187
|
-
const loaded = await entity.load(task.recordId);
|
|
188
|
-
if (!loaded) {
|
|
189
|
-
this.logWarning(`Entity ${task.entityName} with ID ${task.recordId} not found`);
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Perform synchronization (implementation details)
|
|
194
|
-
this.logInfo(`Synchronizing ${task.entityName} #${task.recordId} with ${task.sourceSystem}`);
|
|
195
|
-
|
|
196
|
-
// In a real implementation, you would call external APIs here
|
|
197
|
-
|
|
198
|
-
// Update entity with synchronized data
|
|
199
|
-
await entity.save();
|
|
200
|
-
|
|
201
|
-
return true;
|
|
202
|
-
} catch (error) {
|
|
203
|
-
this.logError(`Error synchronizing data: ${error}`);
|
|
204
|
-
|
|
205
|
-
// If this is a retriable error, return false to trigger retry
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
238
|
+
// Add an entity-specific AI action
|
|
239
|
+
const entityAITask = await QueueManager.AddTask(
|
|
240
|
+
'Entity AI Action',
|
|
241
|
+
{
|
|
242
|
+
entityName: 'Products',
|
|
243
|
+
entityID: 123,
|
|
244
|
+
actionName: 'GenerateDescription'
|
|
245
|
+
},
|
|
246
|
+
{},
|
|
247
|
+
contextUser
|
|
248
|
+
);
|
|
210
249
|
```
|
|
211
250
|
|
|
212
|
-
|
|
251
|
+
## Database Schema
|
|
213
252
|
|
|
214
|
-
|
|
215
|
-
import { QueueManager, LoggingLevel } from '@memberjunction/queue';
|
|
253
|
+
The queue system requires the following database tables:
|
|
216
254
|
|
|
217
|
-
|
|
218
|
-
|
|
255
|
+
### Queue Types Table (`__mj.QueueType`)
|
|
256
|
+
Stores definitions of different queue types (e.g., "Email Notification", "AI Action")
|
|
219
257
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
});
|
|
258
|
+
### Queues Table (`__mj.Queue`)
|
|
259
|
+
Tracks active queue instances with process information:
|
|
260
|
+
- Queue type reference
|
|
261
|
+
- Process details (PID, platform, hostname)
|
|
262
|
+
- Heartbeat timestamp
|
|
263
|
+
- Network information
|
|
227
264
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
265
|
+
### Queue Tasks Table (`__mj.QueueTask`)
|
|
266
|
+
Stores individual tasks:
|
|
267
|
+
- Queue reference
|
|
268
|
+
- Task status
|
|
269
|
+
- Task data (JSON)
|
|
270
|
+
- Task options (JSON)
|
|
271
|
+
- Output and error information
|
|
232
272
|
|
|
233
|
-
|
|
234
|
-
queueManager.startProcessing();
|
|
273
|
+
## Process Management
|
|
235
274
|
|
|
236
|
-
|
|
237
|
-
|
|
275
|
+
The QueueManager automatically captures process information for monitoring:
|
|
276
|
+
- Process ID (PID)
|
|
277
|
+
- Platform and version
|
|
278
|
+
- Working directory
|
|
279
|
+
- Network interfaces
|
|
280
|
+
- Operating system details
|
|
281
|
+
- User information
|
|
282
|
+
- Heartbeat timestamps
|
|
238
283
|
|
|
239
|
-
|
|
240
|
-
|
|
284
|
+
This information helps track queue health and enables failover scenarios.
|
|
285
|
+
|
|
286
|
+
## Configuration
|
|
287
|
+
|
|
288
|
+
Queue behavior can be configured through the implementation:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
export class CustomQueue extends QueueBase {
|
|
292
|
+
private _maxTasks = 5; // Maximum concurrent tasks
|
|
293
|
+
private _checkInterval = 500; // Check interval in milliseconds
|
|
294
|
+
|
|
295
|
+
// Override these values in your constructor
|
|
296
|
+
constructor(QueueRecord: QueueEntity, QueueTypeID: string, ContextUser: UserInfo) {
|
|
297
|
+
super(QueueRecord, QueueTypeID, ContextUser);
|
|
298
|
+
// Customize queue behavior
|
|
299
|
+
this._maxTasks = 10;
|
|
300
|
+
this._checkInterval = 1000;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
241
303
|
```
|
|
242
304
|
|
|
243
|
-
##
|
|
305
|
+
## Best Practices
|
|
306
|
+
|
|
307
|
+
1. **Task Data Structure**: Keep task data serializable as JSON
|
|
308
|
+
2. **Error Handling**: Always return proper TaskResult with error details
|
|
309
|
+
3. **Queue Registration**: Use `@RegisterClass` decorator for automatic registration
|
|
310
|
+
4. **Idempotency**: Design tasks to be safely retryable
|
|
311
|
+
5. **Resource Cleanup**: Clean up resources in finally blocks
|
|
312
|
+
6. **Monitoring**: Check heartbeat timestamps for queue health
|
|
313
|
+
|
|
314
|
+
## Integration with MemberJunction
|
|
244
315
|
|
|
245
|
-
|
|
316
|
+
The queue system integrates seamlessly with:
|
|
317
|
+
- **Entity System**: Use entities for task data and processing
|
|
318
|
+
- **User Context**: All operations respect user permissions
|
|
319
|
+
- **Global Registry**: Automatic queue discovery via class registration
|
|
320
|
+
- **AI Engine**: Built-in support for AI task processing
|
|
246
321
|
|
|
247
|
-
|
|
248
|
-
- `__mj.QueueTask` - Stores individual tasks
|
|
249
|
-
- `__mj.QueueTaskExecution` - Tracks task execution history
|
|
322
|
+
## Build & Development
|
|
250
323
|
|
|
251
|
-
|
|
324
|
+
```bash
|
|
325
|
+
# Build the package
|
|
326
|
+
npm run build
|
|
327
|
+
|
|
328
|
+
# Development mode with auto-reload
|
|
329
|
+
npm run start
|
|
330
|
+
|
|
331
|
+
# TypeScript compilation only
|
|
332
|
+
npm run build
|
|
333
|
+
```
|
|
252
334
|
|
|
253
335
|
## License
|
|
254
336
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/queue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.45.0",
|
|
4
4
|
"description": "MemberJunction: Queue Library for managing server side queues",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
"typescript": "^5.4.5"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@memberjunction/ai": "2.
|
|
23
|
-
"@memberjunction/aiengine": "2.
|
|
24
|
-
"@memberjunction/core": "2.
|
|
25
|
-
"@memberjunction/global": "2.
|
|
26
|
-
"@memberjunction/core-entities": "2.
|
|
22
|
+
"@memberjunction/ai": "2.45.0",
|
|
23
|
+
"@memberjunction/aiengine": "2.45.0",
|
|
24
|
+
"@memberjunction/core": "2.45.0",
|
|
25
|
+
"@memberjunction/global": "2.45.0",
|
|
26
|
+
"@memberjunction/core-entities": "2.45.0",
|
|
27
27
|
"@types/uuid": "^9.0.1",
|
|
28
28
|
"uuid": "^9.0.0"
|
|
29
29
|
}
|