@synkro/core 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +327 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# @synkro/core
|
|
2
|
+
|
|
3
|
+
Lightweight workflow and state machine orchestrator. Define event-driven workflows via configuration or code. Supports Redis and in-memory transports.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Standalone Events** — Simple pub/sub event handlers
|
|
8
|
+
- **Sequential Workflows** — Multi-step workflows that execute in order, with state persistence
|
|
9
|
+
- **Conditional Routing** — Branch to different steps based on handler success or failure
|
|
10
|
+
- **Workflow Chaining** — Trigger follow-up workflows on completion, success, or failure
|
|
11
|
+
- **Retry Support** — Configurable retry logic per step
|
|
12
|
+
- **Transport Options** — Redis for production or in-memory for simple projects and local development
|
|
13
|
+
- **Simple API** — Single `Synkro` class with minimal configuration
|
|
14
|
+
- **TypeScript** — Full type support out of the box
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @synkro/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### In-Memory (no external dependencies)
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { Synkro } from "@synkro/core";
|
|
28
|
+
|
|
29
|
+
const synkro = await Synkro.start({
|
|
30
|
+
transport: "in-memory",
|
|
31
|
+
events: [
|
|
32
|
+
{
|
|
33
|
+
type: "UserSignedUp",
|
|
34
|
+
handler: async (ctx) => {
|
|
35
|
+
console.log("New user:", ctx.payload);
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await synkro.publish("UserSignedUp", { email: "user@example.com" });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Redis (scalable, multi-instance)
|
|
45
|
+
|
|
46
|
+
> Requires a running Redis instance.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { Synkro } from "@synkro/core";
|
|
50
|
+
|
|
51
|
+
const synkro = await Synkro.start({
|
|
52
|
+
transport: "redis",
|
|
53
|
+
redisUrl: "redis://localhost:6379",
|
|
54
|
+
events: [
|
|
55
|
+
{
|
|
56
|
+
type: "UserSignedUp",
|
|
57
|
+
handler: async (ctx) => {
|
|
58
|
+
console.log("New user:", ctx.payload);
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await synkro.publish("UserSignedUp", { email: "user@example.com" });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> The in-memory transport is ideal for simple projects, local development, and testing. For production workloads that require scaling across multiple instances, use Redis.
|
|
68
|
+
|
|
69
|
+
## Workflows
|
|
70
|
+
|
|
71
|
+
Define multi-step sequential workflows. Each step runs after the previous one completes, with state automatically persisted.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const synkro = await Synkro.start({
|
|
75
|
+
transport: "redis",
|
|
76
|
+
redisUrl: "redis://localhost:6379",
|
|
77
|
+
workflows: [
|
|
78
|
+
{
|
|
79
|
+
name: "ProcessOrder",
|
|
80
|
+
steps: [
|
|
81
|
+
{
|
|
82
|
+
type: "ValidateStock",
|
|
83
|
+
handler: async (ctx) => {
|
|
84
|
+
console.log("Checking stock for order:", ctx.requestId);
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "ProcessPayment",
|
|
89
|
+
handler: async (ctx) => {
|
|
90
|
+
console.log("Processing payment...");
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: "SendConfirmation",
|
|
95
|
+
handler: async (ctx) => {
|
|
96
|
+
console.log("Order confirmed!");
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Triggers all 3 steps in sequence
|
|
105
|
+
await synkro.publish("ProcessOrder", { orderId: "abc-123", amount: 49.99 });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Conditional Routing
|
|
109
|
+
|
|
110
|
+
Use `onSuccess` and `onFailure` on a step to branch to different steps based on the handler outcome. If a handler throws (after all retries), the workflow routes to the `onFailure` step. On success, it routes to the `onSuccess` step.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
{
|
|
114
|
+
name: "ProcessDocument",
|
|
115
|
+
steps: [
|
|
116
|
+
{
|
|
117
|
+
type: "RunOCR",
|
|
118
|
+
handler: ocrHandler,
|
|
119
|
+
retry: { maxRetries: 2 },
|
|
120
|
+
onSuccess: "ProcessingSucceeded",
|
|
121
|
+
onFailure: "ProcessingFailed",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: "ProcessingSucceeded",
|
|
125
|
+
handler: async (ctx) => {
|
|
126
|
+
console.log("OCR completed successfully");
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
type: "ProcessingFailed",
|
|
131
|
+
handler: async (ctx) => {
|
|
132
|
+
console.log("OCR failed, notifying support");
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Steps referenced by `onSuccess`/`onFailure` are treated as branch targets. When a branch target completes, the workflow skips over sibling branch targets and advances to the next regular step (if any), or completes.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
{
|
|
143
|
+
name: "ProcessOrder",
|
|
144
|
+
steps: [
|
|
145
|
+
{
|
|
146
|
+
type: "Payment",
|
|
147
|
+
handler: paymentHandler,
|
|
148
|
+
onSuccess: "PaymentCompleted",
|
|
149
|
+
onFailure: "PaymentFailed",
|
|
150
|
+
},
|
|
151
|
+
{ type: "PaymentCompleted", handler: completedHandler },
|
|
152
|
+
{ type: "PaymentFailed", handler: failedHandler },
|
|
153
|
+
{ type: "SendNotification", handler: notifyHandler }, // runs after either branch
|
|
154
|
+
],
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Steps without `onSuccess`/`onFailure` advance sequentially as before.
|
|
159
|
+
|
|
160
|
+
### Workflow Chaining
|
|
161
|
+
|
|
162
|
+
Trigger follow-up workflows when a workflow finishes:
|
|
163
|
+
|
|
164
|
+
- **`onSuccess`** — starts a workflow when the current one completes successfully
|
|
165
|
+
- **`onFailure`** — starts a workflow when the current one fails
|
|
166
|
+
- **`onComplete`** — starts a workflow regardless of outcome (runs after `onSuccess`/`onFailure`)
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const workflows = [
|
|
170
|
+
{
|
|
171
|
+
name: "ProcessOrder",
|
|
172
|
+
onSuccess: "StartShipment",
|
|
173
|
+
onFailure: "HandleError",
|
|
174
|
+
onComplete: "NotifyCustomer",
|
|
175
|
+
steps: [
|
|
176
|
+
{ type: "ValidateStock", handler: stockHandler },
|
|
177
|
+
{ type: "ProcessPayment", handler: paymentHandler },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "StartShipment",
|
|
182
|
+
steps: [
|
|
183
|
+
{ type: "ShipOrder", handler: shipHandler },
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "HandleError",
|
|
188
|
+
steps: [
|
|
189
|
+
{ type: "LogError", handler: errorHandler },
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "NotifyCustomer",
|
|
194
|
+
steps: [
|
|
195
|
+
{ type: "SendEmail", handler: emailHandler },
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Chained workflows inherit the same `requestId` and `payload` from the completed workflow.
|
|
202
|
+
|
|
203
|
+
### Retry
|
|
204
|
+
|
|
205
|
+
Configure retries per step. The handler will be retried up to `maxRetries` times before being considered failed.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
{
|
|
209
|
+
type: "ProcessPayment",
|
|
210
|
+
handler: paymentHandler,
|
|
211
|
+
retry: { maxRetries: 3 },
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## API
|
|
216
|
+
|
|
217
|
+
### `Synkro.start(options): Promise<Synkro>`
|
|
218
|
+
|
|
219
|
+
Creates and returns a running instance.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
type SynkroOptions = {
|
|
223
|
+
transport: "redis" | "in-memory";
|
|
224
|
+
redisUrl?: string; // required when transport is "redis"
|
|
225
|
+
debug?: boolean;
|
|
226
|
+
events?: SynkroEvent[];
|
|
227
|
+
workflows?: SynkroWorkflow[];
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### `synkro.on(eventType, handler, retry?): void`
|
|
232
|
+
|
|
233
|
+
Registers an event handler at runtime.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
synkro.on("StockUpdate", async (ctx) => {
|
|
237
|
+
console.log(ctx.requestId, ctx.payload);
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### `synkro.publish(event, payload?, requestId?): Promise<string>`
|
|
242
|
+
|
|
243
|
+
Publishes an event or starts a workflow. Returns a `requestId` for correlation. A UUID is generated by default, but you can provide your own ID.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
// Auto-generated UUID
|
|
247
|
+
const id = await synkro.publish("UserSignedUp", { email: "user@example.com" });
|
|
248
|
+
|
|
249
|
+
// Custom request ID
|
|
250
|
+
const id = await synkro.publish("UserSignedUp", { email: "user@example.com" }, "my-custom-id");
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `ctx.publish(event, payload?, requestId?): Promise<string>`
|
|
254
|
+
|
|
255
|
+
Publishes an event or starts a workflow from inside a handler. Same signature as `synkro.publish`.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
synkro.on("OrderCompleted", async (ctx) => {
|
|
259
|
+
const { orderId } = ctx.payload as { orderId: string };
|
|
260
|
+
await ctx.publish("SendInvoice", { orderId });
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### `ctx.setPayload(data): void`
|
|
265
|
+
|
|
266
|
+
Merges the given object into `ctx.payload`. The updated payload propagates to subsequent workflow steps and completion/failure events.
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
synkro.on("ValidateStock", async (ctx) => {
|
|
270
|
+
const available = true;
|
|
271
|
+
ctx.setPayload({ stockAvailable: available });
|
|
272
|
+
// ctx.payload is now { ...originalPayload, stockAvailable: true }
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `synkro.stop(): Promise<void>`
|
|
277
|
+
|
|
278
|
+
Disconnects the transport and cleans up resources.
|
|
279
|
+
|
|
280
|
+
## Types
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
type RetryConfig = {
|
|
284
|
+
maxRetries: number;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
type SynkroEvent = {
|
|
288
|
+
type: string;
|
|
289
|
+
handler: HandlerFunction;
|
|
290
|
+
retry?: RetryConfig;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
type SynkroWorkflow = {
|
|
294
|
+
name: string;
|
|
295
|
+
steps: SynkroWorkflowStep[];
|
|
296
|
+
onComplete?: string;
|
|
297
|
+
onSuccess?: string;
|
|
298
|
+
onFailure?: string;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
type SynkroWorkflowStep = {
|
|
302
|
+
type: string;
|
|
303
|
+
handler: HandlerFunction;
|
|
304
|
+
retry?: RetryConfig;
|
|
305
|
+
onSuccess?: string;
|
|
306
|
+
onFailure?: string;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
type HandlerCtx = {
|
|
310
|
+
requestId: string;
|
|
311
|
+
payload: unknown;
|
|
312
|
+
publish: PublishFunction;
|
|
313
|
+
setPayload: (data: Record<string, unknown>) => void;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
type PublishFunction = (
|
|
317
|
+
event: string,
|
|
318
|
+
payload?: unknown,
|
|
319
|
+
requestId?: string,
|
|
320
|
+
) => Promise<string>;
|
|
321
|
+
|
|
322
|
+
type HandlerFunction = (ctx: HandlerCtx) => void | Promise<void>;
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## License
|
|
326
|
+
|
|
327
|
+
ISC
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synkro/core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Lightweight workflow and state machine orchestrator powered by Redis. Define event-driven workflows via configuration or code.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|