From 0eeb1c08e305415178caad65e5430ddef1cc10be Mon Sep 17 00:00:00 2001 From: CahidArda Date: Wed, 21 Aug 2024 21:43:21 +0300 Subject: [PATCH] fix: change qstash/Qstash as QStash --- examples/workflow/nextjs/README.md | 6 ++- examples/workflow/nextjs/app/path/route.ts | 9 +++- examples/workflow/nuxt/README.md | 4 +- examples/workflow/solidjs/README.md | 4 +- examples/workflow/sveltekit/README.md | 4 +- src/client/client.ts | 2 +- src/client/error.ts | 8 ++-- src/client/llm/chat.test.ts | 4 +- src/client/workflow/auto-executor.test.ts | 44 ++++++++--------- src/client/workflow/auto-executor.ts | 48 +++++++++---------- src/client/workflow/context.test.ts | 36 +++++++------- src/client/workflow/context.ts | 10 ++-- src/client/workflow/integration.test.ts | 4 +- src/client/workflow/receiver.test.ts | 20 ++++---- src/client/workflow/serve.test.ts | 18 +++---- src/client/workflow/test-utils.ts | 4 +- src/client/workflow/workflow-parser.test.ts | 14 +++--- src/client/workflow/workflow-parser.ts | 20 ++++---- src/client/workflow/workflow-requests.test.ts | 12 ++--- src/client/workflow/workflow-requests.ts | 16 +++---- 20 files changed, 147 insertions(+), 140 deletions(-) diff --git a/examples/workflow/nextjs/README.md b/examples/workflow/nextjs/README.md index 10a78401..cc90baba 100644 --- a/examples/workflow/nextjs/README.md +++ b/examples/workflow/nextjs/README.md @@ -1,6 +1,8 @@ -# Qstash Workflow Nextjs Example +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fupstash%2Fqstash-js%2Ftree%2Fmain%2Fexamples%2Fworkflow%2Fnextjs&env=QSTASH_TOKEN&project-name=qstash-workflow&repository-name=qstash-workflow&demo-title=Upstash%20-%20QStash%20Workflow%20Example&demo-description=A%20Next.js%20Application%20Utilizing%20QStash%20Workflows) -This project has some routes showcasing how Qstash Workflow can be used in a nextjs project. +# QStash Workflow Nextjs Example + +This project has some routes showcasing how QStash Workflow can be used in a nextjs project. Under the app directory, you will find 10 folders, each corresponding to a workflow API except the `-call-qstash`. The user calls `-call-qstash` with information about which endpoint is to be called in the body. `-call-qstash` publishes a message to QStash. QStash then calls the specified endpoint. diff --git a/examples/workflow/nextjs/app/path/route.ts b/examples/workflow/nextjs/app/path/route.ts index a5a12268..80f79bd3 100644 --- a/examples/workflow/nextjs/app/path/route.ts +++ b/examples/workflow/nextjs/app/path/route.ts @@ -14,9 +14,14 @@ export const POST = serve( return output }); - const result2 = await context.run("step2", async () => { + const result2 = await Promise.all([ context.run("step2", async () => { const output = someWork(result1) console.log("step 2 input", result1, "output", output) - }); + }), context.run("step2", async () => { + const output = someWork(result1) + console.log("step 2 input", result1, "output", output) + })]); + + throw new Error("my -eror") }, ) diff --git a/examples/workflow/nuxt/README.md b/examples/workflow/nuxt/README.md index 0288b733..f1ff5c30 100644 --- a/examples/workflow/nuxt/README.md +++ b/examples/workflow/nuxt/README.md @@ -1,6 +1,6 @@ -# Qstash Workflow Nuxt Example +# QStash Workflow Nuxt Example -This project has some routes showcasing how Qstash Workflow can be used in a nuxt project. +This project has some routes showcasing how QStash Workflow can be used in a nuxt project. Under the `server/api` directory, you will find 6 files, each corresponding to a workflow API except the `-call-qstash`. diff --git a/examples/workflow/solidjs/README.md b/examples/workflow/solidjs/README.md index fdf74ce0..78bba8af 100644 --- a/examples/workflow/solidjs/README.md +++ b/examples/workflow/solidjs/README.md @@ -1,6 +1,6 @@ -# Qstash Workflow Solid.js Example +# QStash Workflow Solid.js Example -This project has some routes showcasing how Qstash Workflow can be used in a Solid.js project. +This project has some routes showcasing how QStash Workflow can be used in a Solid.js project. Under the `src/routes` directory, you will find 7 files. `index.tsx` is the landing page. Rest except the `-call-qstash` are the routes corresponding to a workflow API. diff --git a/examples/workflow/sveltekit/README.md b/examples/workflow/sveltekit/README.md index 2df4108b..b889800e 100644 --- a/examples/workflow/sveltekit/README.md +++ b/examples/workflow/sveltekit/README.md @@ -1,6 +1,6 @@ -# Qstash Workflow Sveltekit Example +# QStash Workflow Sveltekit Example -This project has some routes showcasing how Qstash Workflow can be used in a sveltekit project. +This project has some routes showcasing how QStash Workflow can be used in a sveltekit project. Under the `src/routes` directory, you will find 6 folders, each corresponding to a workflow API except the `-call-qstash`. diff --git a/src/client/client.ts b/src/client/client.ts index 88baf14c..fb2ead7b 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -13,7 +13,7 @@ import { Workflow } from "./workflow"; type ClientConfig = { /** - * Url of the qstash api server. + * Url of the QStash api server. * * This is only used for testing. * diff --git a/src/client/error.ts b/src/client/error.ts index a3c2955b..ae23827b 100644 --- a/src/client/error.ts +++ b/src/client/error.ts @@ -61,17 +61,17 @@ export class QstashDailyRatelimitError extends QstashError { /** * Error raised during Workflow execution */ -export class QstashWorkflowError extends QstashError { +export class QStashWorkflowError extends QstashError { constructor(message: string) { super(message); - this.name = "QstashWorkflowError"; + this.name = "QStashWorkflowError"; } } /** * Raised when the workflow executes a function and aborts */ -export class QstashWorkflowAbort extends Error { +export class QStashWorkflowAbort extends Error { public stepInfo?: Step; public stepName: string; @@ -81,7 +81,7 @@ export class QstashWorkflowAbort extends Error { " Make sure that you await for each step. Also, if you are using try/catch blocks, you should not wrap context.run/sleep/sleepUntil/call methods with try/catch." + ` Aborting workflow after executing step '${stepName}'.` ); - this.name = "QstashWorkflowAbort"; + this.name = "QStashWorkflowAbort"; this.stepName = stepName; this.stepInfo = stepInfo; } diff --git a/src/client/llm/chat.test.ts b/src/client/llm/chat.test.ts index 7a633196..629681b9 100644 --- a/src/client/llm/chat.test.ts +++ b/src/client/llm/chat.test.ts @@ -25,7 +25,7 @@ async function checkStream( expect(expectInStream.every((token) => text.includes(token))).toBeTrue(); } -describe("Test Qstash chat", () => { +describe("Test QStash chat", () => { const client = new Client({ token: process.env.QSTASH_TOKEN! }); test( @@ -173,7 +173,7 @@ describe("Test Qstash chat", () => { }); }); -describe("Test Qstash chat with third party LLMs", () => { +describe("Test QStash chat with third party LLMs", () => { const client = new Client({ token: process.env.QSTASH_TOKEN! }); test( diff --git a/src/client/workflow/auto-executor.test.ts b/src/client/workflow/auto-executor.test.ts index a417086d..04f66d6c 100644 --- a/src/client/workflow/auto-executor.test.ts +++ b/src/client/workflow/auto-executor.test.ts @@ -2,11 +2,11 @@ import { describe, expect, spyOn, test } from "bun:test"; import { WorkflowContext } from "./context"; import { Client } from "../client"; -import { MOCK_QSTASH_SERVER_URL, mockQstashServer, WORKFLOW_ENDPOINT } from "./test-utils"; +import { MOCK_QSTASH_SERVER_URL, mockQStashServer, WORKFLOW_ENDPOINT } from "./test-utils"; import { nanoid } from "nanoid"; import { AutoExecutor } from "./auto-executor"; import type { Step } from "./types"; -import { QstashWorkflowAbort, QstashWorkflowError } from "../error"; +import { QStashWorkflowAbort, QStashWorkflowError } from "../error"; class SpyAutoExecutor extends AutoExecutor { public declare getParallelCallState; @@ -101,13 +101,13 @@ describe("auto-executor", () => { const spyRunSingle = spyOn(context.executor, "runSingle"); const spyRunParallel = spyOn(context.executor, "runParallel"); - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { const throws = context.run("attemptCharge", async () => { return await Promise.resolve({ input: context.requestPayload, success: false }); }); - expect(throws).rejects.toThrowError(QstashWorkflowAbort); + expect(throws).rejects.toThrowError(QStashWorkflowAbort); }, responseFields: { status: 200, @@ -148,7 +148,7 @@ describe("auto-executor", () => { const spyRunSingle = spyOn(context.executor, "runSingle"); const spyRunParallel = spyOn(context.executor, "runParallel"); - await mockQstashServer({ + await mockQStashServer({ execute: async () => { expect(context.executor.stepCount).toBe(0); expect(context.executor.planStepCount).toBe(0); @@ -182,7 +182,7 @@ describe("auto-executor", () => { const spyRunSingle = spyOn(context.executor, "runSingle"); const spyRunParallel = spyOn(context.executor, "runParallel"); - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(context.executor.getParallelCallState(2, 1)).toBe("first"); @@ -190,7 +190,7 @@ describe("auto-executor", () => { context.sleep("sleep for some time", 123), context.sleepUntil("sleep until next day", 123_123), ]); - expect(throws).rejects.toThrowError(QstashWorkflowAbort); + expect(throws).rejects.toThrowError(QStashWorkflowAbort); }, responseFields: { status: 200, @@ -248,7 +248,7 @@ describe("auto-executor", () => { const spyRunSingle = spyOn(context.executor, "runSingle"); const spyRunParallel = spyOn(context.executor, "runParallel"); - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(context.executor.getParallelCallState(2, 1)).toBe("partial"); @@ -256,7 +256,7 @@ describe("auto-executor", () => { context.sleep("sleep for some time", 123), context.sleepUntil("sleep until next day", 123_123), ]); - expect(throws).rejects.toThrowError(QstashWorkflowAbort); + expect(throws).rejects.toThrowError(QStashWorkflowAbort); }, responseFields: { status: 200, @@ -300,7 +300,7 @@ describe("auto-executor", () => { const spyRunSingle = spyOn(context.executor, "runSingle"); const spyRunParallel = spyOn(context.executor, "runParallel"); - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(context.executor.getParallelCallState(2, 1)).toBe("partial"); @@ -308,7 +308,7 @@ describe("auto-executor", () => { context.sleep("sleep for some time", 123), context.sleepUntil("sleep until next day", 123_123), ]); - expect(throws).rejects.toThrowError(QstashWorkflowAbort); + expect(throws).rejects.toThrowError(QStashWorkflowAbort); }, responseFields: { status: 200, @@ -352,7 +352,7 @@ describe("auto-executor", () => { const spyRunSingle = spyOn(context.executor, "runSingle"); const spyRunParallel = spyOn(context.executor, "runParallel"); - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(context.executor.getParallelCallState(2, 1)).toBe("discard"); @@ -360,7 +360,7 @@ describe("auto-executor", () => { context.sleep("sleep for some time", 123), context.sleepUntil("sleep until next day", 123_123), ]); - expect(throws).rejects.toThrowError(QstashWorkflowAbort); + expect(throws).rejects.toThrowError(QStashWorkflowAbort); }, responseFields: { status: 200, @@ -386,7 +386,7 @@ describe("auto-executor", () => { const spyRunSingle = spyOn(context.executor, "runSingle"); const spyRunParallel = spyOn(context.executor, "runParallel"); - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(context.executor.getParallelCallState(2, 1)).toBe("last"); @@ -429,7 +429,7 @@ describe("auto-executor", () => { return await Promise.resolve(true); }); expect(throws).rejects.toThrow( - new QstashWorkflowError( + new QStashWorkflowError( "Incompatible step name. Expected 'wrongName', got 'attemptCharge' from the request" ) ); @@ -438,7 +438,7 @@ describe("auto-executor", () => { const context = getContext([initialStep, singleStep]); const throws = context.sleep("attemptCharge", 10); expect(throws).rejects.toThrow( - new QstashWorkflowError( + new QStashWorkflowError( "Incompatible step type. Expected 'SleepFor', got 'Run' from the request" ) ); @@ -455,7 +455,7 @@ describe("auto-executor", () => { context.sleepUntil("sleep until next day", 123_123), ]); expect(throws).rejects.toThrow( - new QstashWorkflowError( + new QStashWorkflowError( "Incompatible step name. Expected 'wrongName', got 'sleep for some time' from the request" ) ); @@ -469,7 +469,7 @@ describe("auto-executor", () => { context.sleepUntil("sleep until next day", 123_123), ]); expect(throws).rejects.toThrow( - new QstashWorkflowError( + new QStashWorkflowError( "Incompatible step type. Expected 'SleepUntil', got 'SleepFor' from the request" ) ); @@ -485,7 +485,7 @@ describe("auto-executor", () => { context.sleep("wrongName", 10), // wrong step name context.sleepUntil("sleep until next day", 123_123), ]); - expect(throws).rejects.toThrowError(QstashWorkflowAbort); + expect(throws).rejects.toThrowError(QStashWorkflowAbort); }); test("step type", () => { const context = getContext([initialStep, ...parallelSteps.slice(0, 3)]); @@ -495,7 +495,7 @@ describe("auto-executor", () => { context.sleepUntil("sleep for some time", 10), // wrong step type context.sleepUntil("sleep until next day", 123_123), ]); - expect(throws).rejects.toThrowError(QstashWorkflowAbort); + expect(throws).rejects.toThrowError(QStashWorkflowAbort); }); }); @@ -509,7 +509,7 @@ describe("auto-executor", () => { context.sleepUntil("sleep until next day", 123_123), ]); expect(throws).rejects.toThrowError( - new QstashWorkflowError( + new QStashWorkflowError( "Incompatible steps detected in parallel execution: Incompatible step name. Expected 'wrongName', got 'sleep for some time' from the request\n" + ' > Step Names from the request: ["sleep for some time","sleep until next day"]\n' + ' Step Types from the request: ["SleepFor","SleepUntil"]\n' + @@ -527,7 +527,7 @@ describe("auto-executor", () => { context.sleepUntil("sleep until next day", 123_123), ]); expect(throws).rejects.toThrowError( - new QstashWorkflowError( + new QStashWorkflowError( "Incompatible steps detected in parallel execution: Incompatible step type. Expected 'SleepUntil', got 'SleepFor' from the request\n" + ' > Step Names from the request: ["sleep for some time","sleep until next day"]\n' + ' Step Types from the request: ["SleepFor","SleepUntil"]\n' + diff --git a/src/client/workflow/auto-executor.ts b/src/client/workflow/auto-executor.ts index c878def1..06f61eb7 100644 --- a/src/client/workflow/auto-executor.ts +++ b/src/client/workflow/auto-executor.ts @@ -1,4 +1,4 @@ -import { QstashWorkflowAbort, QstashWorkflowError } from "../error"; +import { QStashWorkflowAbort, QStashWorkflowError } from "../error"; import type { WorkflowContext } from "./context"; import type { AsyncStepFunction, ParallelCallState, Step } from "./types"; import { type BaseLazyStep } from "./steps"; @@ -39,14 +39,14 @@ export class AutoExecutor { * * If a function is already executing (this.executingStep), this * means that there is a nested step which is not allowed. In this - * case, addStep throws QstashWorkflowError. + * case, addStep throws QStashWorkflowError. * * @param stepInfo step plan to add * @returns result of the step function */ public async addStep(stepInfo: BaseLazyStep) { if (this.executingStep) { - throw new QstashWorkflowError( + throw new QStashWorkflowError( "A step can not be run inside another step." + ` Tried to run '${stepInfo.stepName}' inside '${this.executingStep}'` ); @@ -131,7 +131,7 @@ export class AutoExecutor { step: resultStep, stepCount: this.stepCount, }); - await this.submitStepsToQstash([resultStep]); + await this.submitStepsToQStash([resultStep]); return resultStep.out as TResult; } @@ -159,7 +159,7 @@ export class AutoExecutor { if (parallelCallState !== "first" && plannedParallelStepCount !== parallelSteps.length) { // user has added/removed a parallel step - throw new QstashWorkflowError( + throw new QStashWorkflowError( `Incompatible number of parallel steps when call state was '${parallelCallState}'.` + ` Expected ${parallelSteps.length}, got ${plannedParallelStepCount} from the request.` ); @@ -182,7 +182,7 @@ export class AutoExecutor { const planSteps = parallelSteps.map((parallelStep, index) => parallelStep.getPlanStep(parallelSteps.length, initialStepCount + index) ); - await this.submitStepsToQstash(planSteps); + await this.submitStepsToQStash(planSteps); break; } case "partial": { @@ -190,11 +190,11 @@ export class AutoExecutor { * Being called by QStash to run one of the parallel steps. Last step in the steps list * indicates which step is to be run * - * Execute the step and call qstash with the result + * Execute the step and call QStash with the result */ const planStep = this.steps.at(-1); if (!planStep || planStep.targetStep === undefined) { - throw new QstashWorkflowError( + throw new QStashWorkflowError( `There must be a last step and it should have targetStep larger than 0.` + `Received: ${JSON.stringify(planStep)}` ); @@ -215,13 +215,13 @@ export class AutoExecutor { parallelSteps.length, planStep.targetStep ); - await this.submitStepsToQstash([resultStep]); + await this.submitStepsToQStash([resultStep]); } catch (error) { - if (error instanceof QstashWorkflowAbort) { + if (error instanceof QStashWorkflowAbort) { throw error; } - throw new QstashWorkflowError( - `Error submitting steps to qstash in partial parallel step execution: ${error}` + throw new QStashWorkflowError( + `Error submitting steps to QStash in partial parallel step execution: ${error}` ); } break; @@ -235,7 +235,7 @@ export class AutoExecutor { * This call to the API should be discarded: no operations are to be made. Parallel steps which are still * running will finish and call QStash eventually. */ - throw new QstashWorkflowAbort("discarded parallel"); + throw new QStashWorkflowAbort("discarded parallel"); } case "last": { /** @@ -309,11 +309,11 @@ export class AutoExecutor { * * @param steps steps to send */ - private async submitStepsToQstash(steps: Step[]) { + private async submitStepsToQStash(steps: Step[]) { // if there are no steps, something went wrong. Raise exception if (steps.length === 0) { - throw new QstashWorkflowError( - `Unable to submit steps to Qstash. Provided list is empty. Current step: ${this.stepCount}` + throw new QStashWorkflowError( + `Unable to submit steps to QStash. Provided list is empty. Current step: ${this.stepCount}` ); } @@ -369,7 +369,7 @@ export class AutoExecutor { }); // if the steps are sent successfully, abort to stop the current request - throw new QstashWorkflowAbort(steps[0].stepName, steps[0]); + throw new QStashWorkflowAbort(steps[0].stepName, steps[0]); } /** @@ -401,7 +401,7 @@ export class AutoExecutor { ) { return result[index] as TResult; } else { - throw new QstashWorkflowError( + throw new QStashWorkflowError( `Unexpected parallel call result while executing step ${index}: '${result}'. Expected ${lazyStepList.length} many items` ); } @@ -418,7 +418,7 @@ export class AutoExecutor { * from the incoming request; compare the step names and types to make sure * that they are the same. * - * Raises `QstashWorkflowError` if there is a difference. + * Raises `QStashWorkflowError` if there is a difference. * * @param lazyStep lazy step created during execution * @param stepFromRequest step parsed from incoming request @@ -426,14 +426,14 @@ export class AutoExecutor { const validateStep = (lazyStep: BaseLazyStep, stepFromRequest: Step): void => { // check step name if (lazyStep.stepName !== stepFromRequest.stepName) { - throw new QstashWorkflowError( + throw new QStashWorkflowError( `Incompatible step name. Expected '${lazyStep.stepName}',` + ` got '${stepFromRequest.stepName}' from the request` ); } // check type name if (lazyStep.stepType !== stepFromRequest.stepType) { - throw new QstashWorkflowError( + throw new QStashWorkflowError( `Incompatible step type. Expected '${lazyStep.stepType}',` + ` got '${stepFromRequest.stepType}' from the request` ); @@ -444,7 +444,7 @@ const validateStep = (lazyStep: BaseLazyStep, stepFromRequest: Step): void => { * validates that each lazy step and step from request has the same step * name and type using `validateStep` method. * - * If there is a difference, raises `QstashWorkflowError` with information + * If there is a difference, raises `QStashWorkflowError` with information * about the difference. * * @param lazySteps list of lazy steps created during parallel execution @@ -456,12 +456,12 @@ const validateParallelSteps = (lazySteps: BaseLazyStep[], stepsFromRequest: Step validateStep(lazySteps[index], stepFromRequest); } } catch (error) { - if (error instanceof QstashWorkflowError) { + if (error instanceof QStashWorkflowError) { const lazyStepNames = lazySteps.map((lazyStep) => lazyStep.stepName); const lazyStepTypes = lazySteps.map((lazyStep) => lazyStep.stepType); const requestStepNames = stepsFromRequest.map((step) => step.stepName); const requestStepTypes = stepsFromRequest.map((step) => step.stepType); - throw new QstashWorkflowError( + throw new QStashWorkflowError( `Incompatible steps detected in parallel execution: ${error.message}` + `\n > Step Names from the request: ${JSON.stringify(requestStepNames)}` + `\n Step Types from the request: ${JSON.stringify(requestStepTypes)}` + diff --git a/src/client/workflow/context.test.ts b/src/client/workflow/context.test.ts index eeaaea30..b7785462 100644 --- a/src/client/workflow/context.test.ts +++ b/src/client/workflow/context.test.ts @@ -1,11 +1,11 @@ /* eslint-disable unicorn/consistent-function-scoping */ /* eslint-disable @typescript-eslint/no-magic-numbers */ import { describe, test, expect } from "bun:test"; -import { MOCK_QSTASH_SERVER_URL, mockQstashServer, WORKFLOW_ENDPOINT } from "./test-utils"; +import { MOCK_QSTASH_SERVER_URL, mockQStashServer, WORKFLOW_ENDPOINT } from "./test-utils"; import { DisabledWorkflowContext, WorkflowContext } from "./context"; import { Client } from "../client"; import { nanoid } from "nanoid"; -import { QstashWorkflowAbort, QstashWorkflowError } from "../error"; +import { QStashWorkflowAbort, QStashWorkflowError } from "../error"; import type { RouteFunction } from "./types"; describe("context tests", () => { @@ -29,7 +29,7 @@ describe("context tests", () => { }); }; expect(throws).toThrow( - new QstashWorkflowError( + new QStashWorkflowError( "A step can not be run inside another step. Tried to run 'inner step' inside 'outer step'" ) ); @@ -51,7 +51,7 @@ describe("context tests", () => { }); }; expect(throws).toThrow( - new QstashWorkflowError( + new QStashWorkflowError( "A step can not be run inside another step. Tried to run 'inner sleep' inside 'outer step'" ) ); @@ -73,7 +73,7 @@ describe("context tests", () => { }); }; expect(throws).toThrow( - new QstashWorkflowError( + new QStashWorkflowError( "A step can not be run inside another step. Tried to run 'inner sleepUntil' inside 'outer step'" ) ); @@ -95,7 +95,7 @@ describe("context tests", () => { }); }; expect(throws).toThrow( - new QstashWorkflowError( + new QStashWorkflowError( "A step can not be run inside another step. Tried to run 'inner call' inside 'outer step'" ) ); @@ -111,7 +111,7 @@ describe("context tests", () => { workflowRunId: "wfr-id", }); - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { const throws = () => @@ -161,13 +161,13 @@ describe("disabled workflow context", () => { describe("should throw abort for each step kind", () => { test("run", async () => { let called = false; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { const throws = disabledContext.run("run-step", async () => { return await Promise.resolve(1); }); - expect(throws).rejects.toThrow(QstashWorkflowAbort); + expect(throws).rejects.toThrow(QStashWorkflowAbort); called = true; }, responseFields: { @@ -180,11 +180,11 @@ describe("disabled workflow context", () => { }); test("sleep", async () => { let called = false; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { const throws = disabledContext.sleep("sleep-step", 1); - expect(throws).rejects.toThrow(QstashWorkflowAbort); + expect(throws).rejects.toThrow(QStashWorkflowAbort); called = true; }, responseFields: { @@ -197,11 +197,11 @@ describe("disabled workflow context", () => { }); test("run", async () => { let called = false; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { const throws = disabledContext.sleepUntil("sleepUntil-step", 1); - expect(throws).rejects.toThrow(QstashWorkflowAbort); + expect(throws).rejects.toThrow(QStashWorkflowAbort); called = true; }, responseFields: { @@ -214,11 +214,11 @@ describe("disabled workflow context", () => { }); test("run", async () => { let called = false; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { const throws = disabledContext.call("call-step", "some-url", "GET"); - expect(throws).rejects.toThrow(QstashWorkflowAbort); + expect(throws).rejects.toThrow(QStashWorkflowAbort); called = true; }, responseFields: { @@ -247,7 +247,7 @@ describe("disabled workflow context", () => { }; let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { const result = await DisabledWorkflowContext.tryAuthentication(endpoint, disabledContext); expect(result.isOk()).toBeTrue(); @@ -270,7 +270,7 @@ describe("disabled workflow context", () => { }; let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { const result = await DisabledWorkflowContext.tryAuthentication(endpoint, disabledContext); expect(result.isOk()).toBeTrue(); @@ -293,7 +293,7 @@ describe("disabled workflow context", () => { }; let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { const result = await DisabledWorkflowContext.tryAuthentication(endpoint, disabledContext); expect(result.isErr()).toBeTrue(); diff --git a/src/client/workflow/context.ts b/src/client/workflow/context.ts index c5144154..06a8f81c 100644 --- a/src/client/workflow/context.ts +++ b/src/client/workflow/context.ts @@ -7,7 +7,7 @@ import type { BaseLazyStep } from "./steps"; import { LazyCallStep, LazyFunctionStep, LazySleepStep, LazySleepUntilStep } from "./steps"; import type { HTTPMethods } from "../types"; import type { WorkflowLogger } from "./logger"; -import { QstashWorkflowAbort } from "../error"; +import { QStashWorkflowAbort } from "../error"; import { Client } from "../client"; export class WorkflowContext { @@ -254,7 +254,7 @@ export class WorkflowContext { } /** - * Workflow context which throws QstashWorkflowAbort before running the steps. + * Workflow context which throws QStashWorkflowAbort before running the steps. * * Used for making a dry run before running any steps to check authentication. * @@ -284,14 +284,14 @@ export class DisabledWorkflowContext< private static readonly disabledMessage = "disabled-qstash-worklfow-run"; /** - * overwrite the WorkflowContext.addStep method to always raise QstashWorkflowAbort + * overwrite the WorkflowContext.addStep method to always raise QStashWorkflowAbort * error in order to stop the execution whenever we encounter a step. * * @param _step */ // eslint-disable-next-line @typescript-eslint/require-await protected async addStep(_step: BaseLazyStep): Promise { - throw new QstashWorkflowAbort(DisabledWorkflowContext.disabledMessage); + throw new QStashWorkflowAbort(DisabledWorkflowContext.disabledMessage); } /** @@ -323,7 +323,7 @@ export class DisabledWorkflowContext< try { await routeFunction(disabledContext); } catch (error) { - if (error instanceof QstashWorkflowAbort && error.stepName === this.disabledMessage) { + if (error instanceof QStashWorkflowAbort && error.stepName === this.disabledMessage) { return ok("step-found"); } return err(error as Error); diff --git a/src/client/workflow/integration.test.ts b/src/client/workflow/integration.test.ts index ac5880bd..9ca6bfa8 100644 --- a/src/client/workflow/integration.test.ts +++ b/src/client/workflow/integration.test.ts @@ -343,7 +343,7 @@ describe.skip("live serve tests", () => { } ); - test( + test.only( "call endpoint", async () => { const thirdPartyResult = "third-party-result"; @@ -379,7 +379,7 @@ describe.skip("live serve tests", () => { const finishState = new FinishState(); await testEndpoint({ finalCount: 7, - waitFor: 10_000, + waitFor: 12_000, initialPayload: "my-payload", finishState, routeFunction: async (context) => { diff --git a/src/client/workflow/receiver.test.ts b/src/client/workflow/receiver.test.ts index e801d3ff..8076a04f 100644 --- a/src/client/workflow/receiver.test.ts +++ b/src/client/workflow/receiver.test.ts @@ -18,7 +18,7 @@ import { serve } from "./serve"; import { getRequestBody, MOCK_QSTASH_SERVER_URL, - mockQstashServer, + mockQStashServer, WORKFLOW_ENDPOINT, } from "./test-utils"; import { Client } from "../client"; @@ -149,7 +149,7 @@ describe("receiver", () => { await endpoint(requestWithoutSignature); }; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(throws).toThrow( @@ -174,7 +174,7 @@ describe("receiver", () => { await endpoint(requestWithoutSignature); }; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(throws).toThrow( @@ -196,7 +196,7 @@ describe("receiver", () => { }); let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { called = true; await endpoint(requestWithHeader); @@ -233,7 +233,7 @@ describe("receiver", () => { await endpoint(thirdPartyRequestWithoutHeader); }; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(throws).toThrow( @@ -265,7 +265,7 @@ describe("receiver", () => { await endpoint(thirdPartyRequestWithoutHeader); }; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(throws).toThrow( @@ -296,7 +296,7 @@ describe("receiver", () => { }); let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { called = true; await endpoint(thirdPartyRequestWithHeader); @@ -344,7 +344,7 @@ describe("receiver", () => { await endpoint(requestWithoutHeader); }; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(throws).toThrow( @@ -370,7 +370,7 @@ describe("receiver", () => { await endpoint(requestWithoutHeader); }; - await mockQstashServer({ + await mockQStashServer({ // eslint-disable-next-line @typescript-eslint/require-await execute: async () => { expect(throws).toThrow( @@ -394,7 +394,7 @@ describe("receiver", () => { }); let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { called = true; await endpoint(thirdPartyRequestWithHeader); diff --git a/src/client/workflow/serve.test.ts b/src/client/workflow/serve.test.ts index 22fb75d8..790539c7 100644 --- a/src/client/workflow/serve.test.ts +++ b/src/client/workflow/serve.test.ts @@ -7,7 +7,7 @@ import { driveWorkflow, getRequest, MOCK_QSTASH_SERVER_URL, - mockQstashServer, + mockQStashServer, WORKFLOW_ENDPOINT, } from "./test-utils"; import { nanoid } from "nanoid"; @@ -44,7 +44,7 @@ describe("serve", () => { const initialPayload = nanoid(); const request = new Request(WORKFLOW_ENDPOINT, { method: "POST", body: initialPayload }); - await mockQstashServer({ + await mockQStashServer({ execute: async () => { await endpoint(request); }, @@ -184,7 +184,7 @@ describe("serve", () => { const request = getRequest(WORKFLOW_ENDPOINT, "wfr-bar", "my-payload", []); let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { // endpoint will throw an error, which will result in a 500 response // when used as an actual endpoint @@ -212,7 +212,7 @@ describe("serve", () => { const request = getRequest(WORKFLOW_ENDPOINT, "wfr-foo", "my-payload", []); let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { const response = await endpoint(request); const { workflowRunId, finishCondition } = (await response.json()) as { @@ -257,7 +257,7 @@ describe("serve", () => { ] const request = getRequest(WORKFLOW_ENDPOINT, "wfr-foo", "my-payload", stepsWithDuplicate); let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { const response = await endpoint(request); const { workflowRunId, finishCondition } = (await response.json()) as { @@ -283,7 +283,7 @@ describe("serve", () => { ] const request = getRequest(WORKFLOW_ENDPOINT, "wfr-foo", "my-payload", stepsWithDuplicate); let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { const response = await endpoint(request); const { workflowRunId, finishCondition } = (await response.json()) as { @@ -332,7 +332,7 @@ describe("serve", () => { receiver: undefined, }); let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { await endpoint(request); called = true; @@ -371,7 +371,7 @@ describe("serve", () => { failureUrl: myFailureEndpoint, }); let called = false; - await mockQstashServer({ + await mockQStashServer({ execute: async () => { await endpoint(request); called = true; @@ -419,7 +419,7 @@ describe("serve", () => { receiver: undefined, failureFunction: myFailureFunction, }); - await mockQstashServer({ + await mockQStashServer({ execute: async () => { await endpoint(request); called = true; diff --git a/src/client/workflow/test-utils.ts b/src/client/workflow/test-utils.ts index 3a6cd14f..68b8bf17 100644 --- a/src/client/workflow/test-utils.ts +++ b/src/client/workflow/test-utils.ts @@ -35,7 +35,7 @@ export type RequestFields = { * @param receivesRequest fields of the request sent to QStash as a result of running * `await execute()`. If set to false, we assert that no request is sent. */ -export const mockQstashServer = async ({ +export const mockQStashServer = async ({ execute, responseFields, receivesRequest, @@ -120,7 +120,7 @@ export const driveWorkflow = async ({ const steps: Step[] = []; for (const { stepsToAdd, responseFields, receivesRequest } of iterations) { steps.push(...stepsToAdd); - await mockQstashServer({ + await mockQStashServer({ execute: () => execute(initialPayload, steps), responseFields, receivesRequest, diff --git a/src/client/workflow/workflow-parser.test.ts b/src/client/workflow/workflow-parser.test.ts index dc3c7bee..0ba2d3fe 100644 --- a/src/client/workflow/workflow-parser.test.ts +++ b/src/client/workflow/workflow-parser.test.ts @@ -10,7 +10,7 @@ import { import { nanoid } from "nanoid"; import type { RawStep, Step, WorkflowServeOptions } from "./types"; import { getRequest, WORKFLOW_ENDPOINT } from "./test-utils"; -import { formatWorkflowError, QstashWorkflowError } from "../error"; +import { formatWorkflowError, QStashWorkflowError } from "../error"; import { Client } from "../client"; import { processOptions } from "./serve"; @@ -51,7 +51,7 @@ describe("Workflow Parser", () => { }); const throws = () => validateRequest(request); - expect(throws).toThrow(new QstashWorkflowError("Couldn't get workflow id from header")); + expect(throws).toThrow(new QStashWorkflowError("Couldn't get workflow id from header")); }); test("should throw when protocol version is incompatible", () => { @@ -64,7 +64,7 @@ describe("Workflow Parser", () => { const throws = () => validateRequest(request); expect(throws).toThrow( - new QstashWorkflowError( + new QStashWorkflowError( `Incompatible workflow sdk protocol version.` + ` Expected ${WORKFLOW_PROTOCOL_VERSION}, got ${requestProtocol} from the request.` ) @@ -114,7 +114,7 @@ describe("Workflow Parser", () => { const requestPayload = (await getPayload(request)) ?? ""; const throws = parseRequest(requestPayload, false); expect(throws).rejects.toThrow( - new QstashWorkflowError("Only first call can have an empty body") + new QStashWorkflowError("Only first call can have an empty body") ); }); @@ -554,7 +554,7 @@ describe("Workflow Parser", () => { const body = { status: 201, header: { myHeader: "value" }, - body: btoa(JSON.stringify(formatWorkflowError(new QstashWorkflowError(failMessage)))), + body: btoa(JSON.stringify(formatWorkflowError(new QStashWorkflowError(failMessage)))), url: WORKFLOW_ENDPOINT, sourceHeader: { Authorization: authorization, @@ -597,7 +597,7 @@ describe("Workflow Parser", () => { expect(result2.isOk() && result2.value === "not-failure-callback").toBeTrue(); }); - test("should throw QstashWorkflowError if header is set but function is not passed", async () => { + test("should throw QStashWorkflowError if header is set but function is not passed", async () => { const request = new Request(WORKFLOW_ENDPOINT, { headers: { [WORKFLOW_FAILURE_HEADER]: "true", @@ -606,7 +606,7 @@ describe("Workflow Parser", () => { const result = await handleFailure(request, "", client, initialPayloadParser); expect(result.isErr()).toBeTrue(); - expect(result.isErr() && result.error.name).toBe(QstashWorkflowError.name); + expect(result.isErr() && result.error.name).toBe(QStashWorkflowError.name); expect(result.isErr() && result.error.message).toBe( "Workflow endpoint is called to handle a failure," + " but a failureFunction is not provided in serve options." + diff --git a/src/client/workflow/workflow-parser.ts b/src/client/workflow/workflow-parser.ts index e37dba5c..af90998f 100644 --- a/src/client/workflow/workflow-parser.ts +++ b/src/client/workflow/workflow-parser.ts @@ -1,6 +1,6 @@ import type { Err, Ok } from "neverthrow"; import { err, ok } from "neverthrow"; -import { QstashWorkflowError } from "../error"; +import { QStashWorkflowError } from "../error"; import { NO_CONCURRENCY, WORKFLOW_FAILURE_HEADER, @@ -45,11 +45,11 @@ const decodeBase64 = (encodedString: string) => { }; /** - * Parses a request coming from Qstash. First parses the string as JSON, which will result + * Parses a request coming from QStash. First parses the string as JSON, which will result * in a list of objects with messageId & body fields. Body will be base64 encoded. * * Body of the first item will be the body of the first request received in the workflow API. - * Rest are steps in Qstash Workflow Step format. + * Rest are steps in QStash Workflow Step format. * * When returning steps, we add the initial payload as initial step. This is to make it simpler * in the rest of the code. @@ -146,7 +146,7 @@ const checkIfLastOneIsDuplicate = async ( const step = steps[index]; if (step.stepId === lastStepId && step.targetStep === lastTargetStepId) { const message = - `Qstash Workflow: The step '${step.stepName}' with id '${step.stepId}'` + + `QStash Workflow: The step '${step.stepName}' with id '${step.stepId}'` + " has run twice during workflow execution. Rest of the workflow will continue running as usual."; await debug?.log("WARN", "RESPONSE_DEFAULT", message); console.warn(message); @@ -160,7 +160,7 @@ const checkIfLastOneIsDuplicate = async ( * Validates the incoming request checking the workflow protocol * version and whether it is the first invocation. * - * Raises `QstashWorkflowError` if: + * Raises `QStashWorkflowError` if: * - it's not the first invocation and expected protocol version doesn't match * the request. * - it's not the first invocation but there is no workflow id in the headers. @@ -176,7 +176,7 @@ export const validateRequest = ( // if it's not the first invocation, verify that the workflow protocal version is correct if (!isFirstInvocation && versionHeader !== WORKFLOW_PROTOCOL_VERSION) { - throw new QstashWorkflowError( + throw new QStashWorkflowError( `Incompatible workflow sdk protocol version. Expected ${WORKFLOW_PROTOCOL_VERSION},` + ` got ${versionHeader} from the request.` ); @@ -187,7 +187,7 @@ export const validateRequest = ( ? `wfr_${nanoid()}` : request.headers.get(WORKFLOW_ID_HEADER) ?? ""; if (workflowRunId.length === 0) { - throw new QstashWorkflowError("Couldn't get workflow id from header"); + throw new QStashWorkflowError("Couldn't get workflow id from header"); } return { @@ -224,7 +224,7 @@ export const parseRequest = async ( } else { // if not the first invocation, make sure that body is not empty and parse payload if (!requestPayload) { - throw new QstashWorkflowError("Only first call can have an empty body"); + throw new QStashWorkflowError("Only first call can have an empty body"); } const { rawInitialPayload, steps } = parsePayload(requestPayload); const isLastDuplicate = await checkIfLastOneIsDuplicate(steps, debug); @@ -243,7 +243,7 @@ export const parseRequest = async ( * attempts to call the failureFunction function. * * If the header is set but failureFunction is not passed, returns - * QstashWorkflowError. + * QStashWorkflowError. * * @param request incoming request * @param failureFunction function to handle the failure @@ -264,7 +264,7 @@ export const handleFailure = async ( if (!failureFunction) { return err( - new QstashWorkflowError( + new QStashWorkflowError( "Workflow endpoint is called to handle a failure," + " but a failureFunction is not provided in serve options." + " Either provide a failureUrl or a failureFunction." diff --git a/src/client/workflow/workflow-requests.test.ts b/src/client/workflow/workflow-requests.test.ts index c32d025f..15d10b2e 100644 --- a/src/client/workflow/workflow-requests.test.ts +++ b/src/client/workflow/workflow-requests.test.ts @@ -9,7 +9,7 @@ import { triggerRouteFunction, triggerWorkflowDelete, } from "./workflow-requests"; -import { QstashWorkflowAbort } from "../error"; +import { QStashWorkflowAbort } from "../error"; import { WorkflowContext } from "./context"; import { Client } from "../client"; import type { Step, StepType } from "./types"; @@ -24,7 +24,7 @@ import { import { MOCK_QSTASH_SERVER_URL, MOCK_SERVER_URL, - mockQstashServer, + mockQStashServer, WORKFLOW_ENDPOINT, } from "./test-utils"; @@ -43,7 +43,7 @@ describe("Workflow Requests", () => { url: WORKFLOW_ENDPOINT, }); - await mockQstashServer({ + await mockQStashServer({ execute: async () => { const result = await triggerFirstInvocation(context); expect(result.isOk()).toBeTrue(); @@ -62,10 +62,10 @@ describe("Workflow Requests", () => { }); describe("triggerRouteFunction", () => { - test("should get step-finished when QstashWorkflowAbort is thrown", async () => { + test("should get step-finished when QStashWorkflowAbort is thrown", async () => { const result = await triggerRouteFunction({ onStep: () => { - throw new QstashWorkflowAbort("name"); + throw new QStashWorkflowAbort("name"); }, onCleanup: async () => { await Promise.resolve(); @@ -179,7 +179,7 @@ describe("Workflow Requests", () => { }); // create mock server and run the code - await mockQstashServer({ + await mockQStashServer({ execute: async () => { const result = await handleThirdPartyCallResult( request, diff --git a/src/client/workflow/workflow-requests.ts b/src/client/workflow/workflow-requests.ts index d143e7ce..0e71542f 100644 --- a/src/client/workflow/workflow-requests.ts +++ b/src/client/workflow/workflow-requests.ts @@ -1,6 +1,6 @@ import type { Err, Ok } from "neverthrow"; import { err, ok } from "neverthrow"; -import { QstashWorkflowAbort, QstashWorkflowError } from "../error"; +import { QStashWorkflowAbort, QStashWorkflowError } from "../error"; import type { WorkflowContext } from "./context"; import { DEFAULT_CONTENT_TYPE, @@ -60,14 +60,14 @@ export const triggerRouteFunction = async ({ onCleanup: () => Promise; }): Promise | Err> => { try { - // When onStep completes successfully, it throws an exception named `QstashWorkflowAbort`, indicating that the step has been successfully executed. + // When onStep completes successfully, it throws an exception named `QStashWorkflowAbort`, indicating that the step has been successfully executed. // This ensures that onCleanup is only called when no exception is thrown. await onStep(); await onCleanup(); return ok("workflow-finished"); } catch (error) { const error_ = error as Error; - return error_ instanceof QstashWorkflowAbort ? ok("step-finished") : err(error_); + return error_ instanceof QStashWorkflowAbort ? ok("step-finished") : err(error_); } }; @@ -113,7 +113,7 @@ export const recreateUserHeaders = (headers: Headers): Headers => { /** * Checks if the request is from a third party call result. If so, - * calls qstash to add the result to the ongoing workflow. + * calls QStash to add the result to the ongoing workflow. * * Otherwise, does nothing. * @@ -127,7 +127,7 @@ export const recreateUserHeaders = (headers: Headers): Headers => { * If so, we send back the result to QStash as a result step. * * @param request Incoming request - * @param client qstash client + * @param client QStash client * @returns */ export const handleThirdPartyCallResult = async ( @@ -150,7 +150,7 @@ export const handleThirdPartyCallResult = async ( // eslint-disable-next-line @typescript-eslint/no-magic-numbers if (!(callbackMessage.status >= 200 && callbackMessage.status < 300)) { await debug?.log("WARN", "SUBMIT_THIRD_PARTY_RESULT", callbackMessage); - // this callback will be retried by the qstash, we just ignore it + // this callback will be retried by the QStash, we just ignore it return ok("call-will-retry"); } @@ -225,7 +225,7 @@ export const handleThirdPartyCallResult = async ( } catch (error) { const isCallReturn = request.headers.get("Upstash-Workflow-Callback"); return err( - new QstashWorkflowError( + new QStashWorkflowError( `Error when handling call return (isCallReturn=${isCallReturn}): ${error}` ) ); @@ -327,7 +327,7 @@ export const verifyRequest = async ( throw new Error("Signature in `Upstash-Signature` header is not valid"); } } catch (error) { - throw new QstashWorkflowError( + throw new QStashWorkflowError( `Failed to verify that the Workflow request comes from QStash: ${error}\n\n` + "If signature is missing, trigger the workflow endpoint by publishing your request to QStash instead of calling it directly.\n\n" + "If you want to disable QStash Verification, you should clear env variables QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY"