Compare commits

...

6 Commits

Author SHA1 Message Date
Matt Aitken 24557a7324 Release 3.0.0-beta.50 2024-07-19 11:34:40 +01:00
github-actions[bot] 8b10a592f3 chore: Update version for release (beta) (#1229)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-19 11:33:36 +01:00
Eric Allam d7089e12a9 Replace findUnique with findFirst when doing a relation aggregate 2024-07-19 10:18:56 +01:00
Eric Allam 162d864517 Fixed prisma dataLoader slow query when getting an attempt payload 2024-07-19 09:42:48 +01:00
Matt Aitken 8ba9987944 Declarative schedules (#1226)
* Added type (STATIC or DYNAMIC) to TaskSchedule. Defaults to dynamic

* WIP with dev indexing of static schedules

* Added a code comment

* First stab at deleting unused static schedules

* Dashboard changes for the static schedules

* Generate the description. Upsert the instances when editing. Fix for the friendlyId

* Don’t allow deleting of static schedules

* Don’t allow enabling/disabling of static schedules

* Added filtering for schedule types

* Syncing of schedule for deployed tasks

* Static schedules are now created for each environment

* Added a second static schedule for testing

* Add the type to the schedule task run payload and the object you get back from the SDK

* Changed static/dynamic to declarative/imperative

* Timezone example

* Changeset

* Updated scheduled docs to include declarative

* When you test a schedule it set the type to “IMPERATIVE”

* Improved the tooltip

* Fix for queue time continuing to rise when a run is canceled/expired etc

* Update the info panel on a selected declarative schedule

* Check if there are no instances. This should never happen but log an error if it does

* Throw errors and push them through to the CLI dev command

* Fail deployments if creating the background tasks or schedules fails

* Format the deployment error so it gets displayed

* Changed the maxed out schedules error message to remove bit about support
2024-07-18 20:24:54 +01:00
nicktrn 7056ce53f2 Add missing dep to webapp image
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 1s
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-infra (push) Has been skipped
2024-07-18 13:58:33 +01:00
104 changed files with 1142 additions and 313 deletions
+1
View File
@@ -167,6 +167,7 @@
"tender-turkeys-compete",
"thick-carrots-sneeze",
"thin-parents-heal",
"thirty-hotels-raise",
"thirty-islands-kiss",
"tidy-balloons-suffer",
"tidy-dryers-sleep",
+6
View File
@@ -0,0 +1,6 @@
---
"@trigger.dev/sdk": patch
"@trigger.dev/core": patch
---
Added declarative cron schedules
@@ -1,6 +1,6 @@
import { XMarkIcon } from "@heroicons/react/20/solid";
import { useNavigate } from "@remix-run/react";
import { RuntimeEnvironment } from "@trigger.dev/database";
import { type RuntimeEnvironment } from "@trigger.dev/database";
import { useCallback } from "react";
import { z } from "zod";
import { Input } from "~/components/primitives/Input";
@@ -17,6 +17,7 @@ import {
SelectTrigger,
SelectValue,
} from "../../primitives/SimpleSelect";
import { ScheduleTypeCombo } from "./ScheduleType";
export const ScheduleListFilters = z.object({
page: z.coerce.number().default(1),
@@ -28,6 +29,7 @@ export const ScheduleListFilters = z.object({
.string()
.optional()
.transform((value) => (value ? value.split(",") : undefined)),
type: z.union([z.literal("declarative"), z.literal("imperative")]).optional(),
search: z.string().optional(),
});
@@ -48,7 +50,7 @@ export function ScheduleFilters({ possibleEnvironments, possibleTasks }: Schedul
const navigate = useNavigate();
const location = useOptimisticLocation();
const searchParams = new URLSearchParams(location.search);
const { environments, tasks, page, search } = ScheduleListFilters.parse(
const { environments, tasks, page, search, type } = ScheduleListFilters.parse(
Object.fromEntries(searchParams.entries())
);
@@ -73,6 +75,10 @@ export function ScheduleFilters({ possibleEnvironments, possibleTasks }: Schedul
handleFilterChange("environments", value === "ALL" ? undefined : value);
}, []);
const handleTypeChange = useCallback((value: string | typeof All) => {
handleFilterChange("type", value === "ALL" ? undefined : value);
}, []);
const handleSearchChange = useThrottle((value: string) => {
handleFilterChange("search", value.length === 0 ? undefined : value);
}, 300);
@@ -97,6 +103,30 @@ export function ScheduleFilters({ possibleEnvironments, possibleTasks }: Schedul
defaultValue={search}
onChange={(e) => handleSearchChange(e.target.value)}
/>
<SelectGroup>
<Select name="type" value={type ?? "ALL"} onValueChange={handleTypeChange}>
<SelectTrigger size="minimal" width="full">
<SelectValue placeholder={"Select type"} className="ml-2 whitespace-nowrap p-0" />
</SelectTrigger>
<SelectContent>
<SelectItem value={"ALL"}>
<Paragraph
variant="extra-small"
className="whitespace-nowrap pl-0.5 transition group-hover:text-text-bright"
>
All types
</Paragraph>
</SelectItem>
<SelectItem value={"declarative"}>
<ScheduleTypeCombo type="DECLARATIVE" className="text-xs text-text-dimmed" />
</SelectItem>
<SelectItem value={"imperative"}>
<ScheduleTypeCombo type="IMPERATIVE" className="text-xs text-text-dimmed" />
</SelectItem>
</SelectContent>
</Select>
</SelectGroup>
<SelectGroup>
<Select
name="environment"
@@ -0,0 +1,29 @@
import { ArchiveBoxIcon, ArrowsRightLeftIcon } from "@heroicons/react/20/solid";
import type { ScheduleType } from "@trigger.dev/database";
import { cn } from "~/utils/cn";
export function ScheduleTypeCombo({ type, className }: { type: ScheduleType; className?: string }) {
return (
<div className={cn("flex items-center space-x-1", className)}>
<ScheduleTypeIcon type={type} />
<span>{scheduleTypeName(type)}</span>
</div>
);
}
export function ScheduleTypeIcon({ type, className }: { type: ScheduleType; className?: string }) {
switch (type) {
case "IMPERATIVE":
return <ArrowsRightLeftIcon className={cn("size-4", className)} />;
case "DECLARATIVE":
return <ArchiveBoxIcon className={cn("size-4", className)} />;
}
}
export function scheduleTypeName(type: ScheduleType) {
switch (type) {
case "IMPERATIVE":
return "Imperative";
case "DECLARATIVE":
return "Declarative";
}
}
@@ -230,8 +230,12 @@ export function TaskRunsTable({
formatDuration(new Date(run.createdAt), new Date(run.startedAt), {
style: "short",
})
) : (
) : run.isCancellable ? (
<LiveTimer startTime={new Date(run.createdAt)} />
) : (
formatDuration(new Date(run.createdAt), new Date(run.updatedAt), {
style: "short",
})
)}
</div>
</TableCell>
@@ -25,7 +25,7 @@ export class ApiRunPresenter {
}: ApiRunOptions) {
const take = Math.min(maxTasks, 50);
return await prisma.jobRun.findUnique({
return await prisma.jobRun.findFirst({
where: {
id: runId,
},
@@ -88,6 +88,7 @@ export class EditSchedulePresenter {
const schedule = await this.#prismaClient.taskSchedule.findFirst({
select: {
id: true,
type: true,
friendlyId: true,
generatorExpression: true,
externalId: true,
@@ -1,4 +1,4 @@
import { Prisma, RuntimeEnvironmentType } from "@trigger.dev/database";
import { Prisma, RuntimeEnvironmentType, ScheduleType } from "@trigger.dev/database";
import { ScheduleListFilters } from "~/components/runs/v3/ScheduleFilters";
import { sqlDatabaseSchema } from "~/db.server";
import { displayableEnvironment } from "~/models/runtimeEnvironment.server";
@@ -16,6 +16,7 @@ const DEFAULT_PAGE_SIZE = 20;
export type ScheduleListItem = {
id: string;
type: ScheduleType;
friendlyId: string;
taskIdentifier: string;
deduplicationKey: string | null;
@@ -44,10 +45,17 @@ export class ScheduleListPresenter extends BasePresenter {
environments,
search,
page,
type,
pageSize = DEFAULT_PAGE_SIZE,
}: ScheduleListOptions) {
const hasFilters =
tasks !== undefined || environments !== undefined || (search !== undefined && search !== "");
type !== undefined ||
tasks !== undefined ||
environments !== undefined ||
(search !== undefined && search !== "");
const filterType =
type === "declarative" ? "DECLARATIVE" : type === "imperative" ? "IMPERATIVE" : undefined;
// Find the project scoped to the organization
const project = await this._replica.project.findFirstOrThrow({
@@ -105,6 +113,7 @@ export class ScheduleListPresenter extends BasePresenter {
environmentId: environments ? { in: environments } : undefined,
},
},
type: filterType,
AND: search
? {
OR: [
@@ -141,6 +150,7 @@ export class ScheduleListPresenter extends BasePresenter {
const rawSchedules = await this._replica.taskSchedule.findMany({
select: {
id: true,
type: true,
friendlyId: true,
taskIdentifier: true,
deduplicationKey: true,
@@ -166,6 +176,7 @@ export class ScheduleListPresenter extends BasePresenter {
},
}
: undefined,
type: filterType,
AND: search
? {
OR: [
@@ -215,11 +226,12 @@ export class ScheduleListPresenter extends BasePresenter {
ON t."scheduleId" = r."scheduleId" AND t."createdAt" = r."LatestRun";`
: [];
const schedules = rawSchedules.map((schedule) => {
const schedules: ScheduleListItem[] = rawSchedules.map((schedule) => {
const latestRun = latestRuns.find((r) => r.scheduleId === schedule.id);
return {
id: schedule.id,
type: schedule.type,
friendlyId: schedule.friendlyId,
taskIdentifier: schedule.taskIdentifier,
deduplicationKey: schedule.deduplicationKey,
@@ -21,6 +21,7 @@ export class ViewSchedulePresenter {
const schedule = await this.#prismaClient.taskSchedule.findFirst({
select: {
id: true,
type: true,
friendlyId: true,
generatorExpression: true,
generatorDescription: true,
@@ -99,6 +100,7 @@ export class ViewSchedulePresenter {
public toJSONResponse(result: NonNullable<Awaited<ReturnType<ViewSchedulePresenter["call"]>>>) {
const response: ScheduleObject = {
id: result.schedule.friendlyId,
type: result.schedule.type,
task: result.schedule.taskIdentifier,
active: result.schedule.active,
nextRun: result.schedule.nextRuns[0],
@@ -12,7 +12,7 @@ const ParamsSchema = z.object({
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { organizationId } = ParamsSchema.parse(params);
const org = await prisma.organization.findUnique({
const org = await prisma.organization.findFirst({
select: {
slug: true,
_count: {
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { organizationId } = ParamsSchema.parse(params);
const org = await prisma.organization.findUnique({
const org = await prisma.organization.findFirst({
select: {
slug: true,
_count: {
@@ -12,7 +12,7 @@ const ParamsSchema = z.object({
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { organizationId } = ParamsSchema.parse(params);
const org = await prisma.organization.findUnique({
const org = await prisma.organization.findFirst({
select: {
slug: true,
_count: {
@@ -1,15 +1,21 @@
import { parse } from "@conform-to/zod";
import { BoltIcon, BoltSlashIcon, PencilSquareIcon, TrashIcon } from "@heroicons/react/20/solid";
import {
BoltIcon,
BoltSlashIcon,
BookOpenIcon,
PencilSquareIcon,
TrashIcon,
} from "@heroicons/react/20/solid";
import { DialogDescription } from "@radix-ui/react-dialog";
import { Form, useLocation } from "@remix-run/react";
import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { token } from "morgan";
import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { ExitIcon } from "~/assets/icons/ExitIcon";
import { InlineCode } from "~/components/code/InlineCode";
import { EnvironmentLabel, EnvironmentLabels } from "~/components/environments/EnvironmentLabel";
import { EnvironmentLabels } from "~/components/environments/EnvironmentLabel";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout, variantClasses } from "~/components/primitives/Callout";
import { DateTime } from "~/components/primitives/DateTime";
import {
Dialog,
@@ -19,6 +25,7 @@ import {
DialogTrigger,
} from "~/components/primitives/Dialog";
import { Header2, Header3 } from "~/components/primitives/Headers";
import { InfoPanel } from "~/components/primitives/InfoPanel";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Property, PropertyTable } from "~/components/primitives/PropertyTable";
import {
@@ -31,11 +38,11 @@ import {
TableRow,
} from "~/components/primitives/Table";
import { EnabledStatus } from "~/components/runs/v3/EnabledStatus";
import { ScheduleTypeCombo } from "~/components/runs/v3/ScheduleType";
import { TaskRunsTable } from "~/components/runs/v3/TaskRunsTable";
import { prisma } from "~/db.server";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useUser } from "~/hooks/useUser";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import { findProjectBySlug } from "~/models/project.server";
import { ViewSchedulePresenter } from "~/presenters/v3/ViewSchedulePresenter.server";
@@ -197,8 +204,15 @@ export default function Page() {
const isUtc = schedule.timezone === "UTC";
const isImperative = schedule.type === "IMPERATIVE";
return (
<div className="grid h-full max-h-full grid-rows-[2.5rem_1fr_3.25rem] overflow-hidden bg-background-bright">
<div
className={cn(
"grid h-full max-h-full overflow-hidden bg-background-bright",
isImperative ? "grid-rows-[2.5rem_1fr_3.25rem]" : "grid-rows-[2.5rem_1fr]"
)}
>
<div className="mx-3 flex items-center justify-between gap-2 border-b border-grid-dimmed">
<Header2 className={cn("whitespace-nowrap")}>{schedule.friendlyId}</Header2>
<LinkButton
@@ -214,6 +228,9 @@ export default function Page() {
<PropertyTable>
<Property label="Schedule ID">{schedule.friendlyId}</Property>
<Property label="Task ID">{schedule.taskIdentifier}</Property>
<Property label="Type">
<ScheduleTypeCombo type={schedule.type} className="text-sm" />
</Property>
<Property label="CRON (UTC)" labelClassName="self-start">
<div className="space-y-2">
<InlineCode variant="extra-small">{schedule.cron}</InlineCode>
@@ -224,15 +241,19 @@ export default function Page() {
<Property label="Environments">
<EnvironmentLabels size="small" environments={schedule.environments} />
</Property>
<Property label="External ID">
{schedule.externalId ? schedule.externalId : ""}
</Property>
<Property label="Deduplication key">
{schedule.userProvidedDeduplicationKey ? schedule.deduplicationKey : ""}
</Property>
<Property label="Status">
<EnabledStatus enabled={schedule.active} />
</Property>
{isImperative && (
<>
<Property label="External ID">
{schedule.externalId ? schedule.externalId : ""}
</Property>
<Property label="Deduplication key">
{schedule.userProvidedDeduplicationKey ? schedule.deduplicationKey : ""}
</Property>
<Property label="Status">
<EnabledStatus enabled={schedule.active} />
</Property>
</>
)}
</PropertyTable>
<div className="flex flex-col gap-1">
<Header3>Last 5 runs</Header3>
@@ -288,68 +309,84 @@ export default function Page() {
</TableBody>
</Table>
</div>
{!isImperative && (
<InfoPanel
title="Editing declarative schedules"
icon={BookOpenIcon}
iconClassName="text-indigo-500"
variant="info"
buttonLabel="Docs"
to="https://trigger.dev/docs/v3/tasks-scheduled"
panelClassName="max-w-full"
>
You can only edit a declarative schedule by updating your schedules.task and then
running the CLI dev and deploy commands.
</InfoPanel>
)}
</div>
</div>
</div>
<div className="flex items-center justify-between gap-2 border-t border-grid-dimmed px-2">
<div className="flex items-center gap-4">
<Form method="post">
<Button
type="submit"
variant="minimal/medium"
LeadingIcon={schedule.active ? BoltSlashIcon : BoltIcon}
leadingIconClassName={schedule.active ? "text-dimmed" : "text-success"}
name="action"
value={schedule.active ? "disable" : "enable"}
>
{schedule.active ? "Disable" : "Enable"}
</Button>
</Form>
<Dialog>
<DialogTrigger asChild>
{isImperative && (
<div className="flex items-center justify-between gap-2 border-t border-grid-dimmed px-2">
<div className="flex items-center gap-4">
<Form method="post">
<Button
type="submit"
variant="minimal/medium"
LeadingIcon={TrashIcon}
leadingIconClassName="text-error"
className="text-error"
LeadingIcon={schedule.active ? BoltSlashIcon : BoltIcon}
leadingIconClassName={schedule.active ? "text-dimmed" : "text-success"}
name="action"
value="delete"
value={schedule.active ? "disable" : "enable"}
>
Delete
{schedule.active ? "Disable" : "Enable"}
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>Delete schedule</DialogHeader>
<DialogDescription>
Are you sure you want to delete this schedule? This can't be reversed.
</DialogDescription>
<DialogFooter>
<Form method="post">
<Button
type="submit"
variant="danger/small"
LeadingIcon={TrashIcon}
name="action"
value="delete"
>
Delete
</Button>
</Form>
</DialogFooter>
</DialogContent>
</Dialog>
</Form>
<Dialog>
<DialogTrigger asChild>
<Button
type="submit"
variant="minimal/medium"
LeadingIcon={TrashIcon}
leadingIconClassName="text-error"
className="text-error"
name="action"
value="delete"
>
Delete
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>Delete schedule</DialogHeader>
<DialogDescription>
Are you sure you want to delete this schedule? This can't be reversed.
</DialogDescription>
<DialogFooter>
<Form method="post">
<Button
type="submit"
variant="danger/small"
LeadingIcon={TrashIcon}
name="action"
value="delete"
>
Delete
</Button>
</Form>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
<div className="flex items-center gap-4">
<LinkButton
variant="tertiary/medium"
to={`${v3EditSchedulePath(organization, project, schedule)}${location.search}`}
LeadingIcon={PencilSquareIcon}
>
Edit schedule
</LinkButton>
</div>
</div>
<div className="flex items-center gap-4">
<LinkButton
variant="tertiary/medium"
to={`${v3EditSchedulePath(organization, project, schedule)}${location.search}`}
LeadingIcon={PencilSquareIcon}
>
Edit schedule
</LinkButton>
</div>
</div>
)}
</div>
);
}
@@ -1,8 +1,8 @@
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { EditSchedulePresenter } from "~/presenters/v3/EditSchedulePresenter.server";
import { requireUserId } from "~/services/session.server";
import { ProjectParamSchema, v3ScheduleParams } from "~/utils/pathBuilder";
import { ProjectParamSchema, v3ScheduleParams, v3SchedulesPath } from "~/utils/pathBuilder";
import { humanToCronSupported } from "~/v3/humanToCron.server";
import { UpsertScheduleForm } from "../resources.orgs.$organizationSlug.projects.$projectParam.schedules.new/route";
@@ -17,6 +17,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
friendlyId: scheduleParam,
});
if (result.schedule?.type === "DECLARATIVE") {
throw redirect(v3SchedulesPath({ slug: organizationSlug }, { slug: projectParam }));
}
return typedjson({ ...result, showGenerateField: humanToCronSupported });
};
@@ -10,6 +10,11 @@ import { EnvironmentLabels } from "~/components/environments/EnvironmentLabel";
import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { DateTime } from "~/components/primitives/DateTime";
import {
ScheduleTypeCombo,
ScheduleTypeIcon,
scheduleTypeName,
} from "~/components/runs/v3/ScheduleType";
import {
Dialog,
DialogContent,
@@ -334,6 +339,51 @@ function SchedulesTable({
<TableRow>
<TableHeaderCell>ID</TableHeaderCell>
<TableHeaderCell>Task ID</TableHeaderCell>
<TableHeaderCell
tooltip={
<div className="flex max-w-xs flex-col gap-4 p-1">
<div>
<div className="mb-0.5 flex items-center gap-1.5 text-sm">
<div className={"flex items-center space-x-1"}>
<ScheduleTypeIcon type={"DECLARATIVE"} className="text-sky-500" />
<span className="font-medium">{scheduleTypeName("DECLARATIVE")}</span>
</div>
</div>
<Paragraph variant="small" className="!text-wrap text-text-dimmed">
Declarative schedules are defined in a{" "}
<InlineCode variant="extra-small">schedules.task</InlineCode> with the{" "}
<InlineCode variant="extra-small">cron</InlineCode>
property. They sync when you update your{" "}
<InlineCode variant="extra-small">schedules.task</InlineCode> definition and run
the CLI dev or deploy commands.
</Paragraph>
</div>
<div>
<div className="mb-0.5 flex items-center gap-1.5 text-sm">
<div className={"flex items-center space-x-1"}>
<ScheduleTypeIcon type={"IMPERATIVE"} className="text-teal-500" />
<span className="font-medium">{scheduleTypeName("IMPERATIVE")}</span>
</div>
</div>
<Paragraph variant="small" className="!text-wrap text-text-dimmed">
Imperative schedules are defined here in the dashboard or by using the SDK
functions to create or delete them. They can be created, updated, disabled, and
deleted from the dashboard or using the SDK.
</Paragraph>
</div>
<div>
<LinkButton
variant="tertiary/medium"
to="https://trigger.dev/docs/v3/tasks-scheduled"
>
View the docs
</LinkButton>
</div>
</div>
}
>
Type
</TableHeaderCell>
<TableHeaderCell>External ID</TableHeaderCell>
<TableHeaderCell>CRON</TableHeaderCell>
<TableHeaderCell hiddenLabel>CRON description</TableHeaderCell>
@@ -362,7 +412,14 @@ function SchedulesTable({
{schedule.taskIdentifier}
</TableCell>
<TableCell to={path} className={cellClass}>
{schedule.externalId ? schedule.externalId : ""}
<ScheduleTypeCombo type={schedule.type} />
</TableCell>
<TableCell to={path} className={cellClass}>
{schedule.type === "IMPERATIVE"
? schedule.externalId
? schedule.externalId
: ""
: "N/A"}
</TableCell>
<TableCell to={path} className={cellClass}>
{schedule.cron}
@@ -384,13 +441,21 @@ function SchedulesTable({
)}
</TableCell>
<TableCell to={path} className={cellClass}>
{schedule.userProvidedDeduplicationKey ? schedule.deduplicationKey : ""}
{schedule.type === "IMPERATIVE"
? schedule.userProvidedDeduplicationKey
? schedule.deduplicationKey
: ""
: "N/A"}
</TableCell>
<TableCell to={path} className={cellClass}>
<EnvironmentLabels environments={schedule.environments} size="small" />
</TableCell>
<TableCell to={path}>
<EnabledStatus enabled={schedule.active} />
{schedule.type === "IMPERATIVE" ? (
<EnabledStatus enabled={schedule.active} />
) : (
"N/A"
)}
</TableCell>
</TableRow>
);
@@ -34,7 +34,7 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
const { organizationSlug } = OrganizationParamsSchema.parse(params);
const organization = await prisma.organization.findUnique({
const organization = await prisma.organization.findFirst({
where: { slug: organizationSlug, members: { some: { userId } } },
select: {
id: true,
@@ -3,7 +3,11 @@ import { CreateBackgroundWorkerRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { CreateBackgroundWorkerService } from "~/v3/services/createBackgroundWorker.server";
import { ServiceValidationError } from "~/v3/services/baseService.server";
import {
CreateBackgroundWorkerService,
CreateDeclarativeScheduleError,
} from "~/v3/services/createBackgroundWorker.server";
const ParamsSchema = z.object({
projectRef: z.string(),
@@ -42,14 +46,26 @@ export async function action({ request, params }: ActionFunctionArgs) {
const service = new CreateBackgroundWorkerService();
const backgroundWorker = await service.call(projectRef, authenticatedEnv, body.data);
try {
const backgroundWorker = await service.call(projectRef, authenticatedEnv, body.data);
return json(
{
id: backgroundWorker.friendlyId,
version: backgroundWorker.version,
contentHash: backgroundWorker.contentHash,
},
{ status: 200 }
);
return json(
{
id: backgroundWorker.friendlyId,
version: backgroundWorker.version,
contentHash: backgroundWorker.contentHash,
},
{ status: 200 }
);
} catch (e) {
logger.error("Failed to create background worker", { error: e });
if (e instanceof ServiceValidationError) {
return json({ error: e.message }, { status: 400 });
} else if (e instanceof CreateDeclarativeScheduleError) {
return json({ error: e.message }, { status: 400 });
}
return json({ error: "Failed to create background worker" }, { status: 500 });
}
}
@@ -88,6 +88,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
const responseObject: ScheduleObject = {
id: schedule.id,
type: schedule.type,
task: schedule.task,
active: schedule.active,
generator: {
@@ -50,6 +50,7 @@ export async function action({ request }: ActionFunctionArgs) {
const responseObject: ScheduleObject = {
id: schedule.id,
type: schedule.type,
task: schedule.task,
active: schedule.active,
generator: {
@@ -107,6 +108,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
return {
data: result.schedules.map((schedule) => ({
id: schedule.friendlyId,
type: schedule.type,
task: schedule.taskIdentifier,
generator: {
type: "CRON",
@@ -37,7 +37,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
}
function projectForUpdates(id: string) {
return prisma.project.findUnique({
return prisma.project.findFirst({
where: {
id,
},
@@ -1080,7 +1080,7 @@ class SharedQueueTasks {
const variables = await this.#buildEnvironmentVariables(
attempt.runtimeEnvironment,
taskRun,
taskRun.id,
machinePreset
);
@@ -1136,16 +1136,18 @@ class SharedQueueTasks {
return;
}
const run = await prisma.taskRun.findUnique({
const run = await prisma.taskRun.findFirst({
where: {
id: runId,
runtimeEnvironmentId: environment.id,
},
include: {
lockedBy: true,
_count: {
select: {
id: true,
traceContext: true,
friendlyId: true,
isTest: true,
lockedBy: {
select: {
attempts: true,
machineConfig: true,
},
},
},
@@ -1156,9 +1158,20 @@ class SharedQueueTasks {
return;
}
const attemptCount = await prisma.taskRunAttempt.count({
where: {
taskRunId: run.id,
},
});
logger.debug("Getting lazy attempt payload for run", {
run,
attemptCount,
});
const machinePreset = machinePresetFromConfig(run.lockedBy?.machineConfig ?? {});
const variables = await this.#buildEnvironmentVariables(environment, run, machinePreset);
const variables = await this.#buildEnvironmentVariables(environment, run.id, machinePreset);
return {
traceContext: run.traceContext as Record<string, unknown>,
@@ -1169,7 +1182,7 @@ class SharedQueueTasks {
runId: run.friendlyId,
messageId: run.id,
isTest: run.isTest,
attemptCount: run._count.attempts,
attemptCount,
} satisfies TaskRunExecutionLazyAttemptPayload;
}
@@ -1203,13 +1216,13 @@ class SharedQueueTasks {
async #buildEnvironmentVariables(
environment: RuntimeEnvironment,
run: TaskRun,
runId: string,
machinePreset: MachinePreset
): Promise<Array<EnvironmentVariable>> {
const variables = await resolveVariablesForEnvironment(environment);
const jwt = await generateJWTTokenForEnvironment(environment, {
run_id: run.id,
run_id: runId,
machine_preset: machinePreset.name,
});
@@ -1217,7 +1230,7 @@ class SharedQueueTasks {
...variables,
...[
{ key: "TRIGGER_JWT", value: jwt },
{ key: "TRIGGER_RUN_ID", value: run.id },
{ key: "TRIGGER_RUN_ID", value: runId },
{
key: "TRIGGER_MACHINE_PRESET",
value: machinePreset.name,
@@ -0,0 +1,93 @@
import { ZodError } from "zod";
import { CronPattern } from "../schedules";
import { BaseService, ServiceValidationError } from "./baseService.server";
import { getLimit } from "~/services/platform.v3.server";
import { getTimezones } from "~/utils/timezones.server";
import { env } from "~/env.server";
type Schedule = {
cron: string;
timezone?: string;
taskIdentifier: string;
friendlyId?: string;
};
export class CheckScheduleService extends BaseService {
public async call(projectId: string, schedule: Schedule) {
//validate the cron expression
try {
CronPattern.parse(schedule.cron);
} catch (e) {
if (e instanceof ZodError) {
throw new ServiceValidationError(`Invalid cron expression: ${e.issues[0].message}`);
}
throw new ServiceValidationError(
`Invalid cron expression: ${e instanceof Error ? e.message : JSON.stringify(e)}`
);
}
//chek it's a valid timezone
if (schedule.timezone) {
const possibleTimezones = getTimezones();
if (!possibleTimezones.includes(schedule.timezone)) {
throw new ServiceValidationError(
`Invalid IANA timezone: '${schedule.timezone}'. View the list of valid timezones at ${env.APP_ORIGIN}/timezones`
);
}
}
//check the task exists
const task = await this._prisma.backgroundWorkerTask.findFirst({
where: {
slug: schedule.taskIdentifier,
projectId: projectId,
},
orderBy: {
createdAt: "desc",
},
});
if (!task) {
throw new ServiceValidationError(
`Task with identifier ${schedule.taskIdentifier} not found in project.`
);
}
if (task.triggerSource !== "SCHEDULED") {
throw new ServiceValidationError(
`Task with identifier ${schedule.taskIdentifier} is not a scheduled task.`
);
}
//if creating a schedule, check they're under the limits
if (!schedule.friendlyId) {
//check they're within their limit
const project = await this._prisma.project.findFirst({
where: {
id: projectId,
},
select: {
organizationId: true,
},
});
if (!project) {
throw new ServiceValidationError("Project not found");
}
const limit = await getLimit(project.organizationId, "schedules", 500);
const schedulesCount = await this._prisma.taskSchedule.count({
where: {
projectId,
},
});
if (schedulesCount >= limit) {
throw new ServiceValidationError(
`You have created ${schedulesCount}/${limit} schedules so you'll need to increase your limits or delete some schedules.`
);
}
}
}
}
@@ -8,6 +8,9 @@ import { generateFriendlyId } from "../friendlyIdentifiers";
import { calculateNextBuildVersion } from "../utils/calculateNextBuildVersion";
import { BaseService } from "./baseService.server";
import { projectPubSub } from "./projectPubSub.server";
import { RegisterNextTaskScheduleInstanceService } from "./registerNextTaskScheduleInstance.server";
import cronstrue from "cronstrue";
import { CheckScheduleService } from "./checkSchedule.server";
export class CreateBackgroundWorkerService extends BaseService {
public async call(
@@ -68,6 +71,12 @@ export class CreateBackgroundWorkerService extends BaseService {
});
await createBackgroundTasks(body.metadata.tasks, backgroundWorker, environment, this._prisma);
await syncDeclarativeSchedules(
body.metadata.tasks,
backgroundWorker,
environment,
this._prisma
);
try {
//send a notification that a new worker has been created
@@ -220,3 +229,155 @@ export async function createBackgroundTasks(
}
}
}
//CreateDeclarativeScheduleError with a message
export class CreateDeclarativeScheduleError extends Error {
constructor(message: string) {
super(message);
this.name = "CreateDeclarativeScheduleError";
}
}
export async function syncDeclarativeSchedules(
tasks: TaskResource[],
worker: BackgroundWorker,
environment: AuthenticatedEnvironment,
prisma: PrismaClientOrTransaction
) {
const tasksWithDeclarativeSchedules = tasks.filter((task) => task.schedule);
logger.info("Syncing declarative schedules", {
tasksWithDeclarativeSchedules,
environment,
});
const existingDeclarativeSchedules = await prisma.taskSchedule.findMany({
where: {
type: "DECLARATIVE",
projectId: environment.projectId,
},
include: {
instances: true,
},
});
const checkSchedule = new CheckScheduleService(prisma);
const registerNextService = new RegisterNextTaskScheduleInstanceService(prisma);
//start out by assuming they're all missing
const missingSchedules = new Set<string>(
existingDeclarativeSchedules.map((schedule) => schedule.id)
);
//create/update schedules (+ instances)
for (const task of tasksWithDeclarativeSchedules) {
if (task.schedule === undefined) continue;
const existingSchedule = existingDeclarativeSchedules.find(
(schedule) =>
schedule.taskIdentifier === task.id &&
schedule.instances.some((instance) => instance.environmentId === environment.id)
);
//this throws errors if the schedule is invalid
await checkSchedule.call(environment.projectId, {
cron: task.schedule.cron,
timezone: task.schedule.timezone,
taskIdentifier: task.id,
friendlyId: existingSchedule?.friendlyId,
});
if (existingSchedule) {
const schedule = await prisma.taskSchedule.update({
where: {
id: existingSchedule.id,
},
data: {
generatorExpression: task.schedule.cron,
generatorDescription: cronstrue.toString(task.schedule.cron),
timezone: task.schedule.timezone,
},
include: {
instances: true,
},
});
missingSchedules.delete(existingSchedule.id);
const instance = schedule.instances.at(0);
if (instance) {
await registerNextService.call(instance.id);
} else {
throw new CreateDeclarativeScheduleError(
`Missing instance for declarative schedule ${schedule.id}`
);
}
} else {
const newSchedule = await prisma.taskSchedule.create({
data: {
friendlyId: generateFriendlyId("sched"),
projectId: environment.projectId,
taskIdentifier: task.id,
generatorExpression: task.schedule.cron,
generatorDescription: cronstrue.toString(task.schedule.cron),
timezone: task.schedule.timezone,
type: "DECLARATIVE",
instances: {
create: [
{
environmentId: environment.id,
},
],
},
},
include: {
instances: true,
},
});
const instance = newSchedule.instances.at(0);
if (instance) {
await registerNextService.call(instance.id);
} else {
throw new CreateDeclarativeScheduleError(
`Missing instance for declarative schedule ${newSchedule.id}`
);
}
}
}
//Delete instances for this environment
//Delete schedules that have no instances left
const potentiallyDeletableSchedules = await prisma.taskSchedule.findMany({
where: {
id: {
in: Array.from(missingSchedules),
},
},
include: {
instances: true,
},
});
for (const schedule of potentiallyDeletableSchedules) {
const canDeleteSchedule =
schedule.instances.length === 0 ||
schedule.instances.every((instance) => instance.environmentId === environment.id);
if (canDeleteSchedule) {
//we can delete schedules with no instances other than ones for the current environment
await prisma.taskSchedule.delete({
where: {
id: schedule.id,
},
});
} else {
//otherwise we delete the instance (other environments remain untouched)
await prisma.taskScheduleInstance.deleteMany({
where: {
taskScheduleId: schedule.id,
environmentId: environment.id,
},
});
}
}
}
@@ -3,7 +3,7 @@ import type { BackgroundWorker } from "@trigger.dev/database";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { generateFriendlyId } from "../friendlyIdentifiers";
import { BaseService } from "./baseService.server";
import { createBackgroundTasks } from "./createBackgroundWorker.server";
import { createBackgroundTasks, syncDeclarativeSchedules } from "./createBackgroundWorker.server";
import { CURRENT_DEPLOYMENT_LABEL } from "~/consts";
import { projectPubSub } from "./projectPubSub.server";
import { marqs } from "~/v3/marqs/index.server";
@@ -50,7 +50,39 @@ export class CreateDeployedBackgroundWorkerService extends BaseService {
},
});
await createBackgroundTasks(body.metadata.tasks, backgroundWorker, environment, this._prisma);
try {
await createBackgroundTasks(
body.metadata.tasks,
backgroundWorker,
environment,
this._prisma
);
await syncDeclarativeSchedules(
body.metadata.tasks,
backgroundWorker,
environment,
this._prisma
);
} catch (error) {
const name = error instanceof Error ? error.name : "UnknownError";
const message = error instanceof Error ? error.message : JSON.stringify(error);
await this._prisma.workerDeployment.update({
where: {
id: deployment.id,
},
data: {
status: "FAILED",
failedAt: new Date(),
errorData: {
name,
message,
},
},
});
throw error;
}
// Link the deployment with the background worker
await this._prisma.workerDeployment.update({
@@ -27,6 +27,20 @@ export class DeleteTaskScheduleService extends BaseService {
}
try {
const schedule = await this._prisma.taskSchedule.findFirst({
where: {
friendlyId,
},
});
if (!schedule) {
throw new Error("Schedule not found");
}
if (schedule.type === "DECLARATIVE") {
throw new Error("Cannot delete declarative schedules");
}
await this._prisma.taskSchedule.delete({
where: {
friendlyId,
@@ -28,6 +28,20 @@ export class SetActiveOnTaskScheduleService extends BaseService {
}
try {
const schedule = await this._prisma.taskSchedule.findFirst({
where: {
friendlyId,
},
});
if (!schedule) {
throw new Error("Schedule not found");
}
if (schedule.type === "DECLARATIVE") {
throw new Error("Cannot enable/disable declarative schedules");
}
await this._prisma.taskSchedule.update({
where: {
friendlyId,
@@ -24,6 +24,7 @@ export class TestTaskService extends BaseService {
case "SCHEDULED": {
const payload = {
scheduleId: "sched_1234",
type: "IMPERATIVE",
timestamp: data.timestamp,
lastTimestamp: data.lastTimestamp,
timezone: data.timezone,
@@ -94,6 +94,7 @@ export class TriggerScheduledTaskService extends BaseService {
const payload = {
scheduleId: instance.taskSchedule.friendlyId,
type: instance.taskSchedule.type,
timestamp: instance.nextScheduledTimestamp,
lastTimestamp: instance.lastScheduledTimestamp ?? undefined,
externalId: instance.taskSchedule.externalId ?? undefined,
@@ -1,16 +1,13 @@
import { Prisma, TaskSchedule } from "@trigger.dev/database";
import cronstrue from "cronstrue";
import { nanoid } from "nanoid";
import { ZodError } from "zod";
import { $transaction, PrismaClientOrTransaction } from "~/db.server";
import { generateFriendlyId } from "../friendlyIdentifiers";
import { CronPattern, UpsertSchedule } from "../schedules";
import { BaseService, ServiceValidationError } from "./baseService.server";
import { RegisterNextTaskScheduleInstanceService } from "./registerNextTaskScheduleInstance.server";
import cronstrue from "cronstrue";
import { UpsertSchedule } from "../schedules";
import { calculateNextScheduledTimestamp } from "../utils/calculateNextSchedule.server";
import { getTimezones } from "~/utils/timezones.server";
import { env } from "~/env.server";
import { getLimit } from "~/services/platform.v3.server";
import { BaseService, ServiceValidationError } from "./baseService.server";
import { CheckScheduleService } from "./checkSchedule.server";
import { RegisterNextTaskScheduleInstanceService } from "./registerNextTaskScheduleInstance.server";
export type UpsertTaskScheduleServiceOptions = UpsertSchedule;
@@ -30,79 +27,9 @@ type InstanceWithEnvironment = Prisma.TaskScheduleInstanceGetPayload<{
export class UpsertTaskScheduleService extends BaseService {
public async call(projectId: string, schedule: UpsertTaskScheduleServiceOptions) {
//validate the cron expression
try {
CronPattern.parse(schedule.cron);
} catch (e) {
if (e instanceof ZodError) {
throw new ServiceValidationError(`Invalid cron expression: ${e.issues[0].message}`);
}
throw new ServiceValidationError(
`Invalid cron expression: ${e instanceof Error ? e.message : JSON.stringify(e)}`
);
}
const task = await this._prisma.backgroundWorkerTask.findFirst({
where: {
slug: schedule.taskIdentifier,
projectId: projectId,
},
orderBy: {
createdAt: "desc",
},
});
if (!task) {
throw new ServiceValidationError(
`Task with identifier ${schedule.taskIdentifier} not found in project.`
);
}
if (task.triggerSource !== "SCHEDULED") {
throw new ServiceValidationError(
`Task with identifier ${schedule.taskIdentifier} is not a scheduled task.`
);
}
//if creating a schedule, check they're under the limits
if (!schedule.friendlyId) {
//check they're within their limit
const project = await this._prisma.project.findFirst({
where: {
id: projectId,
},
select: {
organizationId: true,
},
});
if (!project) {
throw new ServiceValidationError("Project not found");
}
const limit = await getLimit(project.organizationId, "schedules", 500);
const schedulesCount = await this._prisma.taskSchedule.count({
where: {
projectId,
},
});
if (schedulesCount >= limit) {
throw new ServiceValidationError(
`You have created ${schedulesCount}/${limit} schedules so you'll need to increase your limits or delete some schedules. Increase your limits by contacting support.`
);
}
}
if (schedule.timezone) {
const possibleTimezones = getTimezones();
if (!possibleTimezones.includes(schedule.timezone)) {
throw new ServiceValidationError(
`Invalid IANA timezone: "${schedule.timezone}". View the list of valid timezones at ${env.APP_ORIGIN}/timezones`
);
}
}
//this throws errors if the schedule is invalid
const checkSchedule = new CheckScheduleService(this._prisma);
await checkSchedule.call(projectId, schedule);
const result = await $transaction(this._prisma, async (tx) => {
const deduplicationKey =
@@ -126,6 +53,10 @@ export class UpsertTaskScheduleService extends BaseService {
});
if (existingSchedule) {
if (existingSchedule.type === "DECLARATIVE") {
throw new ServiceValidationError("Cannot update a declarative schedule");
}
return await this.#updateExistingSchedule(tx, existingSchedule, schedule, projectId);
} else {
return await this.#createNewSchedule(tx, schedule, projectId, deduplicationKey);
@@ -317,6 +248,7 @@ export class UpsertTaskScheduleService extends BaseService {
#createReturnObject(taskSchedule: TaskSchedule, instances: InstanceWithEnvironment[]) {
return {
id: taskSchedule.friendlyId,
type: taskSchedule.type,
task: taskSchedule.taskIdentifier,
active: taskSchedule.active,
externalId: taskSchedule.externalId,
+1 -1
View File
@@ -54,7 +54,7 @@ RUN pnpm run build --filter=webapp...
# Runner
FROM node:20.11.1-bullseye-slim@sha256:5a5a92b3a8d392691c983719dbdc65d9f30085d6dcd65376e7a32e6fe9bf4cbe AS runner
RUN apt-get update && apt-get install -y openssl
RUN apt-get update && apt-get install -y openssl netcat-openbsd
WORKDIR /triggerdotdev
RUN corepack enable
ENV NODE_ENV production
+9 -5
View File
@@ -16,7 +16,7 @@ paths:
post:
operationId: create_schedule_v1
summary: Create a schedule
description: Create a new schedule based on the specified options.
description: Create a new `IMPERATIVE` schedule based on the specified options.
requestBody:
required: true
content:
@@ -127,7 +127,7 @@ paths:
put:
operationId: update_schedule_v1
summary: Update Schedule
description: Update a schedule by its ID.
description: Update a schedule by its ID. This will only work on `IMPERATIVE` schedules that were created in the dashboard or using the imperative SDK functions like `schedules.create()`.
parameters:
- in: path
name: schedule_id
@@ -173,7 +173,7 @@ paths:
delete:
operationId: delete_schedule_v1
summary: Delete Schedule
description: Delete a schedule by its ID.
description: Delete a schedule by its ID. This will only work on `IMPERATIVE` schedules that were created in the dashboard or using the imperative SDK functions like `schedules.create()`.
parameters:
- in: path
name: schedule_id
@@ -203,7 +203,7 @@ paths:
post:
operationId: deactivate_schedule_v1
summary: Deactivate Schedule.
description: Deactivate a schedule by its ID.
description: Deactivate a schedule by its ID. This will only work on `IMPERATIVE` schedules that were created in the dashboard or using the imperative SDK functions like `schedules.create()`.
parameters:
- in: path
name: schedule_id
@@ -237,7 +237,7 @@ paths:
post:
operationId: activate_schedule_v1
summary: Activate Schedule
description: Activate a schedule by its ID.
description: Activate a schedule by its ID. This will only work on `IMPERATIVE` schedules that were created in the dashboard or using the imperative SDK functions like `schedules.create()`.
parameters:
- in: path
name: schedule_id
@@ -1971,6 +1971,10 @@ components:
example: my-scheduled-task
description: The id of the scheduled task that will be triggered by this
schedule
"type":
type: string
example: IMPERATIVE
description: The type of schedule, `DECLARATIVE` or `IMPERATIVE`. Declarative schedules are declared in your code by setting the `cron` property on a `schedules.task`. Imperative schedules are created in the dashboard or by using the imperative SDK functions like `schedules.create()`.
active:
type: boolean
example: true
+62 -10
View File
@@ -3,21 +3,13 @@ title: "Scheduled tasks"
description: "A task that is triggered on a recurring schedule using CRON syntax."
---
To use scheduled tasks you need to do two things:
1. Define a task in your code using `schedules.task()`.
2. Attach a schedule to the task either using the dashboard or the SDK.
<Info>A task can have multiple schedules attached to it.</Info>
Like all tasks they don't have timeouts, they should be placed inside a [/trigger folder](/v3/trigger-folder), and you [can configure them](/v3/tasks-overview#defining-a-task).
## Defining a scheduled task
This task will run when any of the attached schedules trigger. They have a predefined payload with some useful properties:
```ts
import { schedules } from "@trigger.dev/sdk/v3";
//this task will run when any of the attached schedules trigger
export const firstScheduledTask = schedules.task({
id: "first-scheduled-task",
run: async (payload) => {
@@ -68,6 +60,66 @@ You can see from the comments that the payload has several useful properties:
to do that.
</Note>
Like all tasks they don't have timeouts, they should be placed inside a [/trigger folder](/v3/trigger-folder), and you [can configure them](/v3/tasks-overview#defining-a-task).
## How to attach a schedule
Now that we've defined a scheduled task, we need to define when it will actually run. To do this we need to attach one or more schedules.
There are two ways of doing this:
- **Declarative:** defined on your `schedules.task`. They sync when you run the dev command or deploy.
- **Imperative:** created from the dashboard or by using the imperative SDK functions like `schedules.create()`.
<Info>
A scheduled task can have multiple schedules attached to it, including a declarative schedule
and/or many imperative schedules.
</Info>
### Declarative schedules
These sync when you run the [dev](/v3/cli-dev) or [deploy](/v3/cli-deploy) commands.
To create them you add the `cron` property to your `schedules.task()`. This property is optional and is only used if you want to add a declarative schedule to your task:
```ts
export const firstScheduledTask = schedules.task({
id: "first-scheduled-task",
//every two hours (UTC timezone)
cron: "0 */2 * * *",
run: async (payload, { ctx }) => {
//do something
},
});
```
If you use a string it will be in UTC. Alternatively, you can specify a timezone like this:
```ts
export const secondScheduledTask = schedules.task({
id: "second-scheduled-task",
cron: {
//5am every day Tokyo time
pattern: "0 5 * * *",
timezone: "Asia/Tokyo",
},
run: async (payload) => {},
});
```
When you run the [dev](/v3/cli-dev) or [deploy](/v3/cli-deploy) commands, declarative schedules will be synced. If you add, delete or edit the `cron` property it will be updated when you run these commands. You can view your schedules on the Schedules page in the dashboard.
### Imperative schedules
Alternatively you can explicitly attach schedules to a `schedules.task`. You can do this in the Schedules page in the dashboard by just pressing the "New schedule" button, or you can use the SDK to create schedules.
The advantage of imperative schedules is that they can be created dynamically, for example, you could create a schedule for each user in your database. They can also be activated, disabled, edited, and deleted without deploying new code by using the SDK or dashboard.
To use imperative schedules you need to do two things:
1. Define a task in your code using `schedules.task()`.
2. Attach 1+ schedules to the task either using the dashboard or the SDK.
## Supported CRON syntax
```
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/airtable
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/airtable",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev integration for airtable",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"airtable": "^0.12.1",
"zod": "3.22.3"
},
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/github
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/github",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "The official GitHub integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -30,8 +30,8 @@
"@octokit/request-error": "^5.0.1",
"@octokit/webhooks": "^12.0.10",
"octokit": "^3.1.2",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"zod": "3.22.3"
},
"engines": {
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/linear
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/linear",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev integration for @linear/sdk",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@linear/sdk": "^8.0.0",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"zod": "3.22.3"
},
"engines": {
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/slack
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/openai",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "The official OpenAI integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -42,8 +42,8 @@
},
"dependencies": {
"openai": "^4.16.1",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50"
},
"engines": {
"node": ">=18.0.0"
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/plain
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/plain",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "The official Plain.com integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"build:tsup": "tsup"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"@team-plain/typescript-sdk": "^2.7.0"
},
"engines": {
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/replicate
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/replicate",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev integration for replicate",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"replicate": "^0.18.1",
"zod": "3.22.3"
},
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/resend
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/resend",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "The official Resend.com integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"build:tsup": "tsup"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"resend": "^2.1.0"
},
"engines": {
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/sendgrid
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sendgrid",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev integration for @sendgrid/mail",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@sendgrid/mail": "^7.7.0",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50"
},
"engines": {
"node": ">=16.8.0"
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/shopify
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/shopify",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev integration for @shopify/shopify-api",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@shopify/shopify-api": "^8.0.2",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"zod": "3.22.3"
},
"engines": {
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/slack
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/slack",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "The official Slack integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,7 +25,7 @@
},
"dependencies": {
"@slack/web-api": "^6.8.1",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"zod": "3.22.3"
},
"engines": {
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/stripe
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/stripe",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev integration for stripe",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"stripe": "^12.14.0",
"zod": "3.22.3"
},
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/supabase
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/supabase",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev integration for @supabase/supabase-js",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@supabase/supabase-js": "^2.26.0",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"supabase-management-js": "^1.0.0",
"zod": "3.22.3"
},
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/typeform
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/integration-kit@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/typeform",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "The official Typeform integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.50",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"@typeform/api-client": "^1.8.0",
"zod": "3.22.3"
},
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/astro
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@trigger.dev/astro",
"description": "An Astro-native integration for Trigger.dev background jobs platform",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
@@ -20,7 +20,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50"
},
"devDependencies": {
"astro": "^3.0.12",
+7
View File
@@ -1,5 +1,12 @@
# trigger.dev
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/core@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "trigger.dev",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "A Command-Line Interface for Trigger.dev (v3) projects",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -87,7 +87,7 @@
"@opentelemetry/sdk-trace-base": "^1.22.0",
"@opentelemetry/sdk-trace-node": "^1.22.0",
"@opentelemetry/semantic-conventions": "^1.22.0",
"@trigger.dev/core": "workspace:3.0.0-beta.49",
"@trigger.dev/core": "workspace:3.0.0-beta.50",
"@types/degit": "^2.8.3",
"chalk": "^5.2.0",
"chokidar": "^3.5.3",
+8
View File
@@ -1,5 +1,13 @@
# create-trigger
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/core@3.0.0-beta.50
- @trigger.dev/yalt@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/cli",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "The Trigger.dev CLI",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+2
View File
@@ -1,5 +1,7 @@
# @trigger.dev/core-apps
## 3.0.0-beta.50
## 3.0.0-beta.49
## 3.0.0-beta.48
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@trigger.dev/core-apps",
"description": "Backend core code used across apps",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"private": true,
"license": "MIT",
"main": "./dist/index.js",
+2
View File
@@ -1,5 +1,7 @@
# @trigger.dev/core-backend
## 3.0.0-beta.50
## 3.0.0-beta.49
## 3.0.0-beta.48
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/core-backend",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Core code used across `@trigger.dev/sdk` and Trigger.dev server",
"license": "MIT",
"main": "./dist/index.js",
+6
View File
@@ -1,5 +1,11 @@
# internal-platform
## 3.0.0-beta.50
### Patch Changes
- 8ba998794: Added declarative cron schedules
## 3.0.0-beta.49
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/core",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Core code used across the Trigger.dev SDK and platform",
"license": "MIT",
"main": "./dist/index.js",
+10
View File
@@ -226,10 +226,19 @@ export const CanceledRunResponse = z.object({
export type CanceledRunResponse = z.infer<typeof CanceledRunResponse>;
export const ScheduleType = z.union([z.literal("DECLARATIVE"), z.literal("IMPERATIVE")]);
export const ScheduledTaskPayload = z.object({
/** The schedule id associated with this run (you can have many schedules for the same task).
You can use this to remove the schedule, update it, etc */
scheduleId: z.string(),
/** The type of schedule `"DECLARATIVE"` or `"IMPERATIVE"`.
*
* **DECLARATIVE** defined inline on your `schedules.task` using the `cron` property. They can only be created, updated or deleted by modifying the `cron` property on your task.
*
* **IMPERATIVE** created using the `schedules.create` functions or in the dashboard.
*/
type: ScheduleType,
/** When the task was scheduled to run.
* Note this will be slightly different from `new Date()` because it takes a few ms to run the task.
*
@@ -315,6 +324,7 @@ export type ScheduleGenerator = z.infer<typeof ScheduleGenerator>;
export const ScheduleObject = z.object({
id: z.string(),
type: ScheduleType,
task: z.string(),
active: z.boolean(),
deduplicationKey: z.string().nullish(),
+2 -1
View File
@@ -1,5 +1,5 @@
import { z } from "zod";
import { QueueOptions, RetryOptions } from "./schemas";
import { QueueOptions, RetryOptions, ScheduleMetadata } from "./schemas";
import { MachineConfig } from "./common";
export const TaskResource = z.object({
@@ -10,6 +10,7 @@ export const TaskResource = z.object({
retry: RetryOptions.optional(),
machine: MachineConfig.optional(),
triggerSource: z.string().optional(),
schedule: ScheduleMetadata.optional(),
});
export type TaskResource = z.infer<typeof TaskResource>;
+7
View File
@@ -142,6 +142,11 @@ export const QueueOptions = z.object({
export type QueueOptions = z.infer<typeof QueueOptions>;
export const ScheduleMetadata = z.object({
cron: z.string(),
timezone: z.string(),
});
export const TaskMetadata = z.object({
id: z.string(),
packageVersion: z.string(),
@@ -149,6 +154,7 @@ export const TaskMetadata = z.object({
retry: RetryOptions.optional(),
machine: MachineConfig.optional(),
triggerSource: z.string().optional(),
schedule: ScheduleMetadata.optional(),
});
export type TaskMetadata = z.infer<typeof TaskMetadata>;
@@ -167,6 +173,7 @@ export const TaskMetadataWithFilePath = z.object({
retry: RetryOptions.optional(),
machine: MachineConfig.optional(),
triggerSource: z.string().optional(),
schedule: ScheduleMetadata.optional(),
filePath: z.string(),
exportName: z.string(),
});
@@ -0,0 +1,6 @@
-- CreateEnum
CREATE TYPE "ScheduleType" AS ENUM ('DECLARATIVE', 'IMPERATIVE');
-- AlterTable
ALTER TABLE "TaskSchedule"
ADD COLUMN "type" "ScheduleType" NOT NULL DEFAULT 'IMPERATIVE';
+9
View File
@@ -2231,6 +2231,8 @@ model WorkerDeploymentPromotion {
model TaskSchedule {
id String @id @default(cuid())
type ScheduleType @default(IMPERATIVE)
///users see this as `id`. They start with schedule_
friendlyId String @unique
///a reference to a task (not a foreign key because it's across versions)
@@ -2267,6 +2269,13 @@ model TaskSchedule {
@@unique([projectId, deduplicationKey])
}
enum ScheduleType {
/// defined on your task using the `cron` property
DECLARATIVE
/// explicit calls to the SDK are used to create, or using the dashboard
IMPERATIVE
}
enum ScheduleGeneratorType {
CRON
}
+2
View File
@@ -1,5 +1,7 @@
# @trigger.dev/eslint-plugin
## 3.0.0-beta.50
## 3.0.0-beta.49
## 3.0.0-beta.48
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/eslint-plugin",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "ESLint plugin with trigger.dev best practices",
"keywords": [
"eslint",
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/express
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/express",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Official Express adapter for Trigger.dev",
"license": "MIT",
"main": "./dist/index.js",
@@ -23,7 +23,7 @@
"./package.json": "./package.json"
},
"devDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"@trigger.dev/tsconfig": "workspace:*",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.13",
@@ -39,7 +39,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50"
},
"dependencies": {
"debug": "^4.3.4",
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/hono
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/hono",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "A Trigger.dev adapter for Hono.dev",
"license": "MIT",
"main": "./dist/index.js",
@@ -32,7 +32,7 @@
},
"peerDependencies": {
"hono": "3.x",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50"
},
"devDependencies": {
"@trigger.dev/tsconfig": "workspace:*",
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/integration-kit
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/core@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/integration-kit",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev Integration Kit has helpers to make creating integrations easier",
"license": "MIT",
"main": "./dist/index.js",
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/nestjs
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/nestjs",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Official NestJS adapter for Trigger.dev",
"license": "MIT",
"main": "./dist/index.js",
@@ -23,7 +23,7 @@
"./package.json": "./package.json"
},
"devDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"@trigger.dev/tsconfig": "workspace:*",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.13",
@@ -41,7 +41,7 @@
},
"peerDependencies": {
"@nestjs/common": ">=10.0.0",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50"
},
"dependencies": {
"@nestjs/common": "^10.2.4",
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/nextjs
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/nextjs",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev Next.js integration",
"license": "MIT",
"main": "./dist/index.js",
@@ -41,7 +41,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"next": ">=12.0.0"
},
"dependencies": {
+2
View File
@@ -1,5 +1,7 @@
# @trigger.dev/otlp-importer
## 3.0.0-beta.50
## 3.0.0-beta.49
## 3.0.0-beta.48
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/otlp-importer",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "OpenTelemetry OTLP Importer for Node.js written in TypeScript",
"license": "MIT",
"main": "./dist/index.js",
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/react
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/core@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/react",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev React SDK",
"license": "MIT",
"types": "dist/index.d.ts",
@@ -27,7 +27,7 @@
},
"dependencies": {
"@tanstack/react-query": "5.0.0-beta.2",
"@trigger.dev/core": "workspace:^3.0.0-beta.49",
"@trigger.dev/core": "workspace:^3.0.0-beta.50",
"debug": "^4.3.4",
"zod": "3.22.3"
},
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/remix
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/remix",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev Remix integration",
"license": "MIT",
"main": "./dist/index.js",
@@ -39,7 +39,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50",
"@remix-run/server-runtime": ">1.19.0"
},
"dependencies": {
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/sveltekit
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sveltekit",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "Trigger.dev svelteKit integration",
"license": "MIT",
"main": "./dist/index.js",
@@ -39,7 +39,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.50"
},
"dependencies": {
"debug": "^4.3.4"
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/testing
## 3.0.0-beta.50
### Patch Changes
- Updated dependencies [8ba998794]
- @trigger.dev/sdk@3.0.0-beta.50
- @trigger.dev/core@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@trigger.dev/testing",
"description": "A collection of useful tools to write tests for Trigger.dev.",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+9
View File
@@ -1,5 +1,14 @@
# @trigger.dev/sdk
## 3.0.0-beta.50
### Patch Changes
- 8ba998794: Added declarative cron schedules
- Updated dependencies [8ba998794]
- @trigger.dev/core@3.0.0-beta.50
- @trigger.dev/core-backend@3.0.0-beta.50
## 3.0.0-beta.49
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sdk",
"version": "3.0.0-beta.49",
"version": "3.0.0-beta.50",
"description": "trigger.dev Node.JS SDK",
"license": "MIT",
"main": "./dist/index.js",
@@ -48,8 +48,8 @@
"@opentelemetry/api": "^1.8.0",
"@opentelemetry/api-logs": "^0.48.0",
"@opentelemetry/semantic-conventions": "^1.22.0",
"@trigger.dev/core": "workspace:3.0.0-beta.49",
"@trigger.dev/core-backend": "workspace:3.0.0-beta.49",
"@trigger.dev/core": "workspace:3.0.0-beta.50",
"@trigger.dev/core-backend": "workspace:3.0.0-beta.50",
"chalk": "^5.2.0",
"cronstrue": "^2.21.0",
"debug": "^4.3.4",
+45 -1
View File
@@ -16,13 +16,57 @@ import { Task, TaskOptions, apiClientMissingError, createTask } from "../shared"
import * as SchedulesAPI from "./api";
import { tracer } from "../tracer";
export type ScheduleOptions<
TIdentifier extends string,
TOutput,
TInitOutput extends InitOutput,
> = TaskOptions<TIdentifier, SchedulesAPI.ScheduledTaskPayload, TOutput, TInitOutput> & {
/** You can optionally specify a CRON schedule on your task. You can also dynamically add a schedule in the dashboard or using the SDK functions.
*
* 1. Pass a CRON pattern string
* ```ts
* "0 0 * * *"
* ```
*
* 2. Or an object with a pattern and an optional timezone (default is "UTC")
* ```ts
* {
* pattern: "0 0 * * *",
* timezone: "America/Los_Angeles"
* }
* ```
*
* @link https://trigger.dev/docs/v3/tasks-scheduled
*/
cron?:
| string
| {
pattern: string;
timezone?: string;
};
};
export function task<TIdentifier extends string, TOutput, TInitOutput extends InitOutput>(
params: TaskOptions<TIdentifier, SchedulesAPI.ScheduledTaskPayload, TOutput, TInitOutput>
params: ScheduleOptions<TIdentifier, TOutput, TInitOutput>
): Task<TIdentifier, SchedulesAPI.ScheduledTaskPayload, TOutput> {
const task = createTask(params);
const cron = params.cron
? typeof params.cron === "string"
? params.cron
: params.cron.pattern
: undefined;
const timezone =
(params.cron && typeof params.cron !== "string" ? params.cron.timezone : "UTC") ?? "UTC";
taskCatalog.updateTaskMetadata(task.id, {
triggerSource: "schedule",
schedule: cron
? {
cron: cron,
timezone,
}
: undefined,
});
return task;

Some files were not shown because too many files have changed in this diff Show More