@duraflows/nestjs 3.1.0 → 4.0.1

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.
@@ -36,6 +36,14 @@ let WorkflowExceptionFilter = WorkflowExceptionFilter_1 = class WorkflowExceptio
36
36
  });
37
37
  return;
38
38
  }
39
+ if (exception instanceof core_1.InvalidArgumentError) {
40
+ response.status(common_1.HttpStatus.BAD_REQUEST).send({
41
+ statusCode: common_1.HttpStatus.BAD_REQUEST,
42
+ error: "Bad Request",
43
+ message: exception.message,
44
+ });
45
+ return;
46
+ }
39
47
  // The response body is sanitized, so log the real cause here — otherwise
40
48
  // unmapped domain errors (e.g. optimistic-locking conflicts under
41
49
  // concurrency) become undiagnosable silent 500s. `WorkflowError` wraps the
@@ -6,4 +6,11 @@ export declare const WORKFLOW_DEFINITION_REGISTRY: unique symbol;
6
6
  export declare const WORKFLOW_TRANSACTION_RUNNER: unique symbol;
7
7
  export declare const WORKFLOW_CLOCK: unique symbol;
8
8
  export declare const WORKFLOW_GUARD_REGISTRY: unique symbol;
9
+ /**
10
+ * The resolved module configuration. Deliberately not re-exported from the
11
+ * package barrel: it exists so `forRoot` and `forRootAsync` can publish their
12
+ * config under one token and share a single provider graph, not as a supported
13
+ * extension point.
14
+ */
15
+ export declare const WORKFLOW_MODULE_OPTIONS: unique symbol;
9
16
  //# sourceMappingURL=injection-tokens.d.ts.map
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WORKFLOW_GUARD_REGISTRY = exports.WORKFLOW_CLOCK = exports.WORKFLOW_TRANSACTION_RUNNER = exports.WORKFLOW_DEFINITION_REGISTRY = exports.WORKFLOW_COMMAND_REGISTRY = exports.WORKFLOW_HISTORY_STORE = exports.WORKFLOW_INSTANCE_STORE = exports.WORKFLOW_RUNTIME = void 0;
3
+ exports.WORKFLOW_MODULE_OPTIONS = exports.WORKFLOW_GUARD_REGISTRY = exports.WORKFLOW_CLOCK = exports.WORKFLOW_TRANSACTION_RUNNER = exports.WORKFLOW_DEFINITION_REGISTRY = exports.WORKFLOW_COMMAND_REGISTRY = exports.WORKFLOW_HISTORY_STORE = exports.WORKFLOW_INSTANCE_STORE = exports.WORKFLOW_RUNTIME = void 0;
4
4
  exports.WORKFLOW_RUNTIME = Symbol("WORKFLOW_RUNTIME");
5
5
  exports.WORKFLOW_INSTANCE_STORE = Symbol("WORKFLOW_INSTANCE_STORE");
6
6
  exports.WORKFLOW_HISTORY_STORE = Symbol("WORKFLOW_HISTORY_STORE");
@@ -9,4 +9,11 @@ exports.WORKFLOW_DEFINITION_REGISTRY = Symbol("WORKFLOW_DEFINITION_REGISTRY");
9
9
  exports.WORKFLOW_TRANSACTION_RUNNER = Symbol("WORKFLOW_TRANSACTION_RUNNER");
10
10
  exports.WORKFLOW_CLOCK = Symbol("WORKFLOW_CLOCK");
11
11
  exports.WORKFLOW_GUARD_REGISTRY = Symbol("WORKFLOW_GUARD_REGISTRY");
12
+ /**
13
+ * The resolved module configuration. Deliberately not re-exported from the
14
+ * package barrel: it exists so `forRoot` and `forRootAsync` can publish their
15
+ * config under one token and share a single provider graph, not as a supported
16
+ * extension point.
17
+ */
18
+ exports.WORKFLOW_MODULE_OPTIONS = Symbol("WORKFLOW_MODULE_OPTIONS");
12
19
  //# sourceMappingURL=injection-tokens.js.map
@@ -56,9 +56,9 @@ let WorkflowService = class WorkflowService {
56
56
  * upstream when the instance was created via `createInstanceFor`).
57
57
  */
58
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;
59
+ // `definition` is consumed at type level only -- it is what infers TState.
60
+ // The runtime resolves the instance by uuid and reads its workflowName from
61
+ // the live row, so there is nothing to do with the value at runtime.
62
62
  const result = await this.runtime.triggerEvent(input);
63
63
  return result;
64
64
  }
@@ -38,227 +38,161 @@ const EXPORTED_TOKENS = [
38
38
  injection_tokens_js_1.WORKFLOW_TRANSACTION_RUNNER,
39
39
  injection_tokens_js_1.WORKFLOW_CLOCK,
40
40
  ];
41
+ const CONTROLLERS = [
42
+ workflow_instance_controller_js_1.WorkflowInstanceController,
43
+ workflow_event_controller_js_1.WorkflowEventController,
44
+ workflow_query_controller_js_1.WorkflowQueryController,
45
+ workflow_timeout_controller_js_1.WorkflowTimeoutController,
46
+ ];
47
+ /**
48
+ * `guards` and `guardRegistry` are mutually exclusive: with a custom registry
49
+ * duraflows cannot enumerate guard names, so it can neither merge the array in
50
+ * nor validate definition refs against it.
51
+ *
52
+ * forRoot calls this eagerly, so the error surfaces at module definition.
53
+ * forRootAsync only learns its config once the factory resolves, so it calls
54
+ * this from inside the guard-registry provider instead. Same message, and each
55
+ * entry point keeps the failure timing it has always had.
56
+ */
57
+ function assertGuardConfigExclusive(config) {
58
+ if (config.guardRegistry && config.guards && config.guards.length > 0) {
59
+ throw new Error("WorkflowModule: cannot supply both `guards` and `guardRegistry` — they are mutually exclusive. Pass guards or a custom registry, not both.");
60
+ }
61
+ }
62
+ /**
63
+ * The provider graph shared by both entry points. Every factory reads its
64
+ * configuration from WORKFLOW_MODULE_OPTIONS — supplied as a value by forRoot
65
+ * and as the caller's factory by forRootAsync — so the wiring downstream is
66
+ * identical and cannot drift between the sync and async paths.
67
+ */
68
+ function buildWorkflowProviders(explicitCommands) {
69
+ return [
70
+ ...explicitCommands.map((c) => c.useClass),
71
+ {
72
+ provide: injection_tokens_js_1.WORKFLOW_COMMAND_REGISTRY,
73
+ useFactory: (moduleRef, discoveryService) => {
74
+ const discovered = (0, command_discovery_js_1.discoverDecoratedCommands)(discoveryService);
75
+ const merged = (0, command_discovery_js_1.mergeCommandRegistrations)(explicitCommands, discovered);
76
+ return new nest_command_registry_js_1.NestCommandRegistry(moduleRef, merged);
77
+ },
78
+ inject: [core_1.ModuleRef, core_1.DiscoveryService],
79
+ },
80
+ {
81
+ provide: injection_tokens_js_1.WORKFLOW_GUARD_REGISTRY,
82
+ useFactory: (config) => {
83
+ // Redundant on the forRoot path, which already threw at module
84
+ // definition, but this is forRootAsync's only opportunity to check.
85
+ assertGuardConfigExclusive(config);
86
+ if (config.guardRegistry)
87
+ return config.guardRegistry;
88
+ const registry = new core_2.InMemoryGuardRegistry();
89
+ for (const guard of config.guards ?? []) {
90
+ registry.register(guard.name, guard);
91
+ }
92
+ return registry;
93
+ },
94
+ inject: [injection_tokens_js_1.WORKFLOW_MODULE_OPTIONS],
95
+ },
96
+ {
97
+ provide: injection_tokens_js_1.WORKFLOW_DEFINITION_REGISTRY,
98
+ useFactory: (config, commandRegistry) => {
99
+ const knownCommandNames = commandRegistry.getRegisteredNames();
100
+ // Validate guard refs only when we own the registry (built-in path).
101
+ // With a custom guardRegistry we can't enumerate names, so skip;
102
+ // unresolved refs surface at runtime as WorkflowError.
103
+ const knownGuardNames = config.guardRegistry !== undefined ? undefined : new Set((config.guards ?? []).map((g) => g.name));
104
+ const registry = new core_2.InMemoryDefinitionRegistry({
105
+ validator: new core_2.WorkflowValidator(),
106
+ compiler: new core_2.WorkflowCompiler(),
107
+ validationOptions: { knownCommandNames, knownGuardNames },
108
+ onValidationWarning: logValidationWarning,
109
+ });
110
+ for (const workflow of config.workflows) {
111
+ registry.register(workflow);
112
+ }
113
+ return registry;
114
+ },
115
+ inject: [injection_tokens_js_1.WORKFLOW_MODULE_OPTIONS, injection_tokens_js_1.WORKFLOW_COMMAND_REGISTRY],
116
+ },
117
+ {
118
+ provide: injection_tokens_js_1.WORKFLOW_INSTANCE_STORE,
119
+ useFactory: (config) => config.persistence.instanceStore,
120
+ inject: [injection_tokens_js_1.WORKFLOW_MODULE_OPTIONS],
121
+ },
122
+ {
123
+ provide: injection_tokens_js_1.WORKFLOW_HISTORY_STORE,
124
+ useFactory: (config) => config.persistence.historyStore,
125
+ inject: [injection_tokens_js_1.WORKFLOW_MODULE_OPTIONS],
126
+ },
127
+ {
128
+ provide: injection_tokens_js_1.WORKFLOW_TRANSACTION_RUNNER,
129
+ useFactory: (config) => config.persistence.transactionRunner,
130
+ inject: [injection_tokens_js_1.WORKFLOW_MODULE_OPTIONS],
131
+ },
132
+ {
133
+ provide: injection_tokens_js_1.WORKFLOW_CLOCK,
134
+ useFactory: (config) => config.clock ?? { now: () => new Date() },
135
+ inject: [injection_tokens_js_1.WORKFLOW_MODULE_OPTIONS],
136
+ },
137
+ {
138
+ provide: injection_tokens_js_1.WORKFLOW_RUNTIME,
139
+ useFactory: (config, definitionRegistry, commandRegistry, guardRegistry, instanceStore, historyStore, transactionRunner, clock) => new core_2.WorkflowRuntime({
140
+ definitionRegistry,
141
+ commandRegistry,
142
+ guardRegistry,
143
+ instanceStore,
144
+ historyStore,
145
+ transactionRunner,
146
+ clock,
147
+ observers: config.observers,
148
+ onObserverError: config.onObserverError,
149
+ }),
150
+ inject: [
151
+ injection_tokens_js_1.WORKFLOW_MODULE_OPTIONS,
152
+ injection_tokens_js_1.WORKFLOW_DEFINITION_REGISTRY,
153
+ injection_tokens_js_1.WORKFLOW_COMMAND_REGISTRY,
154
+ injection_tokens_js_1.WORKFLOW_GUARD_REGISTRY,
155
+ injection_tokens_js_1.WORKFLOW_INSTANCE_STORE,
156
+ injection_tokens_js_1.WORKFLOW_HISTORY_STORE,
157
+ injection_tokens_js_1.WORKFLOW_TRANSACTION_RUNNER,
158
+ injection_tokens_js_1.WORKFLOW_CLOCK,
159
+ ],
160
+ },
161
+ workflow_service_js_1.WorkflowService,
162
+ workflow_timeout_service_js_1.WorkflowTimeoutService,
163
+ ];
164
+ }
41
165
  let WorkflowModule = WorkflowModule_1 = class WorkflowModule {
42
166
  static forRoot(options) {
43
- if (options.guardRegistry && options.guards && options.guards.length > 0) {
44
- throw new Error("WorkflowModule: cannot supply both `guards` and `guardRegistry` — they are mutually exclusive. Pass guards or a custom registry, not both.");
45
- }
46
- const controllers = options.enableControllers
47
- ? [workflow_instance_controller_js_1.WorkflowInstanceController, workflow_event_controller_js_1.WorkflowEventController, workflow_query_controller_js_1.WorkflowQueryController, workflow_timeout_controller_js_1.WorkflowTimeoutController]
48
- : [];
49
- const explicitCommands = options.commands ?? [];
50
- const commandClasses = explicitCommands.map((c) => c.useClass);
51
- const clock = options.clock ?? { now: () => new Date() };
52
- const providers = [
53
- ...commandClasses,
54
- {
55
- provide: injection_tokens_js_1.WORKFLOW_COMMAND_REGISTRY,
56
- useFactory: (moduleRef, discoveryService) => {
57
- const discovered = (0, command_discovery_js_1.discoverDecoratedCommands)(discoveryService);
58
- const merged = (0, command_discovery_js_1.mergeCommandRegistrations)(explicitCommands, discovered);
59
- return new nest_command_registry_js_1.NestCommandRegistry(moduleRef, merged);
60
- },
61
- inject: [core_1.ModuleRef, core_1.DiscoveryService],
62
- },
63
- {
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.
67
- useFactory: () => {
68
- if (options.guardRegistry)
69
- return options.guardRegistry;
70
- const registry = new core_2.InMemoryGuardRegistry();
71
- for (const guard of options.guards ?? []) {
72
- registry.register(guard.name, guard);
73
- }
74
- return registry;
75
- },
76
- },
77
- {
78
- provide: injection_tokens_js_1.WORKFLOW_DEFINITION_REGISTRY,
79
- useFactory: (commandRegistry) => {
80
- const knownCommandNames = commandRegistry.getRegisteredNames();
81
- // Validate guard refs only when we own the registry (built-in path).
82
- // With a custom guardRegistry we can't enumerate names, so skip;
83
- // unresolved refs surface at runtime as WorkflowError.
84
- const knownGuardNames = options.guardRegistry !== undefined ? undefined : new Set((options.guards ?? []).map((g) => g.name));
85
- const registry = new core_2.InMemoryDefinitionRegistry({
86
- validator: new core_2.WorkflowValidator(),
87
- compiler: new core_2.WorkflowCompiler(),
88
- validationOptions: { knownCommandNames, knownGuardNames },
89
- onValidationWarning: logValidationWarning,
90
- });
91
- for (const workflow of options.workflows) {
92
- registry.register(workflow);
93
- }
94
- return registry;
95
- },
96
- inject: [injection_tokens_js_1.WORKFLOW_COMMAND_REGISTRY],
97
- },
98
- {
99
- provide: injection_tokens_js_1.WORKFLOW_INSTANCE_STORE,
100
- useValue: options.persistence.instanceStore,
101
- },
102
- {
103
- provide: injection_tokens_js_1.WORKFLOW_HISTORY_STORE,
104
- useValue: options.persistence.historyStore,
105
- },
106
- {
107
- provide: injection_tokens_js_1.WORKFLOW_TRANSACTION_RUNNER,
108
- useValue: options.persistence.transactionRunner,
109
- },
110
- {
111
- provide: injection_tokens_js_1.WORKFLOW_CLOCK,
112
- useValue: clock,
113
- },
114
- {
115
- provide: injection_tokens_js_1.WORKFLOW_RUNTIME,
116
- useFactory: (definitionRegistry, commandRegistry, guardRegistry, instanceStore, historyStore, transactionRunner, wfClock) => new core_2.WorkflowRuntime({
117
- definitionRegistry,
118
- commandRegistry,
119
- guardRegistry,
120
- instanceStore,
121
- historyStore,
122
- transactionRunner,
123
- clock: wfClock,
124
- observers: options.observers,
125
- onObserverError: options.onObserverError,
126
- }),
127
- inject: [
128
- injection_tokens_js_1.WORKFLOW_DEFINITION_REGISTRY,
129
- injection_tokens_js_1.WORKFLOW_COMMAND_REGISTRY,
130
- injection_tokens_js_1.WORKFLOW_GUARD_REGISTRY,
131
- injection_tokens_js_1.WORKFLOW_INSTANCE_STORE,
132
- injection_tokens_js_1.WORKFLOW_HISTORY_STORE,
133
- injection_tokens_js_1.WORKFLOW_TRANSACTION_RUNNER,
134
- injection_tokens_js_1.WORKFLOW_CLOCK,
135
- ],
136
- },
137
- workflow_service_js_1.WorkflowService,
138
- workflow_timeout_service_js_1.WorkflowTimeoutService,
139
- ];
167
+ // Eager, so a misconfiguration fails where it was written rather than at
168
+ // container bootstrap.
169
+ assertGuardConfigExclusive(options);
140
170
  return {
141
171
  module: WorkflowModule_1,
142
172
  global: true, // allow downstream feature modules to inject WorkflowService without re-importing forRoot
143
173
  imports: [core_1.DiscoveryModule],
144
- controllers,
145
- providers,
174
+ controllers: options.enableControllers ? [...CONTROLLERS] : [],
175
+ providers: [
176
+ { provide: injection_tokens_js_1.WORKFLOW_MODULE_OPTIONS, useValue: options },
177
+ ...buildWorkflowProviders(options.commands ?? []),
178
+ ],
146
179
  exports: EXPORTED_TOKENS,
147
180
  };
148
181
  }
149
182
  static forRootAsync(options) {
150
- const controllers = options.enableControllers
151
- ? [workflow_instance_controller_js_1.WorkflowInstanceController, workflow_event_controller_js_1.WorkflowEventController, workflow_query_controller_js_1.WorkflowQueryController, workflow_timeout_controller_js_1.WorkflowTimeoutController]
152
- : [];
153
- const explicitCommands = options.commands ?? [];
154
- const commandClasses = explicitCommands.map((c) => c.useClass);
155
- const configProvider = {
156
- provide: "WORKFLOW_MODULE_OPTIONS",
157
- useFactory: options.useFactory,
158
- inject: (options.inject ?? []),
159
- };
160
- const providers = [
161
- configProvider,
162
- ...commandClasses,
163
- {
164
- provide: injection_tokens_js_1.WORKFLOW_COMMAND_REGISTRY,
165
- useFactory: (moduleRef, discoveryService) => {
166
- const discovered = (0, command_discovery_js_1.discoverDecoratedCommands)(discoveryService);
167
- const merged = (0, command_discovery_js_1.mergeCommandRegistrations)(explicitCommands, discovered);
168
- return new nest_command_registry_js_1.NestCommandRegistry(moduleRef, merged);
169
- },
170
- inject: [core_1.ModuleRef, core_1.DiscoveryService],
171
- },
172
- {
173
- provide: injection_tokens_js_1.WORKFLOW_GUARD_REGISTRY,
174
- useFactory: (config) => {
175
- if (config.guardRegistry && config.guards && config.guards.length > 0) {
176
- throw new Error("WorkflowModule: cannot supply both `guards` and `guardRegistry` — they are mutually exclusive. Pass guards or a custom registry, not both.");
177
- }
178
- if (config.guardRegistry)
179
- return config.guardRegistry;
180
- const registry = new core_2.InMemoryGuardRegistry();
181
- for (const guard of config.guards ?? []) {
182
- registry.register(guard.name, guard);
183
- }
184
- return registry;
185
- },
186
- inject: ["WORKFLOW_MODULE_OPTIONS"],
187
- },
188
- {
189
- provide: injection_tokens_js_1.WORKFLOW_DEFINITION_REGISTRY,
190
- useFactory: (config, commandRegistry) => {
191
- const knownCommandNames = commandRegistry.getRegisteredNames();
192
- // Validate guard refs only when we own the registry (built-in path).
193
- // With a custom guardRegistry we can't enumerate names, so skip;
194
- // unresolved refs surface at runtime as WorkflowError.
195
- const knownGuardNames = config.guardRegistry !== undefined ? undefined : new Set((config.guards ?? []).map((g) => g.name));
196
- const registry = new core_2.InMemoryDefinitionRegistry({
197
- validator: new core_2.WorkflowValidator(),
198
- compiler: new core_2.WorkflowCompiler(),
199
- validationOptions: { knownCommandNames, knownGuardNames },
200
- onValidationWarning: logValidationWarning,
201
- });
202
- for (const workflow of config.workflows) {
203
- registry.register(workflow);
204
- }
205
- return registry;
206
- },
207
- inject: ["WORKFLOW_MODULE_OPTIONS", injection_tokens_js_1.WORKFLOW_COMMAND_REGISTRY],
208
- },
209
- {
210
- provide: injection_tokens_js_1.WORKFLOW_INSTANCE_STORE,
211
- useFactory: (config) => config.persistence.instanceStore,
212
- inject: ["WORKFLOW_MODULE_OPTIONS"],
213
- },
214
- {
215
- provide: injection_tokens_js_1.WORKFLOW_HISTORY_STORE,
216
- useFactory: (config) => config.persistence.historyStore,
217
- inject: ["WORKFLOW_MODULE_OPTIONS"],
218
- },
219
- {
220
- provide: injection_tokens_js_1.WORKFLOW_TRANSACTION_RUNNER,
221
- useFactory: (config) => config.persistence.transactionRunner,
222
- inject: ["WORKFLOW_MODULE_OPTIONS"],
223
- },
224
- {
225
- provide: injection_tokens_js_1.WORKFLOW_CLOCK,
226
- useFactory: (config) => config.clock ?? { now: () => new Date() },
227
- inject: ["WORKFLOW_MODULE_OPTIONS"],
228
- },
229
- {
230
- provide: injection_tokens_js_1.WORKFLOW_RUNTIME,
231
- useFactory: (config, definitionRegistry, commandRegistry, guardRegistry, instanceStore, historyStore, transactionRunner, clock) => new core_2.WorkflowRuntime({
232
- definitionRegistry,
233
- commandRegistry,
234
- guardRegistry,
235
- instanceStore,
236
- historyStore,
237
- transactionRunner,
238
- clock,
239
- observers: config.observers,
240
- onObserverError: config.onObserverError,
241
- }),
242
- inject: [
243
- "WORKFLOW_MODULE_OPTIONS",
244
- injection_tokens_js_1.WORKFLOW_DEFINITION_REGISTRY,
245
- injection_tokens_js_1.WORKFLOW_COMMAND_REGISTRY,
246
- injection_tokens_js_1.WORKFLOW_GUARD_REGISTRY,
247
- injection_tokens_js_1.WORKFLOW_INSTANCE_STORE,
248
- injection_tokens_js_1.WORKFLOW_HISTORY_STORE,
249
- injection_tokens_js_1.WORKFLOW_TRANSACTION_RUNNER,
250
- injection_tokens_js_1.WORKFLOW_CLOCK,
251
- ],
252
- },
253
- workflow_service_js_1.WorkflowService,
254
- workflow_timeout_service_js_1.WorkflowTimeoutService,
255
- ];
256
183
  return {
257
184
  module: WorkflowModule_1,
258
185
  global: true, // allow downstream feature modules to inject WorkflowService without re-importing forRootAsync
259
186
  imports: [core_1.DiscoveryModule, ...(options.imports ?? [])],
260
- controllers,
261
- providers,
187
+ controllers: options.enableControllers ? [...CONTROLLERS] : [],
188
+ providers: [
189
+ {
190
+ provide: injection_tokens_js_1.WORKFLOW_MODULE_OPTIONS,
191
+ useFactory: options.useFactory,
192
+ inject: (options.inject ?? []),
193
+ },
194
+ ...buildWorkflowProviders(options.commands ?? []),
195
+ ],
262
196
  exports: EXPORTED_TOKENS,
263
197
  };
264
198
  }
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  var WorkflowExceptionFilter_1;
8
8
  import { Catch, HttpStatus, Logger } from "@nestjs/common";
9
- import { WorkflowError, WorkflowInstanceNotFoundError, InvalidEventError } from "@duraflows/core";
9
+ import { WorkflowError, WorkflowInstanceNotFoundError, InvalidEventError, InvalidArgumentError } from "@duraflows/core";
10
10
  /**
11
11
  * Maps duraflows domain errors to HTTP statuses for the optional REST
12
12
  * controllers. Without this filter, a missing instance or an event invalid
@@ -33,6 +33,14 @@ let WorkflowExceptionFilter = WorkflowExceptionFilter_1 = class WorkflowExceptio
33
33
  });
34
34
  return;
35
35
  }
36
+ if (exception instanceof InvalidArgumentError) {
37
+ response.status(HttpStatus.BAD_REQUEST).send({
38
+ statusCode: HttpStatus.BAD_REQUEST,
39
+ error: "Bad Request",
40
+ message: exception.message,
41
+ });
42
+ return;
43
+ }
36
44
  // The response body is sanitized, so log the real cause here — otherwise
37
45
  // unmapped domain errors (e.g. optimistic-locking conflicts under
38
46
  // concurrency) become undiagnosable silent 500s. `WorkflowError` wraps the
@@ -6,4 +6,11 @@ export declare const WORKFLOW_DEFINITION_REGISTRY: unique symbol;
6
6
  export declare const WORKFLOW_TRANSACTION_RUNNER: unique symbol;
7
7
  export declare const WORKFLOW_CLOCK: unique symbol;
8
8
  export declare const WORKFLOW_GUARD_REGISTRY: unique symbol;
9
+ /**
10
+ * The resolved module configuration. Deliberately not re-exported from the
11
+ * package barrel: it exists so `forRoot` and `forRootAsync` can publish their
12
+ * config under one token and share a single provider graph, not as a supported
13
+ * extension point.
14
+ */
15
+ export declare const WORKFLOW_MODULE_OPTIONS: unique symbol;
9
16
  //# sourceMappingURL=injection-tokens.d.ts.map
@@ -6,4 +6,11 @@ export const WORKFLOW_DEFINITION_REGISTRY = Symbol("WORKFLOW_DEFINITION_REGISTRY
6
6
  export const WORKFLOW_TRANSACTION_RUNNER = Symbol("WORKFLOW_TRANSACTION_RUNNER");
7
7
  export const WORKFLOW_CLOCK = Symbol("WORKFLOW_CLOCK");
8
8
  export const WORKFLOW_GUARD_REGISTRY = Symbol("WORKFLOW_GUARD_REGISTRY");
9
+ /**
10
+ * The resolved module configuration. Deliberately not re-exported from the
11
+ * package barrel: it exists so `forRoot` and `forRootAsync` can publish their
12
+ * config under one token and share a single provider graph, not as a supported
13
+ * extension point.
14
+ */
15
+ export const WORKFLOW_MODULE_OPTIONS = Symbol("WORKFLOW_MODULE_OPTIONS");
9
16
  //# sourceMappingURL=injection-tokens.js.map
@@ -53,9 +53,9 @@ let WorkflowService = class WorkflowService {
53
53
  * upstream when the instance was created via `createInstanceFor`).
54
54
  */
55
55
  async triggerEventFor(definition, input) {
56
- // `definition` is consumed at type-level only; the runtime resolves
57
- // the instance by uuid and reads its workflowName from the live row.
58
- void definition;
56
+ // `definition` is consumed at type level only -- it is what infers TState.
57
+ // The runtime resolves the instance by uuid and reads its workflowName from
58
+ // the live row, so there is nothing to do with the value at runtime.
59
59
  const result = await this.runtime.triggerEvent(input);
60
60
  return result;
61
61
  }
@@ -10,7 +10,7 @@ import { DiscoveryModule, DiscoveryService, ModuleRef } from "@nestjs/core";
10
10
  import { InMemoryDefinitionRegistry, WorkflowRuntime, WorkflowValidator, WorkflowCompiler, InMemoryGuardRegistry, } from "@duraflows/core";
11
11
  import { NestCommandRegistry } from "./providers/nest-command-registry.js";
12
12
  import { discoverDecoratedCommands, mergeCommandRegistrations } from "./providers/command-discovery.js";
13
- import { WORKFLOW_RUNTIME, WORKFLOW_INSTANCE_STORE, WORKFLOW_HISTORY_STORE, WORKFLOW_COMMAND_REGISTRY, WORKFLOW_DEFINITION_REGISTRY, WORKFLOW_TRANSACTION_RUNNER, WORKFLOW_CLOCK, WORKFLOW_GUARD_REGISTRY, } from "./providers/injection-tokens.js";
13
+ import { WORKFLOW_RUNTIME, WORKFLOW_INSTANCE_STORE, WORKFLOW_HISTORY_STORE, WORKFLOW_COMMAND_REGISTRY, WORKFLOW_DEFINITION_REGISTRY, WORKFLOW_TRANSACTION_RUNNER, WORKFLOW_CLOCK, WORKFLOW_GUARD_REGISTRY, WORKFLOW_MODULE_OPTIONS, } from "./providers/injection-tokens.js";
14
14
  import { WorkflowService } from "./services/workflow.service.js";
15
15
  import { WorkflowTimeoutService } from "./services/workflow-timeout.service.js";
16
16
  import { WorkflowEventController } from "./controllers/workflow-event.controller.js";
@@ -35,227 +35,161 @@ const EXPORTED_TOKENS = [
35
35
  WORKFLOW_TRANSACTION_RUNNER,
36
36
  WORKFLOW_CLOCK,
37
37
  ];
38
+ const CONTROLLERS = [
39
+ WorkflowInstanceController,
40
+ WorkflowEventController,
41
+ WorkflowQueryController,
42
+ WorkflowTimeoutController,
43
+ ];
44
+ /**
45
+ * `guards` and `guardRegistry` are mutually exclusive: with a custom registry
46
+ * duraflows cannot enumerate guard names, so it can neither merge the array in
47
+ * nor validate definition refs against it.
48
+ *
49
+ * forRoot calls this eagerly, so the error surfaces at module definition.
50
+ * forRootAsync only learns its config once the factory resolves, so it calls
51
+ * this from inside the guard-registry provider instead. Same message, and each
52
+ * entry point keeps the failure timing it has always had.
53
+ */
54
+ function assertGuardConfigExclusive(config) {
55
+ if (config.guardRegistry && config.guards && config.guards.length > 0) {
56
+ throw new Error("WorkflowModule: cannot supply both `guards` and `guardRegistry` — they are mutually exclusive. Pass guards or a custom registry, not both.");
57
+ }
58
+ }
59
+ /**
60
+ * The provider graph shared by both entry points. Every factory reads its
61
+ * configuration from WORKFLOW_MODULE_OPTIONS — supplied as a value by forRoot
62
+ * and as the caller's factory by forRootAsync — so the wiring downstream is
63
+ * identical and cannot drift between the sync and async paths.
64
+ */
65
+ function buildWorkflowProviders(explicitCommands) {
66
+ return [
67
+ ...explicitCommands.map((c) => c.useClass),
68
+ {
69
+ provide: WORKFLOW_COMMAND_REGISTRY,
70
+ useFactory: (moduleRef, discoveryService) => {
71
+ const discovered = discoverDecoratedCommands(discoveryService);
72
+ const merged = mergeCommandRegistrations(explicitCommands, discovered);
73
+ return new NestCommandRegistry(moduleRef, merged);
74
+ },
75
+ inject: [ModuleRef, DiscoveryService],
76
+ },
77
+ {
78
+ provide: WORKFLOW_GUARD_REGISTRY,
79
+ useFactory: (config) => {
80
+ // Redundant on the forRoot path, which already threw at module
81
+ // definition, but this is forRootAsync's only opportunity to check.
82
+ assertGuardConfigExclusive(config);
83
+ if (config.guardRegistry)
84
+ return config.guardRegistry;
85
+ const registry = new InMemoryGuardRegistry();
86
+ for (const guard of config.guards ?? []) {
87
+ registry.register(guard.name, guard);
88
+ }
89
+ return registry;
90
+ },
91
+ inject: [WORKFLOW_MODULE_OPTIONS],
92
+ },
93
+ {
94
+ provide: WORKFLOW_DEFINITION_REGISTRY,
95
+ useFactory: (config, commandRegistry) => {
96
+ const knownCommandNames = commandRegistry.getRegisteredNames();
97
+ // Validate guard refs only when we own the registry (built-in path).
98
+ // With a custom guardRegistry we can't enumerate names, so skip;
99
+ // unresolved refs surface at runtime as WorkflowError.
100
+ const knownGuardNames = config.guardRegistry !== undefined ? undefined : new Set((config.guards ?? []).map((g) => g.name));
101
+ const registry = new InMemoryDefinitionRegistry({
102
+ validator: new WorkflowValidator(),
103
+ compiler: new WorkflowCompiler(),
104
+ validationOptions: { knownCommandNames, knownGuardNames },
105
+ onValidationWarning: logValidationWarning,
106
+ });
107
+ for (const workflow of config.workflows) {
108
+ registry.register(workflow);
109
+ }
110
+ return registry;
111
+ },
112
+ inject: [WORKFLOW_MODULE_OPTIONS, WORKFLOW_COMMAND_REGISTRY],
113
+ },
114
+ {
115
+ provide: WORKFLOW_INSTANCE_STORE,
116
+ useFactory: (config) => config.persistence.instanceStore,
117
+ inject: [WORKFLOW_MODULE_OPTIONS],
118
+ },
119
+ {
120
+ provide: WORKFLOW_HISTORY_STORE,
121
+ useFactory: (config) => config.persistence.historyStore,
122
+ inject: [WORKFLOW_MODULE_OPTIONS],
123
+ },
124
+ {
125
+ provide: WORKFLOW_TRANSACTION_RUNNER,
126
+ useFactory: (config) => config.persistence.transactionRunner,
127
+ inject: [WORKFLOW_MODULE_OPTIONS],
128
+ },
129
+ {
130
+ provide: WORKFLOW_CLOCK,
131
+ useFactory: (config) => config.clock ?? { now: () => new Date() },
132
+ inject: [WORKFLOW_MODULE_OPTIONS],
133
+ },
134
+ {
135
+ provide: WORKFLOW_RUNTIME,
136
+ useFactory: (config, definitionRegistry, commandRegistry, guardRegistry, instanceStore, historyStore, transactionRunner, clock) => new WorkflowRuntime({
137
+ definitionRegistry,
138
+ commandRegistry,
139
+ guardRegistry,
140
+ instanceStore,
141
+ historyStore,
142
+ transactionRunner,
143
+ clock,
144
+ observers: config.observers,
145
+ onObserverError: config.onObserverError,
146
+ }),
147
+ inject: [
148
+ WORKFLOW_MODULE_OPTIONS,
149
+ WORKFLOW_DEFINITION_REGISTRY,
150
+ WORKFLOW_COMMAND_REGISTRY,
151
+ WORKFLOW_GUARD_REGISTRY,
152
+ WORKFLOW_INSTANCE_STORE,
153
+ WORKFLOW_HISTORY_STORE,
154
+ WORKFLOW_TRANSACTION_RUNNER,
155
+ WORKFLOW_CLOCK,
156
+ ],
157
+ },
158
+ WorkflowService,
159
+ WorkflowTimeoutService,
160
+ ];
161
+ }
38
162
  let WorkflowModule = WorkflowModule_1 = class WorkflowModule {
39
163
  static forRoot(options) {
40
- if (options.guardRegistry && options.guards && options.guards.length > 0) {
41
- throw new Error("WorkflowModule: cannot supply both `guards` and `guardRegistry` — they are mutually exclusive. Pass guards or a custom registry, not both.");
42
- }
43
- const controllers = options.enableControllers
44
- ? [WorkflowInstanceController, WorkflowEventController, WorkflowQueryController, WorkflowTimeoutController]
45
- : [];
46
- const explicitCommands = options.commands ?? [];
47
- const commandClasses = explicitCommands.map((c) => c.useClass);
48
- const clock = options.clock ?? { now: () => new Date() };
49
- const providers = [
50
- ...commandClasses,
51
- {
52
- provide: WORKFLOW_COMMAND_REGISTRY,
53
- useFactory: (moduleRef, discoveryService) => {
54
- const discovered = discoverDecoratedCommands(discoveryService);
55
- const merged = mergeCommandRegistrations(explicitCommands, discovered);
56
- return new NestCommandRegistry(moduleRef, merged);
57
- },
58
- inject: [ModuleRef, DiscoveryService],
59
- },
60
- {
61
- provide: WORKFLOW_GUARD_REGISTRY,
62
- // guards/guardRegistry mutual exclusion is enforced synchronously at
63
- // the top of forRoot, before this factory can ever run.
64
- useFactory: () => {
65
- if (options.guardRegistry)
66
- return options.guardRegistry;
67
- const registry = new InMemoryGuardRegistry();
68
- for (const guard of options.guards ?? []) {
69
- registry.register(guard.name, guard);
70
- }
71
- return registry;
72
- },
73
- },
74
- {
75
- provide: WORKFLOW_DEFINITION_REGISTRY,
76
- useFactory: (commandRegistry) => {
77
- const knownCommandNames = commandRegistry.getRegisteredNames();
78
- // Validate guard refs only when we own the registry (built-in path).
79
- // With a custom guardRegistry we can't enumerate names, so skip;
80
- // unresolved refs surface at runtime as WorkflowError.
81
- const knownGuardNames = options.guardRegistry !== undefined ? undefined : new Set((options.guards ?? []).map((g) => g.name));
82
- const registry = new InMemoryDefinitionRegistry({
83
- validator: new WorkflowValidator(),
84
- compiler: new WorkflowCompiler(),
85
- validationOptions: { knownCommandNames, knownGuardNames },
86
- onValidationWarning: logValidationWarning,
87
- });
88
- for (const workflow of options.workflows) {
89
- registry.register(workflow);
90
- }
91
- return registry;
92
- },
93
- inject: [WORKFLOW_COMMAND_REGISTRY],
94
- },
95
- {
96
- provide: WORKFLOW_INSTANCE_STORE,
97
- useValue: options.persistence.instanceStore,
98
- },
99
- {
100
- provide: WORKFLOW_HISTORY_STORE,
101
- useValue: options.persistence.historyStore,
102
- },
103
- {
104
- provide: WORKFLOW_TRANSACTION_RUNNER,
105
- useValue: options.persistence.transactionRunner,
106
- },
107
- {
108
- provide: WORKFLOW_CLOCK,
109
- useValue: clock,
110
- },
111
- {
112
- provide: WORKFLOW_RUNTIME,
113
- useFactory: (definitionRegistry, commandRegistry, guardRegistry, instanceStore, historyStore, transactionRunner, wfClock) => new WorkflowRuntime({
114
- definitionRegistry,
115
- commandRegistry,
116
- guardRegistry,
117
- instanceStore,
118
- historyStore,
119
- transactionRunner,
120
- clock: wfClock,
121
- observers: options.observers,
122
- onObserverError: options.onObserverError,
123
- }),
124
- inject: [
125
- WORKFLOW_DEFINITION_REGISTRY,
126
- WORKFLOW_COMMAND_REGISTRY,
127
- WORKFLOW_GUARD_REGISTRY,
128
- WORKFLOW_INSTANCE_STORE,
129
- WORKFLOW_HISTORY_STORE,
130
- WORKFLOW_TRANSACTION_RUNNER,
131
- WORKFLOW_CLOCK,
132
- ],
133
- },
134
- WorkflowService,
135
- WorkflowTimeoutService,
136
- ];
164
+ // Eager, so a misconfiguration fails where it was written rather than at
165
+ // container bootstrap.
166
+ assertGuardConfigExclusive(options);
137
167
  return {
138
168
  module: WorkflowModule_1,
139
169
  global: true, // allow downstream feature modules to inject WorkflowService without re-importing forRoot
140
170
  imports: [DiscoveryModule],
141
- controllers,
142
- providers,
171
+ controllers: options.enableControllers ? [...CONTROLLERS] : [],
172
+ providers: [
173
+ { provide: WORKFLOW_MODULE_OPTIONS, useValue: options },
174
+ ...buildWorkflowProviders(options.commands ?? []),
175
+ ],
143
176
  exports: EXPORTED_TOKENS,
144
177
  };
145
178
  }
146
179
  static forRootAsync(options) {
147
- const controllers = options.enableControllers
148
- ? [WorkflowInstanceController, WorkflowEventController, WorkflowQueryController, WorkflowTimeoutController]
149
- : [];
150
- const explicitCommands = options.commands ?? [];
151
- const commandClasses = explicitCommands.map((c) => c.useClass);
152
- const configProvider = {
153
- provide: "WORKFLOW_MODULE_OPTIONS",
154
- useFactory: options.useFactory,
155
- inject: (options.inject ?? []),
156
- };
157
- const providers = [
158
- configProvider,
159
- ...commandClasses,
160
- {
161
- provide: WORKFLOW_COMMAND_REGISTRY,
162
- useFactory: (moduleRef, discoveryService) => {
163
- const discovered = discoverDecoratedCommands(discoveryService);
164
- const merged = mergeCommandRegistrations(explicitCommands, discovered);
165
- return new NestCommandRegistry(moduleRef, merged);
166
- },
167
- inject: [ModuleRef, DiscoveryService],
168
- },
169
- {
170
- provide: WORKFLOW_GUARD_REGISTRY,
171
- useFactory: (config) => {
172
- if (config.guardRegistry && config.guards && config.guards.length > 0) {
173
- throw new Error("WorkflowModule: cannot supply both `guards` and `guardRegistry` — they are mutually exclusive. Pass guards or a custom registry, not both.");
174
- }
175
- if (config.guardRegistry)
176
- return config.guardRegistry;
177
- const registry = new InMemoryGuardRegistry();
178
- for (const guard of config.guards ?? []) {
179
- registry.register(guard.name, guard);
180
- }
181
- return registry;
182
- },
183
- inject: ["WORKFLOW_MODULE_OPTIONS"],
184
- },
185
- {
186
- provide: WORKFLOW_DEFINITION_REGISTRY,
187
- useFactory: (config, commandRegistry) => {
188
- const knownCommandNames = commandRegistry.getRegisteredNames();
189
- // Validate guard refs only when we own the registry (built-in path).
190
- // With a custom guardRegistry we can't enumerate names, so skip;
191
- // unresolved refs surface at runtime as WorkflowError.
192
- const knownGuardNames = config.guardRegistry !== undefined ? undefined : new Set((config.guards ?? []).map((g) => g.name));
193
- const registry = new InMemoryDefinitionRegistry({
194
- validator: new WorkflowValidator(),
195
- compiler: new WorkflowCompiler(),
196
- validationOptions: { knownCommandNames, knownGuardNames },
197
- onValidationWarning: logValidationWarning,
198
- });
199
- for (const workflow of config.workflows) {
200
- registry.register(workflow);
201
- }
202
- return registry;
203
- },
204
- inject: ["WORKFLOW_MODULE_OPTIONS", WORKFLOW_COMMAND_REGISTRY],
205
- },
206
- {
207
- provide: WORKFLOW_INSTANCE_STORE,
208
- useFactory: (config) => config.persistence.instanceStore,
209
- inject: ["WORKFLOW_MODULE_OPTIONS"],
210
- },
211
- {
212
- provide: WORKFLOW_HISTORY_STORE,
213
- useFactory: (config) => config.persistence.historyStore,
214
- inject: ["WORKFLOW_MODULE_OPTIONS"],
215
- },
216
- {
217
- provide: WORKFLOW_TRANSACTION_RUNNER,
218
- useFactory: (config) => config.persistence.transactionRunner,
219
- inject: ["WORKFLOW_MODULE_OPTIONS"],
220
- },
221
- {
222
- provide: WORKFLOW_CLOCK,
223
- useFactory: (config) => config.clock ?? { now: () => new Date() },
224
- inject: ["WORKFLOW_MODULE_OPTIONS"],
225
- },
226
- {
227
- provide: WORKFLOW_RUNTIME,
228
- useFactory: (config, definitionRegistry, commandRegistry, guardRegistry, instanceStore, historyStore, transactionRunner, clock) => new WorkflowRuntime({
229
- definitionRegistry,
230
- commandRegistry,
231
- guardRegistry,
232
- instanceStore,
233
- historyStore,
234
- transactionRunner,
235
- clock,
236
- observers: config.observers,
237
- onObserverError: config.onObserverError,
238
- }),
239
- inject: [
240
- "WORKFLOW_MODULE_OPTIONS",
241
- WORKFLOW_DEFINITION_REGISTRY,
242
- WORKFLOW_COMMAND_REGISTRY,
243
- WORKFLOW_GUARD_REGISTRY,
244
- WORKFLOW_INSTANCE_STORE,
245
- WORKFLOW_HISTORY_STORE,
246
- WORKFLOW_TRANSACTION_RUNNER,
247
- WORKFLOW_CLOCK,
248
- ],
249
- },
250
- WorkflowService,
251
- WorkflowTimeoutService,
252
- ];
253
180
  return {
254
181
  module: WorkflowModule_1,
255
182
  global: true, // allow downstream feature modules to inject WorkflowService without re-importing forRootAsync
256
183
  imports: [DiscoveryModule, ...(options.imports ?? [])],
257
- controllers,
258
- providers,
184
+ controllers: options.enableControllers ? [...CONTROLLERS] : [],
185
+ providers: [
186
+ {
187
+ provide: WORKFLOW_MODULE_OPTIONS,
188
+ useFactory: options.useFactory,
189
+ inject: (options.inject ?? []),
190
+ },
191
+ ...buildWorkflowProviders(options.commands ?? []),
192
+ ],
259
193
  exports: EXPORTED_TOKENS,
260
194
  };
261
195
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duraflows/nestjs",
3
- "version": "3.1.0",
3
+ "version": "4.0.1",
4
4
  "description": "NestJS integration for duraflows: dependency injection, services, optional REST controllers, and @WorkflowCommand decorator for DI-backed command handlers.",
5
5
  "keywords": [
6
6
  "duraflows",
@@ -54,17 +54,17 @@
54
54
  "class-validator": "^0.15.1"
55
55
  },
56
56
  "peerDependencies": {
57
- "@duraflows/core": "^3.1.0",
57
+ "@duraflows/core": "^4.0.1",
58
58
  "@nestjs/common": "^11.0.0",
59
59
  "@nestjs/core": "^11.0.0",
60
60
  "reflect-metadata": "^0.2.0",
61
61
  "rxjs": "^7.0.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@nestjs/common": "^11.1.24",
65
- "@nestjs/core": "^11.1.24",
66
- "@nestjs/testing": "^11.1.24",
67
- "@duraflows/core": "3.1.0"
64
+ "@nestjs/common": "^11.1.28",
65
+ "@nestjs/core": "^11.1.28",
66
+ "@nestjs/testing": "^11.1.28",
67
+ "@duraflows/core": "4.0.1"
68
68
  },
69
69
  "scripts": {
70
70
  "build": "tsc --build"