nestjs-serverless-workflow 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 (127) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +396 -0
  3. package/dist/adapter/index.d.ts +2 -0
  4. package/dist/adapter/index.d.ts.map +1 -0
  5. package/dist/adapter/index.js +2 -0
  6. package/dist/adapter/index.js.map +1 -0
  7. package/dist/adapter/lambda.adapater.d.ts +4 -0
  8. package/dist/adapter/lambda.adapater.d.ts.map +1 -0
  9. package/dist/adapter/lambda.adapater.js +63 -0
  10. package/dist/adapter/lambda.adapater.js.map +1 -0
  11. package/dist/event-bus/index.d.ts +3 -0
  12. package/dist/event-bus/index.d.ts.map +1 -0
  13. package/dist/event-bus/index.js +3 -0
  14. package/dist/event-bus/index.js.map +1 -0
  15. package/dist/event-bus/sqs/sqs.emitter.d.ts +6 -0
  16. package/dist/event-bus/sqs/sqs.emitter.d.ts.map +1 -0
  17. package/dist/event-bus/sqs/sqs.emitter.js +4 -0
  18. package/dist/event-bus/sqs/sqs.emitter.js.map +1 -0
  19. package/dist/event-bus/types/broker-publisher.interface.d.ts +5 -0
  20. package/dist/event-bus/types/broker-publisher.interface.d.ts.map +1 -0
  21. package/dist/event-bus/types/broker-publisher.interface.js +2 -0
  22. package/dist/event-bus/types/broker-publisher.interface.js.map +1 -0
  23. package/dist/event-bus/types/index.d.ts +3 -0
  24. package/dist/event-bus/types/index.d.ts.map +1 -0
  25. package/dist/event-bus/types/index.js +3 -0
  26. package/dist/event-bus/types/index.js.map +1 -0
  27. package/dist/event-bus/types/workflow-event.interface.d.ts +7 -0
  28. package/dist/event-bus/types/workflow-event.interface.d.ts.map +1 -0
  29. package/dist/event-bus/types/workflow-event.interface.js +2 -0
  30. package/dist/event-bus/types/workflow-event.interface.js.map +1 -0
  31. package/dist/exception/index.d.ts +2 -0
  32. package/dist/exception/index.d.ts.map +1 -0
  33. package/dist/exception/index.js +2 -0
  34. package/dist/exception/index.js.map +1 -0
  35. package/dist/exception/unretriable.exception.d.ts +4 -0
  36. package/dist/exception/unretriable.exception.d.ts.map +1 -0
  37. package/dist/exception/unretriable.exception.js +7 -0
  38. package/dist/exception/unretriable.exception.js.map +1 -0
  39. package/dist/workflow/decorators/default.decorator.d.ts +3 -0
  40. package/dist/workflow/decorators/default.decorator.d.ts.map +1 -0
  41. package/dist/workflow/decorators/default.decorator.js +9 -0
  42. package/dist/workflow/decorators/default.decorator.js.map +1 -0
  43. package/dist/workflow/decorators/event.decorator.d.ts +5 -0
  44. package/dist/workflow/decorators/event.decorator.d.ts.map +1 -0
  45. package/dist/workflow/decorators/event.decorator.js +20 -0
  46. package/dist/workflow/decorators/event.decorator.js.map +1 -0
  47. package/dist/workflow/decorators/index.d.ts +6 -0
  48. package/dist/workflow/decorators/index.d.ts.map +1 -0
  49. package/dist/workflow/decorators/index.js +6 -0
  50. package/dist/workflow/decorators/index.js.map +1 -0
  51. package/dist/workflow/decorators/params.decorator.d.ts +3 -0
  52. package/dist/workflow/decorators/params.decorator.d.ts.map +1 -0
  53. package/dist/workflow/decorators/params.decorator.js +19 -0
  54. package/dist/workflow/decorators/params.decorator.js.map +1 -0
  55. package/dist/workflow/decorators/with-retry.decorator.d.ts +4 -0
  56. package/dist/workflow/decorators/with-retry.decorator.d.ts.map +1 -0
  57. package/dist/workflow/decorators/with-retry.decorator.js +9 -0
  58. package/dist/workflow/decorators/with-retry.decorator.js.map +1 -0
  59. package/dist/workflow/decorators/workflow.decorator.d.ts +6 -0
  60. package/dist/workflow/decorators/workflow.decorator.d.ts.map +1 -0
  61. package/dist/workflow/decorators/workflow.decorator.js +8 -0
  62. package/dist/workflow/decorators/workflow.decorator.js.map +1 -0
  63. package/dist/workflow/index.d.ts +7 -0
  64. package/dist/workflow/index.d.ts.map +1 -0
  65. package/dist/workflow/index.js +7 -0
  66. package/dist/workflow/index.js.map +1 -0
  67. package/dist/workflow/providers/index.d.ts +4 -0
  68. package/dist/workflow/providers/index.d.ts.map +1 -0
  69. package/dist/workflow/providers/index.js +4 -0
  70. package/dist/workflow/providers/index.js.map +1 -0
  71. package/dist/workflow/providers/ochestrator.service.d.ts +32 -0
  72. package/dist/workflow/providers/ochestrator.service.d.ts.map +1 -0
  73. package/dist/workflow/providers/ochestrator.service.js +183 -0
  74. package/dist/workflow/providers/ochestrator.service.js.map +1 -0
  75. package/dist/workflow/providers/router.factory.d.ts +7 -0
  76. package/dist/workflow/providers/router.factory.d.ts.map +1 -0
  77. package/dist/workflow/providers/router.factory.js +18 -0
  78. package/dist/workflow/providers/router.factory.js.map +1 -0
  79. package/dist/workflow/providers/router.service.d.ts +16 -0
  80. package/dist/workflow/providers/router.service.d.ts.map +1 -0
  81. package/dist/workflow/providers/router.service.js +79 -0
  82. package/dist/workflow/providers/router.service.js.map +1 -0
  83. package/dist/workflow/providers/saga.service.d.ts +34 -0
  84. package/dist/workflow/providers/saga.service.d.ts.map +1 -0
  85. package/dist/workflow/providers/saga.service.js +159 -0
  86. package/dist/workflow/providers/saga.service.js.map +1 -0
  87. package/dist/workflow/types/entity.interface.d.ts +33 -0
  88. package/dist/workflow/types/entity.interface.d.ts.map +1 -0
  89. package/dist/workflow/types/entity.interface.js +2 -0
  90. package/dist/workflow/types/entity.interface.js.map +1 -0
  91. package/dist/workflow/types/index.d.ts +7 -0
  92. package/dist/workflow/types/index.d.ts.map +1 -0
  93. package/dist/workflow/types/index.js +9 -0
  94. package/dist/workflow/types/index.js.map +1 -0
  95. package/dist/workflow/types/retry.interface.d.ts +24 -0
  96. package/dist/workflow/types/retry.interface.d.ts.map +1 -0
  97. package/dist/workflow/types/retry.interface.js +10 -0
  98. package/dist/workflow/types/retry.interface.js.map +1 -0
  99. package/dist/workflow/types/saga.interface.d.ts +144 -0
  100. package/dist/workflow/types/saga.interface.d.ts.map +1 -0
  101. package/dist/workflow/types/saga.interface.js +24 -0
  102. package/dist/workflow/types/saga.interface.js.map +1 -0
  103. package/dist/workflow/types/shared.type.d.ts +5 -0
  104. package/dist/workflow/types/shared.type.d.ts.map +1 -0
  105. package/dist/workflow/types/shared.type.js +2 -0
  106. package/dist/workflow/types/shared.type.js.map +1 -0
  107. package/dist/workflow/types/transition-event.interface.d.ts +16 -0
  108. package/dist/workflow/types/transition-event.interface.d.ts.map +1 -0
  109. package/dist/workflow/types/transition-event.interface.js +2 -0
  110. package/dist/workflow/types/transition-event.interface.js.map +1 -0
  111. package/dist/workflow/types/workflow-definition.interface.d.ts +60 -0
  112. package/dist/workflow/types/workflow-definition.interface.d.ts.map +1 -0
  113. package/dist/workflow/types/workflow-definition.interface.js +2 -0
  114. package/dist/workflow/types/workflow-definition.interface.js.map +1 -0
  115. package/dist/workflow/utils/index.d.ts +2 -0
  116. package/dist/workflow/utils/index.d.ts.map +1 -0
  117. package/dist/workflow/utils/index.js +2 -0
  118. package/dist/workflow/utils/index.js.map +1 -0
  119. package/dist/workflow/utils/retry-backoff.d.ts +35 -0
  120. package/dist/workflow/utils/retry-backoff.d.ts.map +1 -0
  121. package/dist/workflow/utils/retry-backoff.js +68 -0
  122. package/dist/workflow/utils/retry-backoff.js.map +1 -0
  123. package/dist/workflow/workflow.module.d.ts +13 -0
  124. package/dist/workflow/workflow.module.d.ts.map +1 -0
  125. package/dist/workflow/workflow.module.js +27 -0
  126. package/dist/workflow/workflow.module.js.map +1 -0
  127. package/package.json +154 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Thomas Do (tung-dnt)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,396 @@
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://joseescrich.com/logos/nestjs-workflow.png">
3
+ <source media="(prefers-color-scheme: light)" srcset="https://joseescrich.com/logos/nestjs-workflow-light.png">
4
+ <img src="https://joseescrich.com/logos/nestjs-workflow.png" alt="NestJS Workflow Logo" width="200" style="margin-bottom:20px">
5
+ </picture>
6
+
7
+ # NestJS Workflow & State Machine
8
+
9
+ A flexible workflow engine built on top of NestJS framework, enabling developers to create, manage, and execute complex workflows in their Node.js applications.
10
+
11
+ ## 🎯 Live Examples & Demos
12
+
13
+ Explore fully functional examples with **interactive visual demos** in our dedicated examples repository:
14
+
15
+ ### 👉 **[View Examples Repository](https://github.com/@nestjs-serverless-workflow-examples)**
16
+
17
+ The repository includes three comprehensive real-world examples:
18
+
19
+ 1. **🚀 User Onboarding Workflow** - Multi-step verification, KYC/AML compliance, risk assessment
20
+ 2. **📦 Order Processing System** - Complete e-commerce lifecycle with payment retry logic
21
+ 3. **📊 Kafka-Driven Inventory** - Real-time event-driven inventory management with Kafka integration
22
+
23
+ Each example features:
24
+
25
+ - ✨ **Interactive Visual Mode** - See workflows in action with real-time state visualization
26
+ - 🎮 **Interactive Controls** - Manually trigger transitions and explore different paths
27
+ - 🤖 **Automated Scenarios** - Pre-built test cases demonstrating various workflow paths
28
+ - 📝 **Full Source Code** - Production-ready implementations you can adapt
29
+
30
+ **[➡️ Get Started with Examples](https://github.com/@nestjs-serverless-workflow-examples#-quick-start)**
31
+
32
+ ## Table of Contents
33
+
34
+ - [Features](#features)
35
+ - [Stateless Architecture](#stateless-architecture)
36
+ - [Installation](#installation)
37
+ - [Quick Start](#quick-start)
38
+ - [Module Registration](#module-registration)
39
+ - [Define a Workflow](#define-a-workflow)
40
+ - [Message Format](#message-format)
41
+ - [Configuring Actions and Conditions](#configuring-actions-and-conditions)
42
+ - [Complete Example with Kafka Integration](#complete-example-with-kafka-integration)
43
+
44
+ ## Features
45
+
46
+ - **🌲 Tree-Shakable**: Modular architecture with subpath exports ensures minimal bundle size
47
+ - **Workflow Definitions**: Define workflows using a simple, declarative syntax
48
+ - **State Management**: Track and persist workflow states
49
+ - **Event-Driven Architecture**: Built on NestJS's event system for flexible workflow triggers
50
+ - **Transition Rules**: Configure complex transition conditions between workflow states
51
+ - **Extensible**: Easily extend with custom actions, conditions, and triggers
52
+ - **TypeScript Support**: Full TypeScript support with strong typing
53
+ - **Integration Friendly**: Seamlessly integrates with existing NestJS applications
54
+ - **Message Broker Integration**: Easily integrate with SQS, Kafka, RabbitMQ, and more
55
+ - **Stateless Design**: Lightweight implementation with no additional storage requirements
56
+ - **Serverless Ready**: Optimized for AWS Lambda with automatic timeout handling
57
+
58
+ ## 📚 Documentation
59
+
60
+ Comprehensive documentation is available:
61
+ - **[Getting Started](./docs/getting-started.md)** - Installation and basic usage
62
+ - **[Workflow Module](./docs/workflow.md)** - State machines and transitions
63
+ - **[Event Bus](./docs/event-bus.md)** - Message broker integration
64
+ - **[Adapters](./docs/adapters.md)** - Runtime environment adapters
65
+ - **[API Documentation](./docs/)** - Full API reference
66
+
67
+ Online documentation: https://@nestjs-serverless-workflow.github.io/libraries/docs/workflow/intro
68
+
69
+ # Stateless Architecture
70
+
71
+ ## NestJS Workflow is designed with a stateless architecture, which offers several key benefits
72
+
73
+ Benefits of Stateless Design
74
+
75
+ - Simplicity: No additional database or storage configuration required
76
+ - Domain-Driven: State is maintained within your domain entities where it belongs
77
+ - Lightweight: Minimal overhead and dependencies
78
+ - Scalability: Easily scales horizontally with your application
79
+ - Flexibility: Works with any persistence layer or storage mechanism
80
+ - Integration: Seamlessly integrates with your existing data model and repositories
81
+ - The workflow engine doesn't maintain any state itself - instead, it operates on your domain entities, reading their current state and applying transitions according to your defined rules. This approach aligns with domain-driven design principles by keeping the state with the entity it belongs to.
82
+
83
+ This stateless design means you can:
84
+
85
+ Use your existing repositories and data access patterns
86
+ Persist workflow state alongside your entity data
87
+ Avoid complex synchronization between separate state stores
88
+ Maintain transactional integrity with your domain operations
89
+
90
+ ```
91
+ // Example of how state is part of your domain entity
92
+ export class Order {
93
+ id: string;
94
+ items: OrderItem[];
95
+ totalAmount: number;
96
+ status: OrderStatus; // The workflow state is a property of your entity
97
+
98
+ // Your domain logic here
99
+ }
100
+ ```
101
+
102
+ The workflow engine simply reads and updates this state property according to your defined transitions, without needing to maintain any separate state storage.
103
+
104
+ ## Installation
105
+
106
+ ```bash
107
+ npm install nestjs-serverless-workflow
108
+ ```
109
+
110
+ Or using bun:
111
+
112
+ ```bash
113
+ bun add nestjs-serverless-workflow
114
+ ```
115
+
116
+ Or using yarn:
117
+
118
+ ```bash
119
+ yarn add nestjs-serverless-workflow
120
+ ```
121
+
122
+ ### Peer Dependencies
123
+
124
+ This library requires the following peer dependencies:
125
+
126
+ ```bash
127
+ npm install @nestjs/common @nestjs/core reflect-metadata rxjs
128
+ ```
129
+
130
+ **Optional Dependencies** (only if you need specific features):
131
+
132
+ - For AWS Lambda adapter: `@types/aws-lambda`
133
+ - For SQS integration: `@aws-sdk/client-sqs`
134
+ - For DynamoDB: `@aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb`
135
+
136
+ ## Quick Start
137
+
138
+ ### 🎮 Try the Interactive Demos First
139
+
140
+ Before diving into code, experience workflows visually with our interactive demos:
141
+
142
+ ```bash
143
+ # Quick demo setup
144
+ git clone https://github.com/@nestjs-serverless-workflow-examples.git
145
+ cd nestjs-workflow-examples/01-user-onboarding
146
+ npm install && npm run demo
147
+ ```
148
+
149
+ You'll see an interactive workflow visualization like this:
150
+
151
+ ```
152
+ ╔══════════════╗ ┌──────────────┐ ┌────────────────┐
153
+ ║ REGISTERED ║ --> │EMAIL_VERIFIED│ --> │PROFILE_COMPLETE│
154
+ ╚══════════════╝ └──────────────┘ └────────────────┘
155
+ (current) ↓ ↓
156
+ ┌──────────────┐ ┌─────────────────┐
157
+ │ SUSPENDED │ │IDENTITY_VERIFIED│
158
+ └──────────────┘ └─────────────────┘
159
+
160
+ ╔══════════╗
161
+ ║ ACTIVE ║
162
+ ╚══════════╝
163
+ ```
164
+
165
+ **[🚀 Explore All Examples](https://github.com/@nestjs-serverless-workflow-examples)**
166
+
167
+ ### How It Works
168
+
169
+ When you configure SQS integration:
170
+
171
+ 1. The workflow engine will connect to the specified SQS queue
172
+ 2. It will subscribe to the topics you've defined in the `events` array
173
+ 3. When a message arrives on a subscribed topic, the workflow engine will:
174
+ - Map the topic to the corresponding workflow event
175
+ - Extract the entity URN from the message
176
+ - Load the entity using your defined `entity.load` function
177
+ - Emit the mapped workflow event with the Kafka message as payload
178
+
179
+ ### Complete Example with SQS Integration
180
+
181
+ ## Using Subpath Exports
182
+
183
+ The package uses modern subpath exports for better tree-shaking. Import only what you need:
184
+
185
+ ```typescript
186
+ import { WorkflowModule } from 'nestjs-serverless-workflow/workflow';
187
+ import { IBrokerPublisher } from 'nestjs-serverless-workflow/event-bus';
188
+ import { LambdaEventHandler } from 'nestjs-serverless-workflow/adapter';
189
+ import { UnretriableException } from 'nestjs-serverless-workflow/exception';
190
+ ```
191
+
192
+ This ensures that your bundle only includes the parts of the library you actually use, resulting in smaller bundle sizes.
193
+
194
+ ## Quick Start
195
+
196
+ ````typescript
197
+ import { Module } from '@nestjs/common';
198
+ import { WorkflowModule, Workflow, OnEvent, Payload, Entity } from 'nestjs-serverless-workflow/workflow';
199
+ import { OrderEntityService } from './order-entity.service';
200
+
201
+
202
+ // Define your entity and state/event enums
203
+ export enum OrderEvent {
204
+ Create = 'order.create',
205
+ Submit = 'order.submit',
206
+ Complete = 'order.complete',
207
+ Fail = 'order.fail',
208
+ }
209
+
210
+ export enum OrderStatus {
211
+ Pending = 'pending',
212
+ Processing = 'processing',
213
+ Completed = 'completed',
214
+ Failed = 'failed',
215
+ }
216
+
217
+ export class Order {
218
+ id: string;
219
+ name: string;
220
+ price: number;
221
+ items: string[];
222
+ status: OrderStatus;
223
+ }
224
+ @Workflow({
225
+ states: {
226
+ finals: [OrderStatus.Completed, OrderStatus.Failed],
227
+ idles: [OrderStatus.Pending, OrderStatus.Processing, OrderStatus.Completed, OrderStatus.Failed],
228
+ failed: OrderStatus.Failed,
229
+ },
230
+ transitions: [
231
+ // Your transitions here
232
+ {
233
+ from: OrderStatus.Pending,
234
+ to: OrderStatus.Processing,
235
+ event: OrderEvent.Submit,
236
+ conditions: [(entity: Order, payload: any) => entity.price > 10],
237
+ },
238
+ {
239
+ from: OrderStatus.Processing,
240
+ to: OrderStatus.Completed,
241
+ event: OrderEvent.Complete,
242
+ },
243
+ {
244
+ from: OrderStatus.Processing,
245
+ to: OrderStatus.Failed,
246
+ event: OrderEvent.Fail,
247
+ }
248
+ ],
249
+ };
250
+ })
251
+ class OrderWorkflowDefinition {
252
+ @OnEvent(OrderEvent.Submit)
253
+ async onSubmit(@Entity entity: Order, @Payload(YourClassValidatorDto) submitData): Promise<Order> {
254
+ // Custom logic on submit event
255
+ }
256
+ }
257
+
258
+ @Module({
259
+ imports: [
260
+ WorkflowModule.register({
261
+ providers: [
262
+ {
263
+ provide: OrderWorkflowDefinition,
264
+ useFactory: (orderEntityService: OrderEntityService, eventEmitter: EventEmitter2) => {
265
+ return new OrderWorkflowDefinition(orderEntityService, eventEmitter);
266
+ },
267
+ inject: [OrderEntityService, EventEmitter2]
268
+ }
269
+ ]
270
+ }),
271
+ ],
272
+ })
273
+ export class AppModule {}
274
+ ```
275
+
276
+ ### Message Format
277
+
278
+ The Kafka messages should include the entity URN so that the workflow engine can load the correct entity. For example:
279
+
280
+ ```json
281
+ {
282
+ "urn": "order-123",
283
+ "price": 150,
284
+ "items": ["Item 1", "Item 2"]
285
+ }
286
+ ```
287
+
288
+ With this setup, your workflow will automatically react to Kafka messages and trigger the appropriate state transitions based on your workflow definition.
289
+
290
+ ### Benefits of Using EntityService
291
+
292
+ Using a dedicated EntityService provides several advantages:
293
+
294
+ 1. **Separation of Concerns**: Keep entity management logic separate from workflow definitions
295
+ 2. **Dependency Injection**: Leverage NestJS dependency injection for your entity operations
296
+ 3. **Reusability**: Use the same EntityService across multiple workflows
297
+ 4. **Testability**: Easier to mock and test your entity operations
298
+ 5. **Database Integration**: Cleanly integrate with your database through repositories
299
+
300
+ This approach is particularly useful for complex applications where entities are stored in databases and require sophisticated loading and persistence logic.
301
+
302
+ ## 📚 Examples & Learning Resources
303
+
304
+ ### Interactive Examples Repository
305
+ The best way to learn is by exploring our **[comprehensive examples repository](https://github.com/@nestjs-serverless-workflow-examples)** which includes:
306
+
307
+ #### 1. User Onboarding Workflow Example
308
+ Demonstrates a real-world user registration and verification system:
309
+ - Progressive profile completion with automatic transitions
310
+ - Multi-factor authentication flows
311
+ - Risk assessment integration
312
+ - Compliance checks (KYC/AML)
313
+ - States: `REGISTERED` → `EMAIL_VERIFIED` → `PROFILE_COMPLETE` → `IDENTITY_VERIFIED` → `ACTIVE`
314
+
315
+ #### 2. E-Commerce Order Processing Example
316
+ Complete order lifecycle management system:
317
+ - Payment processing with retry logic
318
+ - Inventory reservation and management
319
+ - Multi-state shipping workflows
320
+ - Refund and return handling
321
+ - States: `CREATED` → `PAYMENT_PENDING` → `PAID` → `PROCESSING` → `SHIPPED` → `DELIVERED`
322
+
323
+ #### 3. Message-Driven Inventory Management
324
+ Event-driven inventory system with Message Brokers integration:
325
+ - Real-time stock level updates via Message Broker events
326
+ - Automatic reorder triggering
327
+ - Quality control and quarantine workflows
328
+ - Multi-warehouse support
329
+ - Special states for `QUARANTINE`, `AUDITING`, `DAMAGED`, `EXPIRED`
330
+
331
+ ### Running the Examples
332
+
333
+ ```bash
334
+ # Clone the examples repository
335
+ git clone https://github.com/@nestjs-serverless-workflow-examples.git
336
+ cd nestjs-workflow-examples
337
+
338
+ # Install all examples
339
+ npm run install:all
340
+
341
+ # Run interactive demos with visual workflow diagrams
342
+ npm run demo:user-onboarding # User onboarding demo
343
+ npm run demo:order-processing # Order processing demo
344
+ npm run demo:kafka-inventory # Kafka inventory demo
345
+ ```
346
+
347
+ The interactive demos feature:
348
+ - **ASCII-art workflow visualization** showing current state and possible transitions
349
+ - **Real-time state updates** as you interact with the workflow
350
+ - **Menu-driven interface** to trigger events and explore different paths
351
+ - **Automated scenarios** to demonstrate various workflow patterns
352
+
353
+ ## Advanced Usage
354
+ For more advanced usage, including custom actions, conditions, and event handling, please check the documentation and explore the examples repository.
355
+ ```
356
+
357
+ State Machine flow chart
358
+ ```mermaid
359
+ flowchart TD
360
+ Start["emit(event, urn, payload)"]
361
+ LoadEntity["loadEntity(urn)"]
362
+ Found{"entity found?"}
363
+ GetStatus["getEntityStatus(entity)"]
364
+ FindTransition["find matching TransitionEvent (event + state)"]
365
+ NoTransition{"transitionEvent found?"}
366
+ Fallback["call definition.fallback(...) if configured and return"]
367
+ ReturnEntity["return entity"]
368
+ DetermineNext["select concrete transition by evaluating conditions"]
369
+ EventActions["run actionsOnEvent(event) sequentially"]
370
+ InlineActions["run transition.actions (inline) sequentially"]
371
+ FailedCheck{"failed during actions?"}
372
+ UpdateStatus["updateEntityStatus(entity, nextStatus)"]
373
+ StatusActions["run actionsOnStatusChanged(from-to)"]
374
+ PostFailed{"failed after status-change actions?"}
375
+ IdleCheck{"isInIdleStatus(nextStatus)?"}
376
+ FinalCheck{"isInFailedStatus or final?"}
377
+ NextEvent["nextEvent(entity) -> event | null"]
378
+ LoopBack["repeat loop with new currentEvent/currentState"]
379
+ End["return updated entity"]
380
+
381
+ Start --> LoadEntity --> Found
382
+ Found -- no --> ReturnEntity
383
+ Found -- yes --> GetStatus --> FindTransition --> NoTransition
384
+ NoTransition -- no & fallback --> Fallback --> ReturnEntity
385
+ NoTransition -- no & no fallback --> ReturnEntity
386
+ NoTransition -- yes --> DetermineNext --> EventActions --> InlineActions --> FailedCheck
387
+ FailedCheck -- true --> UpdateStatus --> End
388
+ FailedCheck -- false --> UpdateStatus --> StatusActions --> PostFailed
389
+ PostFailed -- true --> UpdateStatus --> End
390
+ PostFailed -- false --> IdleCheck
391
+ IdleCheck -- true --> End
392
+ IdleCheck -- false --> FinalCheck
393
+ FinalCheck -- true --> End
394
+ FinalCheck -- false --> NextEvent --> LoopBack
395
+ ```
396
+ ````
@@ -0,0 +1,2 @@
1
+ export * from './lambda.adapater';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapter/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './lambda.adapater';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapter/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type INestApplicationContext } from '@nestjs/common';
2
+ import { type SQSHandler } from 'aws-lambda';
3
+ export declare const LambdaEventHandler: (app: INestApplicationContext) => SQSHandler;
4
+ //# sourceMappingURL=lambda.adapater.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lambda.adapater.d.ts","sourceRoot":"","sources":["../../src/adapter/lambda.adapater.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,eAAO,MAAM,kBAAkB,GAC5B,KAAK,uBAAuB,KAAG,UA+D/B,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { OrchestratorService } from '@/workflow';
2
+ // NOTDE:
3
+ // - ReportBatchItemFailures must be enabled on SQS event source mapping
4
+ // - Lambda must have sufficient timeout to process messages
5
+ // - maxReceiveCount should be set as high as possible in main queue
6
+ export const LambdaEventHandler = (app) => async (event, context) => {
7
+ // Calculate safety window (5 seconds before timeout)
8
+ const safetyWindowMs = context.getRemainingTimeInMillis() - 5000;
9
+ const workflowRouter = app.get(OrchestratorService);
10
+ // For timeout retry only
11
+ const batchItemFailures = [];
12
+ // Track processed records
13
+ const processedRecords = new Set();
14
+ // Create a promise that will resolve when we need to shut down
15
+ let shutdownResolver;
16
+ const shutdownPromise = new Promise((resolve) => {
17
+ shutdownResolver = resolve;
18
+ });
19
+ // Set timeout for graceful shutdown
20
+ const shutdownTimer = setTimeout(() => {
21
+ console.log(`Triggering graceful shutdown after ${safetyWindowMs}ms`);
22
+ shutdownResolver();
23
+ }, safetyWindowMs);
24
+ try {
25
+ const processingPromises = event.Records.map(async (record, i) => {
26
+ try {
27
+ const event = JSON.parse(record.body);
28
+ console.log('processing record ', i + 1);
29
+ console.log(event);
30
+ // Race between processing and shutdown
31
+ await Promise.race([
32
+ workflowRouter.transit(event),
33
+ shutdownPromise.then(() => {
34
+ console.log('Shutdown promise...');
35
+ // If we're shutting down and this promise hasn't completed,
36
+ // mark it as a failure so SQS can retry it
37
+ if (!processedRecords.has(record.messageId)) {
38
+ batchItemFailures.push({ itemIdentifier: record.messageId });
39
+ console.log(`Marked message ${record.messageId} for retry due to shutdown`);
40
+ }
41
+ }),
42
+ ]);
43
+ // Mark record as successfully processed
44
+ processedRecords.add(record.messageId);
45
+ }
46
+ catch (error) {
47
+ console.error(`Error processing message ${record.messageId}:`, error);
48
+ batchItemFailures.push({ itemIdentifier: record.messageId });
49
+ }
50
+ });
51
+ // Wait for all processing to finish, or for shutdown
52
+ await Promise.race([Promise.all(processingPromises), shutdownPromise]);
53
+ }
54
+ finally {
55
+ // Clean up timeout
56
+ clearTimeout(shutdownTimer);
57
+ }
58
+ console.log(`Completed processing. Failed items: ${batchItemFailures.length}`);
59
+ return {
60
+ batchItemFailures,
61
+ };
62
+ };
63
+ //# sourceMappingURL=lambda.adapater.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lambda.adapater.js","sourceRoot":"","sources":["../../src/adapter/lambda.adapater.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from './sqs/sqs.emitter';
2
+ export * from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/event-bus/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,SAAS,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './sqs/sqs.emitter';
2
+ export * from './types';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/event-bus/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,SAAS,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type IBrokerPublisher } from '../types/broker-publisher.interface';
2
+ import { type IWorkflowEvent } from '../types/workflow-event.interface';
3
+ export declare class SqsEmitter implements IBrokerPublisher {
4
+ emit<T>(_payload: IWorkflowEvent<T>): Promise<void>;
5
+ }
6
+ //# sourceMappingURL=sqs.emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqs.emitter.d.ts","sourceRoot":"","sources":["../../../src/event-bus/sqs/sqs.emitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAExE,qBAAa,UAAW,YAAW,gBAAgB;IAC3C,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAC1D"}
@@ -0,0 +1,4 @@
1
+ export class SqsEmitter {
2
+ async emit(_payload) { }
3
+ }
4
+ //# sourceMappingURL=sqs.emitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqs.emitter.js","sourceRoot":"","sources":["../../../src/event-bus/sqs/sqs.emitter.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,UAAU;IACrB,KAAK,CAAC,IAAI,CAAI,QAA2B,IAAkB,CAAC;CAC7D"}
@@ -0,0 +1,5 @@
1
+ import { type IWorkflowEvent } from './workflow-event.interface';
2
+ export interface IBrokerPublisher {
3
+ emit(event: IWorkflowEvent): Promise<void>;
4
+ }
5
+ //# sourceMappingURL=broker-publisher.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker-publisher.interface.d.ts","sourceRoot":"","sources":["../../../src/event-bus/types/broker-publisher.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=broker-publisher.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker-publisher.interface.js","sourceRoot":"","sources":["../../../src/event-bus/types/broker-publisher.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ export * from './broker-publisher.interface';
2
+ export * from './workflow-event.interface';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/event-bus/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './broker-publisher.interface';
2
+ export * from './workflow-event.interface';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/event-bus/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC"}
@@ -0,0 +1,7 @@
1
+ export interface IWorkflowEvent<T = any> {
2
+ topic: string;
3
+ urn: string | number;
4
+ payload?: T | object | string;
5
+ attempt: number;
6
+ }
7
+ //# sourceMappingURL=workflow-event.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-event.interface.d.ts","sourceRoot":"","sources":["../../../src/event-bus/types/workflow-event.interface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,GAAG;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workflow-event.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-event.interface.js","sourceRoot":"","sources":["../../../src/event-bus/types/workflow-event.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export * from './unretriable.exception';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exception/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './unretriable.exception';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exception/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare class UnretriableException extends Error {
2
+ constructor(message: string);
3
+ }
4
+ //# sourceMappingURL=unretriable.exception.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unretriable.exception.d.ts","sourceRoot":"","sources":["../../src/exception/unretriable.exception.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B"}
@@ -0,0 +1,7 @@
1
+ export class UnretriableException extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'UnretriableException';
5
+ }
6
+ }
7
+ //# sourceMappingURL=unretriable.exception.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unretriable.exception.js","sourceRoot":"","sources":["../../src/exception/unretriable.exception.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ export declare const WORKFLOW_DEFAULT_EVENT = "workflow.default";
2
+ export declare const OnDefault: (target: any, _propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
3
+ //# sourceMappingURL=default.decorator.d.ts.map