Skip to content

DX-2238: add missing bulk action methods to sdk#180

Merged
CahidArda merged 30 commits intomainfrom
DX-2238
Mar 30, 2026
Merged

DX-2238: add missing bulk action methods to sdk#180
CahidArda merged 30 commits intomainfrom
DX-2238

Conversation

@alitariksahin
Copy link
Copy Markdown
Contributor

@alitariksahin alitariksahin commented Feb 6, 2026

client.cancel() — New filter parameters

Previously: Only supported ids, urlStartingWith, and all. Returned void.

Now: Supports additional filter parameters that can be combined. Returns { cancelled: number }.

Parameter Type Description
ids string | string[] Cancel by workflow run ID(s)
urlStartingWith string Cancel workflows whose URL starts with this prefix (cannot combine with workflowUrl)
workflowUrl string Cancel workflows matching this exact URL (cannot combine with urlStartingWith)
fromDate Date | number | string Cancel workflows created after this date
toDate Date | number | string Cancel workflows created before this date
label string Cancel workflows with this label
all boolean Cancel all workflows

workflowUrl vs urlStartingWith: workflowUrl sends workflowUrlExactMatch: true in the request body. These two parameters are mutually exclusive.

Filter-only cancel (no ids/urlStartingWith/workflowUrl/all):

await client.cancel({ label: "my-label", fromDate: 1640995200000 });

client.logs() — New label parameter

const result = await client.logs({ label: "my-workflow-label" });

client.dlq.resume() — New filter-based overload

Previously: Only accepted { dlqId: string | string[] }.

Now: Also accepts a WorkflowBulkFilters object (without dlqId):

// By dlqId (existing)
await client.dlq.resume({ dlqId: "dlq-123" });
await client.dlq.resume({ dlqId: ["dlq-123", "dlq-456"] });

// By filters (new)
await client.dlq.resume({ label: "my-label" });
await client.dlq.resume({ label: "my-label", workflowUrl: "https://example.com/workflow" });
await client.dlq.resume({ all: true });

client.dlq.restart() — New filter-based overload

Same as resume — now also accepts WorkflowBulkFilters:

// By dlqId (existing)
await client.dlq.restart({ dlqId: "dlq-123" });

// By filters (new)
await client.dlq.restart({ label: "my-label" });
await client.dlq.restart({ all: true });

client.dlq.delete() — New method

Delete DLQ messages by ID(s) or by filters. Returns { deleted: number }.

// Single dlqId
await client.dlq.delete("dlq-123");

// Array of dlqIds
await client.dlq.delete(["dlq-123", "dlq-456"]);

// Object with dlqIds
await client.dlq.delete({ dlqIds: ["dlq-123", "dlq-456"] });

// By filters
await client.dlq.delete({ label: "my-label" });
await client.dlq.delete({ workflowUrl: "https://example.com/workflow" });
await client.dlq.delete({ fromDate: 1640995200000, toDate: 1672531200000 });

// Delete all
await client.dlq.delete({ all: true });

WorkflowBulkFilters — New shared type

Used by dlq.resume(), dlq.restart(), and dlq.delete() for filter-based operations:

type WorkflowBulkFilters = {
  workflowRunId?: string;
  workflowUrl?: string;
  fromDate?: Date | number | string;
  toDate?: Date | number | string;
  label?: string;
  all?: boolean;
};

fromDate/toDate accept Date objects, Unix timestamps (number), or numeric strings — all are converted to milliseconds.

@alitariksahin alitariksahin self-assigned this Feb 6, 2026
@linear
Copy link
Copy Markdown

linear bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds bulk filtering capabilities to the workflow client and DLQ client APIs (date range, label, exact URL matching), plus supporting utilities and expanded test coverage to validate the new request shapes.

Changes:

  • Introduces toMs utility for converting Date | number | string inputs into millisecond timestamps for API payloads.
  • Extends Client.cancel to support additional filters (label, from/to date) and exact workflowUrl matching, and to return a structured { cancelled: number } response.
  • Extends DLQ APIs (resume, restart, retryFailureFunction) and adds DLQ.delete, including new tests for these behaviors.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/client/utils.ts Adds toMs helper used for date filter serialization.
src/client/types.ts Adds WorkflowBulkFilters type to represent shared bulk filter inputs.
src/client/index.ts Updates cancel to accept additional filters and exact URL matching; uses toMs for date conversion.
src/client/index.test.ts Updates/expands workflow client tests for new cancel response + filter support and logs label query.
src/client/dlq.ts Adds filter-based resume/restart overloads, updates response parsing, and introduces DLQ delete API.
src/client/dlq.test.ts Expands DLQ tests for new response shapes, filter-based ops, and delete scenarios.
Comments suppressed due to low confidence (1)

src/client/index.ts:83

  • The JSDoc for cancel still states it returns true/"deleted", but the method now returns the QStash response object (e.g. { cancelled: number }). Please update the @returns text (and any surrounding wording like "delete") so the public API docs match the actual return type/behavior.
  /**
   * Cancel an ongoing workflow
   *
   * Returns true if workflow is canceled succesfully. Otherwise, throws error.
   *
   * There are multiple ways you can cancel workflows:
   * - pass one or more workflow run ids to cancel them
   * - pass a workflow url to cancel all runs starting with this url
   * - cancel all pending or active workflow runs
   *
   * ### Cancel a set of workflow runs
   *
   * ```ts
   * // cancel a single workflow
   * await client.cancel({ ids: "<WORKFLOW_RUN_ID>" })
   *
   * // cancel a set of workflow runs
   * await client.cancel({ ids: [
   *   "<WORKFLOW_RUN_ID_1>",
   *   "<WORKFLOW_RUN_ID_2>",
   * ]})
   * ```
   *
   * ### Cancel workflows starting with a url
   *
   * If you have an endpoint called `https://your-endpoint.com` and you
   * want to cancel all workflow runs on it, you can use `urlStartingWith`.
   *
   * Note that this will cancel workflows in all endpoints under
   * `https://your-endpoint.com`.
   *
   * ```ts
   * await client.cancel({ urlStartingWith: "https://your-endpoint.com" })
   * ```
   *
   * ### Cancel *all* workflows
   *
   * To cancel all pending and currently running workflows, you can
   * do it like this:
   *
   * ```ts
   * await client.cancel({ all: true })
   * ```
   *
   * @param ids run id of the workflow to delete
   * @param urlStartingWith cancel workflows starting with this url. Will be ignored
   *   if `ids` parameter is set.
   * @param workflowUrl cancel workflows with this url.
   * @param fromDate cancel workflows created after this date.
   * @param toDate cancel workflows created before this date.
   * @param label cancel workflows with this label.
   * @param all set to true in order to cancel all workflows. Will be ignored
   *   if `ids` or `urlStartingWith` parameters are set.
   * @returns true if workflow is succesfully deleted. Otherwise throws QStashError
   */

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@CahidArda CahidArda changed the title feat: add missing bulk action methods to sdk DX-2238: add missing bulk action methods to sdk Mar 17, 2026
ytkimirti and others added 5 commits March 18, 2026 16:13
- Add legacy overload for cancel({ ids, urlStartingWith, all }) so
  existing callers are not broken by the new signature
- Restore deprecated url and responseStatus fields on WorkflowDLQListFilters
…cancel filters

Make workflowUrl mean exact match by default in cancel filters
and add workflowUrlStartingWith for prefix-match behavior. This
is consistent with how DLQ endpoints already treat workflowUrl.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the workflow SDK’s bulk-action surface area by adding filter-based operations and a new DLQ delete method, while also standardizing pagination cursor handling and updating call sites/tests to the new APIs.

Changes:

  • Extend client.cancel() to support flexible filter combinations, new call signatures (id / ids array), and return { cancelled: number }.
  • Add filter typing infrastructure (filter-types.ts) and reuse it across workflow cancel + DLQ bulk actions; normalize empty-string cursors to undefined.
  • Expand DLQ capabilities: resume() / restart() accept filters and options, and introduce dlq.delete() with bulk/filter support; update tests accordingly.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/workflow-requests.test.ts Updates workflow cancellation calls to new cancel() signatures.
src/serve/serve.test.ts Updates client.cancel() usages to new single-id signature.
src/client/utils.ts Adds cursor normalization + shared bulk query builder for cancel/DLQ endpoints.
src/client/types.ts Makes WorkflowRunLogs.cursor optional; adds deprecated notice; introduces WorkflowBulkFilters.
src/client/index.ts Implements new cancel() overloads/behavior and updates logs() to use query + cursor normalization.
src/client/index.test.ts Updates mocked/live tests for new cancel return type and new logs behavior (cursor + label).
src/client/filter-types.ts Introduces new filter/request types for cancel, DLQ list, DLQ bulk actions, and logs listing.
src/client/dlq.ts Adds filter-based DLQ resume/restart, adds dlq.delete(), and normalizes cursor in responses.
src/client/dlq.test.ts Updates and expands tests for DLQ list/resume/restart + adds coverage for dlq.delete().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

CahidArda and others added 2 commits March 23, 2026 14:48
…param

- Add tests for resume/restart/delete with { dlqIds: [] } to verify
  runtime guard throws instead of sending unscoped bulk requests
- Add live tests (skipped) for cancel/resume/restart/delete with { all: true }
- Remove `all=true` from query parameters in buildBulkActionQueryParameters;
  only count param remains for bulk operations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Enforce workflowUrl/workflowUrlStartingWith mutual exclusivity at
  type level (CancelFilter union with never) and runtime (QstashError)
- Add explicit error in buildBulkActionQueryParameters when no filter
  is provided (catches cancel({}) with a clear message)
- Remove unused WorkflowBulkFilters type from types.ts
- Remove normalizeCursor from logs() to preserve cursor: string contract
  (server always returns "" for this endpoint, not undefined)
});

return response;
return workflowRuns[0];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why [0]

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +151 to +156
): Promise<{ cursor?: string; workflowRuns: DLQResumeRestartResponse[] }>;
/** @deprecated Use `resume(dlqId)` instead */
async resume(request: DLQResumeRestartOptions<string>): Promise<DLQResumeRestartResponse>;
/** @deprecated Use `resume([dlqId1, dlqId2])` instead */
async resume(request: DLQResumeRestartOptions<string[]>): Promise<DLQResumeRestartResponse[]>;
async resume(
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DLQ.resume()/restart() now return { cursor?, workflowRuns } for the recommended call forms (resume("id"), resume([..]), filter/all), but the deprecated { dlqId } overloads return a different shape (single item / array). This makes the return type depend on calling style and creates a breaking change when migrating off the deprecated signature. Consider keeping the return shape consistent across overloads (e.g., only use the wrapper for filter/all bulk modes) or introducing a separate bulk API.

Suggested change
): Promise<{ cursor?: string; workflowRuns: DLQResumeRestartResponse[] }>;
/** @deprecated Use `resume(dlqId)` instead */
async resume(request: DLQResumeRestartOptions<string>): Promise<DLQResumeRestartResponse>;
/** @deprecated Use `resume([dlqId1, dlqId2])` instead */
async resume(request: DLQResumeRestartOptions<string[]>): Promise<DLQResumeRestartResponse[]>;
async resume(
): Promise<
| { cursor?: string; workflowRuns: DLQResumeRestartResponse[] }
| DLQResumeRestartResponse
| DLQResumeRestartResponse[]
>;
/** @deprecated Use `resume(dlqId)` instead */
async resume(
request: DLQResumeRestartOptions<string>
): Promise<
| { cursor?: string; workflowRuns: DLQResumeRestartResponse[] }
| DLQResumeRestartResponse
| DLQResumeRestartResponse[]
>;
/** @deprecated Use `resume([dlqId1, dlqId2])` instead */
async resume(
request: DLQResumeRestartOptions<string[]>
): Promise<
| { cursor?: string; workflowRuns: DLQResumeRestartResponse[] }
| DLQResumeRestartResponse
| DLQResumeRestartResponse[]
>;
async resume(

Copilot uses AI. Check for mistakes.
@CahidArda CahidArda merged commit 711837a into main Mar 30, 2026
22 of 23 checks passed
@CahidArda CahidArda deleted the DX-2238 branch March 30, 2026 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants