Skip to content

Commit 98f5abb

Browse files
committed
fix: rm useFailureFunction option and allow failure callback without failure function in serve
1 parent 89c99da commit 98f5abb

File tree

8 files changed

+52
-61
lines changed

8 files changed

+52
-61
lines changed

src/client/index.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ describe("workflow client", () => {
108108
async () => {
109109
const { workflowRunId } = await liveClient.trigger({
110110
url: "http://requestcatcher.com",
111-
useFailureFunction: true,
112111
});
113112

114113
const cancel = await liveClient.cancel({
@@ -244,6 +243,18 @@ describe("workflow client", () => {
244243
"upstash-telemetry-runtime": expect.stringMatching(/bun@/),
245244
"upstash-telemetry-sdk": expect.stringContaining("@upstash/workflow"),
246245
"upstash-workflow-sdk-version": "1",
246+
"upstash-failure-callback-forward-upstash-label": "test-label",
247+
"upstash-failure-callback": "https://requestcatcher.com/api",
248+
"upstash-failure-callback-feature-set": "LazyFetch,InitialBody,WF_DetectTrigger",
249+
"upstash-failure-callback-forward-upstash-workflow-failure-callback": "true",
250+
"upstash-failure-callback-forward-upstash-workflow-is-failure": "true",
251+
"upstash-failure-callback-forward-user-header": "user-header-value",
252+
"upstash-failure-callback-retries": "15",
253+
"upstash-failure-callback-retry-delay": "1000",
254+
"upstash-failure-callback-workflow-calltype": "failureCall",
255+
"upstash-failure-callback-workflow-init": "false",
256+
"upstash-failure-callback-workflow-runid": `wfr_${myWorkflowRunId}`,
257+
"upstash-failure-callback-workflow-url": "https://requestcatcher.com/api",
247258
},
248259
body,
249260
},
@@ -277,7 +288,6 @@ describe("workflow client", () => {
277288
retries: 15,
278289
retryDelay: "2000",
279290
notBefore: new Date("2100-01-01T00:00:00Z").getTime() / 1000,
280-
useFailureFunction: true,
281291
},
282292
]);
283293
expect(result).toEqual([
@@ -312,6 +322,17 @@ describe("workflow client", () => {
312322
"upstash-telemetry-runtime": expect.stringMatching(/bun@/),
313323
"upstash-telemetry-sdk": expect.stringContaining("@upstash/workflow"),
314324
"upstash-workflow-sdk-version": "1",
325+
"upstash-failure-callback": "https://requestcatcher.com/api",
326+
"upstash-failure-callback-feature-set": "LazyFetch,InitialBody,WF_DetectTrigger",
327+
"upstash-failure-callback-forward-upstash-workflow-failure-callback": "true",
328+
"upstash-failure-callback-forward-upstash-workflow-is-failure": "true",
329+
"upstash-failure-callback-forward-user-header": "user-header-value",
330+
"upstash-failure-callback-retries": "15",
331+
"upstash-failure-callback-retry-delay": "1000",
332+
"upstash-failure-callback-workflow-calltype": "failureCall",
333+
"upstash-failure-callback-workflow-init": "false",
334+
"upstash-failure-callback-workflow-runid": `wfr_${myWorkflowRunId}`,
335+
"upstash-failure-callback-workflow-url": "https://requestcatcher.com/api",
315336
},
316337
body,
317338
},

src/client/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export class Client {
240240
const options = isBatchInput ? params : [params];
241241

242242
const invocations = options.map((option) => {
243-
const failureUrl = option.useFailureFunction ? option.url : option.failureUrl;
243+
const failureUrl = option.failureUrl ?? option.url;
244244
const finalWorkflowRunId = getWorkflowRunId(option.workflowRunId);
245245

246246
const context = new WorkflowContext({

src/client/types.ts

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -427,34 +427,11 @@ export type TriggerOptions = {
427427
* @default false
428428
*/
429429
disableTelemetry?: boolean;
430-
} & (
431-
| {
432-
/**
433-
* URL to call if the first request to the workflow endpoint fails
434-
*/
435-
failureUrl?: never;
436-
/**
437-
* Whether the workflow endpoint has a failure function
438-
* defined to be invoked if the first request fails.
439-
*
440-
* If true, the failureUrl will be ignored.
441-
*/
442-
useFailureFunction?: true;
443-
}
444-
| {
445-
/**
446-
* URL to call if the first request to the workflow endpoint fails
447-
*/
448-
failureUrl?: string;
449-
/**
450-
* Whether the workflow endpoint has a failure function
451-
* defined to be invoked if the first request fails.
452-
*
453-
* If true, the failureUrl will be ignored.
454-
*/
455-
useFailureFunction?: never;
456-
}
457-
);
430+
/**
431+
* URL to call if the first request to the workflow endpoint fails
432+
*/
433+
failureUrl?: string;
434+
};
458435

459436
export type DLQResumeRestartOptions<TDLQId extends string | string[] = string | string[]> = {
460437
dlqId: TDLQId;

src/serve/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,17 @@ export const serveBase = <
148148
if (failureCheck.isErr()) {
149149
// unexpected error during handleFailure
150150
throw failureCheck.error;
151-
} else if (failureCheck.value.result === "is-failure-callback") {
151+
} else if (failureCheck.value.result === "failure-function-executed") {
152152
// is a failure ballback.
153153
await debug?.log("WARN", "RESPONSE_DEFAULT", "failureFunction executed");
154-
return onStepFinish(workflowRunId, "failure-callback", {
155-
condition: "failure-callback",
154+
return onStepFinish(workflowRunId, "failure-callback-executed", {
155+
condition: "failure-callback-executed",
156156
result: failureCheck.value.response,
157157
});
158+
} else if (failureCheck.value.result === "failure-function-undefined") {
159+
return onStepFinish(workflowRunId, "failure-callback-undefined", {
160+
condition: "failure-callback-undefined",
161+
});
158162
}
159163

160164
const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");

src/serve/options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export const processOptions = <TResponse extends Response = Response, TInitialPa
8181
},
8282
status: 429,
8383
}) as TResponse;
84-
} else if (detailedFinishCondition?.condition === "failure-callback") {
84+
} else if (detailedFinishCondition?.condition === "failure-callback-executed") {
8585
return new Response(
8686
JSON.stringify({ result: detailedFinishCondition.result ?? undefined }),
8787
{

src/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,14 @@ export type FinishCondition =
132132
| "duplicate-step"
133133
| "fromCallback"
134134
| "auth-fail"
135-
| "failure-callback"
135+
| "failure-callback-executed"
136+
| "failure-callback-undefined"
136137
| "workflow-already-ended"
137138
| WorkflowNonRetryableError;
138139

139140
export type DetailedFinishCondition =
140141
| {
141-
condition: Exclude<FinishCondition, WorkflowNonRetryableError | "failure-callback">;
142+
condition: Exclude<FinishCondition, WorkflowNonRetryableError | "failure-callback-executed">;
142143
result?: never;
143144
}
144145
| {
@@ -150,7 +151,7 @@ export type DetailedFinishCondition =
150151
result: WorkflowRetryAfterError;
151152
}
152153
| {
153-
condition: "failure-callback";
154+
condition: "failure-callback-executed";
154155
result: string | void;
155156
};
156157

src/workflow-parser.test.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ describe("Workflow Parser", () => {
874874
expect(calledFailureFunction).toBeTrue();
875875
});
876876

877-
test("should throw WorkflowError if header is set but function is not passed", async () => {
877+
test("should not throw if failure callback but there is no failure function", async () => {
878878
let called = false;
879879
const routeFunction = async (context: WorkflowContext) => {
880880
called = true;
@@ -893,14 +893,9 @@ describe("Workflow Parser", () => {
893893
undefined,
894894
undefined
895895
);
896-
expect(result.isErr()).toBeTrue();
897-
expect(result.isErr() && result.error.name).toBe(WorkflowError.name);
898-
expect(result.isErr() && result.error.message).toBe(
899-
"Workflow endpoint is called to handle a failure," +
900-
" but a failureFunction is not provided in serve options." +
901-
" Either provide a failureUrl or a failureFunction."
902-
);
903-
expect(called).toBeFalse(); // not called since we threw before auth check
896+
expect(result.isOk()).toBeTrue();
897+
expect(result.isOk() && result.value.result === "failure-function-undefined").toBeTrue();
898+
expect(called).toBeFalse(); // not called since we returned before auth check
904899
});
905900

906901
test("should return error when the failure function throws an error", async () => {
@@ -965,7 +960,7 @@ describe("Workflow Parser", () => {
965960

966961
expect(result.isOk()).toBeTrue();
967962
expect(result.isOk() && result.value).toEqual({
968-
result: "is-failure-callback",
963+
result: "failure-function-executed",
969964
response: failureFunctionResponse,
970965
});
971966
expect(called).toBeTrue();

src/workflow-parser.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,10 @@ const checkIfLastOneIsDuplicate = async (
140140
debug?: WorkflowLogger
141141
): Promise<boolean> => {
142142
// return false if the length is 0 or 1
143-
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
144143
if (steps.length < 2) {
145144
return false;
146145
}
147146

148-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
149147
const lastStep = steps.at(-1)!;
150148
const lastStepId = lastStep.stepId;
151149
const lastTargetStepId = lastStep.targetStep;
@@ -309,8 +307,9 @@ export const handleFailure = async <TInitialPayload>(
309307
debug?: WorkflowLogger
310308
): Promise<
311309
| Ok<
312-
| { result: "is-failure-callback"; response: string | void }
313-
| { result: "not-failure-callback" },
310+
| { result: "not-failure-callback" }
311+
| { result: "failure-function-executed"; response: string | void }
312+
| { result: "failure-function-undefined" },
314313
never
315314
>
316315
| Err<never, Error>
@@ -320,13 +319,7 @@ export const handleFailure = async <TInitialPayload>(
320319
}
321320

322321
if (!failureFunction) {
323-
return err(
324-
new WorkflowError(
325-
"Workflow endpoint is called to handle a failure," +
326-
" but a failureFunction is not provided in serve options." +
327-
" Either provide a failureUrl or a failureFunction."
328-
)
329-
);
322+
return ok({ result: "failure-function-undefined" });
330323
}
331324

332325
try {
@@ -390,7 +383,7 @@ export const handleFailure = async <TInitialPayload>(
390383
if (authCheck.isErr()) {
391384
// got error while running until first step
392385
await debug?.log("ERROR", "ERROR", { error: authCheck.error.message });
393-
throw authCheck.error;
386+
return err(authCheck.error);
394387
} else if (authCheck.value === "run-ended") {
395388
// finished routeFunction while trying to run until first step.
396389
// either there is no step or auth check resulted in `return`
@@ -404,7 +397,7 @@ export const handleFailure = async <TInitialPayload>(
404397
failHeaders: header,
405398
failStack,
406399
});
407-
return ok({ result: "is-failure-callback", response: failureResponse });
400+
return ok({ result: "failure-function-executed", response: failureResponse });
408401
} catch (error) {
409402
return err(error as Error);
410403
}

0 commit comments

Comments
 (0)