@quanticjs/workflow-quanticflow 3.3.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 ADDED
@@ -0,0 +1,193 @@
1
+ # @quanticjs/workflow-quanticflow
2
+
3
+ QuanticFlow workflow engine adapter for `@quanticjs/workflow`. Connects any QuanticJS application to a standalone [QuanticFlow](https://github.com/quanticjs/quanticflow) instance via REST API.
4
+
5
+ Supports two modes for service task execution:
6
+
7
+ | Mode | Transport | How it works |
8
+ |---|---|---|
9
+ | **Callback** | HTTP webhook | QuanticFlow POSTs to your app's `/workflow-callback/service-task` endpoint |
10
+ | **Event** | Redis Streams | Your app consumes `ServiceTaskStartedEvent` from Redis and signals back via REST |
11
+
12
+ Both modes can run simultaneously (`mode: 'both'`).
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install @quanticjs/workflow-quanticflow
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { Module } from '@nestjs/common';
24
+ import { QuanticFlowWorkflowModule } from '@quanticjs/workflow-quanticflow';
25
+ import { QuanticWorkflowModule } from '@quanticjs/workflow';
26
+
27
+ @Module({
28
+ imports: [
29
+ // 1. Register the QuanticFlow engine adapter
30
+ QuanticFlowWorkflowModule.forRoot({
31
+ url: 'http://quanticflow:3000',
32
+ serviceTaskHandling: {
33
+ mode: 'both', // 'callback' | 'event' | 'both'
34
+ callbackSecret: process.env.CALLBACK_SECRET,
35
+ },
36
+ }),
37
+
38
+ // 2. Wire the @Workflow decorator into the CQRS pipeline
39
+ QuanticWorkflowModule.forRoot(),
40
+ ],
41
+ })
42
+ export class AppModule {}
43
+ ```
44
+
45
+ Now any command decorated with `@Workflow` will be routed to QuanticFlow:
46
+
47
+ ```typescript
48
+ import { Workflow } from '@quanticjs/core';
49
+
50
+ @Workflow('expense-approval')
51
+ @Validate(CreateExpenseValidator)
52
+ export class CreateExpenseCommand {
53
+ constructor(
54
+ readonly amount: number,
55
+ readonly category: string,
56
+ ) {}
57
+ }
58
+ ```
59
+
60
+ ## Service Task Handlers
61
+
62
+ Register handlers that execute when QuanticFlow reaches a service task node:
63
+
64
+ ```typescript
65
+ import { Injectable } from '@nestjs/common';
66
+ import { ServiceTaskHandler, ServiceTaskContext, ServiceTaskResult } from '@quanticjs/workflow-quanticflow';
67
+
68
+ @Injectable()
69
+ export class SendEmailHandler implements ServiceTaskHandler {
70
+ constructor(private readonly mailer: MailerService) {}
71
+
72
+ async execute(context: ServiceTaskContext): Promise<ServiceTaskResult> {
73
+ await this.mailer.send({
74
+ to: context.variables.recipientEmail,
75
+ subject: `Process ${context.correlationId} update`,
76
+ });
77
+ return { signal: 'email-sent', data: { sentAt: new Date() } };
78
+ }
79
+ }
80
+ ```
81
+
82
+ Register handlers in your module — the registry resolves them by name from the DI container:
83
+
84
+ ```typescript
85
+ @Module({
86
+ providers: [SendEmailHandler],
87
+ })
88
+ export class NotificationsModule {}
89
+ ```
90
+
91
+ The handler name in the BPMN definition must match the class name (e.g., `SendEmailHandler`).
92
+
93
+ ## Configuration
94
+
95
+ ```typescript
96
+ QuanticFlowWorkflowModule.forRoot({
97
+ // Required
98
+ url: 'http://quanticflow:3000',
99
+
100
+ // Optional
101
+ requestTimeout: 10000, // HTTP timeout in ms (default: 10000)
102
+
103
+ serviceTaskHandling: {
104
+ mode: 'callback', // 'callback' | 'event' | 'both'
105
+
106
+ // Callback mode options
107
+ callbackSecret: 'hmac-secret', // HMAC-SHA256 signature verification
108
+
109
+ // Event mode options (requires ioredis + @quanticjs/redis)
110
+ streamKey: 'arex:events:services', // Redis stream (default)
111
+ consumerGroup: 'my-app-tasks', // Consumer group name
112
+ consumerName: 'worker-1', // Consumer name (default: consumer-{pid})
113
+ },
114
+ })
115
+ ```
116
+
117
+ ## Using the Client Directly
118
+
119
+ The `QuanticFlowClient` is exported for direct REST API access beyond the `WorkflowEngine` interface:
120
+
121
+ ```typescript
122
+ import { QuanticFlowClient } from '@quanticjs/workflow-quanticflow';
123
+
124
+ @Injectable()
125
+ export class WorkflowDashboardService {
126
+ constructor(private readonly qf: QuanticFlowClient) {}
127
+
128
+ async getMyTasks(userId: string) {
129
+ return this.qf.listTasks({ userId, status: 'pending' });
130
+ }
131
+
132
+ async approveTask(taskId: string) {
133
+ return this.qf.executeTaskAction(taskId, 'approve', { comment: 'Looks good' });
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### Client Methods
139
+
140
+ | Method | Endpoint | Description |
141
+ |---|---|---|
142
+ | `startInstance(definitionId, variables?, correlationId?)` | `POST /workflow/instances` | Start a workflow |
143
+ | `signalInstance(instanceId, signal, data?)` | `POST /workflow/instances/:id/signal` | Send signal |
144
+ | `abortInstance(instanceId)` | `POST /workflow/instances/:id/abort` | Abort workflow |
145
+ | `getInstance(instanceId)` | `GET /workflow/instances/:id` | Get instance detail |
146
+ | `updateVariables(instanceId, variables)` | `PUT /workflow/instances/:id/variables` | Update variables |
147
+ | `listTasks(filters?)` | `GET /workflow/tasks` | List tasks |
148
+ | `getTask(taskId)` | `GET /workflow/tasks/:id` | Get task detail |
149
+ | `claimTask(taskId)` | `POST /workflow/tasks/:id/claim` | Claim task |
150
+ | `executeTaskAction(taskId, action, data?)` | `POST /workflow/tasks/:id/action` | Execute action |
151
+
152
+ ## Architecture
153
+
154
+ ```
155
+ ┌──────────────────────────────────────────────┐
156
+ │ Your NestJS App │
157
+ │ │
158
+ │ @Workflow('expense') │
159
+ │ CreateExpenseCommand ──▶ WorkflowBehavior │
160
+ │ │ │
161
+ │ ┌─────────▼──────────┐ │
162
+ │ │ QuanticFlowWorkflow │ │
163
+ │ │ Engine (HTTP) │ │
164
+ │ └─────────┬──────────┘ │
165
+ │ │ │
166
+ │ ┌───────────────────────────┼────────────┐ │
167
+ │ │ Service Task Handling │ │ │
168
+ │ │ │ │ │
169
+ │ │ ┌─────────────┐ ┌──────▼─────────┐ │ │
170
+ │ │ │ Callback │ │ Event │ │ │
171
+ │ │ │ Controller │ │ Consumer │ │ │
172
+ │ │ │ (webhook) │ │ (Redis Stream)│ │ │
173
+ │ │ └──────┬──────┘ └──────┬─────────┘ │ │
174
+ │ │ │ │ │ │
175
+ │ │ ▼ ▼ │ │
176
+ │ │ ServiceTaskHandlerRegistry │ │
177
+ │ │ │ │ │
178
+ │ │ ▼ │ │
179
+ │ │ SendEmailHandler (your code) │ │
180
+ │ └────────────────────────────────────────┘ │
181
+ └──────────────────────────────────────────────┘
182
+
183
+ │ HTTP / Redis
184
+
185
+ ┌──────────────────┐
186
+ │ QuanticFlow │
187
+ │ (standalone) │
188
+ └──────────────────┘
189
+ ```
190
+
191
+ ## License
192
+
193
+ MIT
@@ -0,0 +1,24 @@
1
+ import { QuanticFlowClient } from './QuanticFlowClient';
2
+ import { ServiceTaskHandlerRegistry } from './ServiceTaskHandlerRegistry';
3
+ import { QuanticFlowWorkflowModuleOptions } from './QuanticFlowWorkflowModule';
4
+ export interface ServiceTaskCallbackPayload {
5
+ instanceId: string;
6
+ definitionId: string;
7
+ stateName: string;
8
+ handlerName: string;
9
+ correlationId?: string;
10
+ variables: Record<string, unknown>;
11
+ }
12
+ export interface ServiceTaskCallbackResponse {
13
+ signal: string;
14
+ data?: unknown;
15
+ }
16
+ export declare class QuanticFlowCallbackController {
17
+ private readonly registry;
18
+ private readonly client;
19
+ private readonly logger;
20
+ private readonly callbackSecret?;
21
+ constructor(registry: ServiceTaskHandlerRegistry, client: QuanticFlowClient, options: QuanticFlowWorkflowModuleOptions);
22
+ handleServiceTask(payload: ServiceTaskCallbackPayload, signature?: string): Promise<ServiceTaskCallbackResponse>;
23
+ private verifySignature;
24
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var QuanticFlowCallbackController_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.QuanticFlowCallbackController = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const QuanticFlowClient_1 = require("./QuanticFlowClient");
19
+ const ServiceTaskHandlerRegistry_1 = require("./ServiceTaskHandlerRegistry");
20
+ const QuanticFlowWorkflowModule_1 = require("./QuanticFlowWorkflowModule");
21
+ const crypto_1 = require("crypto");
22
+ let QuanticFlowCallbackController = QuanticFlowCallbackController_1 = class QuanticFlowCallbackController {
23
+ registry;
24
+ client;
25
+ logger = new common_1.Logger(QuanticFlowCallbackController_1.name);
26
+ callbackSecret;
27
+ constructor(registry, client, options) {
28
+ this.registry = registry;
29
+ this.client = client;
30
+ this.callbackSecret = options.serviceTaskHandling?.callbackSecret;
31
+ }
32
+ async handleServiceTask(payload, signature) {
33
+ this.verifySignature(payload, signature);
34
+ this.logger.log(`Service task callback: handler=${payload.handlerName}, instance=${payload.instanceId}`);
35
+ const handler = await this.registry.resolve(payload.handlerName);
36
+ if (!handler) {
37
+ this.logger.error(`No handler registered for "${payload.handlerName}"`);
38
+ await this.client.signalInstance(payload.instanceId, 'handler-not-found', {
39
+ error: `Handler "${payload.handlerName}" not registered`,
40
+ });
41
+ return { signal: 'handler-not-found' };
42
+ }
43
+ const context = {
44
+ instanceId: payload.instanceId,
45
+ definitionId: payload.definitionId,
46
+ stateName: payload.stateName,
47
+ handlerName: payload.handlerName,
48
+ correlationId: payload.correlationId,
49
+ variables: payload.variables,
50
+ };
51
+ try {
52
+ const result = await handler.execute(context);
53
+ this.logger.log(`Handler "${payload.handlerName}" completed with signal="${result.signal}"`);
54
+ return { signal: result.signal, data: result.data };
55
+ }
56
+ catch (err) {
57
+ const message = err instanceof Error ? err.message : String(err);
58
+ this.logger.error(`Handler "${payload.handlerName}" failed: ${message}`);
59
+ return { signal: 'handler-error', data: { error: message } };
60
+ }
61
+ }
62
+ verifySignature(payload, signature) {
63
+ if (!this.callbackSecret)
64
+ return;
65
+ if (!signature) {
66
+ throw new common_1.ForbiddenException('Missing callback signature');
67
+ }
68
+ const expected = (0, crypto_1.createHmac)('sha256', this.callbackSecret)
69
+ .update(JSON.stringify(payload))
70
+ .digest('hex');
71
+ if (signature !== expected) {
72
+ throw new common_1.ForbiddenException('Invalid callback signature');
73
+ }
74
+ }
75
+ };
76
+ exports.QuanticFlowCallbackController = QuanticFlowCallbackController;
77
+ __decorate([
78
+ (0, common_1.Post)('service-task'),
79
+ (0, common_1.HttpCode)(200),
80
+ __param(0, (0, common_1.Body)()),
81
+ __param(1, (0, common_1.Headers)('x-callback-signature')),
82
+ __metadata("design:type", Function),
83
+ __metadata("design:paramtypes", [Object, String]),
84
+ __metadata("design:returntype", Promise)
85
+ ], QuanticFlowCallbackController.prototype, "handleServiceTask", null);
86
+ exports.QuanticFlowCallbackController = QuanticFlowCallbackController = QuanticFlowCallbackController_1 = __decorate([
87
+ (0, common_1.Controller)('workflow-callback'),
88
+ __param(2, (0, common_1.Inject)(QuanticFlowWorkflowModule_1.QUANTICFLOW_OPTIONS)),
89
+ __metadata("design:paramtypes", [ServiceTaskHandlerRegistry_1.ServiceTaskHandlerRegistry,
90
+ QuanticFlowClient_1.QuanticFlowClient, Object])
91
+ ], QuanticFlowCallbackController);
@@ -0,0 +1,59 @@
1
+ import { HttpService } from '@nestjs/axios';
2
+ export interface QuanticFlowInstance {
3
+ id: string;
4
+ definitionId: string;
5
+ definitionVersion: string;
6
+ status: string;
7
+ currentState: string;
8
+ variables: Record<string, unknown>;
9
+ correlationId: string | null;
10
+ startedBy: string;
11
+ completedAt: string | null;
12
+ createdAt: string;
13
+ updatedAt: string;
14
+ }
15
+ export interface QuanticFlowTask {
16
+ id: string;
17
+ processInstanceId: string;
18
+ stateName: string;
19
+ taskType: string;
20
+ taskName: string;
21
+ assignedRole: string;
22
+ assignedUserId: string | null;
23
+ status: string;
24
+ actions: Array<{
25
+ name: string;
26
+ label: string;
27
+ roles: string[];
28
+ }>;
29
+ outcome: string | null;
30
+ outputData: Record<string, unknown> | null;
31
+ formSchema: Record<string, unknown> | null;
32
+ formData: Record<string, unknown> | null;
33
+ dueAt: string | null;
34
+ completedBy: string | null;
35
+ completedAt: string | null;
36
+ createdAt: string;
37
+ updatedAt: string;
38
+ }
39
+ export interface PaginatedResult<T> {
40
+ items: T[];
41
+ total: number;
42
+ page: number;
43
+ limit: number;
44
+ totalPages: number;
45
+ }
46
+ export declare class QuanticFlowClient {
47
+ private readonly httpService;
48
+ private readonly logger;
49
+ constructor(httpService: HttpService);
50
+ startInstance(definitionId: string, variables?: Record<string, unknown>, correlationId?: string): Promise<QuanticFlowInstance>;
51
+ signalInstance(instanceId: string, signalName: string, signalData?: unknown): Promise<QuanticFlowInstance>;
52
+ abortInstance(instanceId: string): Promise<QuanticFlowInstance>;
53
+ getInstance(instanceId: string): Promise<QuanticFlowInstance>;
54
+ updateVariables(instanceId: string, variables: Record<string, unknown>): Promise<QuanticFlowInstance>;
55
+ listTasks(filters?: Record<string, string | number>): Promise<PaginatedResult<QuanticFlowTask>>;
56
+ getTask(taskId: string): Promise<QuanticFlowTask>;
57
+ claimTask(taskId: string): Promise<QuanticFlowTask>;
58
+ executeTaskAction(taskId: string, actionName: string, actionData?: Record<string, unknown>): Promise<QuanticFlowTask>;
59
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var QuanticFlowClient_1;
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.QuanticFlowClient = void 0;
14
+ const common_1 = require("@nestjs/common");
15
+ const axios_1 = require("@nestjs/axios");
16
+ const rxjs_1 = require("rxjs");
17
+ let QuanticFlowClient = QuanticFlowClient_1 = class QuanticFlowClient {
18
+ httpService;
19
+ logger = new common_1.Logger(QuanticFlowClient_1.name);
20
+ constructor(httpService) {
21
+ this.httpService = httpService;
22
+ }
23
+ async startInstance(definitionId, variables, correlationId) {
24
+ const { data } = await (0, rxjs_1.firstValueFrom)(this.httpService.post('/workflow/instances', {
25
+ definitionId,
26
+ variables,
27
+ correlationId,
28
+ }));
29
+ this.logger.log(`Started instance ${data.id} for definition ${definitionId}`);
30
+ return data;
31
+ }
32
+ async signalInstance(instanceId, signalName, signalData) {
33
+ const { data } = await (0, rxjs_1.firstValueFrom)(this.httpService.post(`/workflow/instances/${instanceId}/signal`, { signalName, data: signalData }));
34
+ this.logger.log(`Signaled instance ${instanceId} with "${signalName}"`);
35
+ return data;
36
+ }
37
+ async abortInstance(instanceId) {
38
+ const { data } = await (0, rxjs_1.firstValueFrom)(this.httpService.post(`/workflow/instances/${instanceId}/abort`));
39
+ this.logger.log(`Aborted instance ${instanceId}`);
40
+ return data;
41
+ }
42
+ async getInstance(instanceId) {
43
+ const { data } = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`/workflow/instances/${instanceId}`));
44
+ return data;
45
+ }
46
+ async updateVariables(instanceId, variables) {
47
+ const { data } = await (0, rxjs_1.firstValueFrom)(this.httpService.put(`/workflow/instances/${instanceId}/variables`, { variables }));
48
+ this.logger.log(`Updated variables for instance ${instanceId}`);
49
+ return data;
50
+ }
51
+ async listTasks(filters) {
52
+ const { data } = await (0, rxjs_1.firstValueFrom)(this.httpService.get('/workflow/tasks', {
53
+ params: filters,
54
+ }));
55
+ return data;
56
+ }
57
+ async getTask(taskId) {
58
+ const { data } = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`/workflow/tasks/${taskId}`));
59
+ return data;
60
+ }
61
+ async claimTask(taskId) {
62
+ const { data } = await (0, rxjs_1.firstValueFrom)(this.httpService.post(`/workflow/tasks/${taskId}/claim`));
63
+ this.logger.log(`Claimed task ${taskId}`);
64
+ return data;
65
+ }
66
+ async executeTaskAction(taskId, actionName, actionData) {
67
+ const { data } = await (0, rxjs_1.firstValueFrom)(this.httpService.post(`/workflow/tasks/${taskId}/action`, {
68
+ actionName,
69
+ data: actionData,
70
+ }));
71
+ this.logger.log(`Executed action "${actionName}" on task ${taskId}`);
72
+ return data;
73
+ }
74
+ };
75
+ exports.QuanticFlowClient = QuanticFlowClient;
76
+ exports.QuanticFlowClient = QuanticFlowClient = QuanticFlowClient_1 = __decorate([
77
+ (0, common_1.Injectable)(),
78
+ __metadata("design:paramtypes", [axios_1.HttpService])
79
+ ], QuanticFlowClient);
@@ -0,0 +1,13 @@
1
+ import type { WorkflowEngine, WorkflowStartResult } from '@quanticjs/core';
2
+ import { QuanticFlowClient } from './QuanticFlowClient';
3
+ export declare class QuanticFlowWorkflowEngine implements WorkflowEngine {
4
+ private readonly client;
5
+ private readonly logger;
6
+ constructor(client: QuanticFlowClient);
7
+ startProcess(processDefinitionId: string, command: object, metadata: {
8
+ commandType: string;
9
+ correlationId?: string;
10
+ }): Promise<WorkflowStartResult>;
11
+ signalProcess(processInstanceId: string, signal: string, data?: unknown): Promise<void>;
12
+ abortProcess(processInstanceId: string): Promise<void>;
13
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var QuanticFlowWorkflowEngine_1;
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.QuanticFlowWorkflowEngine = void 0;
14
+ const common_1 = require("@nestjs/common");
15
+ const QuanticFlowClient_1 = require("./QuanticFlowClient");
16
+ let QuanticFlowWorkflowEngine = QuanticFlowWorkflowEngine_1 = class QuanticFlowWorkflowEngine {
17
+ client;
18
+ logger = new common_1.Logger(QuanticFlowWorkflowEngine_1.name);
19
+ constructor(client) {
20
+ this.client = client;
21
+ }
22
+ async startProcess(processDefinitionId, command, metadata) {
23
+ const variables = {
24
+ ...JSON.parse(JSON.stringify(command)),
25
+ __commandType: metadata.commandType,
26
+ };
27
+ const instance = await this.client.startInstance(processDefinitionId, variables, metadata.correlationId);
28
+ this.logger.log(`Workflow started: instance=${instance.id}, definition=${processDefinitionId}`);
29
+ return {
30
+ workflowInstanceId: instance.id,
31
+ processInstanceId: instance.id,
32
+ status: 'STARTED',
33
+ };
34
+ }
35
+ async signalProcess(processInstanceId, signal, data) {
36
+ await this.client.signalInstance(processInstanceId, signal, data);
37
+ this.logger.log(`Workflow signaled: instance=${processInstanceId}, signal=${signal}`);
38
+ }
39
+ async abortProcess(processInstanceId) {
40
+ await this.client.abortInstance(processInstanceId);
41
+ this.logger.log(`Workflow aborted: instance=${processInstanceId}`);
42
+ }
43
+ };
44
+ exports.QuanticFlowWorkflowEngine = QuanticFlowWorkflowEngine;
45
+ exports.QuanticFlowWorkflowEngine = QuanticFlowWorkflowEngine = QuanticFlowWorkflowEngine_1 = __decorate([
46
+ (0, common_1.Injectable)(),
47
+ __metadata("design:paramtypes", [QuanticFlowClient_1.QuanticFlowClient])
48
+ ], QuanticFlowWorkflowEngine);
@@ -0,0 +1,22 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { ServiceTaskHandler } from './ServiceTaskHandlerRegistry';
3
+ export declare const QUANTICFLOW_OPTIONS: unique symbol;
4
+ export interface ServiceTaskHandlingOptions {
5
+ mode: 'callback' | 'event' | 'both';
6
+ callbackSecret?: string;
7
+ streamKey?: string;
8
+ consumerGroup?: string;
9
+ consumerName?: string;
10
+ }
11
+ export interface QuanticFlowWorkflowModuleOptions {
12
+ url: string;
13
+ requestTimeout?: number;
14
+ serviceTaskHandling?: ServiceTaskHandlingOptions;
15
+ handlers?: Array<{
16
+ name: string;
17
+ useClass: new (...args: unknown[]) => ServiceTaskHandler;
18
+ }>;
19
+ }
20
+ export declare class QuanticFlowWorkflowModule {
21
+ static forRoot(options: QuanticFlowWorkflowModuleOptions): DynamicModule;
22
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var QuanticFlowWorkflowModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.QuanticFlowWorkflowModule = exports.QUANTICFLOW_OPTIONS = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const axios_1 = require("@nestjs/axios");
13
+ const core_1 = require("@quanticjs/core");
14
+ const QuanticFlowClient_1 = require("./QuanticFlowClient");
15
+ const QuanticFlowWorkflowEngine_1 = require("./QuanticFlowWorkflowEngine");
16
+ const QuanticFlowCallbackController_1 = require("./QuanticFlowCallbackController");
17
+ const ServiceTaskEventConsumer_1 = require("./ServiceTaskEventConsumer");
18
+ const ServiceTaskHandlerRegistry_1 = require("./ServiceTaskHandlerRegistry");
19
+ exports.QUANTICFLOW_OPTIONS = Symbol('QUANTICFLOW_OPTIONS');
20
+ let QuanticFlowWorkflowModule = QuanticFlowWorkflowModule_1 = class QuanticFlowWorkflowModule {
21
+ static forRoot(options) {
22
+ const mode = options.serviceTaskHandling?.mode ?? 'callback';
23
+ const enableCallback = mode === 'callback' || mode === 'both';
24
+ const enableEvents = mode === 'event' || mode === 'both';
25
+ const providers = [
26
+ {
27
+ provide: exports.QUANTICFLOW_OPTIONS,
28
+ useValue: options,
29
+ },
30
+ QuanticFlowClient_1.QuanticFlowClient,
31
+ {
32
+ provide: core_1.WORKFLOW_ENGINE,
33
+ useClass: QuanticFlowWorkflowEngine_1.QuanticFlowWorkflowEngine,
34
+ },
35
+ ServiceTaskHandlerRegistry_1.ServiceTaskHandlerRegistry,
36
+ ];
37
+ const controllers = [];
38
+ if (enableCallback) {
39
+ controllers.push(QuanticFlowCallbackController_1.QuanticFlowCallbackController);
40
+ }
41
+ if (enableEvents) {
42
+ providers.push({
43
+ provide: 'QUANTICFLOW_EVENT_OPTIONS',
44
+ useValue: {
45
+ streamKey: options.serviceTaskHandling?.streamKey,
46
+ consumerGroup: options.serviceTaskHandling?.consumerGroup,
47
+ consumerName: options.serviceTaskHandling?.consumerName,
48
+ },
49
+ });
50
+ providers.push(ServiceTaskEventConsumer_1.ServiceTaskEventConsumer);
51
+ }
52
+ if (options.handlers) {
53
+ for (const { name, useClass } of options.handlers) {
54
+ providers.push({
55
+ provide: name,
56
+ useClass,
57
+ });
58
+ }
59
+ }
60
+ return {
61
+ module: QuanticFlowWorkflowModule_1,
62
+ imports: [
63
+ axios_1.HttpModule.register({
64
+ baseURL: options.url,
65
+ timeout: options.requestTimeout ?? 10000,
66
+ }),
67
+ ],
68
+ controllers,
69
+ providers,
70
+ exports: [
71
+ core_1.WORKFLOW_ENGINE,
72
+ QuanticFlowClient_1.QuanticFlowClient,
73
+ ServiceTaskHandlerRegistry_1.ServiceTaskHandlerRegistry,
74
+ ],
75
+ };
76
+ }
77
+ };
78
+ exports.QuanticFlowWorkflowModule = QuanticFlowWorkflowModule;
79
+ exports.QuanticFlowWorkflowModule = QuanticFlowWorkflowModule = QuanticFlowWorkflowModule_1 = __decorate([
80
+ (0, common_1.Global)(),
81
+ (0, common_1.Module)({})
82
+ ], QuanticFlowWorkflowModule);
@@ -0,0 +1,20 @@
1
+ import { Logger } from '@nestjs/common';
2
+ import { RedisStreamConsumer } from '@quanticjs/events';
3
+ import { QuanticFlowClient } from './QuanticFlowClient';
4
+ import { ServiceTaskHandlerRegistry } from './ServiceTaskHandlerRegistry';
5
+ import type { Redis } from 'ioredis';
6
+ export declare class ServiceTaskEventConsumer extends RedisStreamConsumer {
7
+ private readonly registry;
8
+ private readonly client;
9
+ protected readonly logger: Logger;
10
+ readonly streamKey: string;
11
+ readonly consumerGroup: string;
12
+ readonly consumerName: string;
13
+ constructor(redis: Redis | undefined, registry: ServiceTaskHandlerRegistry, client: QuanticFlowClient, options: {
14
+ streamKey?: string;
15
+ consumerGroup?: string;
16
+ consumerName?: string;
17
+ });
18
+ protected shouldHandle(fields: Record<string, string>): boolean;
19
+ handleMessage(fields: Record<string, string>): Promise<void>;
20
+ }
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var ServiceTaskEventConsumer_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.ServiceTaskEventConsumer = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const core_1 = require("@quanticjs/core");
19
+ const events_1 = require("@quanticjs/events");
20
+ const QuanticFlowClient_1 = require("./QuanticFlowClient");
21
+ const ServiceTaskHandlerRegistry_1 = require("./ServiceTaskHandlerRegistry");
22
+ const DEFAULT_STREAM_KEY = 'arex:events:services';
23
+ const DEFAULT_CONSUMER_GROUP = 'quanticflow-service-tasks';
24
+ let ServiceTaskEventConsumer = ServiceTaskEventConsumer_1 = class ServiceTaskEventConsumer extends events_1.RedisStreamConsumer {
25
+ registry;
26
+ client;
27
+ logger = new common_1.Logger(ServiceTaskEventConsumer_1.name);
28
+ streamKey;
29
+ consumerGroup;
30
+ consumerName;
31
+ constructor(redis, registry, client, options) {
32
+ super(redis);
33
+ this.registry = registry;
34
+ this.client = client;
35
+ this.streamKey = options.streamKey ?? DEFAULT_STREAM_KEY;
36
+ this.consumerGroup = options.consumerGroup ?? DEFAULT_CONSUMER_GROUP;
37
+ this.consumerName = options.consumerName ?? `consumer-${process.pid}`;
38
+ }
39
+ shouldHandle(fields) {
40
+ return fields['eventType'] === 'ServiceTaskStartedEvent';
41
+ }
42
+ async handleMessage(fields) {
43
+ const payload = JSON.parse(fields['payload'] ?? '{}');
44
+ const { instanceId, definitionId, stateName, handlerName, correlationId, variables, } = payload;
45
+ this.logger.log(`Service task event: handler=${handlerName}, instance=${instanceId}`);
46
+ const handler = await this.registry.resolve(handlerName);
47
+ if (!handler) {
48
+ this.logger.error(`No handler registered for "${handlerName}"`);
49
+ await this.client.signalInstance(instanceId, 'handler-not-found', {
50
+ error: `Handler "${handlerName}" not registered`,
51
+ });
52
+ return;
53
+ }
54
+ const context = {
55
+ instanceId,
56
+ definitionId,
57
+ stateName,
58
+ handlerName,
59
+ correlationId,
60
+ variables: variables ?? {},
61
+ };
62
+ try {
63
+ const result = await handler.execute(context);
64
+ await this.client.signalInstance(instanceId, result.signal, result.data);
65
+ this.logger.log(`Handler "${handlerName}" completed, signaled instance ${instanceId} with "${result.signal}"`);
66
+ }
67
+ catch (err) {
68
+ const message = err instanceof Error ? err.message : String(err);
69
+ this.logger.error(`Handler "${handlerName}" failed: ${message}`);
70
+ await this.client.signalInstance(instanceId, 'handler-error', {
71
+ error: message,
72
+ handlerName,
73
+ stateName,
74
+ });
75
+ }
76
+ }
77
+ };
78
+ exports.ServiceTaskEventConsumer = ServiceTaskEventConsumer;
79
+ exports.ServiceTaskEventConsumer = ServiceTaskEventConsumer = ServiceTaskEventConsumer_1 = __decorate([
80
+ (0, common_1.Injectable)(),
81
+ __param(0, (0, common_1.Optional)()),
82
+ __param(0, (0, common_1.Inject)(core_1.REDIS_CLIENT)),
83
+ __param(3, (0, common_1.Inject)('QUANTICFLOW_EVENT_OPTIONS')),
84
+ __metadata("design:paramtypes", [Object, ServiceTaskHandlerRegistry_1.ServiceTaskHandlerRegistry,
85
+ QuanticFlowClient_1.QuanticFlowClient, Object])
86
+ ], ServiceTaskEventConsumer);
@@ -0,0 +1,25 @@
1
+ import { ModuleRef } from '@nestjs/core';
2
+ export interface ServiceTaskHandler {
3
+ execute(context: ServiceTaskContext): Promise<ServiceTaskResult>;
4
+ }
5
+ export interface ServiceTaskContext {
6
+ instanceId: string;
7
+ definitionId: string;
8
+ stateName: string;
9
+ handlerName: string;
10
+ correlationId?: string;
11
+ variables: Record<string, unknown>;
12
+ }
13
+ export interface ServiceTaskResult {
14
+ signal: string;
15
+ data?: unknown;
16
+ }
17
+ export declare const SERVICE_TASK_HANDLERS: unique symbol;
18
+ export declare class ServiceTaskHandlerRegistry {
19
+ private readonly moduleRef;
20
+ private readonly logger;
21
+ private readonly handlers;
22
+ constructor(moduleRef: ModuleRef);
23
+ register(name: string, handler: ServiceTaskHandler): void;
24
+ resolve(name: string): Promise<ServiceTaskHandler | undefined>;
25
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var ServiceTaskHandlerRegistry_1;
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.ServiceTaskHandlerRegistry = exports.SERVICE_TASK_HANDLERS = void 0;
14
+ const common_1 = require("@nestjs/common");
15
+ const core_1 = require("@nestjs/core");
16
+ exports.SERVICE_TASK_HANDLERS = Symbol('SERVICE_TASK_HANDLERS');
17
+ let ServiceTaskHandlerRegistry = ServiceTaskHandlerRegistry_1 = class ServiceTaskHandlerRegistry {
18
+ moduleRef;
19
+ logger = new common_1.Logger(ServiceTaskHandlerRegistry_1.name);
20
+ handlers = new Map();
21
+ constructor(moduleRef) {
22
+ this.moduleRef = moduleRef;
23
+ }
24
+ register(name, handler) {
25
+ this.handlers.set(name, handler);
26
+ this.logger.log(`Registered service task handler: ${name}`);
27
+ }
28
+ async resolve(name) {
29
+ const registered = this.handlers.get(name);
30
+ if (registered)
31
+ return registered;
32
+ try {
33
+ const handler = this.moduleRef.get(name, { strict: false });
34
+ if (handler && typeof handler.execute === 'function') {
35
+ this.handlers.set(name, handler);
36
+ return handler;
37
+ }
38
+ }
39
+ catch {
40
+ // Not found in DI container
41
+ }
42
+ return undefined;
43
+ }
44
+ };
45
+ exports.ServiceTaskHandlerRegistry = ServiceTaskHandlerRegistry;
46
+ exports.ServiceTaskHandlerRegistry = ServiceTaskHandlerRegistry = ServiceTaskHandlerRegistry_1 = __decorate([
47
+ (0, common_1.Injectable)(),
48
+ __metadata("design:paramtypes", [core_1.ModuleRef])
49
+ ], ServiceTaskHandlerRegistry);
@@ -0,0 +1,10 @@
1
+ export { QuanticFlowWorkflowModule, QUANTICFLOW_OPTIONS } from './QuanticFlowWorkflowModule';
2
+ export type { QuanticFlowWorkflowModuleOptions, ServiceTaskHandlingOptions, } from './QuanticFlowWorkflowModule';
3
+ export { QuanticFlowClient } from './QuanticFlowClient';
4
+ export type { QuanticFlowInstance, QuanticFlowTask, PaginatedResult, } from './QuanticFlowClient';
5
+ export { QuanticFlowWorkflowEngine } from './QuanticFlowWorkflowEngine';
6
+ export { QuanticFlowCallbackController } from './QuanticFlowCallbackController';
7
+ export type { ServiceTaskCallbackPayload, ServiceTaskCallbackResponse, } from './QuanticFlowCallbackController';
8
+ export { ServiceTaskEventConsumer } from './ServiceTaskEventConsumer';
9
+ export { ServiceTaskHandlerRegistry, SERVICE_TASK_HANDLERS, } from './ServiceTaskHandlerRegistry';
10
+ export type { ServiceTaskHandler, ServiceTaskContext, ServiceTaskResult, } from './ServiceTaskHandlerRegistry';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SERVICE_TASK_HANDLERS = exports.ServiceTaskHandlerRegistry = exports.ServiceTaskEventConsumer = exports.QuanticFlowCallbackController = exports.QuanticFlowWorkflowEngine = exports.QuanticFlowClient = exports.QUANTICFLOW_OPTIONS = exports.QuanticFlowWorkflowModule = void 0;
4
+ var QuanticFlowWorkflowModule_1 = require("./QuanticFlowWorkflowModule");
5
+ Object.defineProperty(exports, "QuanticFlowWorkflowModule", { enumerable: true, get: function () { return QuanticFlowWorkflowModule_1.QuanticFlowWorkflowModule; } });
6
+ Object.defineProperty(exports, "QUANTICFLOW_OPTIONS", { enumerable: true, get: function () { return QuanticFlowWorkflowModule_1.QUANTICFLOW_OPTIONS; } });
7
+ var QuanticFlowClient_1 = require("./QuanticFlowClient");
8
+ Object.defineProperty(exports, "QuanticFlowClient", { enumerable: true, get: function () { return QuanticFlowClient_1.QuanticFlowClient; } });
9
+ var QuanticFlowWorkflowEngine_1 = require("./QuanticFlowWorkflowEngine");
10
+ Object.defineProperty(exports, "QuanticFlowWorkflowEngine", { enumerable: true, get: function () { return QuanticFlowWorkflowEngine_1.QuanticFlowWorkflowEngine; } });
11
+ var QuanticFlowCallbackController_1 = require("./QuanticFlowCallbackController");
12
+ Object.defineProperty(exports, "QuanticFlowCallbackController", { enumerable: true, get: function () { return QuanticFlowCallbackController_1.QuanticFlowCallbackController; } });
13
+ var ServiceTaskEventConsumer_1 = require("./ServiceTaskEventConsumer");
14
+ Object.defineProperty(exports, "ServiceTaskEventConsumer", { enumerable: true, get: function () { return ServiceTaskEventConsumer_1.ServiceTaskEventConsumer; } });
15
+ var ServiceTaskHandlerRegistry_1 = require("./ServiceTaskHandlerRegistry");
16
+ Object.defineProperty(exports, "ServiceTaskHandlerRegistry", { enumerable: true, get: function () { return ServiceTaskHandlerRegistry_1.ServiceTaskHandlerRegistry; } });
17
+ Object.defineProperty(exports, "SERVICE_TASK_HANDLERS", { enumerable: true, get: function () { return ServiceTaskHandlerRegistry_1.SERVICE_TASK_HANDLERS; } });
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@quanticjs/workflow-quanticflow",
3
+ "version": "3.3.0",
4
+ "description": "QuanticFlow workflow engine adapter for @quanticjs/workflow — supports callback and event-driven service task execution",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "test": "jest --passWithNoTests",
13
+ "clean": "rm -rf dist"
14
+ },
15
+ "keywords": [
16
+ "nestjs",
17
+ "cqrs",
18
+ "quanticflow",
19
+ "workflow",
20
+ "bpmn"
21
+ ],
22
+ "author": "turkelk",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@nestjs/axios": "^4.0.0",
26
+ "@quanticjs/core": "^3.3.0",
27
+ "@quanticjs/events": "^3.3.0",
28
+ "axios": "^1.6.0"
29
+ },
30
+ "peerDependencies": {
31
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
32
+ "@nestjs/core": "^10.0.0 || ^11.0.0",
33
+ "@nestjs/cqrs": "^10.0.0 || ^11.0.0",
34
+ "ioredis": "^5.0.0",
35
+ "reflect-metadata": "^0.1.13 || ^0.2.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "ioredis": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "publishConfig": {
43
+ "registry": "https://registry.npmjs.org",
44
+ "access": "public"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/quanticjs/quanticjs-backend.git",
49
+ "directory": "packages/workflow-quanticflow"
50
+ }
51
+ }