diff --git a/apps/docs/mint.json b/apps/docs/mint.json
index 7dd0e81b089..41e403a370c 100644
--- a/apps/docs/mint.json
+++ b/apps/docs/mint.json
@@ -39,7 +39,11 @@
"navigation": [
{
"group": "Getting Started",
- "pages": ["welcome", "getting-started", "get-help"]
+ "pages": [
+ "welcome",
+ "getting-started",
+ "get-help"
+ ]
},
{
"group": "Examples",
@@ -67,11 +71,15 @@
"pages": [
{
"group": "Slack",
- "pages": ["integrations/apis/slack/actions/post-message"]
+ "pages": [
+ "integrations/apis/slack/actions/post-message"
+ ]
},
{
"group": "Resend.com",
- "pages": ["integrations/apis/resend/actions/send-email"]
+ "pages": [
+ "integrations/apis/resend/actions/send-email"
+ ]
}
]
},
@@ -102,7 +110,8 @@
"pages": [
"reference/trigger",
"reference/custom-event",
- "reference/webhook-event"
+ "reference/webhook-event",
+ "reference/schedule-event"
]
},
{
@@ -128,4 +137,4 @@
"github": "https://github.com/triggerdotdev/trigger.dev",
"discord": "https://discord.gg/nkqV9xBYWy"
}
-}
+}
\ No newline at end of file
diff --git a/apps/docs/openapi-spec.yaml b/apps/docs/openapi-spec.yaml
new file mode 100644
index 00000000000..8395e3ad24f
--- /dev/null
+++ b/apps/docs/openapi-spec.yaml
@@ -0,0 +1,36 @@
+openapi: 3.0.3
+info:
+ title: Trigger.dev API
+ version: v1.0.0
+ description: Please see https://docs.trigger.dev for more details.
+ contact:
+ name: Eric
+ email: eric@trigger.dev
+ url: https://trigger.dev
+ termsOfService: https://trigger.dev/terms
+servers:
+ - url: https://app.trigger.dev/api/v1
+ description: Trigger.dev
+paths:
+ /events:
+ post:
+ summary: Send Custom Event
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/customEvent"
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/customEventResponse"
+ "422":
+ description: Unprocessable Entity
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/error"
diff --git a/apps/docs/reference/custom-event.mdx b/apps/docs/reference/custom-event.mdx
index a124418982c..a6a4f892b9e 100644
--- a/apps/docs/reference/custom-event.mdx
+++ b/apps/docs/reference/custom-event.mdx
@@ -7,6 +7,8 @@ description: "Trigger a workflow when a custom event is received."
## Usage
```ts
+import { customEvent, Trigger } from "@trigger.dev/sdk";
+
new Trigger({
id: "user-created-notify-slack",
name: "User Created - Notify Slack",
diff --git a/apps/docs/reference/schedule-event.mdx b/apps/docs/reference/schedule-event.mdx
new file mode 100644
index 00000000000..9cce0351ccb
--- /dev/null
+++ b/apps/docs/reference/schedule-event.mdx
@@ -0,0 +1,68 @@
+---
+title: "scheduleEvent Trigger"
+sidebarTitle: "scheduleEvent"
+description: "Run a workflow on a recurring schedule."
+---
+
+## Usage
+
+```ts
+import { scheduleEvent } from "@trigger.dev/sdk";
+
+new Trigger({
+ id: "usage",
+ name: "usage",
+ on: scheduleEvent({ rateof: { minutes: 10 } }),
+ run: async (event, ctx) => {},
+}).listen();
+```
+
+## Options
+
+You must use one of the following options, but not both:
+
+
+ The rate of the schedule. This can be a number of minutes, hours,
+ or days. For example, `{ rateOf: { minutes: 10 } }` will run
+ every 10 minutes.
+
+
+
+ A cron expression to run the workflow on. For example, `0 0 * * *` will run
+ the workflow every hour at the top of the hour. See
+ [crontab.guru](https://crontab.guru/) for more information.
+
+
+## Event Payload
+
+
+ The time the event was scheduled to run.
+
+
+
+ The time the event last run. This will be `undefined` if the event has never run. Use this parameter to run window queries:
+
+```ts
+import { scheduleEvent, Trigger } from "@trigger.dev/sdk";
+
+new Trigger({
+ id: "usage",
+ name: "usage",
+ on: scheduleEvent({ rateof: { minutes: 10 } }),
+ run: async (event, ctx) => {
+ const { lastRunAt, scheduledTime } = event;
+
+ const query = `SELECT * FROM users WHERE created_at < ${scheduledTime}`;
+
+ if (lastRunAt) {
+ query += ` AND created_at > ${lastRunAt}`;
+ }
+
+ const latestUsers = await db.query(query);
+
+ // ...
+ },
+}).listen();
+```
+
+
diff --git a/apps/docs/reference/trigger.mdx b/apps/docs/reference/trigger.mdx
index 9c8fd5ec9e9..d06f51cbfd0 100644
--- a/apps/docs/reference/trigger.mdx
+++ b/apps/docs/reference/trigger.mdx
@@ -9,6 +9,8 @@ description: "The Trigger class let's you define a workflow that is triggered by
### Usage
```ts
+import { customEvent, Trigger } from "@trigger.dev/sdk";
+
const trigger = new Trigger({
id: "user-created-notify-slack",
name: "User Created - Notify Slack",
@@ -39,11 +41,6 @@ const trigger = new Trigger({
`TRIGGER_API_KEY` environment variable.
-
- Your Trigger.dev API key. If not provided, the API key will be read from the
- `TRIGGER_API_KEY` environment variable.
-
-
The URL of the Trigger.dev WebSocket server. If not provided, the endpoint
will point to the production server.
@@ -87,6 +84,10 @@ const trigger = new Trigger({
for more info.
+
+ Whether or not this trigger is being run as a test.
+
+
A function that can be used to send an event to trigger.dev. See the [Sending
Events](/functions/send-event) page for more info.
diff --git a/apps/docs/reference/webhook-event.mdx b/apps/docs/reference/webhook-event.mdx
index 1785cbac668..4fce5d7825a 100644
--- a/apps/docs/reference/webhook-event.mdx
+++ b/apps/docs/reference/webhook-event.mdx
@@ -7,6 +7,8 @@ description: "Trigger a workflow when a webhook event is received"
## Usage
```ts
+import { webhookEvent, Trigger } from "@trigger.dev/sdk";
+
new Trigger({
id: "caldotcom-to-slack",
name: "Cal.com To Slack",
diff --git a/apps/docs/triggers/scheduled.mdx b/apps/docs/triggers/scheduled.mdx
index 5bea86e99eb..5a88f4b6e89 100644
--- a/apps/docs/triggers/scheduled.mdx
+++ b/apps/docs/triggers/scheduled.mdx
@@ -4,6 +4,8 @@ sidebarTitle: "Scheduled"
description: "Run a workflow on a recurring schedule"
---
+See the [reference](/reference/schedule-event) for more details.
+
## Examples
### Every 5 minutes
@@ -51,3 +53,33 @@ new Trigger({
},
}).listen();
```
+
+## Preventing late runs
+
+To prevent a scheduled trigger from running late, you can set a `triggerTTL` option when creating the `Trigger`, like so:
+
+```ts
+new Trigger({
+ id: "scheduled-workflow",
+ name: "Scheduled Workflow",
+ apiKey: "",
+ on: scheduleEvent({ rateOf: { minutes: 5 } }),
+ triggerTTL: 300,
+ run: async (event, ctx) => {
+ await ctx.logger.info("Received the scheduled event", {
+ event,
+ wallTime: new Date(),
+ });
+
+ return { foo: "bar" };
+ },
+}).listen();
+```
+
+This will prevent the trigger from running if it is running more than `300` seconds behind, which can happen if the server running your `Trigger` code goes down or is otherwise unavailable.
+
+This is especially useful for scheduled triggers that run on a very short interval, like every minute, so you don't get a backlog of runs that all run at once when the server comes back online.
+
+
+ Set your `triggerTTL` to the same time (or double) as the rateOf the trigger.
+
diff --git a/apps/webapp/app/services/externalSources/registerExternalSource.server.ts b/apps/webapp/app/services/externalSources/registerExternalSource.server.ts
index 55d2d8a7b16..282d940df5a 100644
--- a/apps/webapp/app/services/externalSources/registerExternalSource.server.ts
+++ b/apps/webapp/app/services/externalSources/registerExternalSource.server.ts
@@ -26,6 +26,15 @@ export class RegisterExternalSource {
}
if (externalSource.status === "READY") {
+ await this.#prismaClient.workflow.updateMany({
+ where: {
+ externalSourceId: externalSource.id,
+ },
+ data: {
+ status: "READY",
+ },
+ });
+
return true;
}
diff --git a/apps/webapp/app/services/messageBroker.server.ts b/apps/webapp/app/services/messageBroker.server.ts
index 189a5ff1476..2a99648a37f 100644
--- a/apps/webapp/app/services/messageBroker.server.ts
+++ b/apps/webapp/app/services/messageBroker.server.ts
@@ -664,6 +664,7 @@ function createTaskQueue() {
"x-env": run.environment.slug,
"x-workflow-run-id": run.id,
"x-ttl": run.workflow.triggerTtlInSeconds,
+ "x-is-test": run.isTest ? "true" : "false",
},
{
eventTimestamp: run.event.timestamp.getTime(),
diff --git a/apps/webapp/app/services/scheduler/scheduleNextEvent.server.ts b/apps/webapp/app/services/scheduler/scheduleNextEvent.server.ts
index 39284533b8e..7f21d0b26ed 100644
--- a/apps/webapp/app/services/scheduler/scheduleNextEvent.server.ts
+++ b/apps/webapp/app/services/scheduler/scheduleNextEvent.server.ts
@@ -29,7 +29,13 @@ export class ScheduleNextEvent {
const messageId = await taskQueue.publish(
"DELIVER_SCHEDULED_EVENT",
- { externalSourceId: schedulerSource.id, payload: { scheduledTime } },
+ {
+ externalSourceId: schedulerSource.id,
+ payload: {
+ scheduledTime,
+ lastRunAt: fromEvent ? fromEvent.createdAt : undefined,
+ },
+ },
{},
{ deliverAt: scheduledTime.getTime() }
);
diff --git a/apps/webapp/app/triggers/monitoring.server.ts b/apps/webapp/app/triggers/monitoring.server.ts
index 01ff77c68b3..d4ea8637c76 100644
--- a/apps/webapp/app/triggers/monitoring.server.ts
+++ b/apps/webapp/app/triggers/monitoring.server.ts
@@ -5,19 +5,20 @@ import { prisma } from "~/db.server";
export const uptimeCheck = new Trigger({
id: "uptime-check",
name: "Uptime Check",
- on: scheduleEvent({ rateOf: { minutes: 1 } }),
- triggerTTL: 300,
+ on: scheduleEvent({ rateOf: { minutes: 5 } }),
+ logLevel: "info",
+ triggerTTL: 60,
run: async (event, context) => {
+ if (context.environment === "development" && !context.isTest) {
+ return;
+ }
+
// Grab counts of workflows, runs, and steps
const userCount = await prisma.user.count();
const workflowCount = await prisma.workflow.count();
const runCount = await prisma.workflowRun.count();
const stepCount = await prisma.workflowRunStep.count();
- if (context.environment === "development") {
- return;
- }
-
await slack.postMessage("Uptime Notification", {
channelName: "monitoring",
text: `[${context.environment}] Uptime Check: ${userCount} users, ${workflowCount} workflows, ${runCount} runs, ${stepCount} steps.`,
diff --git a/apps/wss/src/runController.ts b/apps/wss/src/runController.ts
index 6070cd8bbdf..6a68016c7e9 100644
--- a/apps/wss/src/runController.ts
+++ b/apps/wss/src/runController.ts
@@ -24,6 +24,7 @@ export type WorkflowRunControllerOptions = {
environment: string;
apiKey: string;
organizationId: string;
+ isTest: boolean;
};
};
@@ -37,6 +38,7 @@ export class WorkflowRunController {
environment: string;
apiKey: string;
organizationId: string;
+ isTest: boolean;
};
#logger: Logger;
diff --git a/apps/wss/src/server.ts b/apps/wss/src/server.ts
index 32171bf2882..c9f446684a5 100644
--- a/apps/wss/src/server.ts
+++ b/apps/wss/src/server.ts
@@ -8,7 +8,6 @@ import {
import {
CommandCatalog,
InternalApiClient,
- MessageCatalogSchema,
TriggerCatalog,
triggerCatalog,
ZodPublisher,
@@ -420,6 +419,10 @@ export class TriggerServer {
organizationId: properties["x-org-id"],
environment: properties["x-env"],
apiKey: properties["x-api-key"],
+ isTest:
+ typeof properties["x-is-test"] === "string"
+ ? properties["x-is-test"] === "true"
+ : false,
},
});
@@ -519,55 +522,3 @@ function safeJsonParse(json?: string) {
return undefined;
}
}
-
-function createForwardHandler<
- TSubscriberSchema extends MessageCatalogSchema,
- THandlerName extends keyof TSubscriberSchema
->(
- schema: TSubscriberSchema,
- handler: THandlerName,
- logger: Logger,
- topicFn: (
- data: z.infer
- ) => string
-) {
- return async (
- id: string,
- data: z.infer,
- properties: z.infer
- ) => {
- const topicName = topicFn(properties);
-
- logger.debug(`Forwarding message to ${topicName}`, data, properties);
-
- const runPublisher = new ZodPublisher({
- schema: schema,
- client: pulsarClient,
- config: {
- topic: topicName,
- batchingEnabled: false,
- },
- });
-
- await runPublisher.initialize();
-
- try {
- await runPublisher.publish(handler, data, properties);
-
- logger.debug(`Forwarded message to ${topicName}`, data, properties);
-
- return true;
- } catch (e) {
- logger.error(
- `Failed to forward message to ${topicName}`,
- e,
- data,
- properties
- );
-
- return false;
- } finally {
- await runPublisher.close();
- }
- };
-}
diff --git a/examples/fetch-playground/src/index.ts b/examples/fetch-playground/src/index.ts
index 9a2f2789f1d..fe37c774b1a 100644
--- a/examples/fetch-playground/src/index.ts
+++ b/examples/fetch-playground/src/index.ts
@@ -39,6 +39,10 @@ new Trigger({
wallTime: new Date(),
});
+ if (ctx.isTest) {
+ await ctx.logger.warn("This is only a test");
+ }
+
const response = await ctx.fetch("do-fetch", `${event.url}${event.path}`, {
method: event.method,
responseSchema: z.any(),
diff --git a/examples/schedule-to-slack/src/index.ts b/examples/schedule-to-slack/src/index.ts
index 4f6eadbe1a2..6ca2262e233 100644
--- a/examples/schedule-to-slack/src/index.ts
+++ b/examples/schedule-to-slack/src/index.ts
@@ -2,20 +2,21 @@ import { Trigger, scheduleEvent } from "@trigger.dev/sdk";
import { slack } from "@trigger.dev/integrations";
const trigger = new Trigger({
- id: "schedule-to-slack",
+ id: "schedule-to-slack-2",
name: "Send to Slack every minute",
- apiKey: "trigger_development_vzNnO2DGBGcG",
+ apiKey: "trigger_dev_zC25mKNn6c0q",
+ endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: scheduleEvent({ rateOf: { minutes: 1 } }),
run: async (event, ctx) => {
await ctx.logger.info("It's me, the annoying slack bot!");
- const response = await slack.postMessage("slaaaaaack", {
- channel: "test-integrations",
- text: `Hello, the time is ${event.scheduledTime}`,
- });
+ // const response = await slack.postMessage("slaaaaaack", {
+ // channelName: "test-integrations",
+ // text: `Hello, the time is ${event.scheduledTime}, and I was last run at ${event.lastRunAt}!`,
+ // });
- return response.message;
+ return event;
},
});
diff --git a/packages/common-schemas/src/events.ts b/packages/common-schemas/src/events.ts
index 26805d54492..5ece387271e 100644
--- a/packages/common-schemas/src/events.ts
+++ b/packages/common-schemas/src/events.ts
@@ -29,6 +29,7 @@ export const EventFilterSchema: z.ZodType = z.lazy(() =>
);
export const ScheduledEventPayloadSchema = z.object({
+ lastRunAt: z.coerce.date().optional(),
scheduledTime: z.coerce.date(),
});
diff --git a/packages/internal-bridge/src/schemas/host.ts b/packages/internal-bridge/src/schemas/host.ts
index bec310e2d37..2209197e711 100644
--- a/packages/internal-bridge/src/schemas/host.ts
+++ b/packages/internal-bridge/src/schemas/host.ts
@@ -14,6 +14,7 @@ export const HostRPCSchema = {
workflowId: z.string(),
organizationId: z.string(),
apiKey: z.string(),
+ isTest: z.boolean().default(false),
}),
}),
response: z.boolean(),
diff --git a/packages/internal-integrations/src/github/index.ts b/packages/internal-integrations/src/github/index.ts
index 660b09c0d00..853746135aa 100644
--- a/packages/internal-integrations/src/github/index.ts
+++ b/packages/internal-integrations/src/github/index.ts
@@ -1,13 +1,14 @@
+import { Webhooks } from "@octokit/webhooks";
+import { github } from "@trigger.dev/providers";
+import crypto from "crypto";
+import { z } from "zod";
+import { getAccessToken } from "../accessInfo";
import {
DisplayProperty,
HandleWebhookOptions,
WebhookConfig,
WebhookIntegration,
} from "../types";
-import { Webhooks } from "@octokit/webhooks";
-import { z } from "zod";
-import { github } from "@trigger.dev/providers";
-import { getAccessToken } from "../accessInfo";
export class GitHubWebhookIntegration implements WebhookIntegration {
keyForSource(source: unknown): string {
@@ -71,9 +72,11 @@ export class GitHubWebhookIntegration implements WebhookIntegration {
"x-forwarded-proto",
]);
+ const id = md5Hash([hookId, deliveryId].join("-"));
+
return {
status: "ok" as const,
- data: { id: hookId, payload: options.request.body, event, context },
+ data: { id, payload: options.request.body, event, context },
};
}
@@ -207,3 +210,10 @@ function omit, K extends keyof T>(
return result;
}
+
+function md5Hash(str: string): string {
+ const hash = crypto.createHash("md5");
+ hash.update(str);
+
+ return hash.digest("hex");
+}
diff --git a/packages/internal-platform/src/messages/catalogs/triggers.ts b/packages/internal-platform/src/messages/catalogs/triggers.ts
index 057308d93e9..53a6d02fe9c 100644
--- a/packages/internal-platform/src/messages/catalogs/triggers.ts
+++ b/packages/internal-platform/src/messages/catalogs/triggers.ts
@@ -7,6 +7,7 @@ const Catalog = {
data: TriggerWorkflowMessageSchema,
properties: WorkflowRunEventPropertiesSchema.extend({
"x-ttl": z.coerce.number().optional(),
+ "x-is-test": z.string().default("false"),
}),
},
};
diff --git a/packages/trigger-integrations/CHANGELOG.md b/packages/trigger-integrations/CHANGELOG.md
index ce14d2ea69e..6910c5dfedd 100644
--- a/packages/trigger-integrations/CHANGELOG.md
+++ b/packages/trigger-integrations/CHANGELOG.md
@@ -1,5 +1,13 @@
# @trigger.dev/integrations
+## 0.1.13
+
+### Patch Changes
+
+- Updated dependencies [e37a200]
+- Updated dependencies [e63d354]
+ - @trigger.dev/sdk@0.2.10
+
## 0.1.12
### Patch Changes
diff --git a/packages/trigger-integrations/package.json b/packages/trigger-integrations/package.json
index 6bae5e18427..4b762884d35 100644
--- a/packages/trigger-integrations/package.json
+++ b/packages/trigger-integrations/package.json
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/integrations",
- "version": "0.1.12",
+ "version": "0.1.13",
"description": "trigger.dev integrations",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
diff --git a/packages/trigger-sdk/CHANGELOG.md b/packages/trigger-sdk/CHANGELOG.md
index 2c8f4ba53c8..1b84f33cfdc 100644
--- a/packages/trigger-sdk/CHANGELOG.md
+++ b/packages/trigger-sdk/CHANGELOG.md
@@ -1,5 +1,12 @@
# @trigger.dev/sdk
+## 0.2.10
+
+### Patch Changes
+
+- e37a200: Added lastRunAt to the scheduleEvent payload
+- e63d354: Added isTest to TriggerContext
+
## 0.2.9
### Patch Changes
diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json
index e40b7e9622b..ad5074fbc28 100644
--- a/packages/trigger-sdk/package.json
+++ b/packages/trigger-sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sdk",
- "version": "0.2.9",
+ "version": "0.2.10",
"description": "trigger.dev Node.JS SDK",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
diff --git a/packages/trigger-sdk/src/client.ts b/packages/trigger-sdk/src/client.ts
index ae181af679f..de8b6bad7ba 100644
--- a/packages/trigger-sdk/src/client.ts
+++ b/packages/trigger-sdk/src/client.ts
@@ -357,6 +357,7 @@ export class TriggerClient {
environment: data.meta.environment,
apiKey: data.meta.apiKey,
organizationId: data.meta.organizationId,
+ isTest: data.meta.isTest,
logger: new ContextLogger(async (level, message, properties) => {
await serverRPC.send("SEND_LOG", {
runId: data.id,
diff --git a/packages/trigger-sdk/src/types.ts b/packages/trigger-sdk/src/types.ts
index 075d94e717d..a99abe05501 100644
--- a/packages/trigger-sdk/src/types.ts
+++ b/packages/trigger-sdk/src/types.ts
@@ -62,6 +62,7 @@ export interface TriggerContext {
apiKey: string;
organizationId: string;
logger: TriggerLogger;
+ isTest: boolean;
sendEvent(key: string, event: TriggerCustomEvent): Promise;
waitFor(key: string, options: WaitForOptions): Promise;
waitUntil(key: string, date: Date): Promise;