#software-engineering ## Why Do We Need This? 1. better readability 2. better observability because step is basically a wrapper function ## How to Implement? ```js inngest.createFunction( { name: "Handle payments" }, { event: "api/invoice.created" }, async ({ event, step }) => { // Wait until the next billing date await step.sleepUntil(event.data.invoiceDate); // Steps automatically retry on error, and only run once on success - automatically, with no work. const charge = await step.run("Charge", async () => { return await stripe.charges.create({ amount: event.data.amount, }); }); await step.run("Update db", async () => { await db.payments.upsert(charge) }); await step.run("Send receipt", async () => { await resend.emails.send({ to: event.user.email, subject: "Your receipt for Inngest", }) }); } ) ``` 1. in each step we have clear tag and name. 2. we wrap the analytics in each tags > [!info] See more > The inspiration is taken from [Inngest - Effortless serverless queues, background jobs, and workflows](https://www.inngest.com/) 1. in the outermost scope we have event 2. event has step 3. step can have substep 4. step number is automatically incremented, 1 → 2 → 3 → 4 …, if there are substeps, they will 1.1 → 1.2 → 1.3 … 5. an event will have a context, the context includes the business information, steps inside the event can obtain the context 1. user id 2. user email 3. event name 4. api endpoint 5. form data 6. body data, etc… 6. a step is typed with return data type and possible error type ```ts const data = step.run(() => { const d1 = step.run(() => {}).possibleError(Service1Error) const d2 = step.run(() => {}).possibleError(Service2Error) const d3 = step.run(() => {}).possibleError(Service3Error) if (d2 > d3) throw new UserError }).possibleError(InternalServiceError) // then this step should have 5 types of error ``` 8. a step can has a `onError` method to determine how to handle the error 9. normally when a step throw an error, it will stop. but you can also use `next` to continue to next step with optional new data. ```ts const newData = step.run(() => {dosomething}).onError((e) => { // the error is typed // handle the error }).next(data) ``` 10. a step Error can be abstract, this will construct an wrapper error that wrap around all other error below ```ts // the step can throw 5 different types of error but we just call it Step1Error const data = step.run(() => {}).abstractError(Step1Error) ``` conclusion: 1. the api we design is very like [[Typesafe error|Typescript Effect]]