diff --git a/.changeset/lemon-jobs-repair.md b/.changeset/lemon-jobs-repair.md
new file mode 100644
index 000000000..c2c42a32d
--- /dev/null
+++ b/.changeset/lemon-jobs-repair.md
@@ -0,0 +1,5 @@
+---
+"@trigger.dev/nestjs": patch
+---
+
+fix: [nestjs integration] fastify HTTP adapter detection now works correctly for response headers
diff --git a/.github/ISSUE_TEMPLATE/instrumentation_request.yml b/.github/ISSUE_TEMPLATE/instrumentation_request.yml
new file mode 100644
index 000000000..157e226fa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/instrumentation_request.yml
@@ -0,0 +1,21 @@
+name: OpenTelemetry Auto-Instrumentation Request
+description: Suggest an SDK that you'd like to be auto-instrumented in the Run log view
+title: "auto-instrumentation: "
+labels: ["🌟 enhancement"]
+body:
+ - type: textarea
+ attributes:
+ label: What API or SDK would you to have automatic spans for?
+ description: A clear description of which API or SDK you'd like, and links to it.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Is there an existing OpenTelemetry auto-instrumentation package?
+ description: You can search for existing ones – https://opentelemetry.io/ecosystem/registry/?component=instrumentation&language=js
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Additional information
+ description: Add any other information related to the feature here. If your feature request is related to any issues or discussions, link them here.
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 25375c084..01f730f61 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -33,7 +33,7 @@
"type": "node-terminal",
"request": "launch",
"name": "Debug V3 Dev CLI",
- "command": "pnpm exec trigger.dev dev",
+ "command": "pnpm exec trigger.dev dev --log-level debug",
"cwd": "${workspaceFolder}/references/v3-catalog",
"sourceMaps": true
},
diff --git a/apps/webapp/app/models/member.server.ts b/apps/webapp/app/models/member.server.ts
index 716590898..bfb51bdf7 100644
--- a/apps/webapp/app/models/member.server.ts
+++ b/apps/webapp/app/models/member.server.ts
@@ -225,3 +225,36 @@ export async function resendInvite({ inviteId }: { inviteId: string }) {
},
});
}
+
+export async function revokeInvite({
+ userId,
+ slug,
+ inviteId,
+}: {
+ userId: string;
+ slug: string;
+ inviteId: string;
+}) {
+ const org = await prisma.organization.findFirst({
+ where: { slug, members: { some: { userId } } },
+ });
+
+ if (!org) {
+ throw new Error("User does not have access to this organization");
+ }
+ const invite = await prisma.orgMemberInvite.delete({
+ where: {
+ id: inviteId,
+ },
+ select: {
+ email: true,
+ organization: true,
+ },
+ });
+
+ if (!invite) {
+ throw new Error("Invite not found");
+ }
+
+ return { email: invite.email, organization: invite.organization };
+}
diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts
index a9759e4d6..6dd1d3ff1 100644
--- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts
+++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts
@@ -55,7 +55,7 @@ export class SpanPresenter {
output: span.output ? JSON.stringify(span.output, null, 2) : undefined,
payload: span.payload ? JSON.stringify(span.payload, null, 2) : undefined,
properties: span.properties ? JSON.stringify(span.properties, null, 2) : undefined,
- showActionBar: (span.properties?.show as any)?.actions === true,
+ showActionBar: span.show?.actions === true,
},
};
}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx
index 80718a213..f6cd7a4fc 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx
@@ -31,7 +31,12 @@ import { useUser } from "~/hooks/useUser";
import { getTeamMembersAndInvites, removeTeamMember } from "~/models/member.server";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { requireUserId } from "~/services/session.server";
-import { inviteTeamMemberPath, organizationTeamPath, resendInvitePath } from "~/utils/pathBuilder";
+import {
+ inviteTeamMemberPath,
+ organizationTeamPath,
+ resendInvitePath,
+ revokeInvitePath,
+} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
@@ -147,8 +152,9 @@ export default function Page() {
Invite sent {}
-
+
+
))}
@@ -272,7 +278,7 @@ function LeaveTeamModal({
function ResendButton({ invite }: { invite: Invite }) {
return (
-
);
}
+
+function RevokeButton({ invite }: { invite: Invite }) {
+ const organization = useOrganization();
+
+ return (
+
+ );
+}
diff --git a/apps/webapp/app/routes/invite-revoke.tsx b/apps/webapp/app/routes/invite-revoke.tsx
new file mode 100644
index 000000000..b066a08ba
--- /dev/null
+++ b/apps/webapp/app/routes/invite-revoke.tsx
@@ -0,0 +1,39 @@
+import { parse } from "@conform-to/zod";
+import { ActionFunction, json } from "@remix-run/server-runtime";
+import { z } from "zod";
+import { revokeInvite } from "~/models/member.server";
+import { redirectWithSuccessMessage } from "~/models/message.server";
+import { requireUserId } from "~/services/session.server";
+import { organizationTeamPath } from "~/utils/pathBuilder";
+
+export const revokeSchema = z.object({
+ inviteId: z.string(),
+ slug: z.string(),
+});
+
+export const action: ActionFunction = async ({ request }) => {
+ const userId = await requireUserId(request);
+
+ const formData = await request.formData();
+ const submission = parse(formData, { schema: revokeSchema });
+
+ if (!submission.value || submission.intent !== "submit") {
+ return json(submission);
+ }
+
+ try {
+ const { email, organization } = await revokeInvite({
+ userId,
+ slug: submission.value.slug,
+ inviteId: submission.value.inviteId,
+ });
+
+ return redirectWithSuccessMessage(
+ organizationTeamPath(organization),
+ request,
+ `Invite revoked for ${email}`
+ );
+ } catch (error: any) {
+ return json({ errors: { body: error.message } }, { status: 400 });
+ }
+};
diff --git a/apps/webapp/app/utils/pathBuilder.ts b/apps/webapp/app/utils/pathBuilder.ts
index 67f396a4c..50a705b58 100644
--- a/apps/webapp/app/utils/pathBuilder.ts
+++ b/apps/webapp/app/utils/pathBuilder.ts
@@ -132,6 +132,10 @@ export function logoutPath() {
return `/logout`;
}
+export function revokeInvitePath() {
+ return `/invite-revoke`;
+}
+
// Org
export function organizationPath(organization: OrgForPath) {
return `/orgs/${organizationParam(organization)}`;
diff --git a/apps/webapp/app/v3/eventRepository.server.ts b/apps/webapp/app/v3/eventRepository.server.ts
index a87ab0b9c..54bea61a0 100644
--- a/apps/webapp/app/v3/eventRepository.server.ts
+++ b/apps/webapp/app/v3/eventRepository.server.ts
@@ -354,6 +354,14 @@ export class EventRepository {
? null
: unflattenAttributes(fullEvent.output as Attributes);
+ const show = unflattenAttributes(
+ filteredAttributes(fullEvent.properties as Attributes, SemanticInternalAttributes.SHOW)
+ )[SemanticInternalAttributes.SHOW] as
+ | {
+ actions?: boolean;
+ }
+ | undefined;
+
const properties = sanitizedAttributes(fullEvent.properties);
const events = transformEvents(span.data.events, fullEvent.metadata as Attributes);
@@ -365,6 +373,7 @@ export class EventRepository {
output,
properties,
events,
+ show,
};
}
@@ -480,14 +489,14 @@ export class EventRepository {
const links: Link[] =
options.spanParentAsLink && propagatedContext?.traceparent
? [
- {
- context: {
- traceId: propagatedContext.traceparent.traceId,
- spanId: propagatedContext.traceparent.spanId,
- traceFlags: TraceFlags.SAMPLED,
+ {
+ context: {
+ traceId: propagatedContext.traceparent.traceId,
+ spanId: propagatedContext.traceparent.spanId,
+ traceFlags: TraceFlags.SAMPLED,
+ },
},
- },
- ]
+ ]
: [];
const eventBuilder = {
@@ -742,9 +751,9 @@ function prepareEvent(event: QueriedEvent): PreparedEvent {
function parseEventsField(events: Prisma.JsonValue): SpanEvents {
const eventsUnflattened = events
? (events as any[]).map((e) => ({
- ...e,
- properties: unflattenAttributes(e.properties as Attributes),
- }))
+ ...e,
+ properties: unflattenAttributes(e.properties as Attributes),
+ }))
: undefined;
const spanEvents = SpanEvents.safeParse(eventsUnflattened);
@@ -938,8 +947,8 @@ function transformException(
...exception,
stacktrace: exception.stacktrace
? correctErrorStackTrace(exception.stacktrace, projectDirAttributeValue, {
- removeFirstLine: true,
- })
+ removeFirstLine: true,
+ })
: undefined,
};
}
@@ -966,4 +975,4 @@ function getNowInNanoseconds(): bigint {
function getDateFromNanoseconds(nanoseconds: bigint) {
return new Date(Number(nanoseconds) / 1_000_000);
-}
\ No newline at end of file
+}
diff --git a/docs/_snippets/coming-soon-slim.mdx b/docs/_snippets/coming-soon-slim.mdx
new file mode 100644
index 000000000..d6c99341b
--- /dev/null
+++ b/docs/_snippets/coming-soon-slim.mdx
@@ -0,0 +1 @@
+
This feature will become available during the Developer Preview.
diff --git a/docs/_snippets/coming-soon.mdx b/docs/_snippets/coming-soon.mdx
new file mode 100644
index 000000000..2c606c36d
--- /dev/null
+++ b/docs/_snippets/coming-soon.mdx
@@ -0,0 +1,3 @@
+
This feature will become available during the Developer Preview.
+
+To get the latest updates on the Developer Preview, [join our Discord community](https://trigger.dev/discord) or follow us on [Twitter](https://twitter.com/triggerdotdev).
diff --git a/docs/_snippets/incomplete-docs.mdx b/docs/_snippets/incomplete-docs.mdx
new file mode 100644
index 000000000..1ecd25cfb
--- /dev/null
+++ b/docs/_snippets/incomplete-docs.mdx
@@ -0,0 +1 @@
+
This documentation is coming soon.
diff --git a/docs/_snippets/v3/code/openai-retry.mdx b/docs/_snippets/v3/code/openai-retry.mdx
new file mode 100644
index 000000000..a0ad35aff
--- /dev/null
+++ b/docs/_snippets/v3/code/openai-retry.mdx
@@ -0,0 +1,34 @@
+```ts /trigger/openai.ts
+import { task } from "@trigger.dev/sdk/v3";
+import OpenAI from "openai";
+
+const openai = new OpenAI({
+ apiKey: process.env.OPENAI_API_KEY,
+});
+
+export const openaiTask = task({
+ id: "openai-task",
+ //specifying retry options overrides the defaults defined in your trigger.config file
+ retry: {
+ maxAttempts: 10,
+ factor: 1.8,
+ minTimeoutInMs: 500,
+ maxTimeoutInMs: 30_000,
+ randomize: false,
+ },
+ run: async (payload: { prompt: string }) => {
+ //if this fails, it will throw an error and retry
+ const chatCompletion = await openai.chat.completions.create({
+ messages: [{ role: "user", content: payload.prompt }],
+ model: "gpt-3.5-turbo",
+ });
+
+ if (chatCompletion.choices[0]?.message.content === undefined) {
+ //sometimes OpenAI returns an empty response, let's retry by throwing an error
+ throw new Error("OpenAI call failed");
+ }
+
+ return chatCompletion.choices[0].message.content;
+ },
+});
+```
diff --git a/docs/_snippets/v3/paused-execution-free.mdx b/docs/_snippets/v3/paused-execution-free.mdx
new file mode 100644
index 000000000..e58678fe9
--- /dev/null
+++ b/docs/_snippets/v3/paused-execution-free.mdx
@@ -0,0 +1,4 @@
+
+ In the Trigger.dev Cloud we automatically pause execution of tasks when they are waiting for
+ longer than a few seconds. You are not charged when execution is paused.
+
diff --git a/docs/documentation/introduction.mdx b/docs/documentation/introduction.mdx
index a09b0aa35..be3b2ddf7 100644
--- a/docs/documentation/introduction.mdx
+++ b/docs/documentation/introduction.mdx
@@ -8,10 +8,7 @@ Trigger.dev is an open source framework for creating long-running Jobs directly
You can use [Trigger.dev Cloud](https://cloud.trigger.dev) or [Self-host Trigger.dev](/documentation/guides/self-hosting) on your own infrastructure.
-
- Trigger.dev v2 currently only supports serverless. We will be adding [support for long-running
- servers](https://github.com/triggerdotdev/trigger.dev/issues/244) soon.
-
+
Trigger.dev v2 currently only supports serverless.
diff --git a/docs/images/v3/environment-variables-actions.png b/docs/images/v3/environment-variables-actions.png
new file mode 100644
index 000000000..174d3461a
Binary files /dev/null and b/docs/images/v3/environment-variables-actions.png differ
diff --git a/docs/images/v3/environment-variables-delete-popover.png b/docs/images/v3/environment-variables-delete-popover.png
new file mode 100644
index 000000000..862f47d6a
Binary files /dev/null and b/docs/images/v3/environment-variables-delete-popover.png differ
diff --git a/docs/images/v3/environment-variables-edit-popover.png b/docs/images/v3/environment-variables-edit-popover.png
new file mode 100644
index 000000000..d1adabc79
Binary files /dev/null and b/docs/images/v3/environment-variables-edit-popover.png differ
diff --git a/docs/images/v3/environment-variables-page.jpg b/docs/images/v3/environment-variables-page.jpg
new file mode 100644
index 000000000..21a6ac94b
Binary files /dev/null and b/docs/images/v3/environment-variables-page.jpg differ
diff --git a/docs/images/v3/environment-variables-panel.jpg b/docs/images/v3/environment-variables-panel.jpg
new file mode 100644
index 000000000..3dd3d6429
Binary files /dev/null and b/docs/images/v3/environment-variables-panel.jpg differ
diff --git a/docs/images/v3/run-in-progress.png b/docs/images/v3/run-in-progress.png
new file mode 100644
index 000000000..3ba86f16a
Binary files /dev/null and b/docs/images/v3/run-in-progress.png differ
diff --git a/docs/images/v3/run-log.png b/docs/images/v3/run-log.png
new file mode 100644
index 000000000..7b695e5ed
Binary files /dev/null and b/docs/images/v3/run-log.png differ
diff --git a/docs/images/v3/test-select-environment.png b/docs/images/v3/test-select-environment.png
new file mode 100644
index 000000000..078162461
Binary files /dev/null and b/docs/images/v3/test-select-environment.png differ
diff --git a/docs/images/v3/test-select-task.png b/docs/images/v3/test-select-task.png
new file mode 100644
index 000000000..ec7a6fac0
Binary files /dev/null and b/docs/images/v3/test-select-task.png differ
diff --git a/docs/images/v3/test-set-payload.png b/docs/images/v3/test-set-payload.png
new file mode 100644
index 000000000..b0c3a5661
Binary files /dev/null and b/docs/images/v3/test-set-payload.png differ
diff --git a/docs/integrations/apis/openai/chat.mdx b/docs/integrations/apis/openai/chat.mdx
index 3766259c5..be5f405fe 100644
--- a/docs/integrations/apis/openai/chat.mdx
+++ b/docs/integrations/apis/openai/chat.mdx
@@ -26,7 +26,7 @@ await io.openai.chat.completions.create("chat-completion", {
Creates a model response for the given chat conversation, but runs the request in the background using [io.backgroundFetch()](/sdk/io/backgroundfetch)
```ts example.ts
-await io.openai.chat.completions.create("chat-completion", {
+await io.openai.chat.completions.backgroundCreate("chat-completion", {
model: "gpt-3.5-turbo",
messages: [
{
diff --git a/docs/mint.json b/docs/mint.json
index a2a62fe2b..7fb380e83 100644
--- a/docs/mint.json
+++ b/docs/mint.json
@@ -49,6 +49,10 @@
{
"name": "Examples",
"url": "https://trigger.dev/apis"
+ },
+ {
+ "name": "v3 Developer Preview",
+ "url": "v3"
}
],
"redirects": [
@@ -427,6 +431,157 @@
{
"group": "Overview",
"pages": ["examples/introduction"]
+ },
+ {
+ "group": "",
+ "pages": ["v3/introduction"]
+ },
+ {
+ "group": "Getting Started",
+ "pages": [
+ "v3/quick-start",
+ "v3/upgrading-from-v2",
+ "v3/changelog",
+ "v3/feature-matrix",
+ "v3/limits"
+ ]
+ },
+ {
+ "group": "Fundamentals",
+ "pages": [
+ "v3/trigger-folder",
+ "v3/tasks-overview",
+ "v3/triggering",
+ {
+ "group": "Task types",
+ "pages": ["v3/tasks-regular", "v3/tasks-scheduled", "v3/tasks-zod", "v3/tasks-webhooks"]
+ }
+ ]
+ },
+ {
+ "group": "Development",
+ "pages": ["v3/cli-dev", "v3/run-tests"]
+ },
+ {
+ "group": "Deployment",
+ "pages": [
+ "v3/deploy-environment-variables",
+ "v3/cli-deploy",
+ "v3/github-actions",
+ {
+ "group": "Deployment integrations",
+ "pages": ["v3/vercel-integration"]
+ }
+ ]
+ },
+ {
+ "group": "Writing tasks",
+ "pages": [
+ "v3/writing-tasks-introduction",
+ "v3/logging",
+ "v3/errors-retrying",
+ {
+ "group": "Wait",
+ "pages": [
+ "v3/wait",
+ "v3/wait-for",
+ "v3/wait-until",
+ "v3/wait-for-event",
+ "v3/wait-for-request"
+ ]
+ },
+ "v3/queue-concurrency",
+ "v3/versioning",
+ "v3/machines",
+ "v3/idempotency",
+ "v3/reattempting-replaying",
+ "v3/trigger-filters",
+ "v3/notifications",
+ "v3/rollbacks",
+ "v3/using-apis",
+ "v3/middleware",
+ "v3/automated-tests"
+ ]
+ },
+ {
+ "group": "Dashboard",
+ "pages": [
+ "v3/dashboard-overview",
+ "v3/dashboard-runs",
+ "v3/dashboard-tests",
+ "v3/dashboard-environment-variables"
+ ]
+ },
+ {
+ "group": "API reference",
+ "pages": [
+ {
+ "group": "Functions",
+ "pages": [
+ "v3/reference-task",
+ "v3/reference-cron-task",
+ "v3/reference-cron-dynamic",
+ "v3/reference-interval-task",
+ "v3/reference-interval-dynamic",
+ "v3/reference-zod-task",
+ "v3/reference-zod-catalog",
+ "v3/reference-task-trigger",
+ "v3/reference-task-trigger-and-wait",
+ "v3/reference-task-batch-trigger",
+ "v3/reference-task-batch-trigger-and-wait",
+ "v3/reference-wait-for",
+ "v3/reference-wait-until",
+ "v3/reference-wait-for-event",
+ "v3/reference-wait-for-request",
+ "v3/reference-retry-on-throw",
+ "v3/reference-retry-fetch",
+ "v3/reference-retry-intercept-fetch",
+ "v3/reference-notification-catalog",
+ "v3/reference-notify",
+ "v3/reference-queue"
+ ]
+ },
+ {
+ "group": "Objects",
+ "pages": ["v3/reference-context"]
+ },
+ {
+ "group": "CLI",
+ "pages": [
+ "v3/reference-cli-init",
+ "v3/reference-cli-dev",
+ "v3/reference-cli-deploy",
+ "v3/reference-cli-login",
+ "v3/reference-cli-logout",
+ "v3/reference-cli-update",
+ "v3/reference-cli-build",
+ "v3/reference-cli-who-am-i"
+ ]
+ },
+ "v3/reference-trigger-config"
+ ]
+ },
+ {
+ "group": "Architecture",
+ "pages": [
+ "v3/architecture-how-it-works",
+ "v3/architecture-multi-tenant-queue",
+ "v3/architecture-reliability"
+ ]
+ },
+ {
+ "group": "Open source",
+ "pages": ["v3/github-repo", "v3/open-source-self-hosting", "v3/open-source-contributing"]
+ },
+ {
+ "group": "Help",
+ "pages": [
+ "v3/help-faqs",
+ "v3/community",
+ "v3/help-email",
+ "v3/help-slack",
+ "v3/help-uptime-status"
+ ]
}
],
"footerSocials": {
diff --git a/docs/v3/architecture-how-it-works.mdx b/docs/v3/architecture-how-it-works.mdx
new file mode 100644
index 000000000..728e16707
--- /dev/null
+++ b/docs/v3/architecture-how-it-works.mdx
@@ -0,0 +1,7 @@
+---
+title: "Architecture: How it works"
+sidebarTitle: "How it works"
+description: "An overview of how Trigger.dev v3 works under the hood."
+---
+
+
diff --git a/docs/v3/architecture-multi-tenant-queue.mdx b/docs/v3/architecture-multi-tenant-queue.mdx
new file mode 100644
index 000000000..b775b401c
--- /dev/null
+++ b/docs/v3/architecture-multi-tenant-queue.mdx
@@ -0,0 +1,7 @@
+---
+title: "Architecture: Multi-tenant queue"
+sidebarTitle: "Multi-tenant queue"
+description: "We built a reliable and fair multi-tenant queue that controls triggering all tasks."
+---
+
+
diff --git a/docs/v3/architecture-reliability.mdx b/docs/v3/architecture-reliability.mdx
new file mode 100644
index 000000000..24bf1474a
--- /dev/null
+++ b/docs/v3/architecture-reliability.mdx
@@ -0,0 +1,7 @@
+---
+title: "Architecture: Reliability"
+sidebarTitle: "Reliability"
+description: "How reliability is achieved with Trigger.dev."
+---
+
+
diff --git a/docs/v3/automated-tests.mdx b/docs/v3/automated-tests.mdx
new file mode 100644
index 000000000..8c6c6cee1
--- /dev/null
+++ b/docs/v3/automated-tests.mdx
@@ -0,0 +1,6 @@
+---
+title: "Automated tests"
+description: "Write automated tests of your tasks."
+---
+
+
diff --git a/docs/v3/changelog.mdx b/docs/v3/changelog.mdx
new file mode 100644
index 000000000..661f4d7ca
--- /dev/null
+++ b/docs/v3/changelog.mdx
@@ -0,0 +1,6 @@
+---
+title: "Changelog"
+url: "https://trigger.dev/changelog"
+---
+
+Our [changelog](https://trigger.dev/changelog) is the best way to stay up to date with the latest changes to Trigger.
diff --git a/docs/v3/cli-deploy.mdx b/docs/v3/cli-deploy.mdx
new file mode 100644
index 000000000..957d2b14d
--- /dev/null
+++ b/docs/v3/cli-deploy.mdx
@@ -0,0 +1,88 @@
+---
+title: "CLI deploy command"
+description: "The `trigger.dev deploy` command can be used to manually deploy."
+---
+
+You run the command like this:
+
+
+
+```bash npm
+npx trigger.dev@v3 deploy
+```
+
+```bash pnpm
+pnpm dlx trigger.dev@v3 deploy
+```
+
+```bash yarn
+yarn dlx trigger.dev@v3 deploy
+```
+
+
+
+It performs a few steps to deploy:
+
+1. Typechecks the code.
+2. Compiles and bundles the code.
+3. Checks that [environment variables](/v3/deploy-environment-variables) are set.
+4. Deploys the code to the cloud.
+5. Registers the tasks as a new version in the environment (prod by default).
+
+## Options
+
+### Environment `--env` or `-e`
+
+Defaults to `prod` but you can specify `staging`.
+
+### Skip typecheck `--skip-typecheck` or `-T`
+
+Skips the pre-build typecheck step.
+
+### Build platform `--build-platform`
+
+The platform to build the deployment image for. Defaults to `linux/amd64`.
+
+### Log level `--log-level` or `-l`
+
+The log level to use (debug, info, log, warn, error, none). Defaults to `log`.
+
+### Set config filename `--config` or `-c`
+
+The name of the config file, found where the command is run from. Defaults to `trigger.config.ts`.
+
+### Set the projectRef `--project-ref` or `-p`
+
+The project ref. Required if there is no config file.
+
+### Self-hosted options
+
+These options are usually only relevant to self-hosters or for local development.
+
+#### Skip deploying the image `--skip-deploy` or `-D`
+
+Load the built image into your local docker.
+
+#### Self-hosted (builds locally) `--self-hosted`
+
+Builds and loads the image using your local docker. Use the `--registry` option to specify the registry to push the image to when using `--self-hosted`, or just use `--push-image` to push to the default registry.
+
+#### Registry `--registry`
+
+
+
+The registry to push the image to when using --self-hosted.
+
+#### Push image `--push-image`
+
+
+
+When using the --self-hosted flag, push the image to the default registry. (defaults to false when not using --registry)
+
+#### Tag the image `--tag`
+
+
+
+Specify the tag to use when pushing the image to the registry.
+
+{/* todo add options, remove the reference docs */}
diff --git a/docs/v3/cli-dev.mdx b/docs/v3/cli-dev.mdx
new file mode 100644
index 000000000..0cad51ea4
--- /dev/null
+++ b/docs/v3/cli-dev.mdx
@@ -0,0 +1,48 @@
+---
+title: "CLI dev command"
+description: "The `trigger.dev dev` command is used to run your tasks locally."
+---
+
+This runs a server on your machine that can execute Trigger.dev tasks:
+
+
+
+```bash npm
+npx trigger.dev@v3 dev
+```
+
+```bash pnpm
+pnpm dlx trigger.dev@v3 dev
+```
+
+```bash yarn
+yarn dlx trigger.dev@v3 dev
+```
+
+
+
+You will see in the terminal that the server is running and listening for requests. When you run a task, you will see it in the terminal along with a link to view it in the dashboard.
+
+It is worth noting that each task runs in a separate Node process. This means that if you have a long-running task, it will not block other tasks from running.
+
+## Options
+
+### Attaching a local debugger
+
+You can use the `--debugger` flag to run the server in debug mode. This will allow you to attach a debugger to the server and debug your tasks.
+
+
+
+```bash npm
+npx trigger.dev@v3 dev --debugger
+```
+
+```bash pnpm
+pnpm dlx trigger.dev@v3 dev --debugger
+```
+
+```bash yarn
+yarn dlx trigger.dev@v3 dev --debugger
+```
+
+
diff --git a/docs/v3/community.mdx b/docs/v3/community.mdx
new file mode 100644
index 000000000..08c6e8242
--- /dev/null
+++ b/docs/v3/community.mdx
@@ -0,0 +1,6 @@
+---
+title: "Community"
+url: "https://trigger.dev/discord"
+---
+
+Please [join our community on Discord](https://trigger.dev/discord) to ask questions, share your projects, and get help from other developers.
diff --git a/docs/v3/dashboard-environment-variables.mdx b/docs/v3/dashboard-environment-variables.mdx
new file mode 100644
index 000000000..9e542e2d6
--- /dev/null
+++ b/docs/v3/dashboard-environment-variables.mdx
@@ -0,0 +1,7 @@
+---
+title: "Dashboard: environment variables"
+sidebarTitle: "Environment Variables"
+description: "Add, edit and delete Environment Variables from the Dashboard."
+---
+
+
diff --git a/docs/v3/dashboard-overview.mdx b/docs/v3/dashboard-overview.mdx
new file mode 100644
index 000000000..36e911f9e
--- /dev/null
+++ b/docs/v3/dashboard-overview.mdx
@@ -0,0 +1,7 @@
+---
+title: "Dashboard: overview"
+sidebarTitle: "Overview"
+description: "The dashboard has many features including: managing your projects, viewing run logs, editing environment variables and more."
+---
+
+
diff --git a/docs/v3/dashboard-runs.mdx b/docs/v3/dashboard-runs.mdx
new file mode 100644
index 000000000..ea48e72ce
--- /dev/null
+++ b/docs/v3/dashboard-runs.mdx
@@ -0,0 +1,7 @@
+---
+title: "Dashboard: runs"
+sidebarTitle: "Runs"
+description: "Find runs and view the detailed traces and logs for each run."
+---
+
+
diff --git a/docs/v3/dashboard-tests.mdx b/docs/v3/dashboard-tests.mdx
new file mode 100644
index 000000000..80da74639
--- /dev/null
+++ b/docs/v3/dashboard-tests.mdx
@@ -0,0 +1,7 @@
+---
+title: "Dashboard: tests"
+sidebarTitle: "Tests"
+description: "Test your tasks in Dev, Staging and Prod from the dashboard."
+---
+
+
diff --git a/docs/v3/deploy-environment-variables.mdx b/docs/v3/deploy-environment-variables.mdx
new file mode 100644
index 000000000..478356b3c
--- /dev/null
+++ b/docs/v3/deploy-environment-variables.mdx
@@ -0,0 +1,65 @@
+---
+title: "Environment Variables"
+description: "Any environment variables used in your tasks need to be added so the deployed code will run successfully."
+---
+
+An environment variable in Node.js is accessed in your code using `process.env.MY_ENV_VAR`.
+
+We deploy your tasks and scale them up and down when they are triggered. So any environment variables you use in your tasks need to accessible to us so your code will run successfully.
+
+## Setting environment variables
+
+
+
+
+ In the sidebar select the "Environment Variables" page, then press the "New environment variable"
+ button. 
+
+
+
+ You can add values for your local dev environment, staging and prod. {" "}
+
+
+
+
+
+ Specifying Dev values is optional. They will be overriden by values in your .env file when running
+ locally.
+
+
+## Editing environment variables
+
+You can edit an environment variable's values. You cannot edit the key name, you must delete and create a new one.
+
+
+
+
+ 
+
+
+
+ 
+
+
+
+
+## Deleting environment variables
+
+
+ Environment variables are fetched and injected before a runs begins. So if you delete one you can
+ cause runs to fail that are expecting variables to be set.
+
+
+
+
+
+ 
+
+
+
+ This will immediately delete the variable. 
+
+
+
diff --git a/docs/v3/errors-retrying.mdx b/docs/v3/errors-retrying.mdx
new file mode 100644
index 000000000..0dde4dd38
--- /dev/null
+++ b/docs/v3/errors-retrying.mdx
@@ -0,0 +1,290 @@
+---
+title: "Errors & Retrying"
+description: "How to deal with errors and write reliable tasks."
+---
+
+When an uncaught error is thrown inside your task, that task attempt will fail.
+
+You can configure retrying in two ways:
+
+1. In your [trigger.config file](/v3/reference-trigger-config) you can set the default retrying behavior for all tasks.
+2. On each task you can set the retrying behavior.
+
+
+ By default when you create your project using the CLI init command we disabled retrying in the DEV
+ environment. You can enable it in your [trigger.config file](/v3/reference-trigger-config).
+
+
+## A simple example with OpenAI
+
+This task will retry 10 times with exponential backoff.
+
+- `openai.chat.completions.create()` can throw an error.
+- The result can be empty and we want to try again. So we manually throw an error.
+
+
+
+## Combining tasks
+
+One way to gain reliability is to break your work into smaller tasks and [trigger](/v3/triggering) them from each other. Each task can have its own retrying behavior:
+
+```ts /trigger/multiple-tasks.ts
+export const myTask = task({
+ id: "my-task",
+ retry: {
+ maxAttempts: 10,
+ },
+ run: async (payload: string) => {
+ const result = await otherTask.triggerAndWait({ payload: "some data" });
+ //...do other stuff
+ },
+});
+
+export const otherTask = task({
+ id: "other-task",
+ retry: {
+ maxAttempts: 5,
+ },
+ run: async (payload: string) => {
+ return {
+ foo: "bar",
+ };
+ },
+});
+```
+
+Another benefit of this approach is that you can view the logs and retry each task independently from the dashboard.
+
+## Retrying smaller parts of a task
+
+Another complimentary strategy is to perform retrying inside of your task.
+
+We provide some useful functions that you can use to retry smaller parts of a task. Of course, you can also write your own logic or use other packages.
+
+### retry.onThrow()
+
+You can retry a block of code that can throw an error, with the same retry settings as a task.
+
+```ts /trigger/retry-on-throw.ts
+export const retryOnThrow = task({
+ id: "retry-on-throw",
+ run: async (payload: any) => {
+ //Will retry up to 3 times. If it fails 3 times it will throw.
+ const result = await retry.onThrow(
+ async ({ attempt }) => {
+ //throw on purpose the first 2 times, obviously this is a contrived example
+ if (attempt < 3) throw new Error("failed");
+ //...
+ return {
+ foo: "bar",
+ };
+ },
+ { maxAttempts: 3, randomize: false }
+ );
+
+ //this will log out after 3 attempts of retry.onThrow
+ logger.info("Result", { result });
+ },
+});
+```
+
+
+ If all of the attempts with `retry.onThrow` fail, an error will be thrown. You can catch this or
+ let it cause a retry of the entire task.
+
+
+### retry.fetch()
+
+You can use `fetch`, `axios`, or any other library in your code.
+
+But we do provide a convenient function to perform HTTP requests with conditional retrying based on the response:
+
+```ts /trigger/retry-fetch.ts
+export const taskWithFetchRetries = task({
+ id: "task-with-fetch-retries",
+ run: async ({ payload, ctx }) => {
+ //if the Response is a 429 (too many requests), it will retry using the data from the response. A lot of good APIs send these headers.
+ const headersResponse = await retry.fetch("http://my.host/test-headers", {
+ retry: {
+ "429": {
+ strategy: "headers",
+ limitHeader: "x-ratelimit-limit",
+ remainingHeader: "x-ratelimit-remaining",
+ resetHeader: "x-ratelimit-reset",
+ resetFormat: "unix_timestamp_in_ms",
+ },
+ },
+ });
+ const json = await headersResponse.json();
+ logger.info("Fetched headers response", { json });
+
+ //if the Response is a 500-599 (issue with the server you're calling), it will retry up to 10 times with exponential backoff
+ const backoffResponse = await retry.fetch("http://my.host/test-backoff", {
+ retry: {
+ "500-599": {
+ strategy: "backoff",
+ maxAttempts: 10,
+ factor: 2,
+ minTimeoutInMs: 1_000,
+ maxTimeoutInMs: 30_000,
+ randomize: false,
+ },
+ },
+ });
+ const json2 = await backoffResponse.json();
+ logger.info("Fetched backoff response", { json2 });
+
+ //You can additionally specify a timeout. In this case if the response takes longer than 1 second, it will retry up to 5 times with exponential backoff
+ const timeoutResponse = await retry.fetch("https://httpbin.org/delay/2", {
+ timeout: {
+ durationInMs: 1000,
+ retry: {
+ maxAttempts: 5,
+ factor: 1.8,
+ minTimeoutInMs: 500,
+ maxTimeoutInMs: 30_000,
+ randomize: false,
+ },
+ },
+ });
+ const json3 = await timeoutResponse.json();
+ logger.info("Fetched timeout response", { json3 });
+
+ return {
+ result: "success",
+ payload,
+ json,
+ json2,
+ json3,
+ };
+ },
+});
+```
+
+
+ If all of the attempts with `retry.fetch` fail, an error will be thrown. You can catch this or let
+ it cause a retry of the entire task.
+
+
+## Advanced error handling and retrying
+
+We provide a `handleError` callback on the task and in your `trigger.config` file. This gets called when an uncaught error is thrown in your task.
+
+You can
+
+- Inspect the error, log it, and return a different error if you'd like.
+- Modify the retrying behavior based on the error, payload, context, etc.
+
+If you don't return anything from the function it will use the settings on the task (or inherited from the config). So you only need to use this to override things.
+
+### OpenAI error handling example
+
+OpenAI calls can fail for a lot of reasons and the ideal retry behavior is different for each.
+
+In this complicated example:
+
+- We skip retrying if there's no Response status.
+- We skip retrying if you've run out of credits.
+- If there are no Response headers we let the normal retrying logic handle it (return undefined).
+- If we've run out of requests or tokens we retry at the time specified in the headers.
+
+```ts
+export const openaiTask = task({
+ id: "openai-task",
+ retry: {
+ maxAttempts: 1,
+ },
+ run: async (payload: { prompt: string }) => {
+ const chatCompletion = await openai.chat.completions.create({
+ messages: [{ role: "user", content: payload.prompt }],
+ model: "gpt-3.5-turbo",
+ });
+
+ return chatCompletion.choices[0].message.content;
+ },
+ handleError: async (payload, error, { ctx, retryAt }) => {
+ if (error instanceof OpenAI.APIError) {
+ if (!error.status) {
+ return {
+ skipRetrying: true,
+ };
+ }
+
+ if (error.status === 429 && error.type === "insufficient_quota") {
+ return {
+ skipRetrying: true,
+ };
+ }
+
+ if (!error.headers) {
+ //returning undefined means the normal retrying logic will be used
+ return;
+ }
+
+ const remainingRequests = error.headers["x-ratelimit-remaining-requests"];
+ const requestResets = error.headers["x-ratelimit-reset-requests"];
+
+ if (typeof remainingRequests === "string" && Number(remainingRequests) === 0) {
+ return {
+ retryAt: calculateISO8601DurationOpenAIVariantResetAt(requestResets),
+ };
+ }
+
+ const remainingTokens = error.headers["x-ratelimit-remaining-tokens"];
+ const tokensResets = error.headers["x-ratelimit-reset-tokens"];
+
+ if (typeof remainingTokens === "string" && Number(remainingTokens) === 0) {
+ return {
+ retryAt: calculateISO8601DurationOpenAIVariantResetAt(tokensResets),
+ };
+ }
+ }
+ },
+});
+```
+
+## Using try/catch to prevent retries
+
+Sometimes you want to catch an error and don't want to retry the task. You can use try/catch as you normally would. In this example we fallback to using Replicate if OpenAI fails.
+
+```ts /trigger/
+import { task } from "@trigger.dev/sdk/v3";
+
+export const openaiTask = task({
+ id: "openai-task",
+ run: async (payload: { prompt: string }) => {
+ try {
+ //if this fails, it will throw an error and retry
+ const chatCompletion = await openai.chat.completions.create({
+ messages: [{ role: "user", content: payload.prompt }],
+ model: "gpt-3.5-turbo",
+ });
+
+ if (chatCompletion.choices[0]?.message.content === undefined) {
+ //sometimes OpenAI returns an empty response, let's retry by throwing an error
+ throw new Error("OpenAI call failed");
+ }
+
+ return chatCompletion.choices[0].message.content;
+ } catch (error) {
+ //use Replicate if OpenAI fails
+ const prediction = await replicate.run(
+ "meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3",
+ {
+ input: {
+ prompt: payload.prompt,
+ max_new_tokens: 250,
+ },
+ }
+ );
+
+ if (prediction.output === undefined) {
+ //retry if Replicate fails
+ throw new Error("Replicate call failed");
+ }
+
+ return prediction.output;
+ }
+ },
+});
+```
diff --git a/docs/v3/feature-matrix.mdx b/docs/v3/feature-matrix.mdx
new file mode 100644
index 000000000..f9edc6f2d
--- /dev/null
+++ b/docs/v3/feature-matrix.mdx
@@ -0,0 +1,10 @@
+---
+title: "Feature matrix"
+description: "What features are currently available in the Developer Preview"
+---
+
+| Feature | Description | Status |
+| -------------------------------------- | ------------------------------------------ | ------ |
+| [Regular tasks](/v3/tasks-regular) | A task that can be triggered from anywhere | ✅ |
+| [Scheduled tasks](/v3/tasks-scheduled) | A task that can be triggered on a schedule | ⏳ |
+| [Webhook tasks](v3/tasks-webhooks) | A task that can be triggered by a webhook | ⏳ |
diff --git a/docs/v3/github-actions.mdx b/docs/v3/github-actions.mdx
new file mode 100644
index 000000000..e59385b11
--- /dev/null
+++ b/docs/v3/github-actions.mdx
@@ -0,0 +1,41 @@
+---
+title: "GitHub Actions"
+description: "You can easily deploy your tasks with GitHub actions."
+---
+
+This simple GitHub action file will deploy you Trigger.dev tasks when new code is pushed to the `main` branch and the `trigger` directory has changes in it.
+
+```yaml .github/workflows/release-trigger.yml
+name: Deploy to Trigger.dev
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - "trigger/**"
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Use Node.js 18.x
+ uses: actions/setup-node@v3
+ with:
+ node-version: "18.x"
+
+ - name: 🚀 Deploy Trigger.dev
+ env:
+ TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}
+ run: |
+ npx trigger.dev@v3 deploy
+```
+
+If you already have a GitHub action file, you can just add the final step "🚀 Deploy Trigger.dev" to your existing file.
+
+You need to add the `TRIGGER_ACCESS_TOKEN` secret to your repository. You can create a new access token by going to your profile page and then clicking on the "Personal Access Tokens" tab.
+
+To set it in GitHub go to your repository, click on "Settings", "Secrets and variables" and then "Actions". Add a new secret with the name `TRIGGER_ACCESS_TOKEN` and use the value of your access token.
diff --git a/docs/v3/github-repo.mdx b/docs/v3/github-repo.mdx
new file mode 100644
index 000000000..62b9dc111
--- /dev/null
+++ b/docs/v3/github-repo.mdx
@@ -0,0 +1,8 @@
+---
+title: "GitHub repo"
+url: "https://github.com/triggerdotdev/trigger.dev"
+---
+
+Trigger.dev is [Open Source on GitHub](https://github.com/triggerdotdev/trigger.dev). You can contribute to the project by submitting issues, pull requests, or simply by using it and providing feedback.
+
+You can also [self-host](/v3/open-source-self-hosting) the project if you want to run it on your own infrastructure.
diff --git a/docs/v3/help-email.mdx b/docs/v3/help-email.mdx
new file mode 100644
index 000000000..097f15f7d
--- /dev/null
+++ b/docs/v3/help-email.mdx
@@ -0,0 +1,6 @@
+---
+title: "Email us"
+url: "https://trigger.dev/contact"
+---
+
+You can [email us](https://trigger.dev/contact) by filling out this form.
diff --git a/docs/v3/help-faqs.mdx b/docs/v3/help-faqs.mdx
new file mode 100644
index 000000000..27a327d3c
--- /dev/null
+++ b/docs/v3/help-faqs.mdx
@@ -0,0 +1,6 @@
+---
+title: "Frequently Asked Questions"
+sidebarTitle: "FAQs"
+---
+
+
diff --git a/docs/v3/help-slack.mdx b/docs/v3/help-slack.mdx
new file mode 100644
index 000000000..9edad7a02
--- /dev/null
+++ b/docs/v3/help-slack.mdx
@@ -0,0 +1,11 @@
+---
+title: "Slack"
+---
+
+If you have a paid Trigger.dev account, you can request a private Slack Connect channel.
+
+To do this:
+
+1. Login to the [Trigger.dev web app](https://cloud.trigger.dev).
+2. Subscribe to a paid plan if you haven't already.
+3. In the bottom-left corner click "Join our Slack".
diff --git a/docs/v3/help-uptime-status.mdx b/docs/v3/help-uptime-status.mdx
new file mode 100644
index 000000000..9774c655b
--- /dev/null
+++ b/docs/v3/help-uptime-status.mdx
@@ -0,0 +1,6 @@
+---
+title: "Uptime status"
+url: "https://trigger.openstatus.dev/"
+---
+
+View the [current Trigger.dev Cloud system status](https://trigger.openstatus.dev/).
diff --git a/docs/v3/idempotency.mdx b/docs/v3/idempotency.mdx
new file mode 100644
index 000000000..ec41273bd
--- /dev/null
+++ b/docs/v3/idempotency.mdx
@@ -0,0 +1,6 @@
+---
+title: "Idempotency"
+description: "An API call or operation is “idempotent” if it has the same result when called more than once."
+---
+
+
diff --git a/docs/v3/introduction.mdx b/docs/v3/introduction.mdx
new file mode 100644
index 000000000..b3ef46512
--- /dev/null
+++ b/docs/v3/introduction.mdx
@@ -0,0 +1,77 @@
+---
+title: "Introduction"
+description: "Welcome to the Trigger.dev v3 documentation."
+---
+
+
+ The Trigger.dev v3 Developer Preview is currently in invite-only early access. [Sign up here to
+ request access](https://trigger.dev/v3-early-access).
+
+
+## What is Trigger.dev (v3)?
+
+Trigger.dev v3 makes it easy to write reliable long-running tasks without timeouts.
+
+- We run your tasks with no timeouts. You don't have to manage any infrastructure (unless you [self-host](/v3/open-source-self-hosting)). Workers are automatically scaled and managed for you.
+- We provide a multi-tenant queue that is used when triggering tasks.
+- We provide an SDK and CLI for writing tasks in your existing codebase, inside [/trigger folders](/v3/trigger-folder).
+- We provide different types of tasks: [regular](/v3/tasks-regular), [scheduled](/v3/tasks-scheduled), [zod](/v3/tasks-zod), [webhooks](/v3/tasks-webhooks).
+- We provide a dashboard for monitoring, debugging, and managing your tasks.
+
+We're [open source](https://github.com/triggerdotdev/trigger.dev) and you can choose to use the [Trigger.dev Cloud](https://cloud.trigger.dev) or [Self-host Trigger.dev](/v3/open-source-self-hosting) on your own infrastructure.
+
+## Getting started
+
+
+
+ Get started in 3 minutes.
+
+
+ Tasks are the core of Trigger.dev. Learn what they are and how to write them.
+
+
+
+## Getting help
+
+We'd love to hear from you or give you a hand getting started. Here are some ways to get in touch with us. We'd also ❤️ your support.
+
+
+
+ The help forum is a great place to get help with any questions about Trigger.dev.
+
+
+
+
+ }
+ href="https://twitter.com/triggerdotdev"
+ color="#1DA1F2"
+ >
+ Follow us on X (Twitter) to get the latest updates and news.
+
+
+ Arrange a call with one of the founders. We can help answer questions and give 1-on-1 help
+ building your first task.
+
+
+ Check us out at triggerdotdev/trigger.dev
+
+
diff --git a/docs/v3/limits.mdx b/docs/v3/limits.mdx
new file mode 100644
index 000000000..908a8a963
--- /dev/null
+++ b/docs/v3/limits.mdx
@@ -0,0 +1,5 @@
+---
+title: "Limits"
+---
+
+
diff --git a/docs/v3/logging.mdx b/docs/v3/logging.mdx
new file mode 100644
index 000000000..a730040c5
--- /dev/null
+++ b/docs/v3/logging.mdx
@@ -0,0 +1,79 @@
+---
+title: "Logging and tracing"
+description: "How to use the built-in logging and tracing system."
+---
+
+
+
+The [run log](/v3/dashboard-runs) shows you exactly what happened in every run of your tasks. It is comprised of logs, traces and spans.
+
+## Logs
+
+You can use `console.log()`, `console.error()`, etc as normal and they will be shown in your run log. This is the standard function so you can use it as you would in any other JavaScript or TypeScript code. Logs from any functions/packages will also be shown.
+
+### logger
+
+We recommend that you use our `logger` object which creates structured logs. Structured logs will make it easier for you to search the logs to quickly find runs.
+
+```ts /trigger/logging.ts
+import { task, logger } from "@trigger.dev/sdk/v3";
+
+export const loggingExample = task({
+ id: "logging-example",
+ run: async (payload: { data: Record }) => {
+ //the first parameter is the message, the second parameter must be a key-value object (Record)
+ logger.debug("Debug message", payload.data);
+ logger.log("Log message", payload.data);
+ logger.info("Info message", payload.data);
+ logger.warn("You've been warned", payload.data);
+ logger.error("Error message", payload.data);
+ },
+});
+```
+
+## Tracing and spans
+
+Tracing is a way to follow the flow of your code. It's very useful for debugging and understanding how your code is working, especially with long-running or complex tasks.
+
+Trigger.dev uses OpenTelemetry tracing under the hood. With automatic tracing for many things like task triggering, task attempts, HTTP requests, and more.
+
+### Automatic instrumentation
+
+| Name | Description |
+| ------------- | -------------------------------- |
+| Task triggers | Task triggers. |
+| Task attempts | Task attempts. |
+| HTTP requests | HTTP requests made by your code. |
+| OpenAI | OpenAI SDK calls. |
+
+We want to provide automatic instrumentation for as many things as possible. Please do [request any automatic instrumentation](https://github.com/triggerdotdev/trigger.dev/issues/new?template=instrumentation_request.yml) you would like to see.
+
+## Add custom traces
+
+If you want to add custom traces to your code, you can use the `logger.trace` function. It will create a new OTEL trace and you can set attributes on it.
+
+```ts
+import { logger, task } from "@trigger.dev/sdk/v3";
+
+export const customTrace = task({
+ id: "custom-trace",
+ run: async (payload) => {
+ //you can wrap code in a trace, and set attributes
+ const user = await logger.trace("fetch-user", async (span) => {
+ span.setAttribute("user.id", "1");
+
+ //...do stuff
+
+ //you can return a value
+ return {
+ id: "1",
+ name: "John Doe",
+ fetchedAt: new Date(),
+ };
+ });
+
+ const usersName = user.name;
+ },
+});
+```
diff --git a/docs/v3/machines.mdx b/docs/v3/machines.mdx
new file mode 100644
index 000000000..24694b67d
--- /dev/null
+++ b/docs/v3/machines.mdx
@@ -0,0 +1,23 @@
+---
+title: "Machines"
+description: "Configure the number of vCPUs and GBs of RAM you want the task to use."
+---
+
+The `machine` configuration is optional. Using higher spec machines will increase the cost of running the task but can also improve the performance of the task if it is CPU or memory bound.
+
+```ts /trigger/heavy-task.ts
+export const heavyTask = task({
+ id: "heavy-task",
+ machine: {
+ cpu: 2,
+ memory: 4,
+ },
+ run: async ({ payload, ctx }) => {
+ //...
+ },
+});
+```
+
+## Possible configurations
+
+
diff --git a/docs/v3/middleware.mdx b/docs/v3/middleware.mdx
new file mode 100644
index 000000000..f1dee48ac
--- /dev/null
+++ b/docs/v3/middleware.mdx
@@ -0,0 +1,6 @@
+---
+title: "Middleware"
+description: "This function is called before the `run` function, it allows you to wrap the run function with custom code."
+---
+
+
diff --git a/docs/v3/notifications.mdx b/docs/v3/notifications.mdx
new file mode 100644
index 000000000..6c65160e0
--- /dev/null
+++ b/docs/v3/notifications.mdx
@@ -0,0 +1,6 @@
+---
+title: "Notifications"
+description: "Send and receive notifications from your tasks to make your other systems aware of changes. For example, you can live-update your website as a task progresses."
+---
+
+
diff --git a/docs/v3/open-source-contributing.mdx b/docs/v3/open-source-contributing.mdx
new file mode 100644
index 000000000..e609f9e08
--- /dev/null
+++ b/docs/v3/open-source-contributing.mdx
@@ -0,0 +1,16 @@
+---
+title: "Contributing"
+description: "You can contribute to Trigger.dev in many ways."
+---
+
+Go to our [GitHub repository](https://github.com/triggerdotdev/trigger.dev) and open an issue or a pull request. We are always looking for contributors to help us improve Trigger.dev. You can contribute in many ways, including:
+
+- Reporting bugs
+- Suggesting new features
+- Writing documentation
+- Writing code
+- Reviewing code
+- Translating the app
+- Sharing the app with others
+- Giving feedback
+- And more!
diff --git a/docs/v3/open-source-self-hosting.mdx b/docs/v3/open-source-self-hosting.mdx
new file mode 100644
index 000000000..87b33a0ce
--- /dev/null
+++ b/docs/v3/open-source-self-hosting.mdx
@@ -0,0 +1,6 @@
+---
+title: "Self-hosting"
+description: "You can self-host Trigger.dev on your own infrastructure."
+---
+
+
diff --git a/docs/v3/queue-concurrency.mdx b/docs/v3/queue-concurrency.mdx
new file mode 100644
index 000000000..020e596c4
--- /dev/null
+++ b/docs/v3/queue-concurrency.mdx
@@ -0,0 +1,181 @@
+---
+title: "Concurrency & Queues"
+description: "Configure what you want to happen when there is more than one run at a time."
+---
+
+Controlling concurrency is useful when you have a task that can't be run concurrently, or when you want to limit the number of runs to avoid overloading a resource.
+
+## One at a time
+
+This task will only ever have a single run executing at a time. All other runs will be queued until the current run is complete.
+
+```ts /trigger/one-at-a-time.ts
+export const oneAtATime = task({
+ id: "one-at-a-time",
+ queue: {
+ concurrencyLimit: 1,
+ },
+ run: async ({ payload, ctx }) => {
+ //...
+ },
+});
+```
+
+## Parallelism
+
+You can execute lots of tasks at once by combining high concurrency with [batch triggering](/v3/triggering) (or just triggering in a loop).
+
+```ts /trigger/parallelism.ts
+export const parallelism = task({
+ id: "parallelism",
+ queue: {
+ concurrencyLimit: 100,
+ },
+ run: async ({ payload, ctx }) => {
+ //...
+ },
+});
+```
+
+
+ Be careful with high concurrency. If you're doing API requests you might hit rate limits. If
+ you're hitting your database you might overload it.
+
+
+
+ Your organization has a maximum concurrency limit which depends on your plan. If you're a paying
+ customer you can request a higher limit by [contacting us](https://www.trigger.dev/contact).
+
+
+## Defining a queue
+
+As well as putting queue settings directly on a task, you can define a queue and reuse it across multiple tasks. This allows you to share the same concurrency limit:
+
+```ts /trigger/queue.ts
+const myQueue = queue({
+ name: "my-queue",
+ concurrencyLimit: 1,
+});
+
+export const task1 = task({
+ id: "task-1",
+ queue: {
+ name: "my-queue",
+ },
+ run: async (payload: { message: string }) => {
+ // ...
+ },
+});
+
+export const task2 = task({
+ id: "task-2",
+ queue: {
+ name: "my-queue",
+ },
+ run: async (payload: { message: string }) => {
+ // ...
+ },
+});
+```
+
+## Setting the concurrency when you trigger a run
+
+When you trigger a task you can override the concurrency limit. This is really useful if you sometimes have high priority runs.
+
+The task:
+
+```ts /trigger/override-concurrency.ts
+const generatePullRequest = task({
+ id: "generate-pull-request",
+ queue: {
+ //normally when triggering this task it will be limited to 1 run at a time
+ concurrencyLimit: 1,
+ },
+ run: async (payload) => {
+ //todo generate a PR using OpenAI
+ },
+});
+```
+
+Triggering from your backend and overriding the concurrency:
+
+```ts app/api/push/route.ts
+import { generatePullRequest } from "~/trigger/override-concurrency";
+
+export async function POST(request: Request) {
+ const data = await request.json();
+
+ if (data.branch === "main") {
+ //trigger the task, with a different queue
+ const handle = await generatePullRequest.trigger({
+ payload: data,
+ options: {
+ queue: {
+ //the "main-branch" queue will have a concurrency limit of 10
+ //this triggered run will use that queue
+ name: "main-branch",
+ concurrencyLimit: 10,
+ },
+ },
+ });
+
+ return Response.json(handle);
+ } else {
+ //triggered with the default (concurrency of 1)
+ const handle = await generatePullRequest.trigger({
+ payload: data,
+ });
+ return Response.json(handle);
+ }
+}
+```
+
+## Concurrency keys and per-tenant queuing
+
+If you're building an application where you want to run tasks for your users, you might want a separate queue for each of your users. (It doesn't have to be users, it can be any entity you want to separately limit the concurrency for.)
+
+You can do this by using `concurrencyKey`. It creates a separate queue for each value of the key.
+
+Your backend code:
+
+```ts app/api/pr/route.ts
+import { generatePullRequest } from "~/trigger/override-concurrency";
+
+export async function POST(request: Request) {
+ const data = await request.json();
+
+ if (data.isFreeUser) {
+ //free users can only have 1 PR generated at a time
+ const handle = await generatePullRequest.trigger({
+ payload: data,
+ options: {
+ queue: {
+ //every free user gets a queue with a concurrency limit of 1
+ name: "free-users",
+ concurrencyLimit: 1,
+ concurrencyKey: data.userId,
+ },
+ },
+ });
+
+ //return a success response with the handle
+ return Response.json(handle);
+ } else {
+ //trigger the task, with a different queue
+ const handle = await generatePullRequest.trigger({
+ payload: data,
+ options: {
+ queue: {
+ //every paid user gets a queue with a concurrency limit of 10
+ name: "paid-users",
+ concurrencyLimit: 10,
+ concurrencyKey: data.userId,
+ },
+ },
+ });
+
+ //return a success response with the handle
+ return Response.json(handle);
+ }
+}
+```
diff --git a/docs/v3/quick-start.mdx b/docs/v3/quick-start.mdx
new file mode 100644
index 000000000..b5fbe6898
--- /dev/null
+++ b/docs/v3/quick-start.mdx
@@ -0,0 +1,119 @@
+---
+title: "Quick start"
+description: "How to get started in 3 minutes using the CLI and SDK."
+---
+
+In this guide we will:
+
+1. Create a `trigger.config.ts` file and a `/trigger` directory with an example task.
+2. Get you to run the task using the CLI.
+3. Show you how to view the run logs for that task.
+
+
+
+
+
+You can either:
+
+- Use the [Trigger.dev Cloud](https://cloud.trigger.dev).
+- Or [self-host](/v3/open-source-self-hosting) the service.
+
+
+
+
+
+Once you've created an account, follow the steps in the app to:
+
+1. Complete your account details.
+2. Create your first Organization and Project.
+
+
+ Make sure you create a "Version 3" project. You might need to [request early
+ access](https://trigger.dev/v3-early-access).
+
+
+
+
+
+
+The easiest way to get started it to use the CLI. It will add Trigger.dev to your existing project, create a `/trigger` folder and give you an example task.
+
+Run this command in the root of your project to get started:
+
+
+
+```bash npm
+npx trigger.dev@v3 init
+```
+
+```bash pnpm
+pnpm dlx trigger.dev@v3 init
+```
+
+```bash yarn
+yarn dlx trigger.dev@v3 init
+```
+
+
+
+It will do a few things:
+
+1. Log you into the CLI if you're not already logged in.
+2. Create a `trigger.config.ts` file in the root of your project.
+3. Ask where you'd like to create the `/trigger` directory.
+4. Create the `/trigger` directory with an example task, `/trigger/example.[ts/js]`.
+
+
+
+
+
+The CLI `dev` command runs a server for your tasks. It will watches for changes in your `/trigger` directory and communicates with the Trigger.dev platform to register your tasks, perform runs, and send data back and forth.
+
+
+
+```bash npm
+npx trigger.dev@v3 dev
+```
+
+```bash pnpm
+pnpm dlx trigger.dev@v3 dev
+```
+
+```bash yarn
+yarn dlx trigger.dev@v3 dev
+```
+
+
+
+
+
+
+
+The CLI `dev` command spits out various useful URLs. Right now we want to visit the Test page it provided.
+
+You should see our Example task in the list, select it. Most tasks have a "payload" which you enter in the JSON editor, but our example task doesn't need any input.
+
+Press the "Run test" button.
+
+
+
+
+
+Congratulations, you should see the run page which will live reload showing you the current state of the run.
+
+If you go back to your terminal you'll see that the dev command also shows tasks that are running and links to the run log.
+
+
+
+
+
+## Next steps
+
+
+
+ Learn how to trigger tasks from your code.
+
+
+ Tasks are the core of Trigger.dev. Learn what they are and how to write them.
+
+
diff --git a/docs/v3/reattempting-replaying.mdx b/docs/v3/reattempting-replaying.mdx
new file mode 100644
index 000000000..8e7ba3252
--- /dev/null
+++ b/docs/v3/reattempting-replaying.mdx
@@ -0,0 +1,6 @@
+---
+title: "Reattempting & Replaying"
+description: "You can reattempt a task that has failed all of its attempts. You can also replay a task with a new version of your code."
+---
+
+
diff --git a/docs/v3/reference-cli-build.mdx b/docs/v3/reference-cli-build.mdx
new file mode 100644
index 000000000..03876fecd
--- /dev/null
+++ b/docs/v3/reference-cli-build.mdx
@@ -0,0 +1,7 @@
+---
+title: "trigger.dev build"
+sidebarTitle: "build"
+description: "This command will build your tasks."
+---
+
+
diff --git a/docs/v3/reference-cli-deploy.mdx b/docs/v3/reference-cli-deploy.mdx
new file mode 100644
index 000000000..d9fd782c4
--- /dev/null
+++ b/docs/v3/reference-cli-deploy.mdx
@@ -0,0 +1,7 @@
+---
+title: "trigger.dev deploy"
+sidebarTitle: "deploy"
+description: "This command will deploy your tasks."
+---
+
+
diff --git a/docs/v3/reference-cli-dev.mdx b/docs/v3/reference-cli-dev.mdx
new file mode 100644
index 000000000..fc822b6dd
--- /dev/null
+++ b/docs/v3/reference-cli-dev.mdx
@@ -0,0 +1,7 @@
+---
+title: "trigger.dev dev"
+sidebarTitle: "dev"
+description: "This command runs your tasks locally."
+---
+
+
diff --git a/docs/v3/reference-cli-init.mdx b/docs/v3/reference-cli-init.mdx
new file mode 100644
index 000000000..65fc1840f
--- /dev/null
+++ b/docs/v3/reference-cli-init.mdx
@@ -0,0 +1,7 @@
+---
+title: "trigger.dev init"
+sidebarTitle: "init"
+description: "This command will setup your v3 project."
+---
+
+
diff --git a/docs/v3/reference-cli-login.mdx b/docs/v3/reference-cli-login.mdx
new file mode 100644
index 000000000..d8c24f97c
--- /dev/null
+++ b/docs/v3/reference-cli-login.mdx
@@ -0,0 +1,7 @@
+---
+title: "trigger.dev login"
+sidebarTitle: "login"
+description: "This command will log you in to the CLI. Required to run any other command."
+---
+
+
diff --git a/docs/v3/reference-cli-logout.mdx b/docs/v3/reference-cli-logout.mdx
new file mode 100644
index 000000000..1ca1453f9
--- /dev/null
+++ b/docs/v3/reference-cli-logout.mdx
@@ -0,0 +1,7 @@
+---
+title: "trigger.dev logout"
+sidebarTitle: "logout"
+description: "This command will log you out of the CLI."
+---
+
+
diff --git a/docs/v3/reference-cli-update.mdx b/docs/v3/reference-cli-update.mdx
new file mode 100644
index 000000000..f526a77a0
--- /dev/null
+++ b/docs/v3/reference-cli-update.mdx
@@ -0,0 +1,7 @@
+---
+title: "trigger.dev update"
+sidebarTitle: "update"
+description: "This command can be used to update all of your trigger.dev packages to the latest versions."
+---
+
+
diff --git a/docs/v3/reference-cli-who-am-i.mdx b/docs/v3/reference-cli-who-am-i.mdx
new file mode 100644
index 000000000..c23acbdc9
--- /dev/null
+++ b/docs/v3/reference-cli-who-am-i.mdx
@@ -0,0 +1,7 @@
+---
+title: "trigger.dev whoami"
+sidebarTitle: "whoami"
+description: "This command will return information about you, the logged in user."
+---
+
+
diff --git a/docs/v3/reference-context.mdx b/docs/v3/reference-context.mdx
new file mode 100644
index 000000000..9b869e270
--- /dev/null
+++ b/docs/v3/reference-context.mdx
@@ -0,0 +1,6 @@
+---
+title: "Context"
+description: "The Context object is part of the `run` function parameter and provides information about the current run."
+---
+
+
diff --git a/docs/v3/reference-cron-dynamic.mdx b/docs/v3/reference-cron-dynamic.mdx
new file mode 100644
index 000000000..e104b9165
--- /dev/null
+++ b/docs/v3/reference-cron-dynamic.mdx
@@ -0,0 +1,6 @@
+---
+title: "cron.dynamic()"
+description: "Trigger a task with many different CRON schedules. For example you can use this to let your users select when they want a reminder."
+---
+
+
diff --git a/docs/v3/reference-cron-task.mdx b/docs/v3/reference-cron-task.mdx
new file mode 100644
index 000000000..c8b8ef9f5
--- /dev/null
+++ b/docs/v3/reference-cron-task.mdx
@@ -0,0 +1,6 @@
+---
+title: "cron.task()"
+description: "Trigger a task on a recurring schedule using a CRON expression."
+---
+
+
diff --git a/docs/v3/reference-interval-dynamic.mdx b/docs/v3/reference-interval-dynamic.mdx
new file mode 100644
index 000000000..140189dab
--- /dev/null
+++ b/docs/v3/reference-interval-dynamic.mdx
@@ -0,0 +1,6 @@
+---
+title: "interval.dynamic()"
+description: "Trigger a task with many different interval schedules. For example you can use this to let your users select how often they want a reminder."
+---
+
+
diff --git a/docs/v3/reference-interval-task.mdx b/docs/v3/reference-interval-task.mdx
new file mode 100644
index 000000000..edbb0496a
--- /dev/null
+++ b/docs/v3/reference-interval-task.mdx
@@ -0,0 +1,6 @@
+---
+title: "interval.task()"
+description: "Trigger a task on a recurring schedule using the time interval you want between runs."
+---
+
+
diff --git a/docs/v3/reference-notification-catalog.mdx b/docs/v3/reference-notification-catalog.mdx
new file mode 100644
index 000000000..258d93737
--- /dev/null
+++ b/docs/v3/reference-notification-catalog.mdx
@@ -0,0 +1,6 @@
+---
+title: "notification.catalog()"
+description: "Create a set of events that can be emitted from your tasks. These can be subscribed to from your application to provide real-time updates to your users."
+---
+
+
diff --git a/docs/v3/reference-notify.mdx b/docs/v3/reference-notify.mdx
new file mode 100644
index 000000000..b905af74b
--- /dev/null
+++ b/docs/v3/reference-notify.mdx
@@ -0,0 +1,6 @@
+---
+title: "notify()"
+description: "Send a notification from your tasks. These can be subscribed to from your application to provide real-time updates to your users."
+---
+
+
diff --git a/docs/v3/reference-queue.mdx b/docs/v3/reference-queue.mdx
new file mode 100644
index 000000000..f6a565690
--- /dev/null
+++ b/docs/v3/reference-queue.mdx
@@ -0,0 +1,6 @@
+---
+title: "queue()"
+description: "Create queue settings that can be used when triggering a task."
+---
+
+
diff --git a/docs/v3/reference-retry-fetch.mdx b/docs/v3/reference-retry-fetch.mdx
new file mode 100644
index 000000000..ede4b00db
--- /dev/null
+++ b/docs/v3/reference-retry-fetch.mdx
@@ -0,0 +1,6 @@
+---
+title: "retry.fetch()"
+description: "Inside a task, do a fetch request that will retry (you can specify the retry conditions)."
+---
+
+
diff --git a/docs/v3/reference-retry-intercept-fetch.mdx b/docs/v3/reference-retry-intercept-fetch.mdx
new file mode 100644
index 000000000..68ced1ba7
--- /dev/null
+++ b/docs/v3/reference-retry-intercept-fetch.mdx
@@ -0,0 +1,6 @@
+---
+title: "retry.interceptFetch()"
+description: "Useful when writing automated tests – it will intercept matching HTTP requests and respond with what you provide."
+---
+
+
diff --git a/docs/v3/reference-retry-on-throw.mdx b/docs/v3/reference-retry-on-throw.mdx
new file mode 100644
index 000000000..4dd2f36c4
--- /dev/null
+++ b/docs/v3/reference-retry-on-throw.mdx
@@ -0,0 +1,6 @@
+---
+title: "retry.onThrow()"
+description: "Inside a task, retry the wrapped code if it throws an error."
+---
+
+
diff --git a/docs/v3/reference-task-batch-trigger-and-wait.mdx b/docs/v3/reference-task-batch-trigger-and-wait.mdx
new file mode 100644
index 000000000..e8402ba8e
--- /dev/null
+++ b/docs/v3/reference-task-batch-trigger-and-wait.mdx
@@ -0,0 +1,6 @@
+---
+title: "task.batchTriggerAndWait()"
+description: "Trigger a task many times at once from inside another task, and wait for all the results."
+---
+
+
diff --git a/docs/v3/reference-task-batch-trigger.mdx b/docs/v3/reference-task-batch-trigger.mdx
new file mode 100644
index 000000000..209ac4701
--- /dev/null
+++ b/docs/v3/reference-task-batch-trigger.mdx
@@ -0,0 +1,6 @@
+---
+title: "task.batchTrigger()"
+description: "Trigger a task many times at once from your code."
+---
+
+
diff --git a/docs/v3/reference-task-trigger-and-wait.mdx b/docs/v3/reference-task-trigger-and-wait.mdx
new file mode 100644
index 000000000..ec98fda17
--- /dev/null
+++ b/docs/v3/reference-task-trigger-and-wait.mdx
@@ -0,0 +1,6 @@
+---
+title: "task.triggerAndWait()"
+description: "Trigger a task from inside another task, and wait for the result."
+---
+
+
diff --git a/docs/v3/reference-task-trigger.mdx b/docs/v3/reference-task-trigger.mdx
new file mode 100644
index 000000000..09fe9b944
--- /dev/null
+++ b/docs/v3/reference-task-trigger.mdx
@@ -0,0 +1,6 @@
+---
+title: "task.trigger()"
+description: "Trigger a task from your code."
+---
+
+
diff --git a/docs/v3/reference-task.mdx b/docs/v3/reference-task.mdx
new file mode 100644
index 000000000..e88b32fb6
--- /dev/null
+++ b/docs/v3/reference-task.mdx
@@ -0,0 +1,6 @@
+---
+title: "task()"
+description: "The task() function is the simplest way to create a long-running task."
+---
+
+
diff --git a/docs/v3/reference-trigger-config.mdx b/docs/v3/reference-trigger-config.mdx
new file mode 100644
index 000000000..540a356db
--- /dev/null
+++ b/docs/v3/reference-trigger-config.mdx
@@ -0,0 +1,7 @@
+---
+title: "The trigger.config.js file"
+sidebarTitle: "trigger.config file"
+description: "This file is used to configure some settings for your project."
+---
+
+
diff --git a/docs/v3/reference-wait-for-event.mdx b/docs/v3/reference-wait-for-event.mdx
new file mode 100644
index 000000000..d2123c54d
--- /dev/null
+++ b/docs/v3/reference-wait-for-event.mdx
@@ -0,0 +1,6 @@
+---
+title: "wait.forEvent()"
+description: "Inside a task, wait until a specific event is received before continuing."
+---
+
+
diff --git a/docs/v3/reference-wait-for-request.mdx b/docs/v3/reference-wait-for-request.mdx
new file mode 100644
index 000000000..63df35636
--- /dev/null
+++ b/docs/v3/reference-wait-for-request.mdx
@@ -0,0 +1,6 @@
+---
+title: "wait.forRequest()"
+description: "Inside a task, wait until a specific request is received before continuing."
+---
+
+
diff --git a/docs/v3/reference-wait-for.mdx b/docs/v3/reference-wait-for.mdx
new file mode 100644
index 000000000..a0ea793c9
--- /dev/null
+++ b/docs/v3/reference-wait-for.mdx
@@ -0,0 +1,6 @@
+---
+title: "wait.for()"
+description: "Inside a task, wait for a period of time before continuing."
+---
+
+
diff --git a/docs/v3/reference-wait-until.mdx b/docs/v3/reference-wait-until.mdx
new file mode 100644
index 000000000..fda6673cc
--- /dev/null
+++ b/docs/v3/reference-wait-until.mdx
@@ -0,0 +1,6 @@
+---
+title: "wait.until()"
+description: "Inside a task, wait until the specified date before continuing."
+---
+
+
diff --git a/docs/v3/reference-zod-catalog.mdx b/docs/v3/reference-zod-catalog.mdx
new file mode 100644
index 000000000..17350b192
--- /dev/null
+++ b/docs/v3/reference-zod-catalog.mdx
@@ -0,0 +1,6 @@
+---
+title: "zod.catalog()"
+description: "Create a set of events with names and payloads that are parsed using Zod schemas."
+---
+
+
diff --git a/docs/v3/reference-zod-task.mdx b/docs/v3/reference-zod-task.mdx
new file mode 100644
index 000000000..48aca5020
--- /dev/null
+++ b/docs/v3/reference-zod-task.mdx
@@ -0,0 +1,6 @@
+---
+title: "zod.task()"
+description: "A task where the payload is parsed using a zod schema."
+---
+
+
diff --git a/docs/v3/rollbacks.mdx b/docs/v3/rollbacks.mdx
new file mode 100644
index 000000000..94600a7fc
--- /dev/null
+++ b/docs/v3/rollbacks.mdx
@@ -0,0 +1,6 @@
+---
+title: "Rollbacks"
+description: "You can rollback changes when errors happen, to give transactional guarantees to your operations."
+---
+
+
diff --git a/docs/v3/run-tests.mdx b/docs/v3/run-tests.mdx
new file mode 100644
index 000000000..2591a695e
--- /dev/null
+++ b/docs/v3/run-tests.mdx
@@ -0,0 +1,26 @@
+---
+title: "Run tests"
+description: "You can use the dashboard to run a test of your tasks."
+---
+
+From the "Test" page in the sidebar of the dashboard you can run a test for any of your tasks, that includes for any environment.
+
+
+
+
+ 
+
+
+
+ 
+
+
+
+ Select a recent payload as a starting point or enter from scratch. Payloads must be valid JSON – you will see helpful errors if it is not. Press the "Run test" button or use the keyboard shortcut to run the test.
+
+
+
+
+
+
+
diff --git a/docs/v3/tasks-overview.mdx b/docs/v3/tasks-overview.mdx
new file mode 100644
index 000000000..9a4715c8e
--- /dev/null
+++ b/docs/v3/tasks-overview.mdx
@@ -0,0 +1,155 @@
+---
+title: "Tasks: Overview"
+sidebarTitle: "Tasks"
+description: "Tasks are functions that can run for a long time and provide strong resilience to failure."
+---
+
+There are different types of tasks including [regular tasks](/v3/tasks-regular), [scheduled tasks](/v3/tasks-scheduled), [zod tasks](/v3/tasks-zod) and [webhook tasks](/v3/tasks-webhooks).
+
+## Hello world task and how to trigger it
+
+Here's an incredibly simple task:
+
+```ts /trigger/hello-world.ts
+import { task } from "@trigger.dev/sdk/v3";
+
+//1. You need to export each task
+export const helloWorld = task({
+ //2. Use a unique id for each task
+ id: "hello-world",
+ //3. The run function is the main function of the task
+ run: async (payload: { message: string }) => {
+ //4. You can write code that runs for a long time here, there are no timeouts
+ console.log(payload.message);
+ },
+});
+```
+
+You can trigger this in two ways:
+
+1. From the dashboard [using the "Test" feature](/v3/develop-run-tests).
+2. Trigger it from your backend code. See the [full triggering guide here](/v3/triggering).
+
+Here's how to trigger a single run from elsewhere in your code:
+
+```ts Your backend code
+import { helloWorldTask } from "./trigger/hello-world";
+
+async function triggerHelloWorld() {
+ //This triggers the task and return a handle
+ const handle = await helloWorld.trigger({ payload: { message: "Hello world!" } });
+
+ //You can use the handle to check the status of the task, cancel and retry it.
+ console.log("Task is running with handle", handle.id);
+}
+```
+
+You can also [trigger a task from another task](/v3/triggering), and wait for the result.
+
+## Defining a `task`
+
+The task function takes an object with the following fields.
+
+### The `id` field
+
+This is used to identify your task so it can be triggered, managed, and you can view runs in the dashboard. This must be unique in your project – we recommend making it descriptive and unique.
+
+### The `run` function
+
+Your custom code inside `run()` will be executed when your task is triggered. It’s an async function that has two arguments:
+
+1. The run payload - the data that you pass to the task when you trigger it.
+2. An object with `ctx` about the run ([Context](/v3/reference-context)), and any output from the optional `init` function that runs before every run attempt.
+
+Anything you return from the `run` function will be the result of the task. Data you return must be JSON serializable: strings, numbers, booleans, arrays, objects, and null.
+
+### `retry` options
+
+A task is retried if an error is thrown, by default we retry 3 times.
+
+You can set the number of retries and the delay between retries in the `retry` field:
+
+```ts /trigger/retry.ts
+export const taskWithRetries = task({
+ id: "task-with-retries",
+ retry: {
+ maxAttempts: 10,
+ factor: 1.8,
+ minTimeoutInMs: 500,
+ maxTimeoutInMs: 30_000,
+ randomize: false,
+ },
+ run: async ({ payload, ctx }) => {
+ //...
+ },
+});
+```
+
+For more information read [the retrying guide](/v3/retrying), or see the [SDK reference](/v3/reference-task).
+
+It's also worth mentioning that you can [retry a block of code](/v3/retrying) inside your tasks as well.
+
+### `queue` options
+
+Queues allow you to control the concurrency of your tasks. This allows you to have one-at-a-time execution and parallel executions. There are also more advanced techniques like having different concurrencies for different sets of your users. For more information read [the concurrency & queues guide](/v3/queue-concurrency).
+
+```ts /trigger/one-at-a-time.ts
+export const oneAtATime = task({
+ id: "one-at-a-time",
+ queue: {
+ concurrencyLimit: 1,
+ },
+ run: async ({ payload, ctx }) => {
+ //...
+ },
+});
+```
+
+### `machine` options
+
+Some tasks require more vCPUs or GBs of RAM. You can specify these requirements in the `machine` field. For more information read [the machines guide](/v3/machines).
+
+```ts /trigger/heavy-task.ts
+export const heavyTask = task({
+ id: "heavy-task",
+ machine: {
+ cpu: 2,
+ memory: 4,
+ },
+ run: async ({ payload, ctx }) => {
+ //...
+ },
+});
+```
+
+### `init` function
+
+This function is called before a run attempt.
+
+### `cleanup` function
+
+This function is called after a run attempt has succeeded or failed.
+
+### `middleware` function
+
+This function is called before the `run` function, it allows you to wrap the run function with custom code. For more information [read the guide](/v3/middleware).
+
+### `onSuccess` function
+
+When a task attempt succeeds, the `onSuccess` function is called. It's useful for sending notifications, logging, or other side effects.
+
+
+
+### `onError` function
+
+When a task attempt fails, the `onError` function is called. It's useful for sending notifications, logging, or other side effects.
+
+
+
+## Next steps
+
+
+
+ Tasks are the core of Trigger.dev. Learn how to write them.
+
+
diff --git a/docs/v3/tasks-regular.mdx b/docs/v3/tasks-regular.mdx
new file mode 100644
index 000000000..c9fb4d233
--- /dev/null
+++ b/docs/v3/tasks-regular.mdx
@@ -0,0 +1,86 @@
+---
+title: "Regular tasks"
+description: "The simplest type of task which can be triggered from elsewhere in your code."
+---
+
+They are defined using the `task()` function and can be [triggered](/v3/triggering) from your backend or inside another task.
+
+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).
+
+## Example tasks
+
+### A task that does an OpenAI call with retrying
+
+Sometimes OpenAI calls can take a long time to complete, or they can fail. This task will retry if the API call fails completely or if the response is empty.
+
+
+
+### A Task that sends emails in a sequence with delays in between
+
+This example uses Resend to send a sequence of emails over several days.
+
+Each email is wrapped in [retry.onThrow](/v3/reference-retry-on-throw). This will retry the block of code if an error is thrown. This is useful when you don't want to retry the whole task, but just a part of it. The entire task will use the default retrying, so can also retry.
+
+Additionally this task uses `wait.for` to wait for a certain amount of time before sending the next email. During the waiting time, the task will be paused and will not consume any resources.
+
+```ts /trigger/email-sequence.ts
+import { Resend } from "resend";
+
+const resend = new Resend(process.env.RESEND_ASP_KEY);
+
+export const emailSequence = task({
+ id: "email-sequence",
+ run: async (payload: { userId: string; email: string; name: string }) => {
+ console.log(`Start email sequence for user ${payload.userId}`, payload);
+
+ //send the first email immediately
+ const firstEmailResult = await retry.onThrow(
+ async ({ attempt }) => {
+ const { data, error } = await resend.emails.send({
+ from: "hello@trigger.dev",
+ to: payload.email,
+ subject: "Welcome to Trigger.dev",
+ html: `Hello ${payload.name},
Welcome to Trigger.dev
`,
+ });
+
+ if (error) {
+ //throwing an error will trigger a retry of this block
+ throw error;
+ }
+
+ return data;
+ },
+ { maxAttempts: 3 }
+ );
+
+ //then wait 3 days
+ await wait.for({ days: 3 });
+
+ //send the second email
+ const secondEmailResult = await retry.onThrow(
+ async ({ attempt }) => {
+ const { data, error } = await resend.emails.send({
+ from: "hello@trigger.dev",
+ to: payload.email,
+ subject: "Some tips for you",
+ html: `Hello ${payload.name},
Here are some tips for you…
`,
+ });
+
+ if (error) {
+ //throwing an error will trigger a retry of this block
+ throw error;
+ }
+
+ return data;
+ },
+ { maxAttempts: 3 }
+ );
+
+ //etc...
+ },
+});
+```
+
+### Other examples
+
+
diff --git a/docs/v3/tasks-scheduled.mdx b/docs/v3/tasks-scheduled.mdx
new file mode 100644
index 000000000..dd0bd6826
--- /dev/null
+++ b/docs/v3/tasks-scheduled.mdx
@@ -0,0 +1,6 @@
+---
+title: "Scheduled tasks"
+description: "A task that is triggered on a recurring schedule using CRON syntax or an interval."
+---
+
+
diff --git a/docs/v3/tasks-webhooks.mdx b/docs/v3/tasks-webhooks.mdx
new file mode 100644
index 000000000..acd20108f
--- /dev/null
+++ b/docs/v3/tasks-webhooks.mdx
@@ -0,0 +1,12 @@
+---
+title: "Webhook tasks"
+description: "A task that is triggered when a webhook is received from an API."
+---
+
+## Built-in webhooks triggers
+
+
+
+## How to manually use Trigger.dev with webhooks
+
+
diff --git a/docs/v3/tasks-zod.mdx b/docs/v3/tasks-zod.mdx
new file mode 100644
index 000000000..0c80b6877
--- /dev/null
+++ b/docs/v3/tasks-zod.mdx
@@ -0,0 +1,6 @@
+---
+title: "Zod tasks"
+description: "You can use Zod to define a catalog of events and then attach those events to your tasks."
+---
+
+
diff --git a/docs/v3/trigger-filters.mdx b/docs/v3/trigger-filters.mdx
new file mode 100644
index 000000000..42ceb71a3
--- /dev/null
+++ b/docs/v3/trigger-filters.mdx
@@ -0,0 +1,6 @@
+---
+title: "Trigger filters"
+description: "You can add filters to your tasks so they're only triggered when certain conditions are met."
+---
+
+
diff --git a/docs/v3/trigger-folder.mdx b/docs/v3/trigger-folder.mdx
new file mode 100644
index 000000000..98c2eaf89
--- /dev/null
+++ b/docs/v3/trigger-folder.mdx
@@ -0,0 +1,25 @@
+---
+title: "/trigger folders"
+description: "Your tasks live inside /trigger folders. Code in these is bundled and deployed together."
+---
+
+## What gets bundled?
+
+We automatically bundle everything for your tasks. This includes:
+
+- Your tasks (they can be in any file inside a /trigger folder, they just need to be exported with a name).
+- Imported npm packages.
+- Other imports from your code.
+
+This means you shouldn't need to think about what gets bundled. Just write your tasks and we'll take care of the rest.
+
+## Multiple `/trigger` folders
+
+You can have multiple `/trigger` folders in your repository.
+
+- Each `/trigger` folder can have many tasks exported from it.
+- Each file inside a `/trigger` folder can export many tasks.
+
+### (Optional) configuration
+
+It is possible to manually set one or more folders as `/trigger` folders in your `trigger.config` file. View the [trigger.config documentation](/v3/referece-trigger-config) for more information.
diff --git a/docs/v3/triggering.mdx b/docs/v3/triggering.mdx
new file mode 100644
index 000000000..dafa811d8
--- /dev/null
+++ b/docs/v3/triggering.mdx
@@ -0,0 +1,186 @@
+---
+title: "Triggering"
+description: "Tasks need to be triggered to run."
+---
+
+There are currently four ways you can trigger any task from your own code:
+
+| Function | Where does this work? | What it does |
+| -------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
+| `yourTask.trigger()` | Anywhere | Triggers a task and gets a handle you can use to monitor and manage the run. It does not wait for the result. |
+| `yourTask.batchTriggerAndWait()` | Anywhere | Triggers a task multiple times and gets a handle you can use to monitor and manage the runs. It does not wait for the results. |
+| `yourTask.triggerAndWait()` | Inside a task | Triggers a task and then waits until it's complete. You get the result data to continue with. |
+| `yourTask.batchTriggerAndWait()` | Inside a task | Triggers a task multiple times in parallel and then waits until they're all complete. You get the resulting data to continue with. |
+
+Additionally, [scheduled tasks](/v3/tasks-scheduled) get automatically triggered on their schedule and [webhooks](/v3/tasks-webhooks) when receiving a webhook.
+
+## From outside of a task
+
+You can trigger any task from your backend code, using either `trigger()` or `batchTrigger()`.
+
+
+ Do not trigger tasks directly from your frontend. If you do, you will leak your private
+ Trigger.dev API key to the world.
+
+
+### trigger()
+
+Triggers a single run of a task with the payload you pass in, and any options you specify. It does NOT wait for the result, you cannot do that from outside a task.
+
+
+
+```ts Next.js API route
+import { emailSequence } from "~/trigger/emails";
+
+//app/email/route.ts
+export async function POST(request: Request) {
+ //get the JSON from the request
+ const data = await request.json();
+
+ //trigger your task
+ const handle = await emailSequence.trigger({ payload: { to: data.email, name: data.name } });
+
+ //return a success response with the handle
+ return Response.json(handle);
+}
+```
+
+```ts Remix
+import { emailSequence } from "~/trigger/emails";
+
+export async function action({ request, params }: ActionFunctionArgs) {
+ if (request.method.toUpperCase() !== "POST") {
+ return json("Method Not Allowed", { status: 405 });
+ }
+
+ //get the JSON from the request
+ const data = await request.json();
+
+ //trigger your task
+ const handle = await emailSequence.trigger({ payload: { to: data.email, name: data.name } });
+
+ //return a success response with the handle
+ return json(handle);
+}
+```
+
+
+
+### batchTrigger()
+
+Triggers multiples runs of a task with the payloads you pass in, and any options you specify. It does NOT wait for the results, you cannot do that from outside a task.
+
+
+
+```ts Next.js API route
+import { emailSequence } from "~/trigger/emails";
+
+//app/email/route.ts
+export async function POST(request: Request) {
+ //get the JSON from the request
+ const data = await request.json();
+
+ //batch trigger your task
+ const batchHandle = await emailSequence.batchTrigger({
+ items: data.users.map((u) => ({ payload: { to: u.email, name: u.name } })),
+ });
+
+ //return a success response with the handle
+ return Response.json(batchHandle);
+}
+```
+
+```ts Remix
+import { emailSequence } from "~/trigger/emails";
+
+export async function action({ request, params }: ActionFunctionArgs) {
+ if (request.method.toUpperCase() !== "POST") {
+ return json("Method Not Allowed", { status: 405 });
+ }
+
+ //get the JSON from the request
+ const data = await request.json();
+
+ //batch trigger your task
+ const batchHandle = await emailSequence.batchTrigger({
+ items: data.users.map((u) => ({ payload: { to: u.email, name: u.name } })),
+ });
+
+ //return a success response with the handle
+ return json(batchHandle);
+}
+```
+
+
+
+## From inside a task
+
+You can trigger tasks from other tasks using `trigger()` or `batchTrigger()`. You can also trigger and wait for the result of triggered tasks using `triggerAndWait()` and `batchTriggerAndWait()`. This is a powerful way to build complex tasks.
+
+### trigger()
+
+This works the same as from outside a task. You call it and you get a handle back, but it does not wait for the result.
+
+```ts /trigger/my-task.ts
+import { myOtherTask } from "~/trigger/my-other-task";
+
+export const myTask = task({
+ id: "my-task",
+ run: async (payload: string) => {
+ const handle = await myOtherTask.trigger({ payload: "some data" });
+
+ //...do other stuff
+ },
+});
+```
+
+### batchTrigger()
+
+This works the same as from outside a task. You call it and you get a handle back, but it does not wait for the results.
+
+```ts /trigger/my-task.ts
+import { myOtherTask } from "~/trigger/my-other-task";
+
+export const myTask = task({
+ id: "my-task",
+ run: async (payload: string) => {
+ const batchHandle = await myOtherTask.batchTrigger({ items: [{ payload: "some data" }] });
+
+ //...do other stuff
+ },
+});
+```
+
+### triggerAndWait()
+
+This is where it gets interesting. You can trigger a task and then wait for the result. This is useful when you need to call a different task and then use the result to continue with your task.
+
+```ts /trigger/parent.ts
+export const parentTask = task({
+ id: "parent-task",
+ run: async (payload: string) => {
+ const result = await batchChildTask.triggerAndWait({ payload: "some-data" });
+ console.log("Result", result);
+
+ //...do stuff with the result
+ },
+});
+```
+
+### batchTriggerAndWait()
+
+You can batch trigger a task and wait for all the results. This is useful for the fan-out pattern, where you need to call a task multiple times and then wait for all the results to continue with your task.
+
+```ts /trigger/nested.ts
+export const batchParentTask = task({
+ id: "parent-task",
+ run: async (payload: string) => {
+ const results = await childTask.batchTriggerAndWait({
+ items: [{ payload: "item4" }, { payload: "item5" }, { payload: "item6" }],
+ });
+ console.log("Results", results);
+
+ //...do stuff with the result
+ },
+});
+```
diff --git a/docs/v3/upgrading-from-v2.mdx b/docs/v3/upgrading-from-v2.mdx
new file mode 100644
index 000000000..b771b79f9
--- /dev/null
+++ b/docs/v3/upgrading-from-v2.mdx
@@ -0,0 +1,6 @@
+---
+title: "Upgrading from v2"
+description: "How to upgrade your v2 jobs to v3 tasks."
+---
+
+
diff --git a/docs/v3/using-apis.mdx b/docs/v3/using-apis.mdx
new file mode 100644
index 000000000..f298e1c25
--- /dev/null
+++ b/docs/v3/using-apis.mdx
@@ -0,0 +1,16 @@
+---
+title: "Using APIs"
+description: "You can use any Node.js library inside the run function, or do HTTP requests."
+---
+
+## Using Node.js SDKs
+
+
+
+## Using fetch or axios
+
+
+
+## Using webhooks
+
+
diff --git a/docs/v3/vercel-integration.mdx b/docs/v3/vercel-integration.mdx
new file mode 100644
index 000000000..ed5e55ae6
--- /dev/null
+++ b/docs/v3/vercel-integration.mdx
@@ -0,0 +1,6 @@
+---
+title: "Vercel integration"
+description: "When you deploy to Vercel, automatically deploy your associated tasks."
+---
+
+
diff --git a/docs/v3/versioning.mdx b/docs/v3/versioning.mdx
new file mode 100644
index 000000000..bc63d2208
--- /dev/null
+++ b/docs/v3/versioning.mdx
@@ -0,0 +1,56 @@
+---
+title: "Versioning"
+description: "We use atomic versioning to ensure that started tasks are not affected by changes to the task code."
+---
+
+A version is a bundle of tasks at a certain point in time.
+
+## Version identifiers
+
+Version identifiers look like this:
+
+- `20240313.1` - March 13th, 2024, version 1
+- `20240313.2` - March 13th, 2024, version 2
+- `20240314.1` - March 14th, 2024, version 1
+
+You can see there are two parts to the version identifier:
+
+- The date (in reverse format)
+- The version number
+
+Versions numbers are incremented each time a new version is created for that date and environment. So it's possible to have `20240313.1` in both the `dev` and `prod` environments.
+
+## Version locking
+
+When a task run starts it is locked to the latest version of the code (for that environment). Once locked it won't change versions, even if you deploy new versions. This is to ensure that a task run is not affected by changes to the code.
+
+### Child tasks and version locking
+
+Trigger and wait functions version lock child task runs to the parent task run version. This ensures the results from child runs match what the parent task is expecting. If you don't wait then version locking doesn't apply.
+
+| Trigger function | Parent task version | Child task version | isLocked |
+| ----------------------- | ------------------- | ------------------ | -------- |
+| `trigger()` | `20240313.2` | Latest | No |
+| `batchTrigger()` | `20240313.2` | Latest | No |
+| `triggerAndWait()` | `20240313.2` | `20240313.2` | Yes |
+| `batchTriggerAndWait()` | `20240313.2` | `20240313.2` | Yes |
+
+## Local development
+
+When running the local server (using `npx trigger.dev dev`), every relevant code change automatically creates a new version of all tasks.
+
+So a task run will continue running on the version it was locked to. We do this by spawning a new process for each task run. This ensures that the task run is not affected by changes to the code.
+
+## Deployment
+
+Every deployment creates a new version of all tasks for that environment.
+
+## Retries and reattempts
+
+When a task has an uncaught error it will [retry](/v3/errors-retrying), assuming you have not set `maxAttempts` to 0. Retries are locked to the original version of the run.
+
+If all the attempts have failed you can start a [reattempt](/v3/reattempting-replaying). This will be version locked to the original version of the run.
+
+## Replays
+
+A "replay" is a new run of a task that uses the same inputs but will use the latest version of the code. This is useful when you fix a bug and want to re-run a task with the same inputs. See [replays](/v3/reattempting-replaying) for more information.
diff --git a/docs/v3/wait-for-event.mdx b/docs/v3/wait-for-event.mdx
new file mode 100644
index 000000000..980e8dee8
--- /dev/null
+++ b/docs/v3/wait-for-event.mdx
@@ -0,0 +1,6 @@
+---
+title: "Wait for event"
+description: "Wait until an event has been received, then continue execution."
+---
+
+
diff --git a/docs/v3/wait-for-request.mdx b/docs/v3/wait-for-request.mdx
new file mode 100644
index 000000000..bc480fa5c
--- /dev/null
+++ b/docs/v3/wait-for-request.mdx
@@ -0,0 +1,6 @@
+---
+title: "Wait for request"
+description: "Wait until a `Request` has been received at the provided URL, then continue execution."
+---
+
+
diff --git a/docs/v3/wait-for.mdx b/docs/v3/wait-for.mdx
new file mode 100644
index 000000000..acdb13543
--- /dev/null
+++ b/docs/v3/wait-for.mdx
@@ -0,0 +1,31 @@
+---
+title: "Wait for"
+description: "Wait for a period of time, then continue execution."
+---
+
+Inside your tasks you can wait for a period of time before you want execution to continue.
+
+```ts /trigger/long-task.ts
+export const veryLongTask = task({
+ id: "very-long-task",
+ run: async (payload) => {
+ await wait.for({ seconds: 5 });
+
+ await wait.for({ minutes: 10 });
+
+ await wait.for({ hours: 1 });
+
+ await wait.for({ days: 1 });
+
+ await wait.for({ weeks: 1 });
+
+ await wait.for({ months: 1 });
+
+ await wait.for({ years: 1 });
+ },
+});
+```
+
+This allows you to write linear code without having to worry about the complexity of scheduling or managing CRON jobs.
+
+
diff --git a/docs/v3/wait-until.mdx b/docs/v3/wait-until.mdx
new file mode 100644
index 000000000..2c84e83ed
--- /dev/null
+++ b/docs/v3/wait-until.mdx
@@ -0,0 +1,38 @@
+---
+title: "Wait until"
+description: "Wait until a date, then continue execution."
+---
+
+This example sends a reminder email to a user at the specified datetime.
+
+```ts /trigger/reminder-email.ts
+export const sendReminderEmail = task({
+ id: "send-reminder-email",
+ run: async (payload: { to: string; name: string; date: string }) => {
+ //wait until the date
+ await wait.until({ date: new Date(payload.date) });
+
+ //todo send email
+ const { data, error } = await resend.emails.send({
+ from: "hello@trigger.dev",
+ to: payload.to,
+ subject: "Don't forget…",
+ html: `Hello ${payload.name},
...
`,
+ });
+ },
+});
+```
+
+This allows you to write linear code without having to worry about the complexity of scheduling or managing CRON jobs.
+
+
+
+## `throwIfInThePast`
+
+You can optionally throw an error if the date is already in the past when the function is called:
+
+```ts
+await wait.until({ date: new Date(date), throwIfInThePast: true });
+```
+
+You can of course use try/catch if you want to do something special in this case.
diff --git a/docs/v3/wait.mdx b/docs/v3/wait.mdx
new file mode 100644
index 000000000..51592c8cb
--- /dev/null
+++ b/docs/v3/wait.mdx
@@ -0,0 +1,16 @@
+---
+title: "Wait: Overview"
+sidebarTitle: "Overview"
+description: "During your run you can wait for a period of time or for something to happen."
+---
+
+Waiting allows you to write complex tasks as a set of async code, without having to scheduled another task or poll for changes.
+
+
+
+| Function | What it does |
+| ----------------------------------------- | ----------------------------------------------------------------------------------------- |
+| [wait.for()](/v3/wait-for) | Waits for a specific period of time, e.g. 1 day. |
+| [wait.until()](/v3/wait-until) | Waits until the provided `Date`. |
+| [wait.forRequest()](/v3/wait-for-request) | Waits until a matching HTTP request is received, and gives you the data to continue with. |
+| [waitForEvent()](/v3/wait-for-event) | Waits for a matching event, like in the example above. |
diff --git a/docs/v3/writing-tasks-introduction.mdx b/docs/v3/writing-tasks-introduction.mdx
new file mode 100644
index 000000000..e08b5ef93
--- /dev/null
+++ b/docs/v3/writing-tasks-introduction.mdx
@@ -0,0 +1,26 @@
+---
+title: "Writing tasks: Introduction"
+sidebarTitle: "Introduction"
+description: "Tasks are the core of Trigger.dev. They are long-running processes that are triggered by events."
+---
+
+Before digging deeper into the details of writing tasks, you should read the [fundamentals of tasks](/v3/tasks-overview) to understand what tasks are and how they work.
+
+## Writing tasks
+
+| Topic | Description |
+| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
+| [Logging](/v3/logging) | View and send logs and traces from your tasks. |
+| [Errors & retrying](/v3/errors-retrying) | How to deal with errors and write reliable tasks. |
+| [Wait](/v3/wait) | Wait for periods of time or for external events to occur before continuing. |
+| [Concurrency & Queues](/v3/queue-concurrency) | Configure what you want to happen when there is more than one run at a time. |
+| [Versioning](/v3/versioning) | How versioning works. |
+| [Machines](/v3/machines) | Configure the CPU and RAM of the machine your task runs on |
+| [Idempotency](/v3/idempotency) | Protect against mutations happening twice. |
+| [Reattempting & Replaying](/v3/reattempting-replaying) | You can reattempt a task that has failed all of its attempts. You can also replay a task with a new version of your code. |
+| [Notifications](/v3/notifications) | Send realtime notifications from your task that you can subscribe to from your backend or frontend. |
+| [Rollbacks](/v3/rollbacks) | Rollback code inside a task when errors happen to provide transactional guarantees. |
+| [Using APIs](/v3/using-apis) | How to use APIs from within your tasks. |
+| [Trigger filters](/v3/trigger-filters) | Prevent unwanted filters where the payload doesn't match your filter. |
+| [Middleware](/v3/middleware) | Middleware can wrap the task run function. |
+| [Automated tests](/v3/automated-tests) | Write automated tests in code. |
diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json
index eb5698bfd..8f17bf321 100644
--- a/packages/cli-v3/package.json
+++ b/packages/cli-v3/package.json
@@ -110,6 +110,7 @@
"gradient-string": "^2.0.2",
"import-meta-resolve": "^4.0.0",
"ink": "^4.4.1",
+ "jsonc-parser": "^3.2.1",
"jsonlines": "^0.1.1",
"liquidjs": "^10.9.2",
"mock-fs": "^5.2.0",
diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts
index 85b13fbfd..d3bc2d894 100644
--- a/packages/cli-v3/src/commands/deploy.ts
+++ b/packages/cli-v3/src/commands/deploy.ts
@@ -1,7 +1,12 @@
import { intro, log, outro, spinner } from "@clack/prompts";
import { depot } from "@depot/cli";
import { context, trace } from "@opentelemetry/api";
-import { ResolvedConfig, detectDependencyVersion, flattenAttributes, recordSpanException } from "@trigger.dev/core/v3";
+import {
+ ResolvedConfig,
+ detectDependencyVersion,
+ flattenAttributes,
+ recordSpanException,
+} from "@trigger.dev/core/v3";
import chalk from "chalk";
import { Command, Option as CommandOption } from "commander";
import { Metafile, build } from "esbuild";
@@ -29,7 +34,7 @@ import {
import { readConfig } from "../utilities/configFiles.js";
import { createTempDir, readJSONFile, writeJSONFile } from "../utilities/fileSystem";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
-import { detectPackageNameFromImportPath } from "../utilities/installPackages";
+import { detectPackageNameFromImportPath, parsePackageName } from "../utilities/installPackages";
import { logger } from "../utilities/logger.js";
import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles";
import { login } from "./login";
@@ -64,11 +69,7 @@ export function configureDeployCommand(program: Command) {
"prod"
)
.option("-T, --skip-typecheck", "Whether to skip the pre-build typecheck")
- .option(
- "-c, --config ",
- "The name of the config file, found at [path]",
- "trigger.config.mjs"
- )
+ .option("-c, --config ", "The name of the config file, found at [path]")
.option(
"-p, --project-ref ",
"The project ref. Required if there is no config file."
@@ -838,12 +839,12 @@ async function compileProject(
workerContents = workerContents.replace(
"__IMPORTED_PROJECT_CONFIG__",
- `import importedConfig from "${configPath}";`
+ `import * as importedConfigExports from "${configPath}"; const importedConfig = importedConfigExports.config; const handleError = importedConfigExports.handleError;`
);
} else {
workerContents = workerContents.replace(
"__IMPORTED_PROJECT_CONFIG__",
- `const importedConfig = undefined;`
+ `const importedConfig = undefined; const handleError = undefined;`
);
}
@@ -983,8 +984,7 @@ async function compileProject(
// Get all the required dependencies from the metaOutputs and save them to /tmp/dir/package.json
const allImports = [...metaOutput.imports, ...entryPointMetaOutput.imports];
- const projectPackageJson = await readJSONFile(join(config.projectDir, "package.json"));
- const dependencies = gatherRequiredDependencies(allImports, projectPackageJson);
+ const dependencies = await gatherRequiredDependencies(allImports, config);
const packageJsonContents = {
name: "trigger-worker",
@@ -1205,10 +1205,12 @@ async function typecheckProject(config: ResolvedConfig, options: DeployCommandOp
// Returns the dependencies that are required by the output that are found in output and the CLI package dependencies
// Returns the dependency names and the version to use (taken from the CLI deps package.json)
-function gatherRequiredDependencies(
+async function gatherRequiredDependencies(
imports: Metafile["outputs"][string]["imports"],
- externalPackageJson?: { dependencies: Record }
+ config: ResolvedConfig
) {
+ const externalPackageJson = await readJSONFile(join(config.projectDir, "package.json"));
+
const dependencies: Record = {};
for (const file of imports) {
@@ -1229,15 +1231,44 @@ function gatherRequiredDependencies(
continue;
}
- const internalDependencyVersion = (packageJson.dependencies as Record)[
- packageName
- ] ?? detectDependencyVersion(packageName);
+ const internalDependencyVersion =
+ (packageJson.dependencies as Record)[packageName] ??
+ detectDependencyVersion(packageName);
if (internalDependencyVersion) {
dependencies[packageName] = internalDependencyVersion;
}
}
+ if (config.additionalPackages) {
+ for (const packageName of config.additionalPackages) {
+ if (dependencies[packageName]) {
+ continue;
+ }
+
+ const packageParts = parsePackageName(packageName);
+
+ if (packageParts.version) {
+ dependencies[packageParts.name] = packageParts.version;
+ continue;
+ } else {
+ const externalDependencyVersion = {
+ ...externalPackageJson?.devDependencies,
+ ...externalPackageJson?.dependencies,
+ }[packageName];
+
+ if (externalDependencyVersion) {
+ dependencies[packageParts.name] = externalDependencyVersion;
+ continue;
+ } else {
+ logger.warn(
+ `Could not find version for package ${packageName}, add a version specifier to the package name (e.g. ${packageParts.name}@latest) or add it to your project's package.json`
+ );
+ }
+ }
+ }
+ }
+
// Make sure we sort the dependencies by key to ensure consistent hashing
return Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)));
}
diff --git a/packages/cli-v3/src/commands/dev.tsx b/packages/cli-v3/src/commands/dev.tsx
index c11a077c5..10b630acf 100644
--- a/packages/cli-v3/src/commands/dev.tsx
+++ b/packages/cli-v3/src/commands/dev.tsx
@@ -27,8 +27,9 @@ import * as packageJson from "../../package.json";
import { CliApiClient } from "../apiClient";
import { CommonCommandOptions, commonOptions, wrapCommandAction } from "../cli/common.js";
import { readConfig } from "../utilities/configFiles";
+import { readJSONFile } from "../utilities/fileSystem";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
-import { detectPackageNameFromImportPath } from "../utilities/installPackages";
+import { detectPackageNameFromImportPath, parsePackageName } from "../utilities/installPackages";
import { logger } from "../utilities/logger.js";
import { isLoggedIn } from "../utilities/session.js";
import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles";
@@ -52,11 +53,7 @@ export function configureDevCommand(program: Command) {
.command("dev")
.description("Run your Trigger.dev tasks locally")
.argument("[path]", "The path to the project", ".")
- .option(
- "-c, --config ",
- "The name of the config file, found at [path]",
- "trigger.config.mjs"
- )
+ .option("-c, --config ", "The name of the config file, found at [path]")
.option(
"-p, --project-ref ",
"The project ref. Required if there is no config file."
@@ -83,16 +80,9 @@ export async function devCommand(dir: string, options: DevCommandOptions) {
return;
}
- let watcher;
-
- try {
- const devInstance = await startDev(dir, options, authorization.auth);
- watcher = devInstance.watcher;
- const { waitUntilExit } = devInstance.devReactElement;
- await waitUntilExit();
- } finally {
- await watcher?.close();
- }
+ const devInstance = await startDev(dir, options, authorization.auth);
+ const { waitUntilExit } = devInstance.devReactElement;
+ await waitUntilExit();
}
async function startDev(
@@ -100,7 +90,6 @@ async function startDev(
options: DevCommandOptions,
authorization: { apiUrl: string; accessToken: string }
) {
- let watcher: ReturnType | undefined;
let rerender: (node: React.ReactNode) => void | undefined;
try {
@@ -110,6 +99,8 @@ async function startDev(
await printStandloneInitialBanner(true);
+ logger.debug("Starting dev session", { dir, options, authorization });
+
let config = await readConfig(dir, {
projectRef: options.projectRef,
configFile: options.config,
@@ -117,23 +108,6 @@ async function startDev(
logger.debug("Initial config", { config });
- if (config.status === "file") {
- watcher = watch(config.path, {
- persistent: true,
- }).on("change", async (_event) => {
- config = await readConfig(dir, { configFile: options.config });
-
- if (config.status === "file") {
- logger.log(`${basename(config.path)} changed...`);
- logger.debug("New config", { config: config.config });
- rerender(await getDevReactElement(config.config, authorization, config.path));
- } else {
- logger.debug("New config", { config: config.config });
- rerender(await getDevReactElement(config.config, authorization));
- }
- });
- }
-
async function getDevReactElement(
configParam: ResolvedConfig,
authorization: { apiUrl: string; accessToken: string },
@@ -181,14 +155,11 @@ async function startDev(
return {
devReactElement,
- watcher,
stop: async () => {
devReactElement.unmount();
- await watcher?.close();
},
};
} catch (e) {
- await watcher?.close();
throw e;
}
}
@@ -341,12 +312,12 @@ function useDev({
entryPointContents = entryPointContents.replace(
"__IMPORTED_PROJECT_CONFIG__",
- `import importedConfig from "${configPath}";`
+ `import * as importedConfigExports from "${configPath}"; const importedConfig = importedConfigExports.config; const handleError = importedConfigExports.handleError;`
);
} else {
entryPointContents = entryPointContents.replace(
"__IMPORTED_PROJECT_CONFIG__",
- `const importedConfig = undefined;`
+ `const importedConfig = undefined; const handleError = undefined;`
);
}
@@ -366,7 +337,7 @@ function useDev({
minify: false,
sourcemap: "external", // does not set the //# sourceMappingURL= comment in the file, we handle it ourselves
packages: "external", // https://esbuild.github.io/api/#packages
- logLevel: "warning",
+ logLevel: "error",
platform: "node",
format: "cjs", // This is needed to support opentelemetry instrumentation that uses module patching
target: ["node18", "es2020"],
@@ -437,7 +408,7 @@ function useDev({
logger.debug(`Wrote background worker to ${fullPath}`);
- const dependencies = gatherRequiredDependencies(metaOutput);
+ const dependencies = await gatherRequiredDependencies(metaOutput, config);
if (sourceMapFile) {
const sourceMapPath = `${fullPath}.map`;
@@ -447,10 +418,13 @@ function useDev({
const environmentVariablesResponse =
await environmentClient.getEnvironmentVariables(config.project);
+ const processEnv = gatherProcessEnv();
+
const backgroundWorker = new BackgroundWorker(fullPath, {
projectConfig: config,
dependencies,
env: {
+ ...processEnv,
TRIGGER_API_URL: apiUrl,
TRIGGER_SECRET_KEY: apiKey,
...(environmentVariablesResponse.success
@@ -678,7 +652,10 @@ function WebsocketFactory(apiKey: string) {
// Returns the dependencies that are required by the output that are found in output and the CLI package dependencies
// Returns the dependency names and the version to use (taken from the CLI deps package.json)
-function gatherRequiredDependencies(outputMeta: Metafile["outputs"][string]) {
+async function gatherRequiredDependencies(
+ outputMeta: Metafile["outputs"][string],
+ config: ResolvedConfig
+) {
const dependencies: Record = {};
for (const file of outputMeta.imports) {
@@ -701,5 +678,56 @@ function gatherRequiredDependencies(outputMeta: Metafile["outputs"][string]) {
}
}
+ if (config.additionalPackages) {
+ const projectPackageJson = await readJSONFile(join(config.projectDir, "package.json"));
+
+ for (const packageName of config.additionalPackages) {
+ if (dependencies[packageName]) {
+ continue;
+ }
+
+ const packageParts = parsePackageName(packageName);
+
+ if (packageParts.version) {
+ dependencies[packageParts.name] = packageParts.version;
+ continue;
+ } else {
+ const externalDependencyVersion = {
+ ...projectPackageJson?.devDependencies,
+ ...projectPackageJson?.dependencies,
+ }[packageName];
+
+ if (externalDependencyVersion) {
+ dependencies[packageParts.name] = externalDependencyVersion;
+ continue;
+ } else {
+ logger.warn(
+ `Could not find version for package ${packageName}, add a version specifier to the package name (e.g. ${packageParts.name}@latest) or add it to your project's package.json`
+ );
+ }
+ }
+ }
+ }
+
return dependencies;
}
+
+function gatherProcessEnv() {
+ const env = {
+ NODE_ENV: process.env.NODE_ENV ?? "development",
+ PATH: process.env.PATH,
+ USER: process.env.USER,
+ SHELL: process.env.SHELL,
+ NVM_INC: process.env.NVM_INC,
+ NVM_DIR: process.env.NVM_DIR,
+ NVM_BIN: process.env.NVM_BIN,
+ LANG: process.env.LANG,
+ TERM: process.env.TERM,
+ NODE_PATH: process.env.NODE_PATH,
+ HOME: process.env.HOME,
+ BUN_INSTALL: process.env.BUN_INSTALL,
+ };
+
+ // Filter out undefined values
+ return Object.fromEntries(Object.entries(env).filter(([key, value]) => value !== undefined));
+}
diff --git a/packages/cli-v3/src/commands/init.ts b/packages/cli-v3/src/commands/init.ts
index 6a8a31c05..03dcfc640 100644
--- a/packages/cli-v3/src/commands/init.ts
+++ b/packages/cli-v3/src/commands/init.ts
@@ -8,6 +8,8 @@ import {
import chalk from "chalk";
import { Command } from "commander";
import { execa } from "execa";
+import { applyEdits, modify } from "jsonc-parser";
+import { writeFile } from "node:fs/promises";
import { join, relative, resolve } from "node:path";
import terminalLink from "terminal-link";
import { z } from "zod";
@@ -24,7 +26,7 @@ import {
} from "../cli/common.js";
import { readConfig } from "../utilities/configFiles.js";
import { createFileFromTemplate } from "../utilities/createFileFromTemplate";
-import { createFile, pathExists } from "../utilities/fileSystem";
+import { createFile, pathExists, readFile } from "../utilities/fileSystem";
import { getUserPackageManager } from "../utilities/getUserPackageManager";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
import { logger } from "../utilities/logger";
@@ -80,7 +82,11 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
intro("Initializing project");
- const authorization = await login({ embedded: true, defaultApiUrl: options.apiUrl, profile: options.profile });
+ const authorization = await login({
+ embedded: true,
+ defaultApiUrl: options.apiUrl,
+ profile: options.profile,
+ });
if (!authorization.ok) {
if (authorization.error === "fetch failed") {
@@ -145,6 +151,12 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
// Create the trigger dir
await createTriggerDir(dir, options);
+ // Add trigger.config.ts to tsconfig.json
+ await addConfigFileToTsConfig(dir, options);
+
+ // Ignore .trigger dir
+ await gitIgnoreDotTriggerDir(dir, options);
+
const projectDashboard = terminalLink(
"project dashboard",
`${authorization.dashboardUrl}/projects/v3/${selectedProject.externalRef}`
@@ -255,6 +267,101 @@ async function createTriggerDir(dir: string, options: InitCommandOptions) {
});
}
+async function gitIgnoreDotTriggerDir(dir: string, options: InitCommandOptions) {
+ return await tracer.startActiveSpan("gitIgnoreDotTriggerDir", async (span) => {
+ try {
+ const projectDir = resolve(process.cwd(), dir);
+ const gitIgnorePath = join(projectDir, ".gitignore");
+
+ span.setAttributes({
+ "cli.projectDir": projectDir,
+ "cli.gitIgnorePath": gitIgnorePath,
+ });
+
+ if (!(await pathExists(gitIgnorePath))) {
+ // Create .gitignore file
+ await createFile(gitIgnorePath, ".trigger");
+
+ log.step(`Added .trigger to .gitignore`);
+
+ span.end();
+
+ return;
+ }
+
+ // Check if .gitignore already contains .trigger
+ const gitIgnoreContent = await readFile(gitIgnorePath);
+
+ if (gitIgnoreContent.includes(".trigger")) {
+ span.end();
+
+ return;
+ }
+
+ const newGitIgnoreContent = `${gitIgnoreContent}\n.trigger`;
+
+ await writeFile(gitIgnorePath, newGitIgnoreContent, "utf-8");
+
+ log.step(`Added .trigger to .gitignore`);
+
+ span.end();
+ } catch (e) {
+ if (!(e instanceof SkipCommandError)) {
+ recordSpanException(span, e);
+ }
+
+ span.end();
+
+ throw e;
+ }
+ });
+}
+
+async function addConfigFileToTsConfig(dir: string, options: InitCommandOptions) {
+ return await tracer.startActiveSpan("createTriggerDir", async (span) => {
+ try {
+ const projectDir = resolve(process.cwd(), dir);
+ const tsconfigPath = join(projectDir, "tsconfig.json");
+
+ span.setAttributes({
+ "cli.projectDir": projectDir,
+ "cli.tsconfigPath": tsconfigPath,
+ });
+
+ const tsconfigContent = await readFile(tsconfigPath);
+
+ const edits = modify(tsconfigContent, ["include", -1], "trigger.config.ts", {
+ isArrayInsertion: true,
+ formattingOptions: {
+ tabSize: 2,
+ insertSpaces: true,
+ eol: "\n",
+ },
+ });
+
+ logger.debug("tsconfig.json edits", { edits });
+
+ const newTsconfigContent = applyEdits(tsconfigContent, edits);
+
+ logger.debug("new tsconfig.json content", { newTsconfigContent });
+
+ await writeFile(tsconfigPath, newTsconfigContent, "utf-8");
+
+ log.step(`Added trigger.config.ts to tsconfig.json`);
+
+ span.end();
+ } catch (e) {
+ if (!(e instanceof SkipCommandError)) {
+ recordSpanException(span, e);
+ }
+
+ span.end();
+
+ throw e;
+ }
+ });
+}
+
async function installPackages(dir: string, options: InitCommandOptions) {
return await tracer.startActiveSpan("installPackages", async (span) => {
const installSpinner = spinner();
@@ -332,8 +439,8 @@ async function writeConfigFile(
spnnr.start("Creating config file");
const projectDir = resolve(process.cwd(), dir);
- const templatePath = resolveInternalFilePath("./templates/trigger.config.mjs");
- const outputPath = join(projectDir, "trigger.config.mjs");
+ const templatePath = resolveInternalFilePath("./templates/trigger.config.ts");
+ const outputPath = join(projectDir, "trigger.config.ts");
span.setAttributes({
"cli.projectDir": projectDir,
diff --git a/packages/cli-v3/src/consts.ts b/packages/cli-v3/src/consts.ts
index 8bb18de4e..e265a6a9e 100644
--- a/packages/cli-v3/src/consts.ts
+++ b/packages/cli-v3/src/consts.ts
@@ -10,4 +10,4 @@ export const PKG_ROOT = path.join(distPath, "../");
export const COMMAND_NAME = "trigger.dev";
export const CLOUD_WEB_URL = "https://cloud.trigger.dev";
export const CLOUD_API_URL = "https://api.trigger.dev";
-export const CONFIG_FILES = ["trigger.config.js", "trigger.config.mjs"];
+export const CONFIG_FILES = ["trigger.config.ts", "trigger.config.js", "trigger.config.mjs"];
diff --git a/packages/cli-v3/src/templates/trigger.config.mjs b/packages/cli-v3/src/templates/trigger.config.ts
similarity index 68%
rename from packages/cli-v3/src/templates/trigger.config.mjs
rename to packages/cli-v3/src/templates/trigger.config.ts
index 2e988ab6e..c9f2a18ae 100644
--- a/packages/cli-v3/src/templates/trigger.config.mjs
+++ b/packages/cli-v3/src/templates/trigger.config.ts
@@ -1,7 +1,6 @@
-// @ts-check
-/** @type {import('@trigger.dev/sdk/v3').Config} */
+import type { ProjectConfig } from "@trigger.dev/core/v3";
-export default {
+export const config: ProjectConfig = {
project: "${projectRef}",
retries: {
enabledInDev: false,
diff --git a/packages/cli-v3/src/utilities/configFiles.ts b/packages/cli-v3/src/utilities/configFiles.ts
index a2f87f0bb..bdabc3157 100644
--- a/packages/cli-v3/src/utilities/configFiles.ts
+++ b/packages/cli-v3/src/utilities/configFiles.ts
@@ -1,14 +1,15 @@
import { Config, ResolvedConfig } from "@trigger.dev/core/v3";
import { findUp } from "find-up";
import { mkdirSync, writeFileSync } from "node:fs";
-import path from "node:path";
+import path, { join } from "node:path";
import { pathToFileURL } from "node:url";
import xdgAppPaths from "xdg-app-paths";
import { z } from "zod";
import { CLOUD_API_URL, CONFIG_FILES } from "../consts.js";
-import { readJSONFileSync } from "./fileSystem.js";
+import { createTempDir, readJSONFileSync } from "./fileSystem.js";
import { logger } from "./logger.js";
import { findTriggerDirectories, resolveTriggerDirectories } from "./taskFiles.js";
+import { build } from "esbuild";
function getGlobalConfigFolderPath() {
const configDir = xdgAppPaths("trigger").config();
@@ -81,6 +82,12 @@ function writeAuthConfigFile(config: UserAuthConfigFile) {
}
async function getConfigPath(dir: string, fileName?: string): Promise {
+ logger.debug("Searching for the config file", {
+ dir,
+ fileName,
+ configFiles: CONFIG_FILES,
+ });
+
return await findUp(fileName ? [fileName] : CONFIG_FILES, { cwd: dir });
}
@@ -91,14 +98,14 @@ export type ReadConfigOptions = {
export type ReadConfigResult =
| {
- status: "file";
- config: ResolvedConfig;
- path: string;
- }
+ status: "file";
+ config: ResolvedConfig;
+ path: string;
+ }
| {
- status: "in-memory";
- config: ResolvedConfig;
- };
+ status: "in-memory";
+ config: ResolvedConfig;
+ };
export async function readConfig(
dir: string,
@@ -106,12 +113,6 @@ export async function readConfig(
): Promise {
const absoluteDir = path.resolve(process.cwd(), dir);
- logger.debug("Searching for the config file", {
- dir,
- options,
- absoluteDir,
- });
-
const configPath = await getConfigPath(dir, options?.configFile);
if (!configPath) {
@@ -128,9 +129,36 @@ export async function readConfig(
}
}
+ const tempDir = await createTempDir();
+
+ const builtConfigFilePath = join(tempDir, "config.mjs");
+ const builtConfigFileHref = pathToFileURL(builtConfigFilePath).href;
+
+ logger.debug("Building config file", {
+ configPath,
+ builtConfigFileHref,
+ builtConfigFilePath,
+ });
+
+ // We need to build the path to the config file, and then import it?
+ await build({
+ entryPoints: [configPath],
+ bundle: true,
+ metafile: true,
+ minify: false,
+ write: true,
+ format: "esm",
+ platform: "node",
+ target: ["es2018", "node18"],
+ outfile: builtConfigFilePath,
+ logLevel: "silent",
+ });
+
// import the config file
- const userConfigModule = await import(`${pathToFileURL(configPath).href}?_ts=${Date.now()}`);
- const rawConfig = await normalizeConfig(userConfigModule ? userConfigModule.default : {});
+ const userConfigModule = await import(builtConfigFileHref);
+ const rawConfig = await normalizeConfig(
+ userConfigModule ? userConfigModule.config : { project: options?.projectRef }
+ );
const config = Config.parse(rawConfig);
return {
diff --git a/packages/cli-v3/src/utilities/installPackages.ts b/packages/cli-v3/src/utilities/installPackages.ts
index fdb678fe4..cfa7566b1 100644
--- a/packages/cli-v3/src/utilities/installPackages.ts
+++ b/packages/cli-v3/src/utilities/installPackages.ts
@@ -1,8 +1,7 @@
-import semver from "semver";
import { execa } from "execa";
-import { logger } from "./logger";
import { join } from "node:path";
import { readJSONFile, writeJSONFile } from "./fileSystem";
+import { logger } from "./logger";
export type InstallPackagesOptions = { cwd?: string };
@@ -12,64 +11,13 @@ export async function installPackages(
) {
const cwd = options?.cwd ?? process.cwd();
- logger.debug(`Installing packages at ${cwd}:`, { packages });
+ logger.debug("Installing packages", { packages });
- // Make sure the cwd has a package.json file (if not create a barebones one)
- try {
- await readJSONFile(join(cwd, "package.json"));
- } catch (error) {
- await writeJSONFile(join(cwd, "package.json"), {
- name: "temp",
- version: "1.0.0",
- description: "",
- });
- }
-
- // Detect with packages have already been installed at the specified version (use semver to compare)
- // and only install the ones that are missing or have a different version
- const installablePackages = await Promise.all(
- Object.entries(packages).map(async ([name, version]) => {
- try {
- const latestVersion = await getPackageVersion(join(cwd, "node_modules", name));
-
- if (!latestVersion) {
- return { name, version };
- }
-
- return semver.satisfies(latestVersion, version) ? undefined : { name, version };
- } catch (error) {
- return { name, version };
- }
- })
- )
- .then((packages) => packages.filter(Boolean))
- .then((packages) =>
- packages.reduce((acc: Record, p) => ({ ...acc, [p!.name]: p!.version }), {})
- );
-
- if (Object.keys(installablePackages).length === 0) {
- return;
- }
-
- logger.debug(`Found installable packages`);
- logger.table(
- Object.entries(installablePackages).map(([name, version]) => ({ name, version })),
- "debug"
- );
+ await setPackageJsonDeps(join(cwd, "package.json"), packages);
const childProcess = execa(
"npm",
- [
- "install",
- ...Object.entries(installablePackages).map(([name, version]) => `${name}@${version}`),
- "--install-strategy",
- "nested",
- "--ignore-scripts",
- "--no-package-lock",
- "--no-audit",
- "--no-fund",
- "--no-save",
- ],
+ ["install", "--install-strategy", "nested", "--ignore-scripts", "--no-audit", "--no-fund"],
{
cwd,
stderr: "inherit",
@@ -113,3 +61,41 @@ export function detectPackageNameFromImportPath(path: string): string {
return path.split("/")[0] as string;
}
}
+
+export function parsePackageName(packageSpecifier: string): { name: string; version?: string } {
+ const parts = packageSpecifier.split("@");
+
+ if (parts.length === 1 && typeof parts[0] === "string") {
+ return { name: parts[0] };
+ }
+
+ if (parts.length === 2 && typeof parts[0] === "string" && typeof parts[1] === "string") {
+ return { name: parts[0], version: parts[1] };
+ }
+
+ return { name: packageSpecifier };
+}
+
+async function setPackageJsonDeps(path: string, deps: Record) {
+ try {
+ const existingPackageJson = await readJSONFile(path);
+
+ const newPackageJson = {
+ ...existingPackageJson,
+ dependencies: {
+ ...deps,
+ },
+ };
+
+ await writeJSONFile(path, newPackageJson);
+ } catch (error) {
+ const defaultPackageJson = {
+ name: "temp",
+ version: "1.0.0",
+ description: "",
+ dependencies: deps,
+ };
+
+ await writeJSONFile(path, defaultPackageJson);
+ }
+}
diff --git a/packages/cli-v3/src/workers/dev/backgroundWorker.ts b/packages/cli-v3/src/workers/dev/backgroundWorker.ts
index 391b162b8..f4034fc24 100644
--- a/packages/cli-v3/src/workers/dev/backgroundWorker.ts
+++ b/packages/cli-v3/src/workers/dev/backgroundWorker.ts
@@ -167,8 +167,8 @@ export class BackgroundWorkerCoordinator {
!completion.ok && completion.skippedRetrying
? " (retrying skipped)"
: !completion.ok && completion.retry !== undefined
- ? ` (retrying in ${completion.retry.delay}ms)`
- : "";
+ ? ` (retrying in ${completion.retry.delay}ms)`
+ : "";
const resultText = !completion.ok
? completion.error.type === "INTERNAL_ERROR" &&
@@ -181,8 +181,8 @@ export class BackgroundWorkerCoordinator {
const errorText = !completion.ok
? this.#formatErrorLog(completion.error)
: "retry" in completion
- ? `retry in ${completion.retry}ms`
- : "";
+ ? `retry in ${completion.retry}ms`
+ : "";
const elapsedText = chalk.dim(`(${elapsed.toFixed(2)}ms)`);
@@ -263,7 +263,7 @@ export class BackgroundWorker {
constructor(
public path: string,
private params: BackgroundWorkerParams
- ) { }
+ ) {}
close() {
if (this._closed) {
@@ -316,7 +316,7 @@ export class BackgroundWorker {
resolved = true;
child.kill();
reject(new Error("Worker timed out"));
- }, 1000);
+ }, 5000);
child.on("message", async (msg: any) => {
const message = this._handler.parseMessage(msg);
@@ -545,7 +545,8 @@ class TaskRunProcess {
logger.debug("initializing task run process", {
env: this.env,
path: this.path,
- })
+ processEnv: process.env,
+ });
this._child = fork(this.path, {
stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"],
@@ -700,7 +701,8 @@ class TaskRunProcess {
}
logger.log(
- `[${this.metadata.version}][${this._currentExecution.run.id}.${this._currentExecution.attempt.number
+ `[${this.metadata.version}][${this._currentExecution.run.id}.${
+ this._currentExecution.attempt.number
}] ${data.toString()}`
);
}
@@ -717,7 +719,8 @@ class TaskRunProcess {
}
logger.error(
- `[${this.metadata.version}][${this._currentExecution.run.id}.${this._currentExecution.attempt.number
+ `[${this.metadata.version}][${this._currentExecution.run.id}.${
+ this._currentExecution.attempt.number
}] ${data.toString()}`
);
}
diff --git a/packages/cli-v3/src/workers/dev/worker-facade.ts b/packages/cli-v3/src/workers/dev/worker-facade.ts
index 9498ac17a..0592703c6 100644
--- a/packages/cli-v3/src/workers/dev/worker-facade.ts
+++ b/packages/cli-v3/src/workers/dev/worker-facade.ts
@@ -1,4 +1,11 @@
-import { Config, ProjectConfig, TaskExecutor, preciseDateOriginNow, type TracingSDK } from "@trigger.dev/core/v3";
+import {
+ Config,
+ ProjectConfig,
+ TaskExecutor,
+ preciseDateOriginNow,
+ type TracingSDK,
+ type HandleErrorFunction,
+} from "@trigger.dev/core/v3";
import "source-map-support/register.js";
__WORKER_SETUP__;
@@ -7,6 +14,7 @@ declare const __WORKER_SETUP__: unknown;
__IMPORTED_PROJECT_CONFIG__;
declare const __IMPORTED_PROJECT_CONFIG__: unknown;
declare const importedConfig: ProjectConfig | undefined;
+declare const handleError: HandleErrorFunction | undefined;
declare const __PROJECT_CONFIG__: Config;
declare const tracingSDK: TracingSDK;
@@ -48,7 +56,7 @@ const otelTaskLogger = new OtelTaskLogger({
logger: otelLogger,
tracer: tracer,
level: "info",
- preciseDateOrigin
+ preciseDateOrigin,
});
logger.setGlobalTaskLogger(otelTaskLogger);
@@ -111,6 +119,7 @@ for (const task of tasks) {
consoleInterceptor,
projectConfig: __PROJECT_CONFIG__,
importedConfig,
+ handleErrorFn: handleError,
})
);
}
diff --git a/packages/cli-v3/src/workers/prod/entry-point.ts b/packages/cli-v3/src/workers/prod/entry-point.ts
index 226a39c0f..3b4743399 100644
--- a/packages/cli-v3/src/workers/prod/entry-point.ts
+++ b/packages/cli-v3/src/workers/prod/entry-point.ts
@@ -56,6 +56,7 @@ class ProdWorker {
this.#backgroundWorker = new ProdBackgroundWorker("worker.js", {
projectConfig: __PROJECT_CONFIG__,
env: {
+ ...gatherProcessEnv(),
TRIGGER_API_URL: this.apiUrl,
TRIGGER_SECRET_KEY: this.apiKey,
OTEL_EXPORTER_OTLP_ENDPOINT:
@@ -583,3 +584,19 @@ class ProdWorker {
const prodWorker = new ProdWorker(HTTP_SERVER_PORT);
prodWorker.start();
+
+function gatherProcessEnv() {
+ const env = {
+ NODE_ENV: process.env.NODE_ENV ?? "production",
+ PATH: process.env.PATH,
+ USER: process.env.USER,
+ SHELL: process.env.SHELL,
+ LANG: process.env.LANG,
+ TERM: process.env.TERM,
+ NODE_PATH: process.env.NODE_PATH,
+ HOME: process.env.HOME,
+ };
+
+ // Filter out undefined values
+ return Object.fromEntries(Object.entries(env).filter(([key, value]) => value !== undefined));
+}
diff --git a/packages/cli-v3/src/workers/prod/worker-facade.ts b/packages/cli-v3/src/workers/prod/worker-facade.ts
index ad492e031..07c3fadee 100644
--- a/packages/cli-v3/src/workers/prod/worker-facade.ts
+++ b/packages/cli-v3/src/workers/prod/worker-facade.ts
@@ -7,6 +7,7 @@ import {
ZodIpcConnection,
type TracingSDK,
preciseDateOriginNow,
+ HandleErrorFunction,
} from "@trigger.dev/core/v3";
import "source-map-support/register.js";
@@ -16,6 +17,7 @@ declare const __WORKER_SETUP__: unknown;
__IMPORTED_PROJECT_CONFIG__;
declare const __IMPORTED_PROJECT_CONFIG__: unknown;
declare const importedConfig: ProjectConfig | undefined;
+declare const handleError: HandleErrorFunction | undefined;
declare const __PROJECT_CONFIG__: Config;
declare const tracingSDK: TracingSDK;
@@ -47,7 +49,7 @@ const otelTaskLogger = new OtelTaskLogger({
logger: otelLogger,
tracer: tracer,
level: "info",
- preciseDateOrigin
+ preciseDateOrigin,
});
logger.setGlobalTaskLogger(otelTaskLogger);
@@ -110,6 +112,7 @@ for (const task of tasks) {
consoleInterceptor,
projectConfig: __PROJECT_CONFIG__,
importedConfig,
+ handleErrorFn: handleError,
})
);
}
diff --git a/packages/core/src/v3/otel/tracingSDK.ts b/packages/core/src/v3/otel/tracingSDK.ts
index b0105a91f..09ba3b8e0 100644
--- a/packages/core/src/v3/otel/tracingSDK.ts
+++ b/packages/core/src/v3/otel/tracingSDK.ts
@@ -104,6 +104,14 @@ export class TracingSDK {
const traceProvider = new NodeTracerProvider({
forceFlushTimeoutMillis: config.forceFlushTimeoutMillis ?? 500,
resource: commonResources,
+ spanLimits: {
+ attributeCountLimit: 1000,
+ attributeValueLengthLimit: 1000,
+ eventCountLimit: 100,
+ attributePerEventCountLimit: 100,
+ linkCountLimit: 10,
+ attributePerLinkCountLimit: 100,
+ },
});
const spanExporter = new OTLPTraceExporter({
@@ -128,6 +136,10 @@ export class TracingSDK {
// To start a logger, you first need to initialize the Logger provider.
const loggerProvider = new LoggerProvider({
resource: commonResources,
+ logRecordLimits: {
+ attributeCountLimit: 1000,
+ attributeValueLengthLimit: 1000,
+ },
});
loggerProvider.addLogRecordProcessor(
diff --git a/packages/core/src/v3/schemas/messages.ts b/packages/core/src/v3/schemas/messages.ts
index 5bcfa27c7..f37ad1c27 100644
--- a/packages/core/src/v3/schemas/messages.ts
+++ b/packages/core/src/v3/schemas/messages.ts
@@ -158,10 +158,21 @@ export const RateLimitOptions = z.discriminatedUnion("type", [
]);
export const RetryOptions = z.object({
+ /** The number of attempts before giving up */
maxAttempts: z.number().int().optional(),
+ /** The exponential factor to use when calculating the next retry time.
+ *
+ * Each subsequent retry will be calculated as `previousTimeout * factor`
+ */
factor: z.number().optional(),
+ /** The minimum time to wait before retrying */
minTimeoutInMs: z.number().int().optional(),
+ /** The maximum time to wait before retrying */
maxTimeoutInMs: z.number().int().optional(),
+ /** Randomize the timeout between retries.
+ *
+ * This can be useful to prevent the thundering herd problem where all retries happen at the same time.
+ */
randomize: z.boolean().optional(),
});
@@ -170,10 +181,44 @@ export type RetryOptions = z.infer;
export type RateLimitOptions = z.infer;
export const QueueOptions = z.object({
+ /** You can define a shared queue and then pass the name in to your task.
+ *
+ * @example
+ *
+ * ```ts
+ * const myQueue = queue({
+ name: "my-queue",
+ concurrencyLimit: 1,
+ });
+
+ export const task1 = task({
+ id: "task-1",
+ queue: {
+ name: "my-queue",
+ },
+ run: async (payload: { message: string }) => {
+ // ...
+ },
+ });
+
+ export const task2 = task({
+ id: "task-2",
+ queue: {
+ name: "my-queue",
+ },
+ run: async (payload: { message: string }) => {
+ // ...
+ },
+ });
+ * ```
+ */
+ name: z.string().optional(),
+ /** An optional property that specifies the maximum number of concurrent run executions.
+ *
+ * If this property is omitted, the task can potentially use up the full concurrency of an environment. */
+ concurrencyLimit: z.number().int().min(1).max(1000).optional(),
/** @deprecated This feature is coming soon */
rateLimit: RateLimitOptions.optional(),
- concurrencyLimit: z.number().int().min(1).max(1000).optional(),
- name: z.string().optional(),
});
export type QueueOptions = z.infer;
diff --git a/packages/core/src/v3/schemas/schemas.ts b/packages/core/src/v3/schemas/schemas.ts
index 5d89c43e5..0c7e6c1a8 100644
--- a/packages/core/src/v3/schemas/schemas.ts
+++ b/packages/core/src/v3/schemas/schemas.ts
@@ -21,6 +21,7 @@ export const Config = z.object({
default: RetryOptions.optional(),
})
.optional(),
+ additionalPackages: z.string().array().optional(),
});
export type Config = z.infer;
diff --git a/packages/core/src/v3/semanticInternalAttributes.ts b/packages/core/src/v3/semanticInternalAttributes.ts
index de8fe1cfd..f2352de38 100644
--- a/packages/core/src/v3/semanticInternalAttributes.ts
+++ b/packages/core/src/v3/semanticInternalAttributes.ts
@@ -28,7 +28,8 @@ export const SemanticInternalAttributes = {
METADATA: "$metadata",
TRIGGER: "$trigger",
PAYLOAD: "$payload",
- SHOW_ACTIONS: "show.actions",
+ SHOW: "$show",
+ SHOW_ACTIONS: "$show.actions",
WORKER_ID: "worker.id",
WORKER_VERSION: "worker.version",
CLI_VERSION: "cli.version",
diff --git a/packages/core/src/v3/types/config.ts b/packages/core/src/v3/types/config.ts
index f73829842..f0e0d4a1c 100644
--- a/packages/core/src/v3/types/config.ts
+++ b/packages/core/src/v3/types/config.ts
@@ -1,4 +1,3 @@
-import { HandleErrorFnParams, HandleErrorResult } from ".";
import { RetryOptions } from "../schemas";
export interface ProjectConfig {
@@ -9,9 +8,5 @@ export interface ProjectConfig {
enabledInDev?: boolean;
default?: RetryOptions;
};
- handleError?: (
- payload: any,
- error: unknown,
- params: HandleErrorFnParams
- ) => HandleErrorResult;
+ additionalPackages?: string[];
}
diff --git a/packages/core/src/v3/types/index.ts b/packages/core/src/v3/types/index.ts
index 35e5a8be6..071742dd9 100644
--- a/packages/core/src/v3/types/index.ts
+++ b/packages/core/src/v3/types/index.ts
@@ -7,7 +7,9 @@ export * from "./config";
export type InitOutput = Record | void | undefined;
export type RunFnParams = Prettify<{
+ /** Metadata about the task, run, attempt, queue, environment, organization, project and batch. */
ctx: Context;
+ /** If you use the `init` function, this will be whatever you returned. */
init?: TInitOutput;
}>;
@@ -48,6 +50,19 @@ export type HandleErrorResult =
| HandleErrorModificationOptions
| Promise;
+export type HandleErrorArgs = {
+ ctx: Context;
+ retry?: RetryOptions;
+ retryAt?: Date;
+ retryDelayInMs?: number;
+};
+
+export type HandleErrorFunction = (
+ payload: any,
+ error: unknown,
+ params: HandleErrorArgs
+) => HandleErrorResult;
+
export type TaskMetadataWithFunctions = TaskMetadataWithFilePath & {
fns: {
run: (payload: any, params: RunFnParams) => Promise;
diff --git a/packages/core/src/v3/workers/taskExecutor.ts b/packages/core/src/v3/workers/taskExecutor.ts
index 6a2db3045..a93ada2a4 100644
--- a/packages/core/src/v3/workers/taskExecutor.ts
+++ b/packages/core/src/v3/workers/taskExecutor.ts
@@ -10,7 +10,7 @@ import {
TaskRunExecutionRetry,
} from "../schemas";
import { SemanticInternalAttributes } from "../semanticInternalAttributes";
-import { ProjectConfig, TaskMetadataWithFunctions } from "../types";
+import { HandleErrorFunction, ProjectConfig, TaskMetadataWithFunctions } from "../types";
import { flattenAttributes } from "../utils/flattenAttributes";
import { accessoryAttributes } from "../utils/styleAttributes";
import { calculateNextRetryDelay } from "../utils/retries";
@@ -25,6 +25,7 @@ export type TaskExecutorOptions = {
consoleInterceptor: ConsoleInterceptor;
projectConfig: Config;
importedConfig: ProjectConfig | undefined;
+ handleErrorFn: HandleErrorFunction | undefined;
};
export class TaskExecutor {
@@ -33,6 +34,7 @@ export class TaskExecutor {
private _consoleInterceptor: ConsoleInterceptor;
private _config: Config;
private _importedConfig: ProjectConfig | undefined;
+ private _handleErrorFn: HandleErrorFunction | undefined;
constructor(
public task: TaskMetadataWithFunctions,
@@ -43,6 +45,7 @@ export class TaskExecutor {
this._consoleInterceptor = options.consoleInterceptor;
this._config = options.projectConfig;
this._importedConfig = options.importedConfig;
+ this._handleErrorFn = options.handleErrorFn;
}
async execute(
@@ -224,7 +227,9 @@ export class TaskExecutor {
| { status: "skipped"; error?: unknown } // skipped is different than noop, it means that the task was skipped from retrying, instead of just not retrying
| { status: "noop"; error?: unknown }
> {
- const retry = this.task.retry ?? this._config.retries?.default;
+ const retriesConfig = this._importedConfig?.retries ?? this._config.retries;
+
+ const retry = this.task.retry ?? retriesConfig?.default;
if (!retry) {
return { status: "noop" };
@@ -234,8 +239,8 @@ export class TaskExecutor {
if (
execution.environment.type === "DEVELOPMENT" &&
- typeof this._config.retries?.enabledInDev === "boolean" &&
- !this._config.retries.enabledInDev
+ typeof retriesConfig?.enabledInDev === "boolean" &&
+ !retriesConfig.enabledInDev
) {
return { status: "skipped" };
}
@@ -251,7 +256,7 @@ export class TaskExecutor {
retryAt: delay ? new Date(Date.now() + delay) : undefined,
})
: this._importedConfig
- ? await this._importedConfig.handleError?.(payload, error, {
+ ? await this._handleErrorFn?.(payload, error, {
ctx,
retry,
retryDelayInMs: delay,
diff --git a/packages/nestjs/src/index.ts b/packages/nestjs/src/index.ts
index e1fbfaebb..1e13a677e 100644
--- a/packages/nestjs/src/index.ts
+++ b/packages/nestjs/src/index.ts
@@ -16,7 +16,7 @@ import {
} from "@nestjs/common";
import { Headers as StandardHeaders, Request as StandardRequest } from "@remix-run/web-fetch";
import { TriggerClient, TriggerClientOptions } from "@trigger.dev/sdk";
-import type { Response } from "express";
+import type { Response as ExpressResponse } from "express";
import type { FastifyReply } from "fastify";
const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } =
@@ -192,15 +192,24 @@ function createControllerByPath(customProvider: InjectionToken, path: string) {
throw new NotFoundException({ error: "Not found" });
}
- if (typeof res.status === "function") {
- // express
- (res as Response).status(response.status);
- (res as Response).set(response.headers);
- } else if (typeof res.code === "function") {
- // fastify
- (res as FastifyReply).code(response.status);
+ /**
+ * NestJS users mostly use either Express or Fastify, but they have
+ * different response object APIs, so we need to figure out which one
+ * is being used and set the status code and headers accordingly.
+ */
+ if (isExpressResponse(res)) {
+ res.status(response.status);
+
if (response.headers) {
- (res as FastifyReply).headers(response.headers);
+ // Merges the headers, so no need to iterate over them
+ res.set(response.headers);
+ }
+ } else if (isFastifyReply(res)) {
+ res.code(response.status);
+
+ if (response.headers) {
+ // Same merge behaviour as Express
+ res.headers(response.headers);
}
} else {
throw new InternalServerErrorException(
@@ -214,3 +223,23 @@ function createControllerByPath(customProvider: InjectionToken, path: string) {
return TriggerDevController;
}
+
+/**
+ * Type guard for Express with unique checks
+ */
+function isExpressResponse(res: unknown): res is ExpressResponse {
+ return (
+ typeof (res as ExpressResponse)?.status === "function" &&
+ typeof (res as ExpressResponse)?.render === "function"
+ );
+}
+
+/**
+ * Type guard for Fastify with unique checks
+ */
+function isFastifyReply(res: unknown): res is FastifyReply {
+ return (
+ typeof (res as FastifyReply)?.code === "function" &&
+ typeof (res as FastifyReply)?.headers === "function"
+ );
+}
diff --git a/packages/trigger-sdk/src/v3/shared.ts b/packages/trigger-sdk/src/v3/shared.ts
index e87c5369f..188317668 100644
--- a/packages/trigger-sdk/src/v3/shared.ts
+++ b/packages/trigger-sdk/src/v3/shared.ts
@@ -38,13 +38,94 @@ export function queue(options: { name: string } & QueueOptions): Queue {
}
export type TaskOptions = {
+ /** An id for your task. This must be unique inside your project and not change between versions. */
id: string;
+ /** The retry settings when an uncaught error is thrown.
+ *
+ * If omitted it will use the values in your `trigger.config.ts` file.
+ *
+ * @example
+ *
+ * ```
+ * export const taskWithRetries = task({
+ id: "task-with-retries",
+ retry: {
+ maxAttempts: 10,
+ factor: 1.8,
+ minTimeoutInMs: 500,
+ maxTimeoutInMs: 30_000,
+ randomize: false,
+ },
+ run: async ({ payload, ctx }) => {
+ //...
+ },
+ });
+ * ```
+ * */
retry?: RetryOptions;
+ /** Used to configure what should happen when more than one run is triggered at the same time.
+ *
+ * @example
+ * one at a time execution
+ *
+ * ```ts
+ * export const oneAtATime = task({
+ id: "one-at-a-time",
+ queue: {
+ concurrencyLimit: 1,
+ },
+ run: async ({ payload, ctx }) => {
+ //...
+ },
+ });
+ * ```
+ */
queue?: QueueOptions;
+ /** Configure the spec of the machine you want your task to run on.
+ *
+ * @example
+ *
+ * ```ts
+ * export const heavyTask = task({
+ id: "heavy-task",
+ machine: {
+ cpu: 2,
+ memory: 4,
+ },
+ run: async ({ payload, ctx }) => {
+ //...
+ },
+ });
+ * ```
+ */
machine?: {
- cpu?: number;
- memory?: number;
+ /** vCPUs. The default is 0.5.
+ *
+ * Possible values:
+ * - 0.25
+ * - 0.5
+ * - 1
+ * - 2
+ * - 4
+ */
+ cpu?: 0.25 | 0.5 | 1 | 2 | 4;
+ /** In GBs of RAM. The default is 0.5.
+ *
+ * Possible values:
+ * - 0.25
+ * - 0.5
+ * - 1
+ * - 2
+ * - 4
+ * - 8
+ */
+ memory?: 0.25 | 0.5 | 1 | 2 | 4 | 8;
};
+ /** This gets called when a task is triggered. It's where you put the code you want to execute.
+ *
+ * @param payload - The payload that is passed to your task when it's triggered. This must be JSON serializable.
+ * @param params - Metadata about the run.
+ */
run: (payload: TPayload, params: RunFnParams) => Promise;
init?: (payload: TPayload, params: InitFnParams) => Promise;
handleError?: (
diff --git a/packages/trigger-sdk/src/v3/tasks.ts b/packages/trigger-sdk/src/v3/tasks.ts
index 0c9a2ad9f..274511f23 100644
--- a/packages/trigger-sdk/src/v3/tasks.ts
+++ b/packages/trigger-sdk/src/v3/tasks.ts
@@ -1,6 +1,24 @@
import { InitOutput } from "@trigger.dev/core/v3";
import { TaskOptions, Task, createTask } from "./shared";
+/** Creates a task that can be triggered
+ * @param options - Task options
+ * @example
+ *
+ * ```ts
+ * import { task } from "@trigger.dev/sdk/v3";
+ *
+ * export const helloWorld = task({
+ id: "hello-world",
+ * run: async (payload: { url: string }) => {
+ * return { hello: "world" };
+ * },
+ * });
+ *
+ * ```
+ *
+ * @returns A task that can be triggered
+ */
export function task(
options: TaskOptions
): Task {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d114e4e94..91e4c0c8b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1080,6 +1080,7 @@ importers:
gradient-string: ^2.0.2
import-meta-resolve: ^4.0.0
ink: ^4.4.1
+ jsonc-parser: ^3.2.1
jsonlines: ^0.1.1
liquidjs: ^10.9.2
mock-fs: ^5.2.0
@@ -1150,6 +1151,7 @@ importers:
gradient-string: 2.0.2
import-meta-resolve: 4.0.0
ink: 4.4.1_7kh72gklg5qjlh5zc6s6v3p6v4
+ jsonc-parser: 3.2.1
jsonlines: 0.1.1
liquidjs: 10.9.3
mock-fs: 5.2.0
@@ -3485,7 +3487,7 @@ packages:
resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/helper-validator-identifier': 7.22.15
+ '@babel/helper-validator-identifier': 7.22.20
chalk: 2.4.2
js-tokens: 4.0.0
@@ -28898,6 +28900,9 @@ packages:
/jsonc-parser/3.2.0:
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
+ /jsonc-parser/3.2.1:
+ resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==}
+
/jsonfile/4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
optionalDependencies:
@@ -31834,7 +31839,7 @@ packages:
bl: 5.1.0
chalk: 5.3.0
cli-cursor: 4.0.0
- cli-spinners: 2.9.1
+ cli-spinners: 2.9.2
is-interactive: 2.0.0
is-unicode-supported: 1.3.0
log-symbols: 5.1.0
@@ -32545,7 +32550,7 @@ packages:
/pkg-types/1.0.3:
resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
dependencies:
- jsonc-parser: 3.2.0
+ jsonc-parser: 3.2.1
mlly: 1.4.2
pathe: 1.1.1
@@ -37088,7 +37093,7 @@ packages:
dependencies:
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
- jest: 29.6.2_@types+node@18.17.1
+ jest: 29.6.2_@types+node@18.15.13
jest-util: 29.6.2
json5: 2.2.3
lodash.memoize: 4.1.2
diff --git a/references/v3-catalog/.gitignore b/references/v3-catalog/.gitignore
new file mode 100644
index 000000000..6524f048d
--- /dev/null
+++ b/references/v3-catalog/.gitignore
@@ -0,0 +1 @@
+.trigger
\ No newline at end of file
diff --git a/references/v3-catalog/src/handleError.ts b/references/v3-catalog/src/handleError.ts
new file mode 100644
index 000000000..ce2d60d68
--- /dev/null
+++ b/references/v3-catalog/src/handleError.ts
@@ -0,0 +1,5 @@
+import type { HandleErrorFunction } from "@trigger.dev/core/v3";
+
+export const handleError: HandleErrorFunction = async (payload, error, { ctx, retry }) => {
+ console.log("GOT TO handleError FUNCTION");
+};
diff --git a/references/v3-catalog/src/trigger/wrangler.ts b/references/v3-catalog/src/trigger/wrangler.ts
new file mode 100644
index 000000000..418aa11da
--- /dev/null
+++ b/references/v3-catalog/src/trigger/wrangler.ts
@@ -0,0 +1,30 @@
+import { logger, task } from "@trigger.dev/sdk/v3";
+import { exec } from "node:child_process";
+import { join } from "node:path";
+
+const wranglerPath = join(__dirname, "node_modules", ".bin", "wrangler");
+
+export const wranglerTask = task({
+ id: "wrangler-task",
+ run: async () => {
+ logger.log(`Running wrangler from ${wranglerPath}`, {
+ processEnv: process.env,
+ cwd: process.cwd(),
+ });
+
+ const version = await new Promise((resolve, reject) => {
+ exec(`${wranglerPath} --version`, (error, stdout, stderr) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ resolve(stdout.trim());
+ });
+ });
+
+ return {
+ version,
+ };
+ },
+});
diff --git a/references/v3-catalog/trigger.config.mjs b/references/v3-catalog/trigger.config.mjs
deleted file mode 100644
index 2b28b67b6..000000000
--- a/references/v3-catalog/trigger.config.mjs
+++ /dev/null
@@ -1,16 +0,0 @@
-// @ts-check
-/** @type {import('@trigger.dev/sdk/v3').Config} */
-
-export default {
- project: "yubjwjsfkxnylobaqvqz",
- retries: {
- enabledInDev: false,
- default: {
- maxAttempts: 3,
- minTimeoutInMs: 1000,
- maxTimeoutInMs: 10000,
- factor: 2,
- randomize: true,
- },
- },
-};
diff --git a/references/v3-catalog/trigger.config.ts b/references/v3-catalog/trigger.config.ts
new file mode 100644
index 000000000..cf442f15a
--- /dev/null
+++ b/references/v3-catalog/trigger.config.ts
@@ -0,0 +1,18 @@
+import type { ProjectConfig } from "@trigger.dev/core/v3";
+
+export { handleError } from "./src/handleError";
+
+export const config: ProjectConfig = {
+ project: "yubjwjsfkxnylobaqvqz",
+ retries: {
+ enabledInDev: true,
+ default: {
+ maxAttempts: 3,
+ minTimeoutInMs: 1000,
+ maxTimeoutInMs: 10000,
+ factor: 2,
+ randomize: true,
+ },
+ },
+ additionalPackages: ["wrangler@3.35.0"],
+};
diff --git a/references/v3-catalog/tsconfig.json b/references/v3-catalog/tsconfig.json
index d09e514c3..790d76991 100644
--- a/references/v3-catalog/tsconfig.json
+++ b/references/v3-catalog/tsconfig.json
@@ -1,6 +1,6 @@
{
"extends": "@trigger.dev/tsconfig/node18.json",
- "include": ["./src/**/*.ts", "trigger.config.mjs"],
+ "include": ["./src/**/*.ts", "trigger.config.ts"],
"compilerOptions": {
"baseUrl": ".",
"lib": ["DOM", "DOM.Iterable"],