@nestjs/workflows 0.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.
Files changed (265) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +50 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +8 -0
  5. package/dist/core/codecs/aes-gcm-payload.codec.d.ts +42 -0
  6. package/dist/core/codecs/aes-gcm-payload.codec.js +119 -0
  7. package/dist/core/codecs/index.d.ts +2 -0
  8. package/dist/core/codecs/index.js +2 -0
  9. package/dist/core/codecs/payload-codecs.d.ts +59 -0
  10. package/dist/core/codecs/payload-codecs.js +130 -0
  11. package/dist/core/index.d.ts +16 -0
  12. package/dist/core/index.js +21 -0
  13. package/dist/core/interfaces/clock.interface.d.ts +18 -0
  14. package/dist/core/interfaces/clock.interface.js +1 -0
  15. package/dist/core/interfaces/index.d.ts +9 -0
  16. package/dist/core/interfaces/index.js +9 -0
  17. package/dist/core/interfaces/leased-worker-options.interface.d.ts +94 -0
  18. package/dist/core/interfaces/leased-worker-options.interface.js +1 -0
  19. package/dist/core/interfaces/limits.interface.d.ts +92 -0
  20. package/dist/core/interfaces/limits.interface.js +1 -0
  21. package/dist/core/interfaces/payload-codec.interface.d.ts +64 -0
  22. package/dist/core/interfaces/payload-codec.interface.js +1 -0
  23. package/dist/core/interfaces/result-waiter-options.interface.d.ts +52 -0
  24. package/dist/core/interfaces/result-waiter-options.interface.js +1 -0
  25. package/dist/core/interfaces/retry-settings.interface.d.ts +71 -0
  26. package/dist/core/interfaces/retry-settings.interface.js +1 -0
  27. package/dist/core/interfaces/schedule-store.interface.d.ts +143 -0
  28. package/dist/core/interfaces/schedule-store.interface.js +1 -0
  29. package/dist/core/interfaces/schedule.interface.d.ts +106 -0
  30. package/dist/core/interfaces/schedule.interface.js +1 -0
  31. package/dist/core/interfaces/scheduler-options.interface.d.ts +152 -0
  32. package/dist/core/interfaces/scheduler-options.interface.js +1 -0
  33. package/dist/core/limits/index.d.ts +1 -0
  34. package/dist/core/limits/index.js +1 -0
  35. package/dist/core/limits/limits.d.ts +61 -0
  36. package/dist/core/limits/limits.js +130 -0
  37. package/dist/core/results/index.d.ts +1 -0
  38. package/dist/core/results/index.js +1 -0
  39. package/dist/core/results/result-waiter.d.ts +54 -0
  40. package/dist/core/results/result-waiter.js +140 -0
  41. package/dist/core/retries/index.d.ts +1 -0
  42. package/dist/core/retries/index.js +1 -0
  43. package/dist/core/retries/retry.d.ts +60 -0
  44. package/dist/core/retries/retry.js +107 -0
  45. package/dist/core/scheduling/cron.util.d.ts +52 -0
  46. package/dist/core/scheduling/cron.util.js +262 -0
  47. package/dist/core/scheduling/encoded-schedule.store.d.ts +26 -0
  48. package/dist/core/scheduling/encoded-schedule.store.js +54 -0
  49. package/dist/core/scheduling/in-memory-schedule.store.d.ts +20 -0
  50. package/dist/core/scheduling/in-memory-schedule.store.js +90 -0
  51. package/dist/core/scheduling/index.d.ts +3 -0
  52. package/dist/core/scheduling/index.js +3 -0
  53. package/dist/core/scheduling/rrule.util.d.ts +55 -0
  54. package/dist/core/scheduling/rrule.util.js +371 -0
  55. package/dist/core/scheduling/schedule-spec.d.ts +57 -0
  56. package/dist/core/scheduling/schedule-spec.js +233 -0
  57. package/dist/core/scheduling/scheduler.d.ts +134 -0
  58. package/dist/core/scheduling/scheduler.js +450 -0
  59. package/dist/core/scheduling/time-zone.util.d.ts +18 -0
  60. package/dist/core/scheduling/time-zone.util.js +88 -0
  61. package/dist/core/time/clock.d.ts +28 -0
  62. package/dist/core/time/clock.js +35 -0
  63. package/dist/core/time/duration.d.ts +21 -0
  64. package/dist/core/time/duration.js +30 -0
  65. package/dist/core/time/index.d.ts +2 -0
  66. package/dist/core/time/index.js +2 -0
  67. package/dist/core/utils/canonical.util.d.ts +2 -0
  68. package/dist/core/utils/canonical.util.js +4 -0
  69. package/dist/core/utils/maybe.util.d.ts +8 -0
  70. package/dist/core/utils/maybe.util.js +14 -0
  71. package/dist/core/utils/payload-place.util.d.ts +10 -0
  72. package/dist/core/utils/payload-place.util.js +32 -0
  73. package/dist/core/workers/index.d.ts +1 -0
  74. package/dist/core/workers/index.js +1 -0
  75. package/dist/core/workers/leased-worker.d.ts +86 -0
  76. package/dist/core/workers/leased-worker.js +280 -0
  77. package/dist/cqrs/decorators/index.d.ts +2 -0
  78. package/dist/cqrs/decorators/index.js +2 -0
  79. package/dist/cqrs/decorators/signal-on.decorator.d.ts +18 -0
  80. package/dist/cqrs/decorators/signal-on.decorator.js +34 -0
  81. package/dist/cqrs/decorators/start-on.decorator.d.ts +18 -0
  82. package/dist/cqrs/decorators/start-on.decorator.js +49 -0
  83. package/dist/cqrs/index.d.ts +3 -0
  84. package/dist/cqrs/index.js +5 -0
  85. package/dist/cqrs/interfaces/index.d.ts +4 -0
  86. package/dist/cqrs/interfaces/index.js +4 -0
  87. package/dist/cqrs/interfaces/signal-on-options.interface.d.ts +22 -0
  88. package/dist/cqrs/interfaces/signal-on-options.interface.js +1 -0
  89. package/dist/cqrs/interfaces/start-on-options.interface.d.ts +23 -0
  90. package/dist/cqrs/interfaces/start-on-options.interface.js +1 -0
  91. package/dist/cqrs/interfaces/workflow-dispatcher-context.interface.d.ts +10 -0
  92. package/dist/cqrs/interfaces/workflow-dispatcher-context.interface.js +1 -0
  93. package/dist/cqrs/interfaces/workflow-event-route.interface.d.ts +20 -0
  94. package/dist/cqrs/interfaces/workflow-event-route.interface.js +1 -0
  95. package/dist/cqrs/utils/event-routes.util.d.ts +7 -0
  96. package/dist/cqrs/utils/event-routes.util.js +22 -0
  97. package/dist/cqrs/workflow-event.publisher.d.ts +52 -0
  98. package/dist/cqrs/workflow-event.publisher.js +257 -0
  99. package/dist/cqrs/workflow-events.explorer.d.ts +27 -0
  100. package/dist/cqrs/workflow-events.explorer.js +92 -0
  101. package/dist/cqrs/workflows-cqrs.module.d.ts +10 -0
  102. package/dist/cqrs/workflows-cqrs.module.js +27 -0
  103. package/dist/decorators/index.d.ts +1 -0
  104. package/dist/decorators/index.js +1 -0
  105. package/dist/decorators/workflow.decorator.d.ts +7 -0
  106. package/dist/decorators/workflow.decorator.js +52 -0
  107. package/dist/errors/child-workflow-failed.error.d.ts +13 -0
  108. package/dist/errors/child-workflow-failed.error.js +14 -0
  109. package/dist/errors/index.d.ts +13 -0
  110. package/dist/errors/index.js +13 -0
  111. package/dist/errors/non-retryable-step.error.d.ts +4 -0
  112. package/dist/errors/non-retryable-step.error.js +7 -0
  113. package/dist/errors/step-failed.error.d.ts +15 -0
  114. package/dist/errors/step-failed.error.js +16 -0
  115. package/dist/errors/step-timeout.error.d.ts +5 -0
  116. package/dist/errors/step-timeout.error.js +8 -0
  117. package/dist/errors/workflow-definition.error.d.ts +10 -0
  118. package/dist/errors/workflow-definition.error.js +13 -0
  119. package/dist/errors/workflow-failed.error.d.ts +20 -0
  120. package/dist/errors/workflow-failed.error.js +17 -0
  121. package/dist/errors/workflow-id-conflict.error.d.ts +6 -0
  122. package/dist/errors/workflow-id-conflict.error.js +9 -0
  123. package/dist/errors/workflow-interrupt.error.d.ts +32 -0
  124. package/dist/errors/workflow-interrupt.error.js +19 -0
  125. package/dist/errors/workflow-non-determinism.error.d.ts +4 -0
  126. package/dist/errors/workflow-non-determinism.error.js +7 -0
  127. package/dist/errors/workflow-not-found.error.d.ts +10 -0
  128. package/dist/errors/workflow-not-found.error.js +13 -0
  129. package/dist/errors/workflow-result-timeout.error.d.ts +11 -0
  130. package/dist/errors/workflow-result-timeout.error.js +13 -0
  131. package/dist/errors/workflow-state.error.d.ts +11 -0
  132. package/dist/errors/workflow-state.error.js +14 -0
  133. package/dist/errors/workflow.error.d.ts +7 -0
  134. package/dist/errors/workflow.error.js +7 -0
  135. package/dist/events/index.d.ts +3 -0
  136. package/dist/events/index.js +3 -0
  137. package/dist/events/workflow-events.interface.d.ts +145 -0
  138. package/dist/events/workflow-events.interface.js +1 -0
  139. package/dist/events/workflow-events.service.d.ts +17 -0
  140. package/dist/events/workflow-events.service.js +38 -0
  141. package/dist/events/workflows.channels.d.ts +3 -0
  142. package/dist/events/workflows.channels.js +9 -0
  143. package/dist/index.d.ts +20 -0
  144. package/dist/index.js +23 -0
  145. package/dist/interfaces/index.d.ts +13 -0
  146. package/dist/interfaces/index.js +13 -0
  147. package/dist/interfaces/serialized-workflow-error.interface.d.ts +6 -0
  148. package/dist/interfaces/serialized-workflow-error.interface.js +1 -0
  149. package/dist/interfaces/workflow-client.interface.d.ts +156 -0
  150. package/dist/interfaces/workflow-client.interface.js +1 -0
  151. package/dist/interfaces/workflow-clock.interface.d.ts +7 -0
  152. package/dist/interfaces/workflow-clock.interface.js +1 -0
  153. package/dist/interfaces/workflow-context.interface.d.ts +241 -0
  154. package/dist/interfaces/workflow-context.interface.js +1 -0
  155. package/dist/interfaces/workflow-decorator-options.interface.d.ts +101 -0
  156. package/dist/interfaces/workflow-decorator-options.interface.js +1 -0
  157. package/dist/interfaces/workflow-instance.interface.d.ts +83 -0
  158. package/dist/interfaces/workflow-instance.interface.js +1 -0
  159. package/dist/interfaces/workflow-payload-codec.interface.d.ts +32 -0
  160. package/dist/interfaces/workflow-payload-codec.interface.js +1 -0
  161. package/dist/interfaces/workflow-retry-options.interface.d.ts +9 -0
  162. package/dist/interfaces/workflow-retry-options.interface.js +1 -0
  163. package/dist/interfaces/workflow-runner.interface.d.ts +11 -0
  164. package/dist/interfaces/workflow-runner.interface.js +1 -0
  165. package/dist/interfaces/workflow-schedule.interface.d.ts +104 -0
  166. package/dist/interfaces/workflow-schedule.interface.js +1 -0
  167. package/dist/interfaces/workflow-storage.interface.d.ts +4 -0
  168. package/dist/interfaces/workflow-storage.interface.js +1 -0
  169. package/dist/interfaces/workflow-store.interface.d.ts +488 -0
  170. package/dist/interfaces/workflow-store.interface.js +1 -0
  171. package/dist/interfaces/workflows-module-options.interface.d.ts +80 -0
  172. package/dist/interfaces/workflows-module-options.interface.js +1 -0
  173. package/dist/mysql/index.d.ts +5 -0
  174. package/dist/mysql/index.js +8 -0
  175. package/dist/mysql/interfaces/index.d.ts +1 -0
  176. package/dist/mysql/interfaces/index.js +1 -0
  177. package/dist/mysql/interfaces/mysql-workflow-store-options.interface.d.ts +25 -0
  178. package/dist/mysql/interfaces/mysql-workflow-store-options.interface.js +1 -0
  179. package/dist/mysql/key-limits.d.ts +30 -0
  180. package/dist/mysql/key-limits.js +41 -0
  181. package/dist/mysql/migrations/index.d.ts +7 -0
  182. package/dist/mysql/migrations/index.js +16 -0
  183. package/dist/mysql/migrations/initial.migration.d.ts +9 -0
  184. package/dist/mysql/migrations/initial.migration.js +104 -0
  185. package/dist/mysql/mysql-workflow.store.d.ts +197 -0
  186. package/dist/mysql/mysql-workflow.store.js +828 -0
  187. package/dist/postgres/errors/index.d.ts +1 -0
  188. package/dist/postgres/errors/index.js +2 -0
  189. package/dist/postgres/index.d.ts +5 -0
  190. package/dist/postgres/index.js +8 -0
  191. package/dist/postgres/interfaces/index.d.ts +1 -0
  192. package/dist/postgres/interfaces/index.js +1 -0
  193. package/dist/postgres/interfaces/postgres-workflow-store-options.interface.d.ts +22 -0
  194. package/dist/postgres/interfaces/postgres-workflow-store-options.interface.js +1 -0
  195. package/dist/postgres/migrations/index.d.ts +6 -0
  196. package/dist/postgres/migrations/index.js +15 -0
  197. package/dist/postgres/migrations/initial.migration.d.ts +6 -0
  198. package/dist/postgres/migrations/initial.migration.js +106 -0
  199. package/dist/postgres/postgres-workflow.store.d.ts +132 -0
  200. package/dist/postgres/postgres-workflow.store.js +586 -0
  201. package/dist/services/index.d.ts +6 -0
  202. package/dist/services/index.js +6 -0
  203. package/dist/services/workflow-client.service.d.ts +146 -0
  204. package/dist/services/workflow-client.service.js +436 -0
  205. package/dist/services/workflow-execution.service.d.ts +267 -0
  206. package/dist/services/workflow-execution.service.js +1076 -0
  207. package/dist/services/workflow-registry.service.d.ts +56 -0
  208. package/dist/services/workflow-registry.service.js +172 -0
  209. package/dist/services/workflow-scheduler.service.d.ts +54 -0
  210. package/dist/services/workflow-scheduler.service.js +173 -0
  211. package/dist/services/workflow-schedules.service.d.ts +60 -0
  212. package/dist/services/workflow-schedules.service.js +193 -0
  213. package/dist/services/workflow-worker.service.d.ts +92 -0
  214. package/dist/services/workflow-worker.service.js +468 -0
  215. package/dist/signals/index.d.ts +1 -0
  216. package/dist/signals/index.js +1 -0
  217. package/dist/signals/workflow.signal.d.ts +17 -0
  218. package/dist/signals/workflow.signal.js +28 -0
  219. package/dist/sql/rate-windows.d.ts +41 -0
  220. package/dist/sql/rate-windows.js +53 -0
  221. package/dist/sql/workflow-rows.d.ts +16 -0
  222. package/dist/sql/workflow-rows.js +102 -0
  223. package/dist/sql/workflow-schema.error.d.ts +21 -0
  224. package/dist/sql/workflow-schema.error.js +15 -0
  225. package/dist/storage/encoded-workflow.store.d.ts +71 -0
  226. package/dist/storage/encoded-workflow.store.js +201 -0
  227. package/dist/storage/index.d.ts +1 -0
  228. package/dist/storage/index.js +1 -0
  229. package/dist/storage/workflow.storage.d.ts +55 -0
  230. package/dist/storage/workflow.storage.js +159 -0
  231. package/dist/stores/in-memory-workflow.store.d.ts +75 -0
  232. package/dist/stores/in-memory-workflow.store.js +380 -0
  233. package/dist/stores/index.d.ts +1 -0
  234. package/dist/stores/index.js +1 -0
  235. package/dist/testing/assertions.d.ts +10 -0
  236. package/dist/testing/assertions.js +72 -0
  237. package/dist/testing/index.d.ts +38 -0
  238. package/dist/testing/index.js +1089 -0
  239. package/dist/testing/schedule-store.contract.d.ts +33 -0
  240. package/dist/testing/schedule-store.contract.js +161 -0
  241. package/dist/utils/index.d.ts +7 -0
  242. package/dist/utils/index.js +7 -0
  243. package/dist/utils/journal-limits.util.d.ts +6 -0
  244. package/dist/utils/journal-limits.util.js +25 -0
  245. package/dist/utils/new-instance.util.d.ts +26 -0
  246. package/dist/utils/new-instance.util.js +87 -0
  247. package/dist/utils/normalize.util.d.ts +5 -0
  248. package/dist/utils/normalize.util.js +11 -0
  249. package/dist/utils/run-timeout.util.d.ts +3 -0
  250. package/dist/utils/run-timeout.util.js +15 -0
  251. package/dist/utils/schedule-spec.util.d.ts +16 -0
  252. package/dist/utils/schedule-spec.util.js +9 -0
  253. package/dist/utils/schedule-store.util.d.ts +15 -0
  254. package/dist/utils/schedule-store.util.js +44 -0
  255. package/dist/utils/serialize-error.util.d.ts +7 -0
  256. package/dist/utils/serialize-error.util.js +22 -0
  257. package/dist/utils/step-scope.util.d.ts +16 -0
  258. package/dist/utils/step-scope.util.js +38 -0
  259. package/dist/workflows.constants.d.ts +10 -0
  260. package/dist/workflows.constants.js +10 -0
  261. package/dist/workflows.module-definition.d.ts +4 -0
  262. package/dist/workflows.module-definition.js +6 -0
  263. package/dist/workflows.module.d.ts +24 -0
  264. package/dist/workflows.module.js +68 -0
  265. package/package.json +123 -0
@@ -0,0 +1,56 @@
1
+ import { type Type } from '@nestjs/common';
2
+ import { DiscoveryService } from '@nestjs/core';
3
+ import type { WorkflowRunner } from '../interfaces/workflow-runner.interface.js';
4
+ import type { WorkflowDeclaredSchedule, WorkflowMetadata } from '../interfaces/workflow-decorator-options.interface.js';
5
+ import type { WorkflowConcurrencyLimit, WorkflowRateLimitRule } from '../interfaces/workflow-store.interface.js';
6
+ export interface WorkflowDefinition extends WorkflowMetadata {
7
+ key: string;
8
+ instance: WorkflowRunner;
9
+ type: Type<unknown>;
10
+ }
11
+ export declare const workflowKey: (name: string, version: number) => string;
12
+ /**
13
+ * Internal: `WorkflowsCqrsModule` calls it from its publisher's constructor, before any
14
+ * definition loads. Without it, a workflow mapped from CQRS events fails the startup check.
15
+ */
16
+ export declare const ROUTE_EVENTS: unique symbol;
17
+ /** Finds every `@Workflow()` provider, wherever it is registered. */
18
+ export declare class WorkflowRegistry {
19
+ private readonly discovery;
20
+ private definitions?;
21
+ private declared?;
22
+ private eventsRouted;
23
+ constructor(discovery: DiscoveryService);
24
+ /** `name@version` keys this process can run. */
25
+ keys(): string[];
26
+ /** The workflow versions this process can run, for claims. */
27
+ versions(): Array<{
28
+ name: string;
29
+ version: number;
30
+ }>;
31
+ /** The concurrency limits of the workflows this process runs, for claims: each name's highest registered version's. */
32
+ limits(): WorkflowConcurrencyLimit[];
33
+ /** The rate limits of the workflows this process runs, for claims: each name's highest registered version's. */
34
+ rateLimits(): WorkflowRateLimitRule[];
35
+ /** The workflow names this process registers. */
36
+ names(): string[];
37
+ /**
38
+ * The schedules the workflows this process runs declare, by id: each name's highest registered version's. Throws
39
+ * for an id two workflows declare.
40
+ */
41
+ schedules(): Map<string, WorkflowDeclaredSchedule & {
42
+ workflow: string;
43
+ }>;
44
+ [ROUTE_EVENTS](): void;
45
+ get(name: string, version: number): WorkflowDefinition | undefined;
46
+ /** Highest registered version of `name`. */
47
+ latest(name: string): WorkflowDefinition | undefined;
48
+ /**
49
+ * Name and version for a `start()` call. A class only identifies the
50
+ * workflow by name: like a name, it starts the highest version registered in
51
+ * this application, so a controller that passes the version 1 class starts
52
+ * version 2 once it is deployed. `version` pins one explicitly.
53
+ */
54
+ resolve(workflow: Type<unknown> | string, version?: number): WorkflowMetadata;
55
+ private load;
56
+ }
@@ -0,0 +1,172 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Injectable } from '@nestjs/common';
11
+ import { DiscoveryService } from '@nestjs/core';
12
+ import { WorkflowNotFoundError } from '../errors/workflow-not-found.error.js';
13
+ import { WORKFLOW_EVENT_ROUTES_METADATA, WORKFLOW_METADATA } from '../workflows.constants.js';
14
+ export const workflowKey = (name, version) => `${name}@${version}`;
15
+ /**
16
+ * Internal: `WorkflowsCqrsModule` calls it from its publisher's constructor, before any
17
+ * definition loads. Without it, a workflow mapped from CQRS events fails the startup check.
18
+ */
19
+ export const ROUTE_EVENTS = Symbol('WorkflowRegistry.routeEvents');
20
+ /** Finds every `@Workflow()` provider, wherever it is registered. */
21
+ let WorkflowRegistry = class WorkflowRegistry {
22
+ constructor(discovery) {
23
+ this.discovery = discovery;
24
+ this.eventsRouted = false;
25
+ }
26
+ /** `name@version` keys this process can run. */
27
+ keys() {
28
+ return [...this.load().keys()];
29
+ }
30
+ /** The workflow versions this process can run, for claims. */
31
+ versions() {
32
+ return [...this.load().values()].map(({ name, version }) => ({ name, version }));
33
+ }
34
+ /** The concurrency limits of the workflows this process runs, for claims: each name's highest registered version's. */
35
+ limits() {
36
+ return this.names().flatMap((name) => {
37
+ const concurrency = this.latest(name).concurrency;
38
+ return concurrency ? [{ workflow: name, limit: concurrency.limit, perKey: concurrency.perKey }] : [];
39
+ });
40
+ }
41
+ /** The rate limits of the workflows this process runs, for claims: each name's highest registered version's. */
42
+ rateLimits() {
43
+ return this.names().flatMap((name) => {
44
+ const rateLimit = this.latest(name).rateLimit;
45
+ return rateLimit ? [{ workflow: name, limit: rateLimit.limit, perKey: rateLimit.perKey }] : [];
46
+ });
47
+ }
48
+ /** The workflow names this process registers. */
49
+ names() {
50
+ return [...new Set([...this.load().values()].map((def) => def.name))];
51
+ }
52
+ /**
53
+ * The schedules the workflows this process runs declare, by id: each name's highest registered version's. Throws
54
+ * for an id two workflows declare.
55
+ */
56
+ schedules() {
57
+ if (this.declared) {
58
+ return this.declared;
59
+ }
60
+ const declared = new Map();
61
+ for (const name of this.names()) {
62
+ for (const schedule of this.latest(name).schedules ?? []) {
63
+ const other = declared.get(schedule.id);
64
+ if (other) {
65
+ throw new Error(`Schedule "${schedule.id}" is declared by two workflows ("${other.workflow}" and "${name}"). Give each schedule its own id.`);
66
+ }
67
+ declared.set(schedule.id, { ...schedule, workflow: name });
68
+ }
69
+ }
70
+ return (this.declared = declared);
71
+ }
72
+ [ROUTE_EVENTS]() {
73
+ this.eventsRouted = true;
74
+ }
75
+ get(name, version) {
76
+ return this.load().get(workflowKey(name, version));
77
+ }
78
+ /** Highest registered version of `name`. */
79
+ latest(name) {
80
+ let latest;
81
+ for (const def of this.load().values()) {
82
+ if (def.name === name && (!latest || def.version > latest.version)) {
83
+ latest = def;
84
+ }
85
+ }
86
+ return latest;
87
+ }
88
+ /**
89
+ * Name and version for a `start()` call. A class only identifies the
90
+ * workflow by name: like a name, it starts the highest version registered in
91
+ * this application, so a controller that passes the version 1 class starts
92
+ * version 2 once it is deployed. `version` pins one explicitly.
93
+ */
94
+ resolve(workflow, version) {
95
+ let name;
96
+ let fallback;
97
+ if (typeof workflow === 'string') {
98
+ name = workflow;
99
+ }
100
+ else {
101
+ const meta = Reflect.getMetadata(WORKFLOW_METADATA, workflow);
102
+ if (!meta) {
103
+ throw new WorkflowNotFoundError(`${workflow.name} is not decorated with @Workflow().`);
104
+ }
105
+ name = meta.name;
106
+ // A class this process does not register (an API pod starting work for
107
+ // a worker pod) still knows its own version and timeout.
108
+ fallback = meta;
109
+ }
110
+ if (version !== undefined) {
111
+ if (!Number.isInteger(version) || version < 1) {
112
+ throw new TypeError(`Invalid version ${version} for workflow "${name}". Use a positive integer.`);
113
+ }
114
+ const known = this.get(name, version) ?? (fallback?.version === version ? fallback : undefined);
115
+ const latest = this.latest(name);
116
+ return {
117
+ name,
118
+ version,
119
+ timeout: known?.timeout,
120
+ concurrency: latest?.concurrency ?? fallback?.concurrency ?? known?.concurrency,
121
+ rateLimit: latest?.rateLimit ?? fallback?.rateLimit ?? known?.rateLimit,
122
+ };
123
+ }
124
+ const def = this.latest(name);
125
+ if (def) {
126
+ return { name: def.name, version: def.version, timeout: def.timeout, concurrency: def.concurrency, rateLimit: def.rateLimit };
127
+ }
128
+ if (fallback !== undefined) {
129
+ return { name, version: fallback.version, timeout: fallback.timeout, concurrency: fallback.concurrency, rateLimit: fallback.rateLimit };
130
+ }
131
+ throw new WorkflowNotFoundError(`Workflow "${name}" is not registered in this application. Register it, or pass { version } to start it from a process that does not run it.`);
132
+ }
133
+ load() {
134
+ if (this.definitions) {
135
+ return this.definitions;
136
+ }
137
+ const definitions = new Map();
138
+ for (const wrapper of this.discovery.getProviders()) {
139
+ const type = (!wrapper.metatype || wrapper.inject ? wrapper.instance?.constructor : wrapper.metatype);
140
+ const meta = type && Reflect.getMetadata(WORKFLOW_METADATA, type);
141
+ if (!meta) {
142
+ continue;
143
+ }
144
+ if (!wrapper.isDependencyTreeStatic() || wrapper.isTransient) {
145
+ throw new Error(`Workflow ${type.name} must be a singleton; request-scoped and transient workflows are not supported.`);
146
+ }
147
+ const key = workflowKey(meta.name, meta.version);
148
+ const existing = definitions.get(key);
149
+ if (existing && existing.type !== type) {
150
+ throw new Error(`Workflow "${key}" is defined twice (${existing.type.name} and ${type.name}). Bump the version of one of them.`);
151
+ }
152
+ const instance = wrapper.instance;
153
+ if (typeof instance?.run !== 'function') {
154
+ throw new Error(`Workflow ${type.name} must have a run(ctx, input) method.`);
155
+ }
156
+ // The main entry never imports @nestjs/cqrs: the decorators leave metadata, and the
157
+ // subpath's module marks the registry. Mapped events with no publisher would start nothing.
158
+ if (!this.eventsRouted && Reflect.getOwnMetadata(WORKFLOW_EVENT_ROUTES_METADATA, type)) {
159
+ throw new Error(`Workflow ${type.name} is started or signalled by CQRS events (@StartOn(), @SignalOn()), but ` +
160
+ 'WorkflowsCqrsModule is not imported, so publishing those events would start and signal nothing. ' +
161
+ "Import WorkflowsCqrsModule from '@nestjs/workflows/cqrs' next to CqrsModule.forRoot().");
162
+ }
163
+ definitions.set(key, { ...meta, key, instance, type });
164
+ }
165
+ return (this.definitions = definitions);
166
+ }
167
+ };
168
+ WorkflowRegistry = __decorate([
169
+ Injectable(),
170
+ __metadata("design:paramtypes", [DiscoveryService])
171
+ ], WorkflowRegistry);
172
+ export { WorkflowRegistry };
@@ -0,0 +1,54 @@
1
+ import { type OnApplicationBootstrap } from '@nestjs/common';
2
+ import type { DeclaredSchedule, ScheduleProduction } from '../core/interfaces/scheduler-options.interface.js';
3
+ import type { ScheduleRecord } from '../core/interfaces/schedule-store.interface.js';
4
+ import { Scheduler } from '../core/scheduling/scheduler.js';
5
+ import { WorkflowEvents } from '../events/workflow-events.service.js';
6
+ import type { WorkflowMetadata } from '../interfaces/workflow-decorator-options.interface.js';
7
+ import type { WorkflowInstance } from '../interfaces/workflow-instance.interface.js';
8
+ import type { WorkflowSchedule } from '../interfaces/workflow-schedule.interface.js';
9
+ import type { WorkflowsModuleOptions } from '../interfaces/workflows-module-options.interface.js';
10
+ import { WorkflowStorage } from '../storage/workflow.storage.js';
11
+ import type { WorkflowScheduleSpec } from '../utils/schedule-spec.util.js';
12
+ import { WorkflowRegistry } from './workflow-registry.service.js';
13
+ /**
14
+ * @internal Workflows' schedules on the core's `Scheduler`: this process's workflows are its targets, their declared
15
+ * schedules its declarations, and an occurrence starts an instance. The `Scheduler` keeps the declared ones in step
16
+ * with the code and starts the occurrences that are due, for the worker; `WorkflowSchedules` is the public side.
17
+ */
18
+ export declare class WorkflowScheduler implements OnApplicationBootstrap {
19
+ private readonly storage;
20
+ private readonly registry;
21
+ private readonly events;
22
+ private readonly logger;
23
+ private readonly clock;
24
+ /** The schedules' engine, over the registered store's schedules. */
25
+ readonly engine: Scheduler<WorkflowScheduleSpec>;
26
+ private declarations?;
27
+ constructor(storage: WorkflowStorage, options: WorkflowsModuleOptions, registry: WorkflowRegistry, events: WorkflowEvents);
28
+ /** Read at each call, never in the constructor: sources register while providers are created. */
29
+ private get store();
30
+ onApplicationBootstrap(): Promise<void>;
31
+ /** @internal Called by the worker: starts the due occurrences of the schedules of the workflows it runs. */
32
+ produce(owner: string, leaseMs: number): Promise<ScheduleProduction>;
33
+ /** The declaration of a schedule the workflows this process runs declare, or `undefined`. */
34
+ declaration(id: string): DeclaredSchedule<WorkflowScheduleSpec> | undefined;
35
+ /**
36
+ * Starts the instance of an occurrence (or of a trigger, `at` being now): the one `create()` stored, and whether it
37
+ * created it. An input or a key that can't be computed skips the occurrence (`null`), logged: retrying would fail
38
+ * the same way. With `rethrow`, that throws instead.
39
+ */
40
+ startOccurrence(record: ScheduleRecord, spec: WorkflowScheduleSpec, declared: DeclaredSchedule<WorkflowScheduleSpec> | undefined, workflow: WorkflowMetadata, at: number, now: number, rethrow?: boolean): Promise<{
41
+ instance: WorkflowInstance;
42
+ created: boolean;
43
+ } | null>;
44
+ /** How `WorkflowSchedules` shows a stored schedule. */
45
+ view(record: ScheduleRecord): WorkflowSchedule;
46
+ /** How `WorkflowSchedules` shows a schedule as stored: without its input, if no codec can read it any more. */
47
+ readableView(record: ScheduleRecord): Promise<WorkflowSchedule>;
48
+ /** The schedules the workflows this process runs declare (each name's highest registered version's), for the `Scheduler`. */
49
+ private declared;
50
+ private fire;
51
+ /** The unfinished instances the schedule started. */
52
+ private running;
53
+ private skipped;
54
+ }
@@ -0,0 +1,173 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { Inject, Injectable, Logger } from '@nestjs/common';
14
+ import { occurrenceId } from '../core/scheduling/schedule-spec.js';
15
+ import { Scheduler } from '../core/scheduling/scheduler.js';
16
+ import { systemClock } from '../core/time/clock.js';
17
+ import { WorkflowStateError } from '../errors/workflow-state.error.js';
18
+ import { WorkflowEvents } from '../events/workflow-events.service.js';
19
+ import { ENGINE_STORE, PAYLOAD_CODECS, WorkflowStorage } from '../storage/workflow.storage.js';
20
+ import { newInstance } from '../utils/new-instance.util.js';
21
+ import { workflowScheduleStore } from '../utils/schedule-store.util.js';
22
+ import { WORKFLOWS_MODULE_OPTIONS } from '../workflows.module-definition.js';
23
+ import { WorkflowRegistry } from './workflow-registry.service.js';
24
+ /** The unfinished instances a schedule's overlap looks at, at most. */
25
+ const RUNNING_PAGE = 100;
26
+ const UNFINISHED = ['pending', 'running', 'suspended', 'compensating'];
27
+ /**
28
+ * @internal Workflows' schedules on the core's `Scheduler`: this process's workflows are its targets, their declared
29
+ * schedules its declarations, and an occurrence starts an instance. The `Scheduler` keeps the declared ones in step
30
+ * with the code and starts the occurrences that are due, for the worker; `WorkflowSchedules` is the public side.
31
+ */
32
+ let WorkflowScheduler = class WorkflowScheduler {
33
+ constructor(storage, options, registry, events) {
34
+ this.storage = storage;
35
+ this.registry = registry;
36
+ this.events = events;
37
+ this.logger = new Logger('Workflows');
38
+ this.clock = options.clock ?? systemClock;
39
+ this.engine = new Scheduler({
40
+ // Read at each call, never in the constructor: sources register while providers are created.
41
+ store: workflowScheduleStore(() => storage.source),
42
+ clock: this.clock,
43
+ codecs: storage[PAYLOAD_CODECS],
44
+ payloadContext: (schedule) => ({ field: 'input', schedule }),
45
+ targets: () => registry.names(),
46
+ declared: () => this.declared(),
47
+ fire: (occurrence) => this.fire(occurrence),
48
+ running: (schedule) => this.running(schedule.id),
49
+ cancel: (id, reason, now) => this.store.requestCancel(id, { reason, now, terminate: false }),
50
+ skipped: (schedule, skip, now) => this.skipped(schedule, skip, now),
51
+ conflictError: (message) => new WorkflowStateError(message),
52
+ labels: { target: (name) => `workflow "${name}"`, targets: 'workflows', payload: 'input', upsert: 'WorkflowSchedules.upsert()' },
53
+ logger: this.logger,
54
+ });
55
+ }
56
+ /** Read at each call, never in the constructor: sources register while providers are created. */
57
+ get store() {
58
+ return this.storage[ENGINE_STORE];
59
+ }
60
+ async onApplicationBootstrap() {
61
+ // A definition error (an id two workflows declare) fails the startup; the store's errors don't.
62
+ this.registry.schedules();
63
+ try {
64
+ await this.engine.synced();
65
+ }
66
+ catch (error) {
67
+ this.logger.error('Saving the schedules the workflows declare failed; the worker tries again before it starts any.', error);
68
+ }
69
+ }
70
+ /** @internal Called by the worker: starts the due occurrences of the schedules of the workflows it runs. */
71
+ produce(owner, leaseMs) {
72
+ return this.engine.produce(owner, leaseMs);
73
+ }
74
+ /** The declaration of a schedule the workflows this process runs declare, or `undefined`. */
75
+ declaration(id) {
76
+ return this.declared().get(id);
77
+ }
78
+ /**
79
+ * Starts the instance of an occurrence (or of a trigger, `at` being now): the one `create()` stored, and whether it
80
+ * created it. An input or a key that can't be computed skips the occurrence (`null`), logged: retrying would fail
81
+ * the same way. With `rethrow`, that throws instead.
82
+ */
83
+ async startOccurrence(record, spec, declared, workflow, at, now, rethrow = false) {
84
+ const id = occurrenceId(record.id, at);
85
+ let data;
86
+ try {
87
+ // A declared schedule's input is its code's (the stored one is for reading), an upserted one's is stored.
88
+ const source = declared ? declared.payload : record.payload;
89
+ const input = typeof source === 'function' ? source({ id: record.id, at }) : source;
90
+ data = newInstance(workflow, id, input ?? undefined, {
91
+ caller: `Schedule "${record.id}"`,
92
+ now,
93
+ priority: spec.priority === 0 ? undefined : spec.priority,
94
+ scheduleId: record.id,
95
+ scheduledAt: at,
96
+ });
97
+ }
98
+ catch (error) {
99
+ if (rethrow) {
100
+ throw error;
101
+ }
102
+ this.logger.error(`Schedule "${record.id}" couldn't start its occurrence of ${new Date(at).toISOString()}: ${error?.message ?? String(error)}`);
103
+ return null;
104
+ }
105
+ const stored = await this.store.create(data);
106
+ if (!stored.created && (stored.instance.workflow !== data.workflow || stored.instance.scheduleId !== record.id)) {
107
+ this.logger.warn(`Schedule "${record.id}" didn't start its occurrence of ${new Date(at).toISOString()}: instance "${id}" exists and isn't the schedule's.`);
108
+ }
109
+ return stored;
110
+ }
111
+ /** How `WorkflowSchedules` shows a stored schedule. */
112
+ view(record) {
113
+ const spec = record.spec;
114
+ const { nextAt, runs, bufferedAt } = this.engine.progress(record);
115
+ return {
116
+ id: record.id,
117
+ workflow: record.target,
118
+ version: spec.version,
119
+ declared: record.declared,
120
+ ...(spec.cron !== undefined ? { cron: spec.cron } : {}),
121
+ ...(spec.every !== undefined ? { every: spec.every } : {}),
122
+ ...(spec.rrule !== undefined ? { rrule: spec.rrule } : {}),
123
+ tz: spec.tz,
124
+ startAt: spec.startAt,
125
+ endAt: spec.endAt,
126
+ limit: spec.limit,
127
+ missed: spec.missed,
128
+ overlap: spec.overlap,
129
+ priority: spec.priority,
130
+ ...(spec.inputFn || record.payload === null ? {} : { input: record.payload }),
131
+ paused: record.paused,
132
+ nextAt,
133
+ runs,
134
+ bufferedAt,
135
+ createdAt: record.createdAt,
136
+ updatedAt: record.updatedAt,
137
+ };
138
+ }
139
+ /** How `WorkflowSchedules` shows a schedule as stored: without its input, if no codec can read it any more. */
140
+ async readableView(record) {
141
+ return this.view(await this.engine.readable(record));
142
+ }
143
+ /** The schedules the workflows this process runs declare (each name's highest registered version's), for the `Scheduler`. */
144
+ declared() {
145
+ this.declarations ??= new Map([...this.registry.schedules()].map(([id, schedule]) => [
146
+ id,
147
+ { id, target: schedule.workflow, version: this.registry.latest(schedule.workflow).version, spec: schedule.spec, payload: schedule.input },
148
+ ]));
149
+ return this.declarations;
150
+ }
151
+ async fire({ schedule, spec, declared, at, now }) {
152
+ const workflow = this.registry.resolve(schedule.target, spec.version ?? undefined);
153
+ const started = await this.startOccurrence(schedule, spec, declared, workflow, at, now);
154
+ return started && { created: started.created };
155
+ }
156
+ /** The unfinished instances the schedule started. */
157
+ async running(schedule) {
158
+ // Read as stored: only the ids count, and an instance no codec can read any more still runs.
159
+ const instances = await this.store.inner.list({ scheduleId: schedule, status: UNFINISHED, limit: RUNNING_PAGE, offset: 0 });
160
+ return instances.map((instance) => instance.id);
161
+ }
162
+ skipped(schedule, skip, now) {
163
+ const workflow = this.registry.resolve(schedule.target, schedule.spec.version ?? undefined);
164
+ this.events.emit({ type: 'schedule-skipped', id: schedule.id, workflow: workflow.name, version: workflow.version, at: now, ...skip });
165
+ }
166
+ };
167
+ WorkflowScheduler = __decorate([
168
+ Injectable(),
169
+ __param(1, Inject(WORKFLOWS_MODULE_OPTIONS)),
170
+ __metadata("design:paramtypes", [WorkflowStorage, Object, WorkflowRegistry,
171
+ WorkflowEvents])
172
+ ], WorkflowScheduler);
173
+ export { WorkflowScheduler };
@@ -0,0 +1,60 @@
1
+ import type { WorkflowStartResult } from '../interfaces/workflow-client.interface.js';
2
+ import type { UpsertWorkflowScheduleOptions, WorkflowSchedule, WorkflowScheduleListFilter, WorkflowSchedulePreviewOptions, WorkflowSchedulePreviewSpec } from '../interfaces/workflow-schedule.interface.js';
3
+ import type { WorkflowsModuleOptions } from '../interfaces/workflows-module-options.interface.js';
4
+ import { WorkflowRegistry } from './workflow-registry.service.js';
5
+ import { WorkflowScheduler } from './workflow-scheduler.service.js';
6
+ import { WorkflowWorker } from './workflow-worker.service.js';
7
+ /**
8
+ * Schedules that start workflow instances: a cron expression, an interval or an RFC 5545 rule, in a time zone.
9
+ * Declare them with `@Workflow(name, { schedules })`, or save them at runtime with `upsert()`; every worker that
10
+ * runs the workflow starts their occurrences, each exactly once. Also `WorkflowClient.schedules`.
11
+ */
12
+ export declare class WorkflowSchedules {
13
+ private readonly registry;
14
+ private readonly scheduler;
15
+ private readonly worker;
16
+ private readonly clock;
17
+ constructor(options: WorkflowsModuleOptions, registry: WorkflowRegistry, scheduler: WorkflowScheduler, worker: WorkflowWorker);
18
+ /** The core's engine of schedules, over the registered store's. */
19
+ private get engine();
20
+ /**
21
+ * Creates or replaces the schedule with this id, and returns it. A schedule whose timing (`cron`, `every`,
22
+ * `rrule`, `tz`, `startAt`, `endAt`) changes starts over from now, with its first occurrence after now; one
23
+ * whose timing doesn't keeps its next occurrence, whatever else changes. Its runs, and whether it is paused,
24
+ * carry over. Throws `WorkflowStateError` for a schedule a workflow declares (change it in the code), and a
25
+ * `TypeError` for invalid options.
26
+ */
27
+ upsert<W>(id: string, schedule: UpsertWorkflowScheduleOptions<W>): Promise<WorkflowSchedule>;
28
+ /** The schedule, or `null` for an unknown id. */
29
+ get(id: string): Promise<WorkflowSchedule | null>;
30
+ /** Schedules by id, all or a workflow's; at most `limit` (default 100). */
31
+ list(filter?: WorkflowScheduleListFilter): Promise<WorkflowSchedule[]>;
32
+ /**
33
+ * Deletes the schedule, and returns whether it existed. The instances it started keep running. Throws
34
+ * `WorkflowStateError` for a schedule a workflow declares: remove it from the code, or `pause()` it.
35
+ */
36
+ remove(id: string): Promise<boolean>;
37
+ /**
38
+ * Stops starting occurrences until `resume()`, at once: a worker that is starting the schedule's occurrences at
39
+ * that moment starts only those it had already decided on. The instances it started keep running. A declared
40
+ * schedule stays paused across deploys. Throws `WorkflowNotFoundError` for an unknown id.
41
+ */
42
+ pause(id: string): Promise<WorkflowSchedule>;
43
+ /**
44
+ * Starts occurrences again, from the first one after now: those due while it was paused are passed over,
45
+ * whatever `missed` says. Throws `WorkflowNotFoundError` for an unknown id.
46
+ */
47
+ resume(id: string): Promise<WorkflowSchedule>;
48
+ /**
49
+ * Starts an instance of the schedule now, whatever its `overlap`, even while it is paused, and outside its
50
+ * `limit`; its id is `<schedule id>@<ISO time of now>`, and it counts as the schedule's for the overlap of the
51
+ * occurrences after it. Throws `WorkflowNotFoundError` for an unknown id.
52
+ */
53
+ trigger(id: string): Promise<WorkflowStartResult>;
54
+ /**
55
+ * The times (epoch milliseconds) of the next occurrences after `from` (default: now), at most `count` (default
56
+ * 10), as a schedule saved at `from` would start them: of a stored schedule (by id, within what its `limit` has
57
+ * left, paused or not), or of a spec, which isn't saved. Throws `WorkflowNotFoundError` for an unknown id.
58
+ */
59
+ preview(schedule: string | WorkflowSchedulePreviewSpec, options?: WorkflowSchedulePreviewOptions): Promise<number[]>;
60
+ }