@duraflows/nestjs 2.0.0 → 3.0.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 (109) hide show
  1. package/README.md +38 -0
  2. package/dist/cjs/controllers/dto/history-query.dto.d.ts +2 -2
  3. package/dist/cjs/controllers/dto/history-query.dto.js +10 -4
  4. package/dist/cjs/controllers/dto/timeout-process.dto.d.ts +1 -1
  5. package/dist/cjs/controllers/dto/timeout-process.dto.js +6 -2
  6. package/dist/cjs/controllers/workflow-event.controller.js +3 -1
  7. package/dist/cjs/controllers/workflow-instance.controller.js +3 -1
  8. package/dist/cjs/controllers/workflow-query.controller.js +5 -3
  9. package/dist/cjs/controllers/workflow-timeout.controller.js +4 -2
  10. package/dist/cjs/filters/workflow-exception.filter.d.ts +13 -0
  11. package/dist/cjs/filters/workflow-exception.filter.js +58 -0
  12. package/dist/cjs/index.d.ts +2 -1
  13. package/dist/cjs/index.js +5 -1
  14. package/dist/cjs/providers/nest-command-registry.js +30 -1
  15. package/dist/cjs/services/workflow.service.d.ts +22 -1
  16. package/dist/cjs/services/workflow.service.js +35 -0
  17. package/dist/cjs/workflow.module.d.ts +9 -2
  18. package/dist/cjs/workflow.module.js +13 -4
  19. package/dist/controllers/dto/history-query.dto.d.ts +2 -2
  20. package/dist/controllers/dto/history-query.dto.js +11 -5
  21. package/dist/controllers/dto/timeout-process.dto.d.ts +1 -1
  22. package/dist/controllers/dto/timeout-process.dto.js +7 -3
  23. package/dist/controllers/workflow-event.controller.js +4 -2
  24. package/dist/controllers/workflow-instance.controller.js +4 -2
  25. package/dist/controllers/workflow-query.controller.js +6 -4
  26. package/dist/controllers/workflow-timeout.controller.js +5 -3
  27. package/dist/filters/workflow-exception.filter.d.ts +13 -0
  28. package/dist/filters/workflow-exception.filter.js +55 -0
  29. package/dist/index.d.ts +2 -1
  30. package/dist/index.js +3 -1
  31. package/dist/providers/nest-command-registry.js +30 -1
  32. package/dist/services/workflow.service.d.ts +22 -1
  33. package/dist/services/workflow.service.js +35 -0
  34. package/dist/workflow.module.d.ts +9 -2
  35. package/dist/workflow.module.js +14 -5
  36. package/package.json +12 -6
  37. package/dist/cjs/controllers/dto/available-events-query.dto.d.ts.map +0 -1
  38. package/dist/cjs/controllers/dto/available-events-query.dto.js.map +0 -1
  39. package/dist/cjs/controllers/dto/create-instance.dto.d.ts.map +0 -1
  40. package/dist/cjs/controllers/dto/create-instance.dto.js.map +0 -1
  41. package/dist/cjs/controllers/dto/history-query.dto.d.ts.map +0 -1
  42. package/dist/cjs/controllers/dto/history-query.dto.js.map +0 -1
  43. package/dist/cjs/controllers/dto/index.d.ts.map +0 -1
  44. package/dist/cjs/controllers/dto/index.js.map +0 -1
  45. package/dist/cjs/controllers/dto/timeout-process.dto.d.ts.map +0 -1
  46. package/dist/cjs/controllers/dto/timeout-process.dto.js.map +0 -1
  47. package/dist/cjs/controllers/dto/trigger-event.dto.d.ts.map +0 -1
  48. package/dist/cjs/controllers/dto/trigger-event.dto.js.map +0 -1
  49. package/dist/cjs/controllers/workflow-event.controller.d.ts.map +0 -1
  50. package/dist/cjs/controllers/workflow-event.controller.js.map +0 -1
  51. package/dist/cjs/controllers/workflow-instance.controller.d.ts.map +0 -1
  52. package/dist/cjs/controllers/workflow-instance.controller.js.map +0 -1
  53. package/dist/cjs/controllers/workflow-query.controller.d.ts.map +0 -1
  54. package/dist/cjs/controllers/workflow-query.controller.js.map +0 -1
  55. package/dist/cjs/controllers/workflow-timeout.controller.d.ts.map +0 -1
  56. package/dist/cjs/controllers/workflow-timeout.controller.js.map +0 -1
  57. package/dist/cjs/decorators/workflow-command.decorator.d.ts.map +0 -1
  58. package/dist/cjs/decorators/workflow-command.decorator.js.map +0 -1
  59. package/dist/cjs/index.d.ts.map +0 -1
  60. package/dist/cjs/index.js.map +0 -1
  61. package/dist/cjs/providers/command-discovery.d.ts.map +0 -1
  62. package/dist/cjs/providers/command-discovery.js.map +0 -1
  63. package/dist/cjs/providers/injection-tokens.d.ts.map +0 -1
  64. package/dist/cjs/providers/injection-tokens.js.map +0 -1
  65. package/dist/cjs/providers/nest-command-registry.d.ts.map +0 -1
  66. package/dist/cjs/providers/nest-command-registry.js.map +0 -1
  67. package/dist/cjs/services/workflow-timeout.service.d.ts.map +0 -1
  68. package/dist/cjs/services/workflow-timeout.service.js.map +0 -1
  69. package/dist/cjs/services/workflow.service.d.ts.map +0 -1
  70. package/dist/cjs/services/workflow.service.js.map +0 -1
  71. package/dist/cjs/workflow.module.d.ts.map +0 -1
  72. package/dist/cjs/workflow.module.js.map +0 -1
  73. package/dist/controllers/dto/available-events-query.dto.d.ts.map +0 -1
  74. package/dist/controllers/dto/available-events-query.dto.js.map +0 -1
  75. package/dist/controllers/dto/create-instance.dto.d.ts.map +0 -1
  76. package/dist/controllers/dto/create-instance.dto.js.map +0 -1
  77. package/dist/controllers/dto/history-query.dto.d.ts.map +0 -1
  78. package/dist/controllers/dto/history-query.dto.js.map +0 -1
  79. package/dist/controllers/dto/index.d.ts.map +0 -1
  80. package/dist/controllers/dto/index.js.map +0 -1
  81. package/dist/controllers/dto/timeout-process.dto.d.ts.map +0 -1
  82. package/dist/controllers/dto/timeout-process.dto.js.map +0 -1
  83. package/dist/controllers/dto/trigger-event.dto.d.ts.map +0 -1
  84. package/dist/controllers/dto/trigger-event.dto.js.map +0 -1
  85. package/dist/controllers/workflow-event.controller.d.ts.map +0 -1
  86. package/dist/controllers/workflow-event.controller.js.map +0 -1
  87. package/dist/controllers/workflow-instance.controller.d.ts.map +0 -1
  88. package/dist/controllers/workflow-instance.controller.js.map +0 -1
  89. package/dist/controllers/workflow-query.controller.d.ts.map +0 -1
  90. package/dist/controllers/workflow-query.controller.js.map +0 -1
  91. package/dist/controllers/workflow-timeout.controller.d.ts.map +0 -1
  92. package/dist/controllers/workflow-timeout.controller.js.map +0 -1
  93. package/dist/decorators/workflow-command.decorator.d.ts.map +0 -1
  94. package/dist/decorators/workflow-command.decorator.js.map +0 -1
  95. package/dist/index.d.ts.map +0 -1
  96. package/dist/index.js.map +0 -1
  97. package/dist/providers/command-discovery.d.ts.map +0 -1
  98. package/dist/providers/command-discovery.js.map +0 -1
  99. package/dist/providers/injection-tokens.d.ts.map +0 -1
  100. package/dist/providers/injection-tokens.js.map +0 -1
  101. package/dist/providers/nest-command-registry.d.ts.map +0 -1
  102. package/dist/providers/nest-command-registry.js.map +0 -1
  103. package/dist/services/workflow-timeout.service.d.ts.map +0 -1
  104. package/dist/services/workflow-timeout.service.js.map +0 -1
  105. package/dist/services/workflow.service.d.ts.map +0 -1
  106. package/dist/services/workflow.service.js.map +0 -1
  107. package/dist/tsconfig.cjs.tsbuildinfo +0 -1
  108. package/dist/workflow.module.d.ts.map +0 -1
  109. package/dist/workflow.module.js.map +0 -1
package/README.md CHANGED
@@ -7,7 +7,9 @@ Part of the [duraflows](https://github.com/camcima/duraflows) monorepo.
7
7
  ## Features
8
8
 
9
9
  - Dynamic NestJS module with `forRoot()` and `forRootAsync()` configuration
10
+ - Registered as a **global module** -- feature modules can inject `WorkflowService` without re-importing `WorkflowModule.forRoot()`
10
11
  - `WorkflowService` for creating instances, triggering events, and querying state
12
+ - Type-safe `createInstanceFor()` / `triggerEventFor()` variants that narrow `currentState` / `fromState` / `toState` to the state union of a `WorkflowDefinition<TState>`
11
13
  - `WorkflowTimeoutService` for processing expired workflows
12
14
  - Optional REST controllers for full HTTP API
13
15
  - `@WorkflowCommand` decorator with automatic discovery
@@ -98,6 +100,40 @@ export class OrderService {
98
100
  }
99
101
  ```
100
102
 
103
+ ### Type-Safe Variants
104
+
105
+ When you have a typed `WorkflowDefinition<TState>` in hand, use `createInstanceFor` / `triggerEventFor` to narrow `currentState`, `fromState`, and `toState` to the state union instead of `string`:
106
+
107
+ ```ts
108
+ import type { WorkflowDefinition } from "@duraflows/nestjs";
109
+
110
+ type OrderState = "new" | "paid" | "shipped" | "cancelled";
111
+
112
+ const orderWorkflow: WorkflowDefinition<OrderState> = {
113
+ name: "order",
114
+ initialState: "new",
115
+ states: {
116
+ new: { events: { PaymentReceived: { targetState: "paid" }, Cancel: { targetState: "cancelled" } } },
117
+ paid: { events: { Ship: { targetState: "shipped" } } },
118
+ shipped: {},
119
+ cancelled: {},
120
+ },
121
+ };
122
+
123
+ // `instance.currentState` is typed as OrderState, not string
124
+ const instance = await workflowService.createInstanceFor(orderWorkflow, {
125
+ metadata: { orderId: "abc" },
126
+ });
127
+
128
+ // `result.fromState` / `result.toState` are typed as OrderState
129
+ const result = await workflowService.triggerEventFor(orderWorkflow, {
130
+ workflowInstanceUuid: instance.uuid,
131
+ eventName: "PaymentReceived",
132
+ });
133
+ ```
134
+
135
+ `triggerEventFor` does not verify at runtime that the instance belongs to the supplied definition -- the type narrowing is the caller's contract. Pair it with `createInstanceFor` to keep that invariant.
136
+
101
137
  ### @WorkflowCommand Decorator
102
138
 
103
139
  Mark command handlers for automatic discovery:
@@ -121,6 +157,8 @@ export class SendToWarehouseCommand implements IWorkflowCommand {
121
157
 
122
158
  When `enableControllers: true` is set, the following endpoints are registered:
123
159
 
160
+ > **Security:** the generated controllers ship **without authentication**. They expose instance creation, arbitrary event triggering, full history reads, and `POST /workflows/timeouts/process` (an administrative bulk operation). Apply your own auth guards and rate limiting before enabling them in production — e.g. a global `APP_GUARD`, or guards bound per controller. Note also that `triggerEvent` responses include each command's `CommandResult` verbatim, so command authors must not place sensitive data in `CommandResult.error` / `message` if the controllers are enabled.
161
+
124
162
  | Controller | Endpoints |
125
163
  | ---------------------------- | -------------------------------------- |
126
164
  | `WorkflowInstanceController` | Create and retrieve workflow instances |
@@ -1,6 +1,6 @@
1
1
  export declare class HistoryQueryDto {
2
- limit?: string;
3
- offset?: string;
2
+ limit?: number;
3
+ offset?: number;
4
4
  }
5
5
  export declare class HistoryParamsDto {
6
6
  workflowInstanceUuid: string;
@@ -10,6 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.HistoryParamsDto = exports.HistoryQueryDto = void 0;
13
+ const class_transformer_1 = require("class-transformer");
13
14
  const class_validator_1 = require("class-validator");
14
15
  class HistoryQueryDto {
15
16
  limit;
@@ -18,13 +19,18 @@ class HistoryQueryDto {
18
19
  exports.HistoryQueryDto = HistoryQueryDto;
19
20
  __decorate([
20
21
  (0, class_validator_1.IsOptional)(),
21
- (0, class_validator_1.IsNumberString)(),
22
- __metadata("design:type", String)
22
+ (0, class_transformer_1.Type)(() => Number),
23
+ (0, class_validator_1.IsInt)(),
24
+ (0, class_validator_1.Min)(1),
25
+ (0, class_validator_1.Max)(500),
26
+ __metadata("design:type", Number)
23
27
  ], HistoryQueryDto.prototype, "limit", void 0);
24
28
  __decorate([
25
29
  (0, class_validator_1.IsOptional)(),
26
- (0, class_validator_1.IsNumberString)(),
27
- __metadata("design:type", String)
30
+ (0, class_transformer_1.Type)(() => Number),
31
+ (0, class_validator_1.IsInt)(),
32
+ (0, class_validator_1.Min)(0),
33
+ __metadata("design:type", Number)
28
34
  ], HistoryQueryDto.prototype, "offset", void 0);
29
35
  class HistoryParamsDto {
30
36
  workflowInstanceUuid;
@@ -1,4 +1,4 @@
1
1
  export declare class TimeoutProcessQueryDto {
2
- limit?: string;
2
+ limit?: number;
3
3
  }
4
4
  //# sourceMappingURL=timeout-process.dto.d.ts.map
@@ -10,6 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.TimeoutProcessQueryDto = void 0;
13
+ const class_transformer_1 = require("class-transformer");
13
14
  const class_validator_1 = require("class-validator");
14
15
  class TimeoutProcessQueryDto {
15
16
  limit;
@@ -17,7 +18,10 @@ class TimeoutProcessQueryDto {
17
18
  exports.TimeoutProcessQueryDto = TimeoutProcessQueryDto;
18
19
  __decorate([
19
20
  (0, class_validator_1.IsOptional)(),
20
- (0, class_validator_1.IsNumberString)(),
21
- __metadata("design:type", String)
21
+ (0, class_transformer_1.Type)(() => Number),
22
+ (0, class_validator_1.IsInt)(),
23
+ (0, class_validator_1.Min)(1),
24
+ (0, class_validator_1.Max)(1000),
25
+ __metadata("design:type", Number)
22
26
  ], TimeoutProcessQueryDto.prototype, "limit", void 0);
23
27
  //# sourceMappingURL=timeout-process.dto.js.map
@@ -16,6 +16,7 @@ exports.WorkflowEventController = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const workflow_service_js_1 = require("../services/workflow.service.js");
18
18
  const index_js_1 = require("./dto/index.js");
19
+ const workflow_exception_filter_js_1 = require("../filters/workflow-exception.filter.js");
19
20
  let WorkflowEventController = class WorkflowEventController {
20
21
  workflowService;
21
22
  constructor(workflowService) {
@@ -42,7 +43,8 @@ __decorate([
42
43
  ], WorkflowEventController.prototype, "triggerEvent", null);
43
44
  exports.WorkflowEventController = WorkflowEventController = __decorate([
44
45
  (0, common_1.Controller)("workflows"),
45
- (0, common_1.UsePipes)(new common_1.ValidationPipe({ transform: true, whitelist: true })),
46
+ (0, common_1.UseFilters)(workflow_exception_filter_js_1.WorkflowExceptionFilter),
47
+ (0, common_1.UsePipes)(new common_1.ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true })),
46
48
  __metadata("design:paramtypes", [workflow_service_js_1.WorkflowService])
47
49
  ], WorkflowEventController);
48
50
  //# sourceMappingURL=workflow-event.controller.js.map
@@ -16,6 +16,7 @@ exports.WorkflowInstanceController = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const workflow_service_js_1 = require("../services/workflow.service.js");
18
18
  const index_js_1 = require("./dto/index.js");
19
+ const workflow_exception_filter_js_1 = require("../filters/workflow-exception.filter.js");
19
20
  let WorkflowInstanceController = class WorkflowInstanceController {
20
21
  workflowService;
21
22
  constructor(workflowService) {
@@ -54,7 +55,8 @@ __decorate([
54
55
  ], WorkflowInstanceController.prototype, "getInstance", null);
55
56
  exports.WorkflowInstanceController = WorkflowInstanceController = __decorate([
56
57
  (0, common_1.Controller)("workflows"),
57
- (0, common_1.UsePipes)(new common_1.ValidationPipe({ transform: true, whitelist: true })),
58
+ (0, common_1.UseFilters)(workflow_exception_filter_js_1.WorkflowExceptionFilter),
59
+ (0, common_1.UsePipes)(new common_1.ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true })),
58
60
  __metadata("design:paramtypes", [workflow_service_js_1.WorkflowService])
59
61
  ], WorkflowInstanceController);
60
62
  //# sourceMappingURL=workflow-instance.controller.js.map
@@ -16,6 +16,7 @@ exports.WorkflowQueryController = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const workflow_service_js_1 = require("../services/workflow.service.js");
18
18
  const index_js_1 = require("./dto/index.js");
19
+ const workflow_exception_filter_js_1 = require("../filters/workflow-exception.filter.js");
19
20
  let WorkflowQueryController = class WorkflowQueryController {
20
21
  workflowService;
21
22
  constructor(workflowService) {
@@ -28,8 +29,8 @@ let WorkflowQueryController = class WorkflowQueryController {
28
29
  }
29
30
  async getHistory(params, query) {
30
31
  return this.workflowService.getHistory(params.workflowInstanceUuid, {
31
- limit: query.limit ? parseInt(query.limit, 10) : undefined,
32
- offset: query.offset ? parseInt(query.offset, 10) : undefined,
32
+ limit: query.limit,
33
+ offset: query.offset,
33
34
  });
34
35
  }
35
36
  };
@@ -52,7 +53,8 @@ __decorate([
52
53
  ], WorkflowQueryController.prototype, "getHistory", null);
53
54
  exports.WorkflowQueryController = WorkflowQueryController = __decorate([
54
55
  (0, common_1.Controller)("workflows"),
55
- (0, common_1.UsePipes)(new common_1.ValidationPipe({ transform: true, whitelist: true })),
56
+ (0, common_1.UseFilters)(workflow_exception_filter_js_1.WorkflowExceptionFilter),
57
+ (0, common_1.UsePipes)(new common_1.ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true })),
56
58
  __metadata("design:paramtypes", [workflow_service_js_1.WorkflowService])
57
59
  ], WorkflowQueryController);
58
60
  //# sourceMappingURL=workflow-query.controller.js.map
@@ -16,13 +16,14 @@ exports.WorkflowTimeoutController = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const workflow_timeout_service_js_1 = require("../services/workflow-timeout.service.js");
18
18
  const index_js_1 = require("./dto/index.js");
19
+ const workflow_exception_filter_js_1 = require("../filters/workflow-exception.filter.js");
19
20
  let WorkflowTimeoutController = class WorkflowTimeoutController {
20
21
  timeoutService;
21
22
  constructor(timeoutService) {
22
23
  this.timeoutService = timeoutService;
23
24
  }
24
25
  async processExpired(query) {
25
- return this.timeoutService.processExpiredWorkflows(query.limit ? parseInt(query.limit, 10) : undefined);
26
+ return this.timeoutService.processExpiredWorkflows(query.limit);
26
27
  }
27
28
  };
28
29
  exports.WorkflowTimeoutController = WorkflowTimeoutController;
@@ -35,7 +36,8 @@ __decorate([
35
36
  ], WorkflowTimeoutController.prototype, "processExpired", null);
36
37
  exports.WorkflowTimeoutController = WorkflowTimeoutController = __decorate([
37
38
  (0, common_1.Controller)("workflows/timeouts"),
38
- (0, common_1.UsePipes)(new common_1.ValidationPipe({ transform: true, whitelist: true })),
39
+ (0, common_1.UseFilters)(workflow_exception_filter_js_1.WorkflowExceptionFilter),
40
+ (0, common_1.UsePipes)(new common_1.ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true })),
39
41
  __metadata("design:paramtypes", [workflow_timeout_service_js_1.WorkflowTimeoutService])
40
42
  ], WorkflowTimeoutController);
41
43
  //# sourceMappingURL=workflow-timeout.controller.js.map
@@ -0,0 +1,13 @@
1
+ import { type ArgumentsHost, type ExceptionFilter } from "@nestjs/common";
2
+ import { WorkflowError } from "@duraflows/core";
3
+ /**
4
+ * Maps duraflows domain errors to HTTP statuses for the optional REST
5
+ * controllers. Without this filter, a missing instance or an event invalid
6
+ * for the current state surfaces as a generic 500 and leaks internal
7
+ * messages.
8
+ */
9
+ export declare class WorkflowExceptionFilter implements ExceptionFilter {
10
+ private readonly logger;
11
+ catch(exception: WorkflowError, host: ArgumentsHost): void;
12
+ }
13
+ //# sourceMappingURL=workflow-exception.filter.d.ts.map
@@ -0,0 +1,58 @@
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 WorkflowExceptionFilter_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.WorkflowExceptionFilter = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const core_1 = require("@duraflows/core");
13
+ /**
14
+ * Maps duraflows domain errors to HTTP statuses for the optional REST
15
+ * controllers. Without this filter, a missing instance or an event invalid
16
+ * for the current state surfaces as a generic 500 and leaks internal
17
+ * messages.
18
+ */
19
+ let WorkflowExceptionFilter = WorkflowExceptionFilter_1 = class WorkflowExceptionFilter {
20
+ logger = new common_1.Logger(WorkflowExceptionFilter_1.name);
21
+ catch(exception, host) {
22
+ const response = host.switchToHttp().getResponse();
23
+ if (exception instanceof core_1.WorkflowInstanceNotFoundError) {
24
+ response.status(common_1.HttpStatus.NOT_FOUND).send({
25
+ statusCode: common_1.HttpStatus.NOT_FOUND,
26
+ error: "Not Found",
27
+ message: exception.message,
28
+ });
29
+ return;
30
+ }
31
+ if (exception instanceof core_1.InvalidEventError) {
32
+ response.status(common_1.HttpStatus.CONFLICT).send({
33
+ statusCode: common_1.HttpStatus.CONFLICT,
34
+ error: "Conflict",
35
+ message: exception.message,
36
+ });
37
+ return;
38
+ }
39
+ // The response body is sanitized, so log the real cause here — otherwise
40
+ // unmapped domain errors (e.g. optimistic-locking conflicts under
41
+ // concurrency) become undiagnosable silent 500s. `WorkflowError` wraps the
42
+ // originating DI/persistence failure as `cause` (e.g. NestCommandRegistry
43
+ // attaches the container error), so prefer the cause's message and stack —
44
+ // the wrapper alone hides the underlying failure from operators.
45
+ const cause = exception.cause instanceof Error ? exception.cause : undefined;
46
+ this.logger.error(cause ? `${exception.message} (cause: ${cause.message})` : exception.message, (cause ?? exception).stack);
47
+ response.status(common_1.HttpStatus.INTERNAL_SERVER_ERROR).send({
48
+ statusCode: common_1.HttpStatus.INTERNAL_SERVER_ERROR,
49
+ error: "Internal Server Error",
50
+ message: "Internal server error",
51
+ });
52
+ }
53
+ };
54
+ exports.WorkflowExceptionFilter = WorkflowExceptionFilter;
55
+ exports.WorkflowExceptionFilter = WorkflowExceptionFilter = WorkflowExceptionFilter_1 = __decorate([
56
+ (0, common_1.Catch)(core_1.WorkflowError)
57
+ ], WorkflowExceptionFilter);
58
+ //# sourceMappingURL=workflow-exception.filter.js.map
@@ -10,9 +10,10 @@ export { WorkflowInstanceController } from "./controllers/workflow-instance.cont
10
10
  export { WorkflowEventController } from "./controllers/workflow-event.controller.js";
11
11
  export { WorkflowQueryController } from "./controllers/workflow-query.controller.js";
12
12
  export { WorkflowTimeoutController } from "./controllers/workflow-timeout.controller.js";
13
+ export { WorkflowExceptionFilter } from "./filters/workflow-exception.filter.js";
13
14
  export { TriggerEventDto, TriggerEventParamsDto, HistoryQueryDto, HistoryParamsDto, AvailableEventsParamsDto, TimeoutProcessQueryDto, CreateInstanceDto, } from "./controllers/dto/index.js";
14
15
  export type { WorkflowDefinition, WorkflowStateDefinition, WorkflowEventDefinition, WorkflowOnEnterDefinition, WorkflowCommandRef, WorkflowTimeoutDefinition, CommandResult, WorkflowExecutionContext, WorkflowInstance, WorkflowExecutionResult, AvailableWorkflowEvent, CreateWorkflowInstanceInput, TriggerWorkflowEventInput, ProcessExpiredWorkflowsInput, ProcessExpiredWorkflowsResult, GetAvailableEventsInput, WorkflowInstanceStore, WorkflowHistoryStore, WorkflowHistoryRecord, WorkflowTransactionRunner, WorkflowClock, WorkflowPersistenceProvider, WorkflowDefinitionRegistry, WorkflowCommandRegistry, ValidationResult, ValidationError, CompiledWorkflow, OnEnterChainResult, OnEnterHopResult, WorkflowRuntimeClient, } from "@duraflows/core";
15
- export { WorkflowError, WorkflowDefinitionError, InvalidEventError, CommandFailureError, OnEnterDepthExceededError, WorkflowValidator, WorkflowCompiler, CommandExecutor, EventExecutor, OnEnterExecutor, TimeoutResolver, WorkflowRuntime, WorkflowHandle, InMemoryDefinitionRegistry, InMemoryCommandRegistry, InMemoryGuardRegistry, } from "@duraflows/core";
16
+ export { WorkflowError, WorkflowDefinitionError, WorkflowInstanceNotFoundError, InvalidEventError, CommandFailureError, OnEnterDepthExceededError, WorkflowValidator, WorkflowCompiler, CommandExecutor, EventExecutor, OnEnterExecutor, TimeoutResolver, WorkflowRuntime, WorkflowHandle, InMemoryDefinitionRegistry, InMemoryCommandRegistry, InMemoryGuardRegistry, } from "@duraflows/core";
16
17
  export type { WorkflowObserver, StateEnterEvent, ObserverErrorHandler } from "@duraflows/core";
17
18
  export type { WorkflowGuard, WorkflowGuardRef, WorkflowGuardRegistry } from "@duraflows/core";
18
19
  //# sourceMappingURL=index.d.ts.map
package/dist/cjs/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InMemoryGuardRegistry = exports.InMemoryCommandRegistry = exports.InMemoryDefinitionRegistry = exports.WorkflowHandle = exports.WorkflowRuntime = exports.TimeoutResolver = exports.OnEnterExecutor = exports.EventExecutor = exports.CommandExecutor = exports.WorkflowCompiler = exports.WorkflowValidator = exports.OnEnterDepthExceededError = exports.CommandFailureError = exports.InvalidEventError = exports.WorkflowDefinitionError = exports.WorkflowError = exports.CreateInstanceDto = exports.TimeoutProcessQueryDto = exports.AvailableEventsParamsDto = exports.HistoryParamsDto = exports.HistoryQueryDto = exports.TriggerEventParamsDto = exports.TriggerEventDto = exports.WorkflowTimeoutController = exports.WorkflowQueryController = exports.WorkflowEventController = exports.WorkflowInstanceController = exports.WORKFLOW_COMMAND_METADATA_KEY = exports.WorkflowCommand = exports.NestCommandRegistry = exports.WORKFLOW_CLOCK = exports.WORKFLOW_TRANSACTION_RUNNER = exports.WORKFLOW_DEFINITION_REGISTRY = exports.WORKFLOW_GUARD_REGISTRY = exports.WORKFLOW_COMMAND_REGISTRY = exports.WORKFLOW_HISTORY_STORE = exports.WORKFLOW_INSTANCE_STORE = exports.WORKFLOW_RUNTIME = exports.WorkflowTimeoutService = exports.WorkflowService = exports.WorkflowModule = void 0;
3
+ exports.InMemoryGuardRegistry = exports.InMemoryCommandRegistry = exports.InMemoryDefinitionRegistry = exports.WorkflowHandle = exports.WorkflowRuntime = exports.TimeoutResolver = exports.OnEnterExecutor = exports.EventExecutor = exports.CommandExecutor = exports.WorkflowCompiler = exports.WorkflowValidator = exports.OnEnterDepthExceededError = exports.CommandFailureError = exports.InvalidEventError = exports.WorkflowInstanceNotFoundError = exports.WorkflowDefinitionError = exports.WorkflowError = exports.CreateInstanceDto = exports.TimeoutProcessQueryDto = exports.AvailableEventsParamsDto = exports.HistoryParamsDto = exports.HistoryQueryDto = exports.TriggerEventParamsDto = exports.TriggerEventDto = exports.WorkflowExceptionFilter = exports.WorkflowTimeoutController = exports.WorkflowQueryController = exports.WorkflowEventController = exports.WorkflowInstanceController = exports.WORKFLOW_COMMAND_METADATA_KEY = exports.WorkflowCommand = exports.NestCommandRegistry = exports.WORKFLOW_CLOCK = exports.WORKFLOW_TRANSACTION_RUNNER = exports.WORKFLOW_DEFINITION_REGISTRY = exports.WORKFLOW_GUARD_REGISTRY = exports.WORKFLOW_COMMAND_REGISTRY = exports.WORKFLOW_HISTORY_STORE = exports.WORKFLOW_INSTANCE_STORE = exports.WORKFLOW_RUNTIME = exports.WorkflowTimeoutService = exports.WorkflowService = exports.WorkflowModule = void 0;
4
4
  // Module
5
5
  var workflow_module_js_1 = require("./workflow.module.js");
6
6
  Object.defineProperty(exports, "WorkflowModule", { enumerable: true, get: function () { return workflow_module_js_1.WorkflowModule; } });
@@ -34,6 +34,9 @@ var workflow_query_controller_js_1 = require("./controllers/workflow-query.contr
34
34
  Object.defineProperty(exports, "WorkflowQueryController", { enumerable: true, get: function () { return workflow_query_controller_js_1.WorkflowQueryController; } });
35
35
  var workflow_timeout_controller_js_1 = require("./controllers/workflow-timeout.controller.js");
36
36
  Object.defineProperty(exports, "WorkflowTimeoutController", { enumerable: true, get: function () { return workflow_timeout_controller_js_1.WorkflowTimeoutController; } });
37
+ // Filters
38
+ var workflow_exception_filter_js_1 = require("./filters/workflow-exception.filter.js");
39
+ Object.defineProperty(exports, "WorkflowExceptionFilter", { enumerable: true, get: function () { return workflow_exception_filter_js_1.WorkflowExceptionFilter; } });
37
40
  // DTOs
38
41
  var index_js_1 = require("./controllers/dto/index.js");
39
42
  Object.defineProperty(exports, "TriggerEventDto", { enumerable: true, get: function () { return index_js_1.TriggerEventDto; } });
@@ -46,6 +49,7 @@ Object.defineProperty(exports, "CreateInstanceDto", { enumerable: true, get: fun
46
49
  var core_1 = require("@duraflows/core");
47
50
  Object.defineProperty(exports, "WorkflowError", { enumerable: true, get: function () { return core_1.WorkflowError; } });
48
51
  Object.defineProperty(exports, "WorkflowDefinitionError", { enumerable: true, get: function () { return core_1.WorkflowDefinitionError; } });
52
+ Object.defineProperty(exports, "WorkflowInstanceNotFoundError", { enumerable: true, get: function () { return core_1.WorkflowInstanceNotFoundError; } });
49
53
  Object.defineProperty(exports, "InvalidEventError", { enumerable: true, get: function () { return core_1.InvalidEventError; } });
50
54
  Object.defineProperty(exports, "CommandFailureError", { enumerable: true, get: function () { return core_1.CommandFailureError; } });
51
55
  Object.defineProperty(exports, "OnEnterDepthExceededError", { enumerable: true, get: function () { return core_1.OnEnterDepthExceededError; } });
@@ -19,7 +19,24 @@ class NestCommandRegistry {
19
19
  if (!cls) {
20
20
  throw new core_1.WorkflowError(`Command "${name}" not found in registry`);
21
21
  }
22
- return this.moduleRef.get(cls, { strict: false });
22
+ try {
23
+ return this.moduleRef.get(cls, { strict: false });
24
+ }
25
+ catch (error) {
26
+ // `ModuleRef.get()` fails for many reasons — a missing provider, a
27
+ // circular dependency, a throwing constructor — not only unsupported
28
+ // scopes. Wrap every failure with a generic resolution message and only
29
+ // add the singleton-scope hint when the underlying error is Nest's
30
+ // scoped-provider rejection, so the guidance is never misleading. The
31
+ // original error is attached as the cause either way.
32
+ let message = `Command "${name}" could not be resolved from the NestJS container.`;
33
+ if (isScopedProviderError(error)) {
34
+ message +=
35
+ ` Workflow command providers must be singleton-scoped (the default); ` +
36
+ `REQUEST- and TRANSIENT-scoped providers are not supported.`;
37
+ }
38
+ throw new core_1.WorkflowError(message, error);
39
+ }
23
40
  }
24
41
  has(name) {
25
42
  return this.registrations.has(name);
@@ -29,4 +46,16 @@ class NestCommandRegistry {
29
46
  }
30
47
  }
31
48
  exports.NestCommandRegistry = NestCommandRegistry;
49
+ /**
50
+ * Nest throws `InvalidClassScopeException` (message: "... is marked as a scoped
51
+ * provider ...") when `get()` is called on a REQUEST/TRANSIENT provider. That
52
+ * class is not part of Nest's public API, so match on the constructor name with
53
+ * a message-content fallback to survive version drift.
54
+ */
55
+ function isScopedProviderError(error) {
56
+ if (!(error instanceof Error)) {
57
+ return false;
58
+ }
59
+ return error.constructor.name === "InvalidClassScopeException" || /scoped provider/i.test(error.message);
60
+ }
32
61
  //# sourceMappingURL=nest-command-registry.js.map
@@ -1,10 +1,31 @@
1
1
  import { WorkflowHandle } from "@duraflows/core";
2
- import type { WorkflowRuntime, CreateWorkflowInstanceInput, TriggerWorkflowEventInput, GetAvailableEventsInput, WorkflowInstance, WorkflowExecutionResult, AvailableWorkflowEvent, WorkflowHistoryRecord } from "@duraflows/core";
2
+ import type { WorkflowRuntime, CreateWorkflowInstanceInput, TriggerWorkflowEventInput, GetAvailableEventsInput, WorkflowDefinition, WorkflowInstance, WorkflowExecutionResult, AvailableWorkflowEvent, WorkflowHistoryRecord } from "@duraflows/core";
3
3
  export declare class WorkflowService {
4
4
  private readonly runtime;
5
5
  constructor(runtime: WorkflowRuntime);
6
6
  createInstance(input: CreateWorkflowInstanceInput): Promise<WorkflowInstance>;
7
+ /**
8
+ * Type-safe variant of {@link createInstance} that binds the resulting
9
+ * instance's `currentState` to the state union of the supplied
10
+ * `WorkflowDefinition`. Use when the caller has a typed definition in
11
+ * hand and wants to avoid widening `currentState` to `string`.
12
+ *
13
+ * The `workflowName` is read from the definition; callers pass only the
14
+ * non-name portion of `CreateWorkflowInstanceInput`.
15
+ */
16
+ createInstanceFor<TState extends string>(definition: WorkflowDefinition<TState>, input?: Omit<CreateWorkflowInstanceInput, "workflowName">): Promise<WorkflowInstance<TState>>;
7
17
  triggerEvent(input: TriggerWorkflowEventInput): Promise<WorkflowExecutionResult>;
18
+ /**
19
+ * Type-safe variant of {@link triggerEvent} that narrows the resulting
20
+ * `fromState`/`toState` to the state union of the supplied
21
+ * `WorkflowDefinition`. Use when the caller has a typed definition in
22
+ * hand and wants to avoid widening to `string`.
23
+ *
24
+ * Does not validate at runtime that the instance belongs to the given
25
+ * definition — that is the caller's responsibility (typically enforced
26
+ * upstream when the instance was created via `createInstanceFor`).
27
+ */
28
+ triggerEventFor<TState extends string>(definition: WorkflowDefinition<TState>, input: TriggerWorkflowEventInput): Promise<WorkflowExecutionResult<TState>>;
8
29
  getAvailableEvents(input: GetAvailableEventsInput): Promise<AvailableWorkflowEvent[]>;
9
30
  getInstance(uuid: string): Promise<WorkflowInstance | null>;
10
31
  getHistory(workflowInstanceUuid: string, options?: {
@@ -24,9 +24,44 @@ let WorkflowService = class WorkflowService {
24
24
  async createInstance(input) {
25
25
  return this.runtime.createInstance(input);
26
26
  }
27
+ /**
28
+ * Type-safe variant of {@link createInstance} that binds the resulting
29
+ * instance's `currentState` to the state union of the supplied
30
+ * `WorkflowDefinition`. Use when the caller has a typed definition in
31
+ * hand and wants to avoid widening `currentState` to `string`.
32
+ *
33
+ * The `workflowName` is read from the definition; callers pass only the
34
+ * non-name portion of `CreateWorkflowInstanceInput`.
35
+ */
36
+ async createInstanceFor(definition, input = {}) {
37
+ const instance = await this.runtime.createInstance({
38
+ ...input,
39
+ workflowName: definition.name,
40
+ });
41
+ // The runtime initialises `currentState` from `definition.initialState`,
42
+ // which is `TState`. Narrowing here is justified by that invariant.
43
+ return instance;
44
+ }
27
45
  async triggerEvent(input) {
28
46
  return this.runtime.triggerEvent(input);
29
47
  }
48
+ /**
49
+ * Type-safe variant of {@link triggerEvent} that narrows the resulting
50
+ * `fromState`/`toState` to the state union of the supplied
51
+ * `WorkflowDefinition`. Use when the caller has a typed definition in
52
+ * hand and wants to avoid widening to `string`.
53
+ *
54
+ * Does not validate at runtime that the instance belongs to the given
55
+ * definition — that is the caller's responsibility (typically enforced
56
+ * upstream when the instance was created via `createInstanceFor`).
57
+ */
58
+ async triggerEventFor(definition, input) {
59
+ // `definition` is consumed at type-level only; the runtime resolves
60
+ // the instance by uuid and reads its workflowName from the live row.
61
+ void definition;
62
+ const result = await this.runtime.triggerEvent(input);
63
+ return result;
64
+ }
30
65
  async getAvailableEvents(input) {
31
66
  return this.runtime.getAvailableEvents(input);
32
67
  }
@@ -1,4 +1,4 @@
1
- import { type DynamicModule, type Type, type InjectionToken } from "@nestjs/common";
1
+ import { type DynamicModule, type Type, type InjectionToken, type OptionalFactoryDependency } from "@nestjs/common";
2
2
  import type { WorkflowDefinition, WorkflowClock, WorkflowPersistenceProvider, WorkflowObserver, ObserverErrorHandler, WorkflowGuard, WorkflowGuardRegistry } from "@duraflows/core";
3
3
  import { type WorkflowCommandRegistration } from "./providers/nest-command-registry.js";
4
4
  export interface WorkflowModuleOptions {
@@ -26,7 +26,14 @@ export interface WorkflowModuleAsyncOptions<TArgs extends unknown[] = unknown[]>
26
26
  commands?: WorkflowCommandRegistration[];
27
27
  enableControllers?: boolean;
28
28
  useFactory: (...args: TArgs) => Promise<WorkflowModuleFactoryConfig> | WorkflowModuleFactoryConfig;
29
- inject?: InjectionToken[];
29
+ /**
30
+ * DI tokens resolved and passed to `useFactory`, positionally. The tuple
31
+ * length is type-checked against the factory's parameter list when `TArgs`
32
+ * is supplied (e.g. `forRootAsync<[Pool, AuditObserver]>({ ... })`).
33
+ */
34
+ inject?: {
35
+ [K in keyof TArgs]: InjectionToken | OptionalFactoryDependency;
36
+ };
30
37
  }
31
38
  export declare class WorkflowModule {
32
39
  static forRoot(options: WorkflowModuleOptions): DynamicModule;
@@ -20,6 +20,12 @@ const workflow_event_controller_js_1 = require("./controllers/workflow-event.con
20
20
  const workflow_query_controller_js_1 = require("./controllers/workflow-query.controller.js");
21
21
  const workflow_timeout_controller_js_1 = require("./controllers/workflow-timeout.controller.js");
22
22
  const workflow_instance_controller_js_1 = require("./controllers/workflow-instance.controller.js");
23
+ // Surfaces non-fatal definition warnings (e.g. unreachable states) that the
24
+ // registry would otherwise compute and discard.
25
+ const validationLogger = new common_1.Logger("WorkflowModule");
26
+ const logValidationWarning = (workflowName, warning) => {
27
+ validationLogger.warn(`Workflow "${workflowName}": ${warning.message} (${warning.path})`);
28
+ };
23
29
  const EXPORTED_TOKENS = [
24
30
  workflow_service_js_1.WorkflowService,
25
31
  workflow_timeout_service_js_1.WorkflowTimeoutService,
@@ -56,10 +62,9 @@ let WorkflowModule = WorkflowModule_1 = class WorkflowModule {
56
62
  },
57
63
  {
58
64
  provide: injection_tokens_js_1.WORKFLOW_GUARD_REGISTRY,
65
+ // guards/guardRegistry mutual exclusion is enforced synchronously at
66
+ // the top of forRoot, before this factory can ever run.
59
67
  useFactory: () => {
60
- if (options.guardRegistry && options.guards && options.guards.length > 0) {
61
- throw new Error("WorkflowModule: cannot supply both `guards` and `guardRegistry` — they are mutually exclusive. Pass guards or a custom registry, not both.");
62
- }
63
68
  if (options.guardRegistry)
64
69
  return options.guardRegistry;
65
70
  const registry = new core_2.InMemoryGuardRegistry();
@@ -81,6 +86,7 @@ let WorkflowModule = WorkflowModule_1 = class WorkflowModule {
81
86
  validator: new core_2.WorkflowValidator(),
82
87
  compiler: new core_2.WorkflowCompiler(),
83
88
  validationOptions: { knownCommandNames, knownGuardNames },
89
+ onValidationWarning: logValidationWarning,
84
90
  });
85
91
  for (const workflow of options.workflows) {
86
92
  registry.register(workflow);
@@ -133,6 +139,7 @@ let WorkflowModule = WorkflowModule_1 = class WorkflowModule {
133
139
  ];
134
140
  return {
135
141
  module: WorkflowModule_1,
142
+ global: true, // allow downstream feature modules to inject WorkflowService without re-importing forRoot
136
143
  imports: [core_1.DiscoveryModule],
137
144
  controllers,
138
145
  providers,
@@ -148,7 +155,7 @@ let WorkflowModule = WorkflowModule_1 = class WorkflowModule {
148
155
  const configProvider = {
149
156
  provide: "WORKFLOW_MODULE_OPTIONS",
150
157
  useFactory: options.useFactory,
151
- inject: options.inject ?? [],
158
+ inject: (options.inject ?? []),
152
159
  };
153
160
  const providers = [
154
161
  configProvider,
@@ -190,6 +197,7 @@ let WorkflowModule = WorkflowModule_1 = class WorkflowModule {
190
197
  validator: new core_2.WorkflowValidator(),
191
198
  compiler: new core_2.WorkflowCompiler(),
192
199
  validationOptions: { knownCommandNames, knownGuardNames },
200
+ onValidationWarning: logValidationWarning,
193
201
  });
194
202
  for (const workflow of config.workflows) {
195
203
  registry.register(workflow);
@@ -247,6 +255,7 @@ let WorkflowModule = WorkflowModule_1 = class WorkflowModule {
247
255
  ];
248
256
  return {
249
257
  module: WorkflowModule_1,
258
+ global: true, // allow downstream feature modules to inject WorkflowService without re-importing forRootAsync
250
259
  imports: [core_1.DiscoveryModule, ...(options.imports ?? [])],
251
260
  controllers,
252
261
  providers,
@@ -1,6 +1,6 @@
1
1
  export declare class HistoryQueryDto {
2
- limit?: string;
3
- offset?: string;
2
+ limit?: number;
3
+ offset?: number;
4
4
  }
5
5
  export declare class HistoryParamsDto {
6
6
  workflowInstanceUuid: string;
@@ -7,20 +7,26 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { IsOptional, IsNumberString, IsUUID } from "class-validator";
10
+ import { Type } from "class-transformer";
11
+ import { IsInt, IsOptional, IsUUID, Max, Min } from "class-validator";
11
12
  export class HistoryQueryDto {
12
13
  limit;
13
14
  offset;
14
15
  }
15
16
  __decorate([
16
17
  IsOptional(),
17
- IsNumberString(),
18
- __metadata("design:type", String)
18
+ Type(() => Number),
19
+ IsInt(),
20
+ Min(1),
21
+ Max(500),
22
+ __metadata("design:type", Number)
19
23
  ], HistoryQueryDto.prototype, "limit", void 0);
20
24
  __decorate([
21
25
  IsOptional(),
22
- IsNumberString(),
23
- __metadata("design:type", String)
26
+ Type(() => Number),
27
+ IsInt(),
28
+ Min(0),
29
+ __metadata("design:type", Number)
24
30
  ], HistoryQueryDto.prototype, "offset", void 0);
25
31
  export class HistoryParamsDto {
26
32
  workflowInstanceUuid;
@@ -1,4 +1,4 @@
1
1
  export declare class TimeoutProcessQueryDto {
2
- limit?: string;
2
+ limit?: number;
3
3
  }
4
4
  //# sourceMappingURL=timeout-process.dto.d.ts.map
@@ -7,13 +7,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { IsOptional, IsNumberString } from "class-validator";
10
+ import { Type } from "class-transformer";
11
+ import { IsInt, IsOptional, Max, Min } from "class-validator";
11
12
  export class TimeoutProcessQueryDto {
12
13
  limit;
13
14
  }
14
15
  __decorate([
15
16
  IsOptional(),
16
- IsNumberString(),
17
- __metadata("design:type", String)
17
+ Type(() => Number),
18
+ IsInt(),
19
+ Min(1),
20
+ Max(1000),
21
+ __metadata("design:type", Number)
18
22
  ], TimeoutProcessQueryDto.prototype, "limit", void 0);
19
23
  //# sourceMappingURL=timeout-process.dto.js.map