@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.
Files changed (2) hide show
  1. package/README.md +327 -0
  2. 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.1",
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",