Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59277731c4 | |||
| 742d16087b | |||
| 0ab51d62bc | |||
| 756024da78 | |||
| c47ad5e038 | |||
| 888344797e | |||
| 4a7caa68b6 | |||
| 3bc54d36f7 | |||
| 5295a3f798 | |||
| 14429b8b66 | |||
| 5dd1bcc589 | |||
| ed5ee16de5 | |||
| 076185d19f | |||
| 1e07964d81 | |||
| f406e59c45 | |||
| 75550f535c | |||
| 2b48e6d04b | |||
| fe14947bb0 | |||
| 0afa119721 |
@@ -0,0 +1,7 @@
|
||||
REWRITE_HOSTNAME=
|
||||
AWS_SQS_ACCESS_KEY_ID=
|
||||
AWS_SQS_SECRET_ACCESS_KEY=
|
||||
AWS_SQS_QUEUE_URL=
|
||||
AWS_SQS_REGION=
|
||||
#optional
|
||||
#REWRITE_PORT=
|
||||
@@ -0,0 +1,13 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
@@ -0,0 +1,172 @@
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
\*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
\*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
\*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
\*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
.cache/
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.\*
|
||||
|
||||
# wrangler project
|
||||
|
||||
.dev.vars
|
||||
.wrangler/
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# proxy
|
||||
|
||||
## 0.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/core@2.2.7
|
||||
@@ -0,0 +1,68 @@
|
||||
# Trigger.dev proxy
|
||||
|
||||
This is an optional module that can be used to proxy and queue requests to the Trigger.dev API.
|
||||
|
||||
## Why?
|
||||
|
||||
The Trigger.dev API is designed to be fast and reliable. However, if you have a lot of traffic, you may want to use this proxy to queue requests to the API. It intercepts some requests to the API and adds them to an AWS SQS queue, then the webapp can be setup to process the queue.
|
||||
|
||||
## Current features
|
||||
|
||||
- Intercepts `sendEvent` requests and adds them to an AWS SQS queue. The webapp then reads from the queue and creates the events.
|
||||
|
||||
## Setup
|
||||
|
||||
### Create an AWS SQS queue
|
||||
|
||||
In AWS you should create a new AWS SQS queue with appropriate security settings. You will need the queue URL for the next step.
|
||||
|
||||
### Environment variables
|
||||
|
||||
#### Cloudflare secrets
|
||||
|
||||
Locally you should copy the `.dev.var.example` file to `.dev.var` and fill in the values.
|
||||
|
||||
When deploying you should use `wrangler` (the Cloudflare CLI tool) to set secrets. Make sure you set the correct --env ("staging" or "prod")
|
||||
|
||||
```bash
|
||||
wrangler secret put REWRITE_HOSTNAME --env staging
|
||||
wrangler secret put AWS_SQS_ACCESS_KEY_ID --env staging
|
||||
wrangler secret put AWS_SQS_SECRET_ACCESS_KEY --env staging
|
||||
wrangler secret put AWS_SQS_QUEUE_URL --env staging
|
||||
wrangler secret put AWS_SQS_REGION --env staging
|
||||
```
|
||||
|
||||
You need to set your API CNAME entry to be proxied by Cloudflare. You can do this in the Cloudflare dashboard.
|
||||
|
||||
#### Webapp
|
||||
|
||||
These env vars also need setting in the webapp.
|
||||
|
||||
```bash
|
||||
AWS_SQS_REGION
|
||||
AWS_SQS_ACCESS_KEY_ID
|
||||
AWS_SQS_SECRET_ACCESS_KEY
|
||||
AWS_SQS_QUEUE_URL
|
||||
AWS_SQS_BATCH_SIZE
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
Staging:
|
||||
|
||||
```bash
|
||||
npx wrangler@latest deploy --route "<your-api-subdomain>/*" --env staging
|
||||
```
|
||||
|
||||
Prod:
|
||||
|
||||
```bash
|
||||
npx wrangler@latest deploy --route "<your-api-subdomain>/*" --env prod
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Set the environment variables as described above.
|
||||
|
||||
1. `pnpm install`
|
||||
2. `pnpm run dev --filter proxy`
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "proxy",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20230419.0",
|
||||
"typescript": "^5.0.4",
|
||||
"wrangler": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sqs": "^3.445.0",
|
||||
"@trigger.dev/core": "workspace:*",
|
||||
"ulidx": "^2.2.1",
|
||||
"zod": "3.22.3",
|
||||
"zod-error": "1.5.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const AuthorizationHeaderSchema = z.string().regex(/^Bearer .+$/);
|
||||
|
||||
export function getApiKeyFromRequest(request: Request) {
|
||||
const rawAuthorization = request.headers.get("Authorization");
|
||||
|
||||
const authorization = AuthorizationHeaderSchema.safeParse(rawAuthorization);
|
||||
if (!authorization.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = authorization.data.replace(/^Bearer /, "");
|
||||
const type = isPrivateApiKey(apiKey) ? ("PRIVATE" as const) : ("PUBLIC" as const);
|
||||
return { apiKey, type };
|
||||
}
|
||||
|
||||
function isPrivateApiKey(key: string) {
|
||||
return key.startsWith("tr_");
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
|
||||
import { ApiEventLog, SendEventBodySchema } from "@trigger.dev/core";
|
||||
import { generateErrorMessage } from "zod-error";
|
||||
import { Env } from "..";
|
||||
import { getApiKeyFromRequest } from "../apikey";
|
||||
import { json } from "../json";
|
||||
import { calculateDeliverAt } from "./utils";
|
||||
|
||||
/** Adds the event to an AWS SQS queue, so it can be consumed from the main Trigger.dev API */
|
||||
export async function queueEvent(request: Request, env: Env): Promise<Response> {
|
||||
//check there's a private API key
|
||||
const apiKeyResult = getApiKeyFromRequest(request);
|
||||
if (!apiKeyResult || apiKeyResult.type !== "PRIVATE") {
|
||||
return json(
|
||||
{ error: "Invalid or Missing API key" },
|
||||
{
|
||||
status: 401,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//parse the request body
|
||||
try {
|
||||
const anyBody = await request.json();
|
||||
const body = SendEventBodySchema.safeParse(anyBody);
|
||||
if (!body.success) {
|
||||
return json(
|
||||
{ error: generateErrorMessage(body.error.issues) },
|
||||
{
|
||||
status: 422,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// The AWS SDK tries to use crypto from off of the window,
|
||||
// so we need to trick it into finding it where it expects it
|
||||
globalThis.global = globalThis;
|
||||
|
||||
const client = new SQSClient({
|
||||
region: env.AWS_SQS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: env.AWS_SQS_ACCESS_KEY_ID,
|
||||
secretAccessKey: env.AWS_SQS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
const timestamp = body.data.event.timestamp ?? new Date();
|
||||
|
||||
//add the event to the queue
|
||||
const send = new SendMessageCommand({
|
||||
// use wrangler secrets to provide this global variable
|
||||
QueueUrl: env.AWS_SQS_QUEUE_URL,
|
||||
MessageBody: JSON.stringify({
|
||||
event: { ...body.data.event, timestamp },
|
||||
options: body.data.options,
|
||||
apiKey: apiKeyResult.apiKey,
|
||||
}),
|
||||
});
|
||||
|
||||
const queuedEvent = await client.send(send);
|
||||
console.log("Queued event", queuedEvent);
|
||||
|
||||
//respond with the event
|
||||
const event: ApiEventLog = {
|
||||
id: body.data.event.id,
|
||||
name: body.data.event.name,
|
||||
payload: body.data.event.payload,
|
||||
context: body.data.event.context,
|
||||
timestamp,
|
||||
deliverAt: calculateDeliverAt(body.data.options),
|
||||
};
|
||||
|
||||
return json(event, {
|
||||
status: 200,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("queueEvent error", e);
|
||||
return json(
|
||||
{
|
||||
error: `Failed to send event: ${e instanceof Error ? e.message : JSON.stringify(e)}`,
|
||||
},
|
||||
{
|
||||
status: 422,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { SQSClient, SendMessageBatchCommand } from "@aws-sdk/client-sqs";
|
||||
import { ApiEventLog, SendBulkEventsBodySchema } from "@trigger.dev/core";
|
||||
import { generateErrorMessage } from "zod-error";
|
||||
import { Env } from "..";
|
||||
import { getApiKeyFromRequest } from "../apikey";
|
||||
import { json } from "../json";
|
||||
import { calculateDeliverAt } from "./utils";
|
||||
|
||||
/** Adds the event to an AWS SQS queue, so it can be consumed from the main Trigger.dev API */
|
||||
export async function queueEvents(request: Request, env: Env): Promise<Response> {
|
||||
//check there's a private API key
|
||||
const apiKeyResult = getApiKeyFromRequest(request);
|
||||
if (!apiKeyResult || apiKeyResult.type !== "PRIVATE") {
|
||||
return json(
|
||||
{ error: "Invalid or Missing API key" },
|
||||
{
|
||||
status: 401,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//parse the request body
|
||||
try {
|
||||
const anyBody = await request.json();
|
||||
const body = SendBulkEventsBodySchema.safeParse(anyBody);
|
||||
if (!body.success) {
|
||||
return json(
|
||||
{ error: generateErrorMessage(body.error.issues) },
|
||||
{
|
||||
status: 422,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// The AWS SDK tries to use crypto from off of the window,
|
||||
// so we need to trick it into finding it where it expects it
|
||||
globalThis.global = globalThis;
|
||||
|
||||
const client = new SQSClient({
|
||||
region: env.AWS_SQS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: env.AWS_SQS_ACCESS_KEY_ID,
|
||||
secretAccessKey: env.AWS_SQS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedEvents: ApiEventLog[] = body.data.events.map((event) => {
|
||||
const timestamp = event.timestamp ?? new Date();
|
||||
return {
|
||||
...event,
|
||||
payload: event.payload,
|
||||
timestamp,
|
||||
};
|
||||
});
|
||||
|
||||
//divide updatedEvents into multiple batches of 10 (max size SQS accepts)
|
||||
const batches: ApiEventLog[][] = [];
|
||||
let currentBatch: ApiEventLog[] = [];
|
||||
for (let i = 0; i < updatedEvents.length; i++) {
|
||||
currentBatch.push(updatedEvents[i]);
|
||||
if (currentBatch.length === 10) {
|
||||
batches.push(currentBatch);
|
||||
currentBatch = [];
|
||||
}
|
||||
}
|
||||
if (currentBatch.length > 0) {
|
||||
batches.push(currentBatch);
|
||||
}
|
||||
|
||||
//loop through the batches and send them
|
||||
for (let i = 0; i < batches.length; i++) {
|
||||
const batch = batches[i];
|
||||
//add the event to the queue
|
||||
const send = new SendMessageBatchCommand({
|
||||
// use wrangler secrets to provide this global variable
|
||||
QueueUrl: env.AWS_SQS_QUEUE_URL,
|
||||
Entries: batch.map((event, index) => ({
|
||||
Id: `event-${index}`,
|
||||
MessageBody: JSON.stringify({
|
||||
event,
|
||||
options: body.data.options,
|
||||
apiKey: apiKeyResult.apiKey,
|
||||
}),
|
||||
})),
|
||||
});
|
||||
|
||||
const queuedEvent = await client.send(send);
|
||||
console.log("Queued events", queuedEvent);
|
||||
}
|
||||
|
||||
//respond with the events
|
||||
const events: ApiEventLog[] = updatedEvents.map((event) => ({
|
||||
...event,
|
||||
payload: event.payload,
|
||||
deliverAt: calculateDeliverAt(body.data.options),
|
||||
}));
|
||||
|
||||
return json(events, {
|
||||
status: 200,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("queueEvents error", e);
|
||||
return json(
|
||||
{
|
||||
error: `Failed to send events: ${e instanceof Error ? e.message : JSON.stringify(e)}`,
|
||||
},
|
||||
{
|
||||
status: 422,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { SendEventOptions } from "@trigger.dev/core";
|
||||
|
||||
export function calculateDeliverAt(options?: SendEventOptions) {
|
||||
// If deliverAt is a string and a valid date, convert it to a Date object
|
||||
if (options?.deliverAt) {
|
||||
return options?.deliverAt;
|
||||
}
|
||||
|
||||
// deliverAfter is the number of seconds to wait before delivering the event
|
||||
if (options?.deliverAfter) {
|
||||
return new Date(Date.now() + options.deliverAfter * 1000);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { queueEvent } from "./events/queueEvent";
|
||||
import { queueEvents } from "./events/queueEvents";
|
||||
|
||||
export interface Env {
|
||||
/** The hostname needs to be changed to allow requests to pass to the Trigger.dev platform */
|
||||
REWRITE_HOSTNAME: string;
|
||||
REWRITE_PORT?: string;
|
||||
AWS_SQS_ACCESS_KEY_ID: string;
|
||||
AWS_SQS_SECRET_ACCESS_KEY: string;
|
||||
AWS_SQS_QUEUE_URL: string;
|
||||
AWS_SQS_REGION: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
if (!env.REWRITE_HOSTNAME) throw new Error("Missing REWRITE_HOSTNAME");
|
||||
console.log("url", request.url);
|
||||
|
||||
if (!queueingIsEnabled(env)) {
|
||||
console.log("Missing AWS credentials. Passing through to the origin.");
|
||||
return redirectToOrigin(request, env);
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
switch (url.pathname) {
|
||||
case "/api/v1/events": {
|
||||
if (request.method === "POST") {
|
||||
return queueEvent(request, env);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "/api/v1/events/bulk": {
|
||||
if (request.method === "POST") {
|
||||
return queueEvents(request, env);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//the same request but with the hostname (and port) changed
|
||||
return redirectToOrigin(request, env);
|
||||
},
|
||||
};
|
||||
|
||||
function redirectToOrigin(request: Request, env: Env) {
|
||||
const newUrl = new URL(request.url);
|
||||
newUrl.hostname = env.REWRITE_HOSTNAME;
|
||||
newUrl.port = env.REWRITE_PORT || newUrl.port;
|
||||
|
||||
const requestInit: RequestInit = {
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
};
|
||||
|
||||
console.log("rewritten url", newUrl.toString());
|
||||
return fetch(newUrl.toString(), requestInit);
|
||||
}
|
||||
|
||||
function queueingIsEnabled(env: Env) {
|
||||
return (
|
||||
env.AWS_SQS_ACCESS_KEY_ID &&
|
||||
env.AWS_SQS_SECRET_ACCESS_KEY &&
|
||||
env.AWS_SQS_QUEUE_URL &&
|
||||
env.AWS_SQS_REGION
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export function json(body: any, init?: ResponseInit) {
|
||||
const headers = {
|
||||
"content-type": "application/json",
|
||||
...(init?.headers ?? {}),
|
||||
};
|
||||
|
||||
const responseInit: ResponseInit = {
|
||||
...(init ?? {}),
|
||||
headers,
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(body), responseInit);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
"lib": [
|
||||
"es2021"
|
||||
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
|
||||
"jsx": "react" /* Specify what JSX code is generated. */,
|
||||
|
||||
"module": "es2022" /* Specify what module code is generated. */,
|
||||
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||
|
||||
"types": [
|
||||
"@cloudflare/workers-types"
|
||||
] /* Specify type package names to be included without being referenced in a source file. */,
|
||||
"resolveJsonModule": true /* Enable importing .json files */,
|
||||
|
||||
"allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */,
|
||||
"checkJs": false /* Enable error reporting in type-checked JavaScript files. */,
|
||||
|
||||
"noEmit": true /* Disable emitting files from a compilation. */,
|
||||
|
||||
"isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
|
||||
"allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@trigger.dev/core": ["../../packages/core/src/index"],
|
||||
"@trigger.dev/core/*": ["../../packages/core/src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
name = "proxy"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2023-10-30"
|
||||
compatibility_flags = [ "nodejs_compat" ]
|
||||
|
||||
[env.staging]
|
||||
[env.prod]
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { ApiAuthenticationMethodApiKey, Integration } from "~/services/externalApis/types";
|
||||
import { docsIntegrationPath } from "~/utils/pathBuilder";
|
||||
import { Integration } from "~/services/externalApis/types";
|
||||
import { apiReferencePath, docsIntegrationPath } from "~/utils/pathBuilder";
|
||||
import { LinkButton } from "../primitives/Buttons";
|
||||
import { Header1, Header2 } from "../primitives/Headers";
|
||||
import { NamedIconInBox } from "../primitives/NamedIcon";
|
||||
@@ -48,6 +48,15 @@ export function ConnectToIntegrationSheet({
|
||||
<Paragraph variant="small">{integration.description}</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
<LinkButton
|
||||
to={apiReferencePath(integration.identifier)}
|
||||
variant="secondary/small"
|
||||
TrailingIcon="arrow-up-right"
|
||||
trailingIconClassName="h-4 w-4 text-slate-400"
|
||||
target="_blank"
|
||||
>
|
||||
View examples
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
to={docsIntegrationPath(integration.identifier)}
|
||||
variant="secondary/small"
|
||||
@@ -83,13 +92,6 @@ export function ConnectToIntegrationSheet({
|
||||
variant="description"
|
||||
/>
|
||||
)}
|
||||
<RadioGroupItem
|
||||
id="custom"
|
||||
value="custom"
|
||||
label="Fetch/Existing SDK"
|
||||
description={`Alternatively, use ${integration.name} without our integration.`}
|
||||
variant="description"
|
||||
/>
|
||||
</RadioGroup>
|
||||
|
||||
{integrationMethod && (
|
||||
@@ -132,7 +134,5 @@ function SelectedIntegrationMethod({
|
||||
callbackUrl={callbackUrl}
|
||||
/>
|
||||
);
|
||||
case "custom":
|
||||
return <CustomHelp name={integration.name} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +1,124 @@
|
||||
import { CodeBlock } from "../code/CodeBlock";
|
||||
import { useState } from "react";
|
||||
import { CodeExample } from "~/routes/resources.codeexample";
|
||||
import { Api } from "~/services/externalApis/apis.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Feedback } from "../Feedback";
|
||||
import { Header1, Header2 } from "../primitives/Headers";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
import { TextLink } from "../primitives/TextLink";
|
||||
|
||||
const fallbackExamples = [
|
||||
{
|
||||
title: "Post to Slack when meetings are booked or cancelled.",
|
||||
slug: "cal-slack-meeting-alert",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/cal-http-endpoint.ts",
|
||||
},
|
||||
{
|
||||
title: "Translate some text with DeepL.",
|
||||
slug: "translate-text-with-deepl",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/deepl.ts",
|
||||
},
|
||||
{
|
||||
title: "Create a Discord bot and send a message to a channel.",
|
||||
slug: "discord-bot-send-message",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/discord.ts",
|
||||
},
|
||||
{
|
||||
title: "Retrieve a Notion page by ID.",
|
||||
slug: "retrieve-notion-page",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/notion.ts",
|
||||
},
|
||||
];
|
||||
|
||||
export function CustomHelp({ api }: { api: Api }) {
|
||||
const [selectedExample, setSelectedExample] = useState(0);
|
||||
|
||||
const changeCodeExample = (index: number) => {
|
||||
setSelectedExample(index);
|
||||
};
|
||||
|
||||
export function CustomHelp({ name }: { name: string }) {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<Header1 className="mb-2">You can use any API with requests or an SDK</Header1>
|
||||
<Header2 className="mb-2">How to use an SDK</Header2>
|
||||
<Header1 className="mb-2">Using an API with an SDK or requests</Header1>
|
||||
<Paragraph spacing>
|
||||
You can call SDK methods from inside the run function, but you should wrap them in a Task to
|
||||
make sure they're resumable.
|
||||
You can use Trigger.dev with any existing Node SDK or even just using fetch. You can
|
||||
subscribe to any API with{" "}
|
||||
<TextLink href="https://trigger.dev/docs/documentation/concepts/http-endpoints">
|
||||
HTTP endpoints
|
||||
</TextLink>{" "}
|
||||
and perform actions by wrapping tasks using{" "}
|
||||
<TextLink
|
||||
href="https://trigger.dev/docs/documentation/guides/writing-jobs-step-by-step#create-your-own-tasks"
|
||||
className="font-mono"
|
||||
>
|
||||
io.runTask
|
||||
</TextLink>
|
||||
. This makes your background job resumable and appear in our dashboard.
|
||||
</Paragraph>
|
||||
<Paragraph spacing>Here's an example with the official GitHub SDK</Paragraph>
|
||||
<CodeBlock
|
||||
code={`
|
||||
client.defineJob({
|
||||
id: "scheduled-job-1",
|
||||
name: "Scheduled Job 1",
|
||||
version: "0.1.1",
|
||||
trigger: cronTrigger({
|
||||
cron: "*/5 * * * *", // every 5 minutes
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
//wrap an SDK call in io.runTask so it's resumable and displays in logs
|
||||
const repo = await io.runTask(
|
||||
"Get repo",
|
||||
async () => {
|
||||
//this is the regular GitHub SDK
|
||||
const response = await octokit.rest.repos.get({
|
||||
owner: "triggerdotdev",
|
||||
repo: "trigger.dev",
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
//you can add metadata to the task to improve the display in the logs
|
||||
{ name: "Get repo", icon: "github" }
|
||||
);
|
||||
},
|
||||
});
|
||||
`}
|
||||
highlightedRanges={[[9, 22]]}
|
||||
className="mb-4"
|
||||
/>
|
||||
<Header2 className="mb-2">How to use fetch</Header2>
|
||||
<Paragraph spacing>
|
||||
You can use the fetch API to make requests to any API. Or a different request library like
|
||||
axios if you'd prefer. Again wrapping the request in a Task will make sure it's resumable.
|
||||
</Paragraph>
|
||||
<CodeBlock
|
||||
code={`
|
||||
client.defineJob({
|
||||
id: "scheduled-job-1",
|
||||
name: "Scheduled Job 1",
|
||||
version: "0.1.1",
|
||||
trigger: cronTrigger({
|
||||
cron: "*/5 * * * *", // every 5 minutes
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
//wrap anything in io.runTask so it's resumable and displays in logs
|
||||
const repo = await io.runTask(
|
||||
"Get org",
|
||||
async () => {
|
||||
//you can use fetch, axios, or any other library to make requests
|
||||
const response = await fetch('https://api.github.com/orgs/nodejs');
|
||||
return response.json();
|
||||
},
|
||||
//you can add metadata to the task to improve the display in the logs
|
||||
{ name: "Get org", icon: "github" }
|
||||
);
|
||||
},
|
||||
});
|
||||
`}
|
||||
highlightedRanges={[[9, 19]]}
|
||||
className="mb-4"
|
||||
/>
|
||||
|
||||
{api.examples && api.examples.length > 0 ? (
|
||||
<>
|
||||
<Header2 className="mb-2">Example {api.name} code</Header2>
|
||||
<Paragraph spacing className="mb-4">
|
||||
This is how you can use {api.name} with Trigger.dev. This code can be copied and
|
||||
modified to suit your use-case.
|
||||
</Paragraph>
|
||||
{api.examples.length > 1 && (
|
||||
<div className=" flex w-full flex-row gap-4 overflow-x-scroll scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700">
|
||||
{api.examples?.map((example, index) => (
|
||||
<button
|
||||
onClick={() => changeCodeExample(index)}
|
||||
key={example.codeUrl}
|
||||
className={cn(
|
||||
"w-64 min-w-[16rem] p-2 transition-colors duration-300 sm:w-full sm:rounded",
|
||||
"border-px focus:border-px cursor-pointer border border-slate-900 bg-slate-900 text-slate-300 transition duration-300 hover:bg-slate-800 focus:border focus:border-indigo-600"
|
||||
)}
|
||||
>
|
||||
{example.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<CodeExample example={api.examples[selectedExample]} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Header2 className="mb-2">Example code using fetch / an existing SDK</Header2>
|
||||
<Paragraph spacing className="mb-4">
|
||||
You can use one of our examples below as a starting point / reference for your projects.
|
||||
Please{" "}
|
||||
<Feedback
|
||||
button={
|
||||
<span className="cursor-pointer text-indigo-500 transition duration-300 hover:text-indigo-400">
|
||||
reach out to us
|
||||
</span>
|
||||
}
|
||||
defaultValue="help"
|
||||
/>{" "}
|
||||
if you're having any issues.
|
||||
</Paragraph>
|
||||
|
||||
<div className=" flex w-full flex-row gap-4 overflow-x-scroll scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700 sm:h-full">
|
||||
{fallbackExamples.map((example, index) => (
|
||||
<button
|
||||
onClick={() => changeCodeExample(index)}
|
||||
key={example.codeUrl}
|
||||
className={cn(
|
||||
"w-64 min-w-[16rem] p-2 transition-colors duration-300 sm:w-full sm:rounded",
|
||||
"border-px focus:border-px cursor-pointer border border-slate-900 bg-slate-900 text-slate-300 transition duration-300 hover:bg-slate-800 focus:border focus:border-indigo-600"
|
||||
)}
|
||||
>
|
||||
{example.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<CodeExample example={fallbackExamples[selectedExample]} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import React from "react";
|
||||
import { Api } from "~/services/externalApis/apis";
|
||||
import { Button } from "../primitives/Buttons";
|
||||
import { Callout } from "../primitives/Callout";
|
||||
import { Api } from "~/services/externalApis/apis.server";
|
||||
import { Header1 } from "../primitives/Headers";
|
||||
import { NamedIconInBox } from "../primitives/NamedIcon";
|
||||
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../primitives/Sheet";
|
||||
import { CustomHelp } from "./CustomHelp";
|
||||
import { CheckIcon } from "@heroicons/react/24/solid";
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
|
||||
export function NoIntegrationSheet({
|
||||
api,
|
||||
@@ -31,27 +27,9 @@ export function NoIntegrationSheet({
|
||||
<NamedIconInBox name={api.identifier} className="h-9 w-9" />
|
||||
<Header1>{api.name}</Header1>
|
||||
</div>
|
||||
{requested ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<CheckIcon className="h-4 w-4 text-green-500" />
|
||||
<Paragraph variant="small">
|
||||
We'll let you know when the Integration is available.
|
||||
</Paragraph>
|
||||
</div>
|
||||
) : (
|
||||
<fetcher.Form method="post" action={`/resources/apivote/${api.identifier}`}>
|
||||
<Button
|
||||
variant="primary/small"
|
||||
disabled={isLoading}
|
||||
LeadingIcon={isLoading ? "spinner-white" : undefined}
|
||||
>
|
||||
{isLoading ? "Saving…" : `I want an Integration for ${api.name}`}
|
||||
</Button>
|
||||
</fetcher.Form>
|
||||
)}
|
||||
</SheetHeader>
|
||||
<SheetBody>
|
||||
<CustomHelp name={api.name} />
|
||||
<CustomHelp api={api} />
|
||||
</SheetBody>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
@@ -33,7 +33,19 @@ import { Spinner } from "../primitives/Spinner";
|
||||
import type { DetailedTask } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route";
|
||||
|
||||
export function TaskDetail({ task }: { task: DetailedTask }) {
|
||||
const { name, description, icon, status, params, properties, output, style, attempts } = task;
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
status,
|
||||
params,
|
||||
properties,
|
||||
output,
|
||||
outputIsUndefined,
|
||||
style,
|
||||
attempts,
|
||||
noop,
|
||||
} = task;
|
||||
|
||||
const startedAt = task.startedAt ? new Date(task.startedAt) : undefined;
|
||||
const completedAt = task.completedAt ? new Date(task.completedAt) : undefined;
|
||||
@@ -140,16 +152,18 @@ export function TaskDetail({ task }: { task: DetailedTask }) {
|
||||
<Paragraph variant="small">No input</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<Header3>Output</Header3>
|
||||
{output ? (
|
||||
<ClientOnly fallback={<Spinner />}>
|
||||
{() => <CodeBlock code={output} maxLines={35} />}
|
||||
</ClientOnly>
|
||||
) : (
|
||||
<Paragraph variant="small">No output</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
{!noop && (
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<Header3>Output</Header3>
|
||||
{output && !outputIsUndefined ? (
|
||||
<ClientOnly fallback={<Spinner />}>
|
||||
{() => <CodeBlock code={output} maxLines={35} />}
|
||||
</ClientOnly>
|
||||
) : (
|
||||
<Paragraph variant="small">No output</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</RunPanelBody>
|
||||
</RunPanel>
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function $transaction<R>(
|
||||
return await (prisma as PrismaClient).$transaction(fn, options);
|
||||
} catch (error) {
|
||||
if (isPrismaKnownError(error)) {
|
||||
logger.debug("prisma.$transaction error", {
|
||||
logger.error("prisma.$transaction error", {
|
||||
code: error.code,
|
||||
meta: error.meta,
|
||||
stack: error.stack,
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
OperatingSystemPlatform,
|
||||
} from "./components/primitives/OperatingSystemProvider";
|
||||
import { env } from "./env.server";
|
||||
import { getSharedSqsEventConsumer } from "./services/events/sqsEventConsumer";
|
||||
import { singleton } from "./utils/singleton";
|
||||
|
||||
const ABORT_DELAY = 30000;
|
||||
|
||||
@@ -190,3 +192,5 @@ function logError(error: unknown, request?: Request) {
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
const sqsEventConsumer = singleton("sqsEventConsumer", getSharedSqsEventConsumer);
|
||||
|
||||
@@ -49,6 +49,15 @@ const EnvironmentSchema = z.object({
|
||||
TASK_OPERATION_WORKER_CONCURRENCY: z.coerce.number().int().default(10),
|
||||
TASK_OPERATION_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000),
|
||||
GRACEFUL_SHUTDOWN_TIMEOUT: z.coerce.number().int().default(60000),
|
||||
/** Optional. Only used if you use the apps/proxy */
|
||||
AWS_SQS_REGION: z.string().optional(),
|
||||
/** Optional. Only used if you use the apps/proxy */
|
||||
AWS_SQS_ACCESS_KEY_ID: z.string().optional(),
|
||||
/** Optional. Only used if you use the apps/proxy */
|
||||
AWS_SQS_SECRET_ACCESS_KEY: z.string().optional(),
|
||||
/** Optional. Only used if you use the apps/proxy */
|
||||
AWS_SQS_QUEUE_URL: z.string().optional(),
|
||||
AWS_SQS_BATCH_SIZE: z.coerce.number().int().optional().default(10),
|
||||
DISABLE_SSE: z.string().optional(),
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
|
||||
type EventSourceOptions = {
|
||||
init?: EventSourceInit;
|
||||
event?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -13,11 +14,15 @@ type EventSourceOptions = {
|
||||
*/
|
||||
export function useEventSource(
|
||||
url: string | URL,
|
||||
{ event = "message", init }: EventSourceOptions = {}
|
||||
{ event = "message", init, disabled }: EventSourceOptions = {}
|
||||
) {
|
||||
const [data, setData] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventSource = new EventSource(url, init);
|
||||
eventSource.addEventListener(event ?? "message", handler);
|
||||
|
||||
@@ -32,7 +37,7 @@ export function useEventSource(
|
||||
eventSource.removeEventListener(event ?? "message", handler);
|
||||
eventSource.close();
|
||||
};
|
||||
}, [url, event, init]);
|
||||
}, [url, event, init, disabled]);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { JobRun, Task, TaskAttempt } from "@trigger.dev/database";
|
||||
import { CachedTask, ServerTask } from "@trigger.dev/core";
|
||||
|
||||
export type TaskWithAttempts = Task & { attempts: TaskAttempt[]; run: JobRun };
|
||||
export type TaskWithAttempts = Task & {
|
||||
attempts: TaskAttempt[];
|
||||
run: { forceYieldImmediately: boolean };
|
||||
};
|
||||
|
||||
export function taskWithAttemptsToServerTask(task: TaskWithAttempts): ServerTask {
|
||||
return {
|
||||
@@ -15,7 +18,7 @@ export function taskWithAttemptsToServerTask(task: TaskWithAttempts): ServerTask
|
||||
status: task.status,
|
||||
description: task.description,
|
||||
params: task.params as any,
|
||||
output: task.output as any,
|
||||
output: task.outputIsUndefined ? undefined : (task.output as any),
|
||||
context: task.context as any,
|
||||
properties: task.properties as any,
|
||||
style: task.style as any,
|
||||
@@ -32,7 +35,7 @@ export function taskWithAttemptsToServerTask(task: TaskWithAttempts): ServerTask
|
||||
|
||||
export type TaskForCaching = Pick<
|
||||
Task,
|
||||
"id" | "status" | "idempotencyKey" | "noop" | "output" | "parentId"
|
||||
"id" | "status" | "idempotencyKey" | "noop" | "output" | "parentId" | "outputIsUndefined"
|
||||
>;
|
||||
|
||||
export function prepareTasksForCaching(
|
||||
@@ -105,7 +108,7 @@ function prepareTaskForCaching(task: TaskForCaching): CachedTask {
|
||||
status: task.status,
|
||||
idempotencyKey: task.idempotencyKey,
|
||||
noop: task.noop,
|
||||
output: task.output as any,
|
||||
output: task.outputIsUndefined ? undefined : (task.output as any),
|
||||
parentId: task.parentId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { run as graphileRun, parseCronItems } from "graphile-worker";
|
||||
import omit from "lodash.omit";
|
||||
import { z } from "zod";
|
||||
import { PrismaClient, PrismaClientOrTransaction } from "~/db.server";
|
||||
import { workerLogger as logger } from "~/services/logger.server";
|
||||
import { workerLogger as logger, trace } from "~/services/logger.server";
|
||||
|
||||
export interface MessageCatalogSchema {
|
||||
[key: string]: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>;
|
||||
@@ -270,7 +270,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
|
||||
spec,
|
||||
});
|
||||
|
||||
const job = await this.#addJob(
|
||||
const { job, durationInMs } = await this.#addJob(
|
||||
identifier as string,
|
||||
payload,
|
||||
spec,
|
||||
@@ -282,6 +282,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
|
||||
payload,
|
||||
spec,
|
||||
job,
|
||||
durationInMs,
|
||||
});
|
||||
|
||||
return job;
|
||||
@@ -304,6 +305,8 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
|
||||
spec: TaskSpec,
|
||||
tx: PrismaClientOrTransaction
|
||||
) {
|
||||
const now = performance.now();
|
||||
|
||||
const results = await tx.$queryRawUnsafe(
|
||||
`SELECT * FROM ${this.graphileWorkerSchema}.add_job(
|
||||
identifier => $1::text,
|
||||
@@ -327,6 +330,8 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
|
||||
spec.jobKeyMode || null
|
||||
);
|
||||
|
||||
const durationInMs = performance.now() - now;
|
||||
|
||||
const rows = AddJobResultsSchema.safeParse(results);
|
||||
|
||||
if (!rows.success) {
|
||||
@@ -337,7 +342,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
|
||||
|
||||
const job = rows.data[0];
|
||||
|
||||
return job as GraphileJob;
|
||||
return { job: job as GraphileJob, durationInMs: Math.floor(durationInMs) };
|
||||
}
|
||||
|
||||
async #removeJob(jobKey: string, tx: PrismaClientOrTransaction) {
|
||||
@@ -471,7 +476,15 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
|
||||
throw new Error(`No task for message type: ${String(typeName)}`);
|
||||
}
|
||||
|
||||
await task.handler(payload, job);
|
||||
await trace(
|
||||
{
|
||||
worker_job: job,
|
||||
worker_name: this.#name,
|
||||
},
|
||||
async () => {
|
||||
await task.handler(payload, job);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async #handleRecurringTask(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { PrismaClient, prisma } from "~/db.server";
|
||||
import { env } from "~/env.server";
|
||||
import { Organization } from "~/models/organization.server";
|
||||
import { Project } from "~/models/project.server";
|
||||
import { Api, apisList } from "~/services/externalApis/apis";
|
||||
import { Api, apisList } from "~/services/externalApis/apis.server";
|
||||
import { integrationCatalog } from "~/services/externalApis/integrationCatalog.server";
|
||||
import { Integration, OAuthClientSchema } from "~/services/externalApis/types";
|
||||
import { getSecretStore } from "~/services/secrets/secretStore.server";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { RedactSchema } from "@trigger.dev/core";
|
||||
import { StyleSchema } from "@trigger.dev/core";
|
||||
import { RedactSchema, StyleSchema } from "@trigger.dev/core";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { mergeProperties } from "~/utils/mergeProperties.server";
|
||||
import { Redactor } from "~/utils/redactor";
|
||||
@@ -58,6 +57,7 @@ export class TaskDetailsPresenter {
|
||||
outputProperties: true,
|
||||
params: true,
|
||||
output: true,
|
||||
outputIsUndefined: true,
|
||||
error: true,
|
||||
startedAt: true,
|
||||
completedAt: true,
|
||||
@@ -89,9 +89,11 @@ export class TaskDetailsPresenter {
|
||||
return {
|
||||
...task,
|
||||
redact: undefined,
|
||||
output: task.output
|
||||
? JSON.stringify(this.#stringifyOutputWithRedactions(task.output, task.redact), null, 2)
|
||||
: undefined,
|
||||
output: JSON.stringify(
|
||||
this.#stringifyOutputWithRedactions(task.output, task.redact),
|
||||
null,
|
||||
2
|
||||
),
|
||||
connection: task.runConnection,
|
||||
params: task.params as Record<string, any>,
|
||||
properties: mergeProperties(task.properties, task.outputProperties),
|
||||
@@ -101,7 +103,7 @@ export class TaskDetailsPresenter {
|
||||
|
||||
#stringifyOutputWithRedactions(output: any, redact: unknown): any {
|
||||
if (!output) {
|
||||
return;
|
||||
return output;
|
||||
}
|
||||
|
||||
const parsedRedact = RedactSchema.safeParse(redact);
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function Integrations() {
|
||||
<PageContainer>
|
||||
<PageHeader>
|
||||
<PageTitleRow>
|
||||
<PageTitle title="Integrations" />
|
||||
<PageTitle title="Integrations & APIs" />
|
||||
<PageButtons>
|
||||
<LinkButton
|
||||
to={docsPath("/integrations/introduction")}
|
||||
|
||||
+16
-15
@@ -1,6 +1,5 @@
|
||||
import { Await, useLoaderData } from "@remix-run/react";
|
||||
import { LoaderFunctionArgs, SerializeFrom, defer } from "@remix-run/server-runtime";
|
||||
import { Suspense } from "react";
|
||||
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { Spinner } from "~/components/primitives/Spinner";
|
||||
import { TaskDetail } from "~/components/run/TaskDetail";
|
||||
import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
|
||||
@@ -12,26 +11,28 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
|
||||
const { taskParam } = TaskParamsSchema.parse(params);
|
||||
|
||||
const presenter = new TaskDetailsPresenter();
|
||||
const taskPromise = presenter.call({
|
||||
const task = await presenter.call({
|
||||
userId,
|
||||
id: taskParam,
|
||||
});
|
||||
|
||||
return defer({
|
||||
taskPromise,
|
||||
return typedjson({
|
||||
task,
|
||||
});
|
||||
};
|
||||
|
||||
export type DetailedTask = NonNullable<Awaited<SerializeFrom<typeof loader>["taskPromise"]>>;
|
||||
export type DetailedTask = NonNullable<UseDataFunctionReturn<typeof loader>["task"]>;
|
||||
|
||||
export default function Page() {
|
||||
const { taskPromise } = useLoaderData<typeof loader>();
|
||||
const { task } = useTypedLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<Await resolve={taskPromise} errorElement={<p>Error loading task!</p>}>
|
||||
{(resolvedTask) => resolvedTask && <TaskDetail task={resolvedTask as any} />}
|
||||
</Await>
|
||||
</Suspense>
|
||||
);
|
||||
if (!task) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <TaskDetail task={task} />;
|
||||
}
|
||||
|
||||
+1
@@ -67,6 +67,7 @@ export default function Page() {
|
||||
const revalidator = useRevalidator();
|
||||
const events = useEventSource(runStreamingPath(organization, project, job, run), {
|
||||
event: "message",
|
||||
disabled: !!run.completedAt,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (events !== null) {
|
||||
|
||||
+15
-14
@@ -1,6 +1,5 @@
|
||||
import { Await, useLoaderData } from "@remix-run/react";
|
||||
import { LoaderFunctionArgs, defer } from "@remix-run/server-runtime";
|
||||
import { Suspense } from "react";
|
||||
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { Spinner } from "~/components/primitives/Spinner";
|
||||
import { TaskDetail } from "~/components/run/TaskDetail";
|
||||
import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
|
||||
@@ -12,24 +11,26 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
|
||||
const { taskParam } = TriggerSourceRunTaskParamsSchema.parse(params);
|
||||
|
||||
const presenter = new TaskDetailsPresenter();
|
||||
const taskPromise = presenter.call({
|
||||
const task = await presenter.call({
|
||||
userId,
|
||||
id: taskParam,
|
||||
});
|
||||
|
||||
return defer({
|
||||
taskPromise,
|
||||
return typedjson({
|
||||
task,
|
||||
});
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const { taskPromise } = useLoaderData<typeof loader>();
|
||||
const { task } = useTypedLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<Await resolve={taskPromise} errorElement={<p>Error loading task!</p>}>
|
||||
{(resolvedTask) => resolvedTask && <TaskDetail task={resolvedTask as any} />}
|
||||
</Await>
|
||||
</Suspense>
|
||||
);
|
||||
if (!task) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <TaskDetail task={task} />;
|
||||
}
|
||||
|
||||
@@ -191,7 +191,8 @@ export class CompleteRunTaskService {
|
||||
},
|
||||
data: {
|
||||
status: "COMPLETED",
|
||||
output: taskBody.output ?? undefined,
|
||||
output: taskBody.output as any,
|
||||
outputIsUndefined: typeof taskBody.output === "undefined",
|
||||
completedAt: new Date(),
|
||||
outputProperties: taskBody.properties,
|
||||
},
|
||||
|
||||
@@ -2,21 +2,16 @@ import type { ActionFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import {
|
||||
API_VERSIONS,
|
||||
RunTaskBodyOutput,
|
||||
RunTaskBodyOutputSchema,
|
||||
RunTaskResponseWithCachedTasksBody,
|
||||
ServerTask,
|
||||
} from "@trigger.dev/core";
|
||||
import { TaskStatus } from "@trigger.dev/database";
|
||||
import { z } from "zod";
|
||||
import { $transaction, PrismaClient, prisma } from "~/db.server";
|
||||
import { env } from "~/env.server";
|
||||
import { prepareTasksForCaching, taskWithAttemptsToServerTask } from "~/models/task.server";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { prepareTasksForCaching } from "~/models/task.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { generateSecret } from "~/services/sources/utils.server";
|
||||
import { ulid } from "~/services/ulid.server";
|
||||
import { taskOperationWorker, workerQueue } from "~/services/worker.server";
|
||||
import { RunTaskService } from "~/services/tasks/runTask.server";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
runId: z.string(),
|
||||
@@ -161,184 +156,3 @@ class ChangeRequestLazyLoadedCachedTasks {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class RunTaskService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
runId: string,
|
||||
idempotencyKey: string,
|
||||
taskBody: RunTaskBodyOutput
|
||||
): Promise<ServerTask | undefined> {
|
||||
const task = await $transaction(this.#prismaClient, async (tx) => {
|
||||
const existingTask = await tx.task.findUnique({
|
||||
where: {
|
||||
runId_idempotencyKey: {
|
||||
runId,
|
||||
idempotencyKey,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
attempts: true,
|
||||
run: true,
|
||||
},
|
||||
});
|
||||
|
||||
const delayUntilInFuture = taskBody.delayUntil && taskBody.delayUntil.getTime() > Date.now();
|
||||
const callbackEnabled = taskBody.callback?.enabled;
|
||||
|
||||
if (existingTask) {
|
||||
if (existingTask.status === "CANCELED") {
|
||||
const existingTaskStatus =
|
||||
delayUntilInFuture || callbackEnabled || taskBody.trigger
|
||||
? "WAITING"
|
||||
: taskBody.noop
|
||||
? "COMPLETED"
|
||||
: "RUNNING";
|
||||
|
||||
const resumedExistingTask = await tx.task.update({
|
||||
where: {
|
||||
id: existingTask.id,
|
||||
},
|
||||
data: {
|
||||
status: existingTaskStatus,
|
||||
startedAt: new Date(),
|
||||
completedAt: existingTaskStatus === "COMPLETED" ? new Date() : undefined,
|
||||
},
|
||||
include: {
|
||||
run: true,
|
||||
attempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
return resumedExistingTask;
|
||||
}
|
||||
|
||||
return existingTask;
|
||||
}
|
||||
|
||||
const run = await tx.jobRun.findUnique({
|
||||
where: {
|
||||
id: runId,
|
||||
},
|
||||
select: {
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!run) throw new Error("Run not found");
|
||||
|
||||
// If task.delayUntil is set and is in the future, we'll set the task's status to "WAITING", else set it to RUNNING
|
||||
let status: TaskStatus;
|
||||
|
||||
if (run.status === "CANCELED") {
|
||||
status = "CANCELED";
|
||||
} else {
|
||||
status =
|
||||
delayUntilInFuture || callbackEnabled || taskBody.trigger
|
||||
? "WAITING"
|
||||
: taskBody.noop
|
||||
? "COMPLETED"
|
||||
: "RUNNING";
|
||||
}
|
||||
|
||||
const taskId = ulid();
|
||||
const callbackUrl = callbackEnabled
|
||||
? `${env.APP_ORIGIN}/api/v1/tasks/${taskId}/callback/${generateSecret(12)}`
|
||||
: undefined;
|
||||
|
||||
const task = await tx.task.create({
|
||||
data: {
|
||||
id: taskId,
|
||||
idempotencyKey,
|
||||
displayKey: taskBody.displayKey,
|
||||
runConnection: taskBody.connectionKey
|
||||
? {
|
||||
connect: {
|
||||
runId_key: {
|
||||
runId,
|
||||
key: taskBody.connectionKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
icon: taskBody.icon,
|
||||
run: {
|
||||
connect: {
|
||||
id: runId,
|
||||
},
|
||||
},
|
||||
parent: taskBody.parentId ? { connect: { id: taskBody.parentId } } : undefined,
|
||||
name: taskBody.name ?? "Task",
|
||||
description: taskBody.description,
|
||||
status,
|
||||
startedAt: new Date(),
|
||||
completedAt: status === "COMPLETED" || status === "CANCELED" ? new Date() : undefined,
|
||||
noop: taskBody.noop,
|
||||
delayUntil: taskBody.delayUntil,
|
||||
params: taskBody.params ?? undefined,
|
||||
properties: this.#filterProperties(taskBody.properties) ?? undefined,
|
||||
redact: taskBody.redact ?? undefined,
|
||||
operation: taskBody.operation,
|
||||
callbackUrl,
|
||||
style: taskBody.style ?? { style: "normal" },
|
||||
childExecutionMode: taskBody.parallel ? "PARALLEL" : "SEQUENTIAL",
|
||||
attempts: {
|
||||
create: {
|
||||
number: 1,
|
||||
status: "PENDING",
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
run: true,
|
||||
attempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (task.status === "RUNNING" && typeof taskBody.operation === "string") {
|
||||
// We need to schedule the operation
|
||||
await taskOperationWorker.enqueue(
|
||||
"performTaskOperation",
|
||||
{
|
||||
id: task.id,
|
||||
},
|
||||
{ tx, runAt: task.delayUntil ?? undefined, jobKey: `operation:${task.id}` }
|
||||
);
|
||||
} else if (task.status === "WAITING" && callbackUrl && taskBody.callback) {
|
||||
if (taskBody.callback.timeoutInSeconds > 0) {
|
||||
// We need to schedule the callback timeout
|
||||
await workerQueue.enqueue(
|
||||
"processCallbackTimeout",
|
||||
{
|
||||
id: task.id,
|
||||
},
|
||||
{
|
||||
tx,
|
||||
runAt: new Date(Date.now() + taskBody.callback.timeoutInSeconds * 1000),
|
||||
jobKey: `process-callback:${task.id}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return task;
|
||||
});
|
||||
|
||||
return task ? taskWithAttemptsToServerTask(task) : undefined;
|
||||
}
|
||||
|
||||
#filterProperties(properties: RunTaskBodyOutput["properties"]): RunTaskBodyOutput["properties"] {
|
||||
if (!properties) return;
|
||||
|
||||
return properties.filter((property) => {
|
||||
if (!property) return false;
|
||||
|
||||
return typeof property.label === "string" && typeof property.text === "string";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import { LoaderFunctionArgs, json } from "@remix-run/server-runtime";
|
||||
import { useEffect } from "react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { CodeBlock } from "~/components/code/CodeBlock";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { Spinner } from "~/components/primitives/Spinner";
|
||||
import { ApiExample } from "~/services/externalApis/apis.server";
|
||||
import { requireUser } from "~/services/session.server";
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
await requireUser(request);
|
||||
const url = new URL(request.url);
|
||||
const codeUrl = url.searchParams.get("url");
|
||||
invariant(typeof codeUrl === "string", "codeUrl is required");
|
||||
const decodedCodeUrl = decodeURIComponent(codeUrl);
|
||||
const response = await fetch(decodedCodeUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const code = await response.text();
|
||||
|
||||
const hideCodeRegex = /(\n)?\/\/ hide-code[\s\S]*?\/\/ end-hide-code(\n)*/gm;
|
||||
const cleanedCode = code?.replace(hideCodeRegex, "\n");
|
||||
|
||||
return json({
|
||||
code: cleanedCode,
|
||||
});
|
||||
}
|
||||
|
||||
export function CodeExample({ example }: { example: ApiExample }) {
|
||||
const customerFetcher = useFetcher<typeof loader>();
|
||||
|
||||
useEffect(() => {
|
||||
customerFetcher.load(`/resources/codeexample?url=${encodeURIComponent(example.codeUrl)}`);
|
||||
}, [example.codeUrl]);
|
||||
|
||||
if (customerFetcher.state === "loading")
|
||||
return (
|
||||
<div className="flex h-96 w-full items-center justify-center gap-2 rounded-md border border-slate-800 font-mono">
|
||||
<Spinner />
|
||||
<Paragraph>Loading example code</Paragraph>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
customerFetcher.data && <CodeBlock code={customerFetcher.data.code ?? ""} className="mt-2" />
|
||||
);
|
||||
}
|
||||
@@ -20,7 +20,19 @@ export async function authenticateApiRequest(
|
||||
request: Request,
|
||||
{ allowPublicKey = false }: { allowPublicKey?: boolean } = {}
|
||||
): Promise<ApiAuthenticationResult | undefined> {
|
||||
const result = getApiKeyFromRequest(request);
|
||||
const apiKey = getApiKeyFromRequest(request);
|
||||
if (!apiKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
return authenticateApiKey(apiKey, { allowPublicKey });
|
||||
}
|
||||
|
||||
export async function authenticateApiKey(
|
||||
apiKey: string,
|
||||
{ allowPublicKey = false }: { allowPublicKey?: boolean } = {}
|
||||
): Promise<ApiAuthenticationResult | undefined> {
|
||||
const result = getApiKeyResult(apiKey);
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
@@ -69,6 +81,10 @@ export function getApiKeyFromRequest(request: Request) {
|
||||
}
|
||||
|
||||
const apiKey = authorization.data.replace(/^Bearer /, "");
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
export function getApiKeyResult(apiKey: string) {
|
||||
const type = isPublicApiKey(apiKey) ? ("PUBLIC" as const) : ("PRIVATE" as const);
|
||||
return { apiKey, type };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server";
|
||||
import { taskOperationWorker } from "../worker.server";
|
||||
import { EphemeralDispatchableSchema } from "~/models/eventDispatcher.server";
|
||||
import { fetch } from "@whatwg-node/fetch";
|
||||
import { ExpireDispatcherService } from "./expireDispatcher.server";
|
||||
|
||||
export class InvokeEphemeralDispatcherService {
|
||||
@@ -61,23 +60,14 @@ export class InvokeEphemeralDispatcherService {
|
||||
accountId: eventRecord.externalAccount ? eventRecord.externalAccount.identifier : undefined,
|
||||
};
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
abortController.abort();
|
||||
}, 5000);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to invoke ephemeral dispatcher: ${response.statusText} [${response.status}]`
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ConnectionAuth,
|
||||
EndpointHeadersSchema,
|
||||
ErrorWithStackSchema,
|
||||
ExecuteJobHeadersSchema,
|
||||
HttpSourceResponseSchema,
|
||||
IndexEndpointResponseSchema,
|
||||
NormalizedResponseSchema,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
RegisterTriggerBodyV1,
|
||||
RunJobBody,
|
||||
RunJobResponseSchema,
|
||||
RunNotification,
|
||||
ValidateResponse,
|
||||
ValidateResponseSchema,
|
||||
} from "@trigger.dev/core";
|
||||
@@ -148,6 +150,7 @@ export class EndpointApi {
|
||||
response,
|
||||
parser: RunJobResponseSchema,
|
||||
errorParser: ErrorWithStackSchema,
|
||||
headersParser: ExecuteJobHeadersSchema,
|
||||
durationInMs: Math.floor(performance.now() - startTimeInMs),
|
||||
};
|
||||
}
|
||||
@@ -367,6 +370,20 @@ export class EndpointApi {
|
||||
durationInMs: Math.floor(performance.now() - startTimeInMs),
|
||||
};
|
||||
}
|
||||
|
||||
async deliverRunNotification(notification: RunNotification<any>) {
|
||||
const response = await safeFetch(this.url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-trigger-api-key": this.apiKey,
|
||||
"x-trigger-action": "RUN_NOTIFICATION",
|
||||
},
|
||||
body: JSON.stringify(notification),
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
async function safeFetch(url: string, options: RequestInit) {
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import { Consumer } from "sqs-consumer";
|
||||
import { PrismaClientOrTransaction, prisma } from "~/db.server";
|
||||
import { logger, trace } from "../logger.server";
|
||||
import { Message, SQSClient } from "@aws-sdk/client-sqs";
|
||||
import { authenticateApiKey } from "../apiAuth.server";
|
||||
import { SendEventBodySchema } from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import { fromZodError } from "zod-validation-error";
|
||||
import { IngestSendEvent } from "./ingestSendEvent.server";
|
||||
import { env } from "~/env.server";
|
||||
import { singleton } from "~/utils/singleton";
|
||||
|
||||
type SqsEventConsumerOptions = {
|
||||
queueUrl: string;
|
||||
/** This cannot be higher than the AWS limit of 10. */
|
||||
batchSize: number;
|
||||
region: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
};
|
||||
|
||||
const messageSchema = SendEventBodySchema.extend({
|
||||
apiKey: z.string(),
|
||||
});
|
||||
|
||||
export class SqsEventConsumer {
|
||||
readonly #ingestEventService: IngestSendEvent;
|
||||
readonly #consumer: Consumer;
|
||||
|
||||
constructor(
|
||||
readonly prismaClient: PrismaClientOrTransaction = prisma,
|
||||
options: SqsEventConsumerOptions
|
||||
) {
|
||||
this.#ingestEventService = new IngestSendEvent();
|
||||
|
||||
logger.debug("SqsEventConsumer starting", {
|
||||
queueUrl: options.queueUrl,
|
||||
region: options.region,
|
||||
});
|
||||
|
||||
this.#consumer = Consumer.create({
|
||||
queueUrl: options.queueUrl,
|
||||
batchSize: options.batchSize,
|
||||
sqs: new SQSClient({
|
||||
region: options.region,
|
||||
credentials: {
|
||||
accessKeyId: options.accessKeyId,
|
||||
secretAccessKey: options.secretAccessKey,
|
||||
},
|
||||
}),
|
||||
handleMessage: async (message) => {
|
||||
await trace({ sqsMessage: message }, async () => await this.#processEvent(message));
|
||||
},
|
||||
});
|
||||
|
||||
this.#consumer.on("error", (err, message) => {
|
||||
logger.error("SqsEventConsumer error", { error: err.message, sqsMessage: message });
|
||||
//todo what do we want to do here?
|
||||
});
|
||||
|
||||
this.#consumer.on("processing_error", (err, message) => {
|
||||
logger.error("SqsEventConsumer processing_error", {
|
||||
error: err.message,
|
||||
sqsMessage: message,
|
||||
});
|
||||
//todo what do we want to do here?
|
||||
});
|
||||
|
||||
this.#consumer.on("timeout_error", (err, message) => {
|
||||
logger.error("SqsEventConsumer timeout_error", { error: err.message, sqsMessage: message });
|
||||
//todo what do we want to do here?
|
||||
});
|
||||
|
||||
//Stop the consumer if the process is terminated
|
||||
process.on("SIGTERM", () => {
|
||||
this.stop();
|
||||
});
|
||||
|
||||
this.#consumer.start();
|
||||
}
|
||||
|
||||
public stop() {
|
||||
logger.debug("SqsEventConsumer stopping");
|
||||
this.#consumer.stop({ abort: true });
|
||||
}
|
||||
|
||||
async #processEvent(message: Message) {
|
||||
logger.debug("SqsEventConsumer processing event");
|
||||
|
||||
//parse the body
|
||||
if (!message.Body) {
|
||||
logger.error("SqsEventConsumer message has no body");
|
||||
return;
|
||||
}
|
||||
|
||||
const body = messageSchema.safeParse(JSON.parse(message.Body));
|
||||
if (!body.success) {
|
||||
logger.error("SqsEventConsumer message body is invalid", {
|
||||
error: fromZodError(body.error).message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//authenticate API Key
|
||||
const authenticationResult = await authenticateApiKey(body.data.apiKey);
|
||||
if (!authenticationResult) {
|
||||
logger.warn("SqsEventConsumer message has invalid API key");
|
||||
return;
|
||||
}
|
||||
|
||||
const authenticatedEnv = authenticationResult.environment;
|
||||
|
||||
logger.info("sqs_event", { event: body.data.event, options: body.data.options });
|
||||
|
||||
const event = await this.#ingestEventService.call(
|
||||
authenticatedEnv,
|
||||
body.data.event,
|
||||
body.data.options
|
||||
);
|
||||
|
||||
if (!event) {
|
||||
logger.error("SqsEventConsumer failed to create event");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("SqsEventConsumer processed event", { event });
|
||||
}
|
||||
}
|
||||
|
||||
export function getSharedSqsEventConsumer() {
|
||||
if (
|
||||
env.AWS_SQS_QUEUE_URL &&
|
||||
env.AWS_SQS_REGION &&
|
||||
env.AWS_SQS_ACCESS_KEY_ID &&
|
||||
env.AWS_SQS_SECRET_ACCESS_KEY
|
||||
) {
|
||||
const consumer = new SqsEventConsumer(undefined, {
|
||||
queueUrl: env.AWS_SQS_QUEUE_URL,
|
||||
batchSize: env.AWS_SQS_BATCH_SIZE,
|
||||
region: env.AWS_SQS_REGION,
|
||||
accessKeyId: env.AWS_SQS_ACCESS_KEY_ID,
|
||||
secretAccessKey: env.AWS_SQS_SECRET_ACCESS_KEY,
|
||||
});
|
||||
|
||||
return consumer;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"The SqsEventConsumer is disabled because AWS credentials are missing. This is OK as this is an optional feature."
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { PrismaClientOrTransaction, prisma } from "~/db.server";
|
||||
import { logger } from "../logger.server";
|
||||
|
||||
export type CreateExecutionEventInput = {
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
jobId: string;
|
||||
runId: string;
|
||||
eventTime: Date;
|
||||
eventType: "start" | "finish";
|
||||
drift?: number;
|
||||
};
|
||||
|
||||
export class CreateExecutionEventService {
|
||||
constructor(private prismaClient: PrismaClientOrTransaction = prisma) {}
|
||||
|
||||
public async call(input: CreateExecutionEventInput) {
|
||||
await this.prismaClient.$executeRaw`
|
||||
INSERT INTO "triggerdotdev_events"."run_executions" (
|
||||
"organization_id",
|
||||
"project_id",
|
||||
"environment_id",
|
||||
"job_id",
|
||||
"run_id",
|
||||
"event_time",
|
||||
"event_type",
|
||||
"drift_amount_in_ms"
|
||||
) VALUES (
|
||||
${input.organizationId},
|
||||
${input.projectId},
|
||||
${input.environmentId},
|
||||
${input.jobId},
|
||||
${input.runId},
|
||||
${input.eventTime},
|
||||
${input.eventType === "start" ? 1 : -1},
|
||||
${input.drift}
|
||||
)
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createExecutionEvent(
|
||||
input: CreateExecutionEventInput,
|
||||
options?: { prismaClient?: PrismaClientOrTransaction }
|
||||
) {
|
||||
const service = new CreateExecutionEventService(options?.prismaClient);
|
||||
|
||||
try {
|
||||
return await service.call(input);
|
||||
} catch (error) {
|
||||
logger.error("Error creating execution event", { error });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,804 @@
|
||||
export type Api = {
|
||||
identifier: string;
|
||||
name: string;
|
||||
examples?: ApiExample[];
|
||||
};
|
||||
|
||||
export type ApiExample = {
|
||||
title: string;
|
||||
version: string;
|
||||
codeUrl: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export const apisList = [
|
||||
{
|
||||
identifier: "airtable",
|
||||
name: "Airtable",
|
||||
examples: [
|
||||
{
|
||||
title: "Update Airtable when a new subscription is added to Stripe.",
|
||||
slug: "stripe-sub-update-airtable",
|
||||
version: "1.0.0",
|
||||
|
||||
codeUrl:
|
||||
"https://raw.githubusercontent.com/triggerdotdev/jobs-showcase/main/src/stripeNewSubscriptionUpdateAirtable.ts",
|
||||
},
|
||||
{
|
||||
title: "Add a new record to Airtable when a Typeform response is submitted.",
|
||||
version: "1.0.0",
|
||||
slug: "new-airtable-record-from-typeform",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/typeformNewSubmissionUpdateAirtable.ts",
|
||||
},
|
||||
{
|
||||
title: "Update Airtable database when there is a sale in Stripe.",
|
||||
version: "1.0.0",
|
||||
slug: "update-airtable-when-stripe-account-updated",
|
||||
codeUrl:
|
||||
"https://raw.githubusercontent.com/triggerdotdev/jobs-showcase/main/src/syncStripeWithAirtable.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "algolia",
|
||||
name: "Algolia",
|
||||
},
|
||||
{
|
||||
identifier: "anthropic",
|
||||
name: "Anthropic",
|
||||
},
|
||||
{
|
||||
identifier: "appsmith",
|
||||
name: "Appsmith",
|
||||
},
|
||||
{
|
||||
identifier: "appwrite",
|
||||
name: "Appwrite",
|
||||
},
|
||||
{
|
||||
identifier: "asana",
|
||||
name: "Asana",
|
||||
examples: [
|
||||
{
|
||||
title: "Get user details from Asana",
|
||||
slug: "get-user-details",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/asana.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "atlassian",
|
||||
name: "Atlassian",
|
||||
},
|
||||
{
|
||||
identifier: "aws",
|
||||
name: "AWS",
|
||||
examples: [
|
||||
{
|
||||
title: "Trigger an AWS Lambda function with a defined payload and log the results.",
|
||||
slug: "get-user-details",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/aws.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "brex",
|
||||
name: "Brex",
|
||||
examples: [
|
||||
{
|
||||
title: "Create a new title in a Brex account.",
|
||||
slug: "create-new-brex-title",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/brex.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "caldotcom",
|
||||
name: "Cal.com",
|
||||
examples: [
|
||||
{
|
||||
title: "Send a Slack message when meetings are booked or cancelled.",
|
||||
slug: "cal-slack-meeting-alert",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/cal-http-endpoint.ts",
|
||||
},
|
||||
{
|
||||
title: "Find all Cal.com bookings for a user.",
|
||||
slug: "cal-find-bookings",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/cal.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "clerk",
|
||||
name: "Clerk",
|
||||
},
|
||||
{
|
||||
identifier: "clickup",
|
||||
name: "ClickUp",
|
||||
},
|
||||
{
|
||||
identifier: "coda",
|
||||
name: "Coda",
|
||||
},
|
||||
{
|
||||
identifier: "crowddotdev",
|
||||
name: "Crowd.dev",
|
||||
},
|
||||
{
|
||||
identifier: "deepl",
|
||||
name: "DeepL",
|
||||
examples: [
|
||||
{
|
||||
title: "Translate some text with DeepL.",
|
||||
slug: "translate-text-with-deepl",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/deepl.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "discord",
|
||||
name: "Discord",
|
||||
examples: [
|
||||
{
|
||||
title: "Create a Discord bot and send a message to a channel.",
|
||||
slug: "discord-bot-send-message",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/discord.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "documenso",
|
||||
name: "Documenso",
|
||||
},
|
||||
{
|
||||
identifier: "dropbox",
|
||||
name: "Dropbox",
|
||||
},
|
||||
{
|
||||
identifier: "facebook",
|
||||
name: "Facebook",
|
||||
},
|
||||
{
|
||||
identifier: "fastify",
|
||||
name: "Fastify",
|
||||
},
|
||||
{
|
||||
identifier: "flickr",
|
||||
name: "Flickr",
|
||||
},
|
||||
{
|
||||
identifier: "github",
|
||||
name: "GitHub",
|
||||
examples: [
|
||||
{
|
||||
title: "Send a message to a Slack channel when a repo is starred.",
|
||||
slug: "github-star-to-slack",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/gitHubNewStarToSlack.ts",
|
||||
},
|
||||
{
|
||||
title: "Create a Linear issue when a pull request is opened on a GitHub repo.",
|
||||
slug: "linear-ticket-on-github-pr",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/linearCreateIssueOnPR.ts",
|
||||
},
|
||||
{
|
||||
title:
|
||||
"Send a reminder message to a Slack channel if a GitHub issue is left open for 24 hours.",
|
||||
slug: "github-issue-reminder",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/gitHubIssueReminder.ts",
|
||||
},
|
||||
|
||||
{
|
||||
title: "Add a custom label to a GitHub issue.",
|
||||
slug: "github-custom-label",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/gitHubNewIssueOpened.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "giphy",
|
||||
name: "Giphy",
|
||||
},
|
||||
{
|
||||
identifier: "gmail",
|
||||
name: "Gmail",
|
||||
examples: [
|
||||
{
|
||||
title: "Send an email using Gmail.",
|
||||
slug: "send-email-with-gmail",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/gmail.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "googlecalendar",
|
||||
name: "Google Calendar",
|
||||
examples: [
|
||||
{
|
||||
title: "Create a new Google Calendar event",
|
||||
slug: "create-google-calendar-event",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/google-calendar.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "googledocs",
|
||||
name: "Google Docs",
|
||||
},
|
||||
{
|
||||
identifier: "googledrive",
|
||||
name: "Google Drive",
|
||||
examples: [
|
||||
{
|
||||
title: "Update a filename in Google Drive.",
|
||||
slug: "update-google-drive-filename",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/google-drive.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "googlemaps",
|
||||
name: "Google Maps",
|
||||
examples: [
|
||||
{
|
||||
title: "Make a geocode request with Google Maps.",
|
||||
slug: "google-maps-geocode",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/google-maps.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "googlesheets",
|
||||
name: "Google Sheets",
|
||||
examples: [
|
||||
{
|
||||
title: "Insert data into a row in Google Sheets.",
|
||||
slug: "insert-data-into-google-sheets",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/google-sheets.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "hubspot",
|
||||
name: "HubSpot",
|
||||
examples: [
|
||||
{
|
||||
title: "Create a contact in HubSpot.",
|
||||
slug: "create-contact-in-hubspot",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/hubspot.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "huggingface",
|
||||
name: "Hugging Face",
|
||||
examples: [
|
||||
{
|
||||
title: "Text classification with Hugging Face.",
|
||||
slug: "text-classification-with-hugging-face",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/hugging-face.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "infisical",
|
||||
name: "Infisical",
|
||||
},
|
||||
{
|
||||
identifier: "instagram",
|
||||
name: "Instagram",
|
||||
examples: [
|
||||
{
|
||||
title: "Post an image to Instagram",
|
||||
slug: "post-image-to-instagram",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/instagram.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "instabug",
|
||||
name: "Instabug",
|
||||
},
|
||||
{
|
||||
identifier: "keep",
|
||||
name: "Keep",
|
||||
},
|
||||
{
|
||||
identifier: "lemonsqueezy",
|
||||
name: "Lemon Squeezy",
|
||||
examples: [
|
||||
{
|
||||
title: "Get store information from Lemon Squeezy.",
|
||||
slug: "get-store-information",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/lemon-squeezy.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "linkedin",
|
||||
name: "LinkedIn",
|
||||
},
|
||||
{
|
||||
identifier: "linear",
|
||||
name: "Linear",
|
||||
examples: [
|
||||
{
|
||||
title: "Post Linear issues to Slack every weekday at 9am using Cron.",
|
||||
slug: "daily-linear-issues-slack-alert",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/linearIssuesDailySlackAlert.ts",
|
||||
},
|
||||
{
|
||||
title: "Create a Linear issue when a pull request is opened on a GitHub repo.",
|
||||
slug: "linear-ticket-on-pr",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/linearCreateIssueOnPR.ts",
|
||||
},
|
||||
{
|
||||
title: "Automatically comment and like any new Linear issues.",
|
||||
slug: "automatically-comment-and-like-linear-issues",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/linearNewIssueReply.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "loops",
|
||||
name: "Loops",
|
||||
examples: [
|
||||
{
|
||||
title: "Create a new contact in Loops.",
|
||||
slug: "create-new-contact-in-loops",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/loops.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "lotus",
|
||||
name: "Lotus",
|
||||
},
|
||||
{
|
||||
identifier: "mailchimp",
|
||||
name: "Mailchimp",
|
||||
},
|
||||
{
|
||||
identifier: "mailgun",
|
||||
name: "Mailgun",
|
||||
examples: [
|
||||
{
|
||||
title: "Send an email with Mailgun.",
|
||||
slug: "send-email-with-mailgun",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/mailgun.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "microsoftazure",
|
||||
name: "Microsoft Azure",
|
||||
},
|
||||
{
|
||||
identifier: "monday",
|
||||
name: "Monday",
|
||||
},
|
||||
{
|
||||
identifier: "mux",
|
||||
name: "Mux",
|
||||
},
|
||||
{
|
||||
identifier: "notion",
|
||||
name: "Notion",
|
||||
examples: [
|
||||
{
|
||||
title: "Retrieve a Notion page by ID.",
|
||||
slug: "retrieve-notion-page",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/notion.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "novu",
|
||||
name: "Novu",
|
||||
examples: [
|
||||
{
|
||||
title: "Create a new subscriber in Novu",
|
||||
slug: "create-new-subscriber-in-novu",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/novu.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "openai",
|
||||
name: "OpenAI",
|
||||
examples: [
|
||||
{
|
||||
title: "Summarize GitHub commits using OpenAI and then post them to Slack.",
|
||||
slug: "openai-summarize-github-commits",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://raw.githubusercontent.com/triggerdotdev/jobs-showcase/main/src/summarizeGitHubCommits.ts",
|
||||
},
|
||||
{
|
||||
title: "Generate a random joke using OpenAI.",
|
||||
slug: "openai-generate-random-joke",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/jobs-showcase/raw/main/src/openAITellMeAJoke.ts",
|
||||
},
|
||||
{
|
||||
title: "Generate an image from a prompt using OpenAI.",
|
||||
slug: "openai-generate-image",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/openAIGenerateImage.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "pagerduty",
|
||||
name: "PagerDuty",
|
||||
},
|
||||
{
|
||||
identifier: "plain",
|
||||
name: "Plain",
|
||||
examples: [
|
||||
{
|
||||
title: "Update or create customer information based on an identifier.",
|
||||
slug: "plain-update-customer-information",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/plainUpdateCustomer.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "posthog",
|
||||
name: "Posthog",
|
||||
},
|
||||
{
|
||||
identifier: "raycast",
|
||||
name: "Raycast",
|
||||
},
|
||||
{
|
||||
identifier: "reddit",
|
||||
name: "Reddit",
|
||||
},
|
||||
{
|
||||
identifier: "replicate",
|
||||
name: "Replicate",
|
||||
examples: [
|
||||
{
|
||||
title: "Generate a cinematic image with Replicate.",
|
||||
slug: "generate-cinematic-image",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/replicateCinematicPrompt.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "resend",
|
||||
name: "Resend",
|
||||
examples: [
|
||||
{
|
||||
title: "Send a drip email campaign over 30 days, triggered by an event.",
|
||||
slug: "resend-send-drip-campaign",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/resendDripCampaign.tsx",
|
||||
},
|
||||
{
|
||||
title: "Send an email built using React with Resend.",
|
||||
slug: "send-react-email",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://raw.githubusercontent.com/triggerdotdev/jobs-showcase/main/src/resendSendReactEmail.tsx",
|
||||
},
|
||||
{
|
||||
title: "Send a basic email with Resend.",
|
||||
slug: "resend-send-basic-email",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/resendSendBasicEmail.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "salesforce",
|
||||
name: "Salesforce",
|
||||
examples: [
|
||||
{
|
||||
title: "Create a new contact in Salesforce.",
|
||||
slug: "salesforce-create-contact",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/salesforce.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "segment",
|
||||
name: "Segment",
|
||||
examples: [
|
||||
{
|
||||
title: "Get source information from Segment.",
|
||||
slug: "segment-get-source-information",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/segment.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "sendgrid",
|
||||
name: "SendGrid",
|
||||
examples: [
|
||||
{
|
||||
title: "Send an activity summary email to users at 4pm every Friday.",
|
||||
slug: "sendgrid-send-activity-summary",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/weeklyUserActivitySummary.ts",
|
||||
},
|
||||
{
|
||||
title: "SendGrid send basic email.",
|
||||
slug: "sendgrid-send-basic-email",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/sendGridSendBasicEmail.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "shopify",
|
||||
name: "Shopify",
|
||||
examples: [
|
||||
{
|
||||
title: "Update a product variant price in Shopify.",
|
||||
slug: "shopify-update-product-variant-price",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/shopify.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "slack",
|
||||
name: "Slack",
|
||||
examples: [
|
||||
{
|
||||
title: "Posts Linear issues to Slack every weekday at 9am using Cron.",
|
||||
slug: "slack-daily-linear-issues",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/linearIssuesDailySlackAlert.ts",
|
||||
},
|
||||
{
|
||||
title: "Summarize GitHub commits using OpenAI and then post them to Slack.",
|
||||
slug: "slack-openai-summarize-github-commits",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://raw.githubusercontent.com/triggerdotdev/jobs-showcase/main/src/summarizeGitHubCommits.ts",
|
||||
},
|
||||
{
|
||||
title: "Send an activity summary email, and post it to Slack at 4pm every Friday.",
|
||||
slug: "slack-sendgrid-send-activity-summary",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/weeklyUserActivitySummary.ts",
|
||||
},
|
||||
{
|
||||
title: "Send a message to a Slack channel when a GitHub repo is starred.",
|
||||
slug: "slack-post-github",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/gitHubNewStarToSlack.ts",
|
||||
},
|
||||
{
|
||||
title:
|
||||
"Send a reminder message to a Slack channel if a GitHub issue is left open for 24 hours.",
|
||||
slug: "slack-github-issue-reminder",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/gitHubIssueReminder.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "snyk",
|
||||
name: "Snyk",
|
||||
examples: [
|
||||
{
|
||||
title: "Get user details from Snyk.",
|
||||
slug: "snyk-get-user-details",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/snyk.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "spotify",
|
||||
name: "Spotify",
|
||||
},
|
||||
{
|
||||
identifier: "stabilityai",
|
||||
name: "Stability AI",
|
||||
examples: [
|
||||
{
|
||||
title: "Generate an image with Stability AI.",
|
||||
slug: "stabilityai-generate-image",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/stability.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "stripe",
|
||||
name: "Stripe",
|
||||
examples: [
|
||||
{
|
||||
title: "Update Supabase every time a Stripe account is updated.",
|
||||
slug: "stripe-supabase-update",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/supabaseStripeUpdateDatabase.ts",
|
||||
},
|
||||
{
|
||||
title: "Update Airtable when a new subscription is added to Stripe.",
|
||||
slug: "stripe-sub-update-airtable",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://raw.githubusercontent.com/triggerdotdev/jobs-showcase/main/src/stripeNewSubscriptionUpdateAirtable.ts",
|
||||
},
|
||||
{
|
||||
title: "Update Airtable database when there is a sale in Stripe.",
|
||||
version: "1.0.0",
|
||||
slug: "update-airtable-when-stripe-account-updated",
|
||||
codeUrl:
|
||||
"https://raw.githubusercontent.com/triggerdotdev/jobs-showcase/main/src/syncStripeWithAirtable.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "supabase",
|
||||
name: "Supabase",
|
||||
examples: [
|
||||
{
|
||||
title: "Update Supabase every time a Stripe account is updated.",
|
||||
slug: "stripe-supabase-update",
|
||||
version: "1.0.0",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/supabaseStripeUpdateDatabase.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "svix",
|
||||
name: "Svix",
|
||||
examples: [
|
||||
{
|
||||
title: "Create an application in Svix",
|
||||
slug: "svix-create-application",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/svix.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "todoist",
|
||||
name: "Todoist",
|
||||
examples: [
|
||||
{
|
||||
title: "Add a new project in Todoist.",
|
||||
slug: "todoist-add-new-project",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/todoist.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "trello",
|
||||
name: "Trello",
|
||||
},
|
||||
|
||||
{
|
||||
identifier: "twilio",
|
||||
name: "Twilio",
|
||||
examples: [
|
||||
{
|
||||
title: "Send an SMS or WhatsApp message with Twilio",
|
||||
slug: "twilio-send-sms-or-whatsapp-message",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/twilio.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "typeform",
|
||||
name: "Typeform",
|
||||
examples: [
|
||||
{
|
||||
title: "Add a new record to Airtable when a Typeform response is submitted.",
|
||||
version: "1.0.0",
|
||||
slug: "new-airtable-record-from-typeform",
|
||||
codeUrl:
|
||||
"https://github.com/triggerdotdev/jobs-showcase/raw/main/src/typeformNewSubmissionUpdateAirtable.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
identifier: "whatsapp",
|
||||
name: "WhatsApp",
|
||||
examples: [
|
||||
{
|
||||
title: "Send a message to a WhatsApp number",
|
||||
slug: "whatapp-send-message",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/whatsapp.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "x",
|
||||
name: "X (Twitter)",
|
||||
examples: [
|
||||
{
|
||||
title: "Post a post to an X (Twitter) account",
|
||||
slug: "post-to-x",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/x.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "youtube",
|
||||
name: "YouTube",
|
||||
examples: [
|
||||
{
|
||||
title: "Search for a YouTube video",
|
||||
slug: "youtube-search-video",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/youtube.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
identifier: "zbd",
|
||||
name: "ZBD",
|
||||
examples: [
|
||||
{
|
||||
title: "Send Satoshis to a ZBD account.",
|
||||
slug: "zbd-send-satoshis",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/zbd.ts",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -1,223 +0,0 @@
|
||||
export type Api = {
|
||||
identifier: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const apisList = [
|
||||
{
|
||||
identifier: "airtable",
|
||||
name: "Airtable",
|
||||
},
|
||||
{
|
||||
identifier: "algolia",
|
||||
name: "Algolia",
|
||||
},
|
||||
{
|
||||
identifier: "anthropic",
|
||||
name: "Anthropic",
|
||||
},
|
||||
{
|
||||
identifier: "appsmith",
|
||||
name: "Appsmith",
|
||||
},
|
||||
{
|
||||
identifier: "asana",
|
||||
name: "Asana",
|
||||
},
|
||||
{
|
||||
identifier: "aws",
|
||||
name: "AWS",
|
||||
},
|
||||
{
|
||||
identifier: "caldotcom",
|
||||
name: "Cal.com",
|
||||
},
|
||||
{
|
||||
identifier: "clickup",
|
||||
name: "ClickUp",
|
||||
},
|
||||
{
|
||||
identifier: "coda",
|
||||
name: "Coda",
|
||||
},
|
||||
{
|
||||
identifier: "discord",
|
||||
name: "Discord",
|
||||
},
|
||||
{
|
||||
identifier: "dropbox",
|
||||
name: "Dropbox",
|
||||
},
|
||||
{
|
||||
identifier: "facebook",
|
||||
name: "Facebook",
|
||||
},
|
||||
{
|
||||
identifier: "flickr",
|
||||
name: "Flickr",
|
||||
},
|
||||
{
|
||||
identifier: "github",
|
||||
name: "GitHub",
|
||||
},
|
||||
{
|
||||
identifier: "giphy",
|
||||
name: "Giphy",
|
||||
},
|
||||
{
|
||||
identifier: "gmail",
|
||||
name: "Gmail",
|
||||
},
|
||||
{
|
||||
identifier: "googlecalendar",
|
||||
name: "Google Calendar",
|
||||
},
|
||||
{
|
||||
identifier: "googlecloudplatform",
|
||||
name: "Google Cloud Platform",
|
||||
},
|
||||
{
|
||||
identifier: "googledocs",
|
||||
name: "Google Docs",
|
||||
},
|
||||
{
|
||||
identifier: "googledrive",
|
||||
name: "Google Drive",
|
||||
},
|
||||
{
|
||||
identifier: "googlemaps",
|
||||
name: "Google Maps",
|
||||
},
|
||||
{
|
||||
identifier: "hubspot",
|
||||
name: "HubSpot",
|
||||
},
|
||||
{
|
||||
identifier: "instagram",
|
||||
name: "Instagram",
|
||||
},
|
||||
{
|
||||
identifier: "infisical",
|
||||
name: "Infisical",
|
||||
},
|
||||
{
|
||||
identifier: "linkedin",
|
||||
name: "LinkedIn",
|
||||
},
|
||||
{
|
||||
identifier: "linear",
|
||||
name: "Linear",
|
||||
},
|
||||
{
|
||||
identifier: "loops",
|
||||
name: "Loops",
|
||||
},
|
||||
{
|
||||
identifier: "mailchimp",
|
||||
name: "Mailchimp",
|
||||
},
|
||||
{
|
||||
identifier: "mailgun",
|
||||
name: "Mailgun",
|
||||
},
|
||||
{
|
||||
identifier: "microsoftazure",
|
||||
name: "Microsoft Azure",
|
||||
},
|
||||
{
|
||||
identifier: "mux",
|
||||
name: "Mux",
|
||||
},
|
||||
{
|
||||
identifier: "notion",
|
||||
name: "Notion",
|
||||
},
|
||||
{
|
||||
identifier: "openai",
|
||||
name: "OpenAI",
|
||||
},
|
||||
{
|
||||
identifier: "plain",
|
||||
name: "Plain",
|
||||
},
|
||||
{
|
||||
identifier: "posthog",
|
||||
name: "Posthog",
|
||||
},
|
||||
{
|
||||
identifier: "reddit",
|
||||
name: "Reddit",
|
||||
},
|
||||
{
|
||||
identifier: "replicate",
|
||||
name: "Replicate",
|
||||
},
|
||||
{
|
||||
identifier: "resend",
|
||||
name: "Resend",
|
||||
},
|
||||
{
|
||||
identifier: "salesforce",
|
||||
name: "Salesforce",
|
||||
},
|
||||
{
|
||||
identifier: "segment",
|
||||
name: "Segment",
|
||||
},
|
||||
{
|
||||
identifier: "sendgrid",
|
||||
name: "SendGrid",
|
||||
},
|
||||
{
|
||||
identifier: "shopify",
|
||||
name: "Shopify",
|
||||
},
|
||||
{
|
||||
identifier: "slack",
|
||||
name: "Slack",
|
||||
},
|
||||
{
|
||||
identifier: "spotify",
|
||||
name: "Spotify",
|
||||
},
|
||||
{
|
||||
identifier: "stabilityai",
|
||||
name: "Stability AI",
|
||||
},
|
||||
{
|
||||
identifier: "stripe",
|
||||
name: "Stripe",
|
||||
},
|
||||
{
|
||||
identifier: "supabase",
|
||||
name: "Supabase",
|
||||
},
|
||||
{
|
||||
identifier: "svix",
|
||||
name: "Svix",
|
||||
},
|
||||
{
|
||||
identifier: "trello",
|
||||
name: "Trello",
|
||||
},
|
||||
{
|
||||
identifier: "twilio",
|
||||
name: "Twilio",
|
||||
},
|
||||
{
|
||||
identifier: "twitter",
|
||||
name: "Twitter",
|
||||
},
|
||||
{
|
||||
identifier: "typeform",
|
||||
name: "Typeform",
|
||||
},
|
||||
{
|
||||
identifier: "whatsapp",
|
||||
name: "WhatsApp",
|
||||
},
|
||||
{
|
||||
identifier: "youtube",
|
||||
name: "YouTube",
|
||||
},
|
||||
];
|
||||
@@ -80,7 +80,7 @@ export class HandleHttpEndpointService {
|
||||
});
|
||||
|
||||
if (!httpEndpointEnvironment) {
|
||||
logger.error("Could not find http endpoint environment", {
|
||||
logger.debug("Could not find http endpoint environment", {
|
||||
httpEndpointId: httpEndpoint.id,
|
||||
environmentId: environment.id,
|
||||
});
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
import type { LogLevel } from "@trigger.dev/core";
|
||||
import { Logger } from "@trigger.dev/core";
|
||||
import { sensitiveDataReplacer } from "./sensitiveDataReplacer";
|
||||
import { singleton } from "~/utils/singleton";
|
||||
import { AsyncLocalStorage } from "async_hooks";
|
||||
|
||||
const currentFieldsStore = new AsyncLocalStorage<Record<string, unknown>>();
|
||||
|
||||
export function trace<T>(fields: Record<string, unknown>, fn: () => T): T {
|
||||
return currentFieldsStore.run(fields, fn);
|
||||
}
|
||||
|
||||
export const logger = new Logger(
|
||||
"webapp",
|
||||
(process.env.APP_LOG_LEVEL ?? "debug") as LogLevel,
|
||||
["examples", "output", "connectionString", "payload"],
|
||||
sensitiveDataReplacer
|
||||
sensitiveDataReplacer,
|
||||
() => {
|
||||
const fields = currentFieldsStore.getStore();
|
||||
return fields ? { ...fields } : {};
|
||||
}
|
||||
);
|
||||
|
||||
export const workerLogger = new Logger(
|
||||
"worker",
|
||||
(process.env.APP_LOG_LEVEL ?? "debug") as LogLevel,
|
||||
["examples", "output", "connectionString"],
|
||||
sensitiveDataReplacer
|
||||
sensitiveDataReplacer,
|
||||
() => {
|
||||
const fields = currentFieldsStore.getStore();
|
||||
return fields ? { ...fields } : {};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { RunNotification } from "@trigger.dev/core";
|
||||
import { subtle } from "node:crypto";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { EndpointApi } from "../endpointApi.server";
|
||||
|
||||
// Infer the type of the #findSubscription method
|
||||
type FoundSubscription = NonNullable<
|
||||
@@ -87,6 +88,34 @@ export class DeliverRunSubscriptionService {
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "ENDPOINT": {
|
||||
const endpointId = subscription.recipient;
|
||||
|
||||
if (endpointId !== subscription.run.endpointId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const client = new EndpointApi(
|
||||
subscription.run.environment.apiKey,
|
||||
subscription.run.endpoint.url
|
||||
);
|
||||
|
||||
const response = await client.deliverRunNotification(payload);
|
||||
|
||||
if (!response) {
|
||||
throw new Error(
|
||||
`Failed to deliver endpoint notification to ${subscription.run.endpoint.url}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to deliver endpoint notification to ${subscription.run.endpoint.url}: [${response.status}] ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -107,6 +136,19 @@ export class DeliverRunSubscriptionService {
|
||||
organization: true,
|
||||
project: true,
|
||||
event: true,
|
||||
endpoint: true,
|
||||
tasks: {
|
||||
where: {
|
||||
status: "ERRORED",
|
||||
},
|
||||
take: 1,
|
||||
orderBy: {
|
||||
startedAt: "desc",
|
||||
},
|
||||
include: {
|
||||
attempts: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -114,7 +156,20 @@ export class DeliverRunSubscriptionService {
|
||||
}
|
||||
|
||||
#getPayload(run: FoundRun): RunNotification<any> {
|
||||
const { id, job, version, statuses, environment, organization, project, event } = run;
|
||||
const { id, job, version, statuses, environment, organization, project, event, tasks } = run;
|
||||
|
||||
const task = tasks[0]
|
||||
? {
|
||||
id: tasks[0].idempotencyKey,
|
||||
cacheKey: tasks[0].displayKey,
|
||||
status: tasks[0].status,
|
||||
name: tasks[0].name,
|
||||
icon: tasks[0].icon,
|
||||
startedAt: tasks[0].startedAt,
|
||||
error: tasks[0].output,
|
||||
params: tasks[0].params,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const payload = {
|
||||
id,
|
||||
@@ -123,10 +178,11 @@ export class DeliverRunSubscriptionService {
|
||||
startedAt: run.startedAt,
|
||||
updatedAt: run.updatedAt,
|
||||
completedAt: run.completedAt,
|
||||
isTest: run.isTest,
|
||||
executionDurationInMs: run.executionDuration,
|
||||
executionCount: run.executionCount,
|
||||
job: {
|
||||
id: job.id,
|
||||
id: job.slug,
|
||||
version: version.version,
|
||||
},
|
||||
statuses: statuses.map((status) => ({
|
||||
@@ -155,8 +211,14 @@ export class DeliverRunSubscriptionService {
|
||||
id: event.id,
|
||||
context: event.context,
|
||||
timestamp: event.timestamp,
|
||||
payload: event.payload,
|
||||
},
|
||||
...(run.status === "SUCCESS" ? { output: run.output } : { error: run.output }),
|
||||
...(run.status === "SUCCESS"
|
||||
? { output: run.output }
|
||||
: {
|
||||
error: run.output,
|
||||
task,
|
||||
}),
|
||||
};
|
||||
|
||||
return payload as RunNotification<any>;
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
ApiEventLog,
|
||||
AutoYieldMetadata,
|
||||
ConnectionAuth,
|
||||
EndpointHeadersSchema,
|
||||
RunJobAutoYieldWithCompletedTaskExecutionError,
|
||||
RunJobBody,
|
||||
RunJobError,
|
||||
@@ -34,10 +33,11 @@ import { CompleteRunTaskService } from "~/routes/api.v1.runs.$runId.tasks.$id.co
|
||||
import { formatError } from "~/utils/formatErrors.server";
|
||||
import { safeJsonZodParse } from "~/utils/json";
|
||||
import { EndpointApi } from "../endpointApi.server";
|
||||
import { createExecutionEvent } from "../executions/createExecutionEvent.server";
|
||||
import { logger } from "../logger.server";
|
||||
import { forceYieldCoordinator } from "./forceYieldCoordinator.server";
|
||||
import { workerQueue } from "../worker.server";
|
||||
import { ResumeTaskService } from "../tasks/resumeTask.server";
|
||||
import { workerQueue } from "../worker.server";
|
||||
import { forceYieldCoordinator } from "./forceYieldCoordinator.server";
|
||||
|
||||
type FoundRun = NonNullable<Awaited<ReturnType<typeof findRun>>>;
|
||||
type FoundTask = FoundRun["tasks"][number];
|
||||
@@ -67,7 +67,7 @@ export class PerformRunExecutionV3Service {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(input: PerformRunExecutionV3Input) {
|
||||
public async call(input: PerformRunExecutionV3Input, driftInMs: number = 0) {
|
||||
const run = await findRun(this.#prismaClient, input.id);
|
||||
|
||||
if (!run) {
|
||||
@@ -80,7 +80,7 @@ export class PerformRunExecutionV3Service {
|
||||
break;
|
||||
}
|
||||
case "EXECUTE_JOB": {
|
||||
await this.#executeJob(run, input);
|
||||
await this.#executeJob(run, input, driftInMs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ export class PerformRunExecutionV3Service {
|
||||
});
|
||||
}
|
||||
}
|
||||
async #executeJob(run: FoundRun, input: PerformRunExecutionV3Input) {
|
||||
async #executeJob(run: FoundRun, input: PerformRunExecutionV3Input, driftInMs: number = 0) {
|
||||
try {
|
||||
const { isRetry, resumeTaskId } = input;
|
||||
|
||||
@@ -275,9 +275,30 @@ export class PerformRunExecutionV3Service {
|
||||
|
||||
forceYieldCoordinator.registerRun(run.id);
|
||||
|
||||
const { response, parser, errorParser, durationInMs } = await client.executeJobRequest(
|
||||
executionBody
|
||||
);
|
||||
await createExecutionEvent({
|
||||
eventType: "start",
|
||||
eventTime: new Date(),
|
||||
drift: driftInMs,
|
||||
organizationId: run.organizationId,
|
||||
environmentId: run.environmentId,
|
||||
projectId: run.projectId,
|
||||
jobId: run.jobId,
|
||||
runId: run.id,
|
||||
});
|
||||
|
||||
const { response, parser, errorParser, headersParser, durationInMs } =
|
||||
await client.executeJobRequest(executionBody);
|
||||
|
||||
await createExecutionEvent({
|
||||
eventType: "finish",
|
||||
eventTime: new Date(),
|
||||
drift: 0,
|
||||
organizationId: run.organizationId,
|
||||
environmentId: run.environmentId,
|
||||
projectId: run.projectId,
|
||||
jobId: run.jobId,
|
||||
runId: run.id,
|
||||
});
|
||||
|
||||
forceYieldCoordinator.deregisterRun(run.id);
|
||||
|
||||
@@ -289,7 +310,7 @@ export class PerformRunExecutionV3Service {
|
||||
|
||||
// Update the endpoint version if it has changed
|
||||
const rawHeaders = Object.fromEntries(response.headers.entries());
|
||||
const headers = EndpointHeadersSchema.safeParse(rawHeaders);
|
||||
const headers = headersParser.safeParse(rawHeaders);
|
||||
|
||||
if (
|
||||
headers.success &&
|
||||
@@ -306,6 +327,58 @@ export class PerformRunExecutionV3Service {
|
||||
});
|
||||
}
|
||||
|
||||
if (headers.success && headers.data["x-trigger-run-metadata"] && !run.internal) {
|
||||
logger.debug("Endpoint responded with run metadata", {
|
||||
metadata: headers.data["x-trigger-run-metadata"],
|
||||
});
|
||||
|
||||
if (
|
||||
headers.data["x-trigger-run-metadata"].successSubscription &&
|
||||
!run.subscriptions.some((s) => s.event === "SUCCESS")
|
||||
) {
|
||||
await this.#prismaClient.jobRunSubscription.upsert({
|
||||
where: {
|
||||
runId_recipient_event: {
|
||||
runId: run.id,
|
||||
recipient: run.endpoint.id,
|
||||
event: "SUCCESS",
|
||||
},
|
||||
},
|
||||
create: {
|
||||
runId: run.id,
|
||||
recipient: run.endpoint.id,
|
||||
recipientMethod: "ENDPOINT",
|
||||
event: "SUCCESS",
|
||||
status: "ACTIVE",
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
headers.data["x-trigger-run-metadata"].failedSubscription &&
|
||||
!run.subscriptions.some((s) => s.event === "FAILURE")
|
||||
) {
|
||||
await this.#prismaClient.jobRunSubscription.upsert({
|
||||
where: {
|
||||
runId_recipient_event: {
|
||||
runId: run.id,
|
||||
recipient: run.endpoint.id,
|
||||
event: "FAILURE",
|
||||
},
|
||||
},
|
||||
create: {
|
||||
runId: run.id,
|
||||
recipient: run.endpoint.id,
|
||||
recipientMethod: "ENDPOINT",
|
||||
event: "FAILURE",
|
||||
status: "ACTIVE",
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const rawBody = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -1220,6 +1293,7 @@ async function findRun(prisma: PrismaClientOrTransaction, id: string) {
|
||||
status: true,
|
||||
noop: true,
|
||||
output: true,
|
||||
outputIsUndefined: true,
|
||||
parentId: true,
|
||||
},
|
||||
orderBy: {
|
||||
@@ -1233,6 +1307,11 @@ async function findRun(prisma: PrismaClientOrTransaction, id: string) {
|
||||
organization: true,
|
||||
},
|
||||
},
|
||||
subscriptions: {
|
||||
where: {
|
||||
recipientMethod: "ENDPOINT",
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
tasks: true,
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import { RunTaskBodyOutput, ServerTask } from "@trigger.dev/core";
|
||||
import { TaskStatus } from "@trigger.dev/database";
|
||||
import { $transaction, PrismaClient, prisma } from "~/db.server";
|
||||
import { env } from "~/env.server";
|
||||
import { taskWithAttemptsToServerTask } from "~/models/task.server";
|
||||
import { generateSecret } from "~/services/sources/utils.server";
|
||||
import { ulid } from "~/services/ulid.server";
|
||||
import { taskOperationWorker, workerQueue } from "~/services/worker.server";
|
||||
|
||||
export class RunTaskService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
runId: string,
|
||||
idempotencyKey: string,
|
||||
taskBody: RunTaskBodyOutput
|
||||
): Promise<ServerTask | undefined> {
|
||||
const delayUntilInFuture = taskBody.delayUntil
|
||||
? taskBody.delayUntil.getTime() > Date.now()
|
||||
: false;
|
||||
const callbackEnabled = taskBody.callback?.enabled ?? false;
|
||||
|
||||
// First
|
||||
const existingTask = await this.#handleExistingTask(
|
||||
runId,
|
||||
idempotencyKey,
|
||||
taskBody,
|
||||
delayUntilInFuture,
|
||||
callbackEnabled
|
||||
);
|
||||
|
||||
if (existingTask) {
|
||||
return taskWithAttemptsToServerTask(existingTask);
|
||||
}
|
||||
|
||||
const run = await this.#prismaClient.jobRun.findUnique({
|
||||
where: {
|
||||
id: runId,
|
||||
},
|
||||
select: {
|
||||
status: true,
|
||||
forceYieldImmediately: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!run) throw new Error("Run not found");
|
||||
|
||||
const runConnection = taskBody.connectionKey
|
||||
? await this.#prismaClient.runConnection.findUnique({
|
||||
where: {
|
||||
runId_key: {
|
||||
runId,
|
||||
key: taskBody.connectionKey,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const results = await $transaction(this.#prismaClient, async (tx) => {
|
||||
// If task.delayUntil is set and is in the future, we'll set the task's status to "WAITING", else set it to RUNNING
|
||||
let status: TaskStatus;
|
||||
|
||||
if (run.status === "CANCELED") {
|
||||
status = "CANCELED";
|
||||
} else {
|
||||
status =
|
||||
delayUntilInFuture || callbackEnabled || taskBody.trigger
|
||||
? "WAITING"
|
||||
: taskBody.noop
|
||||
? "COMPLETED"
|
||||
: "RUNNING";
|
||||
}
|
||||
|
||||
const taskId = ulid();
|
||||
const callbackUrl = callbackEnabled
|
||||
? `${env.APP_ORIGIN}/api/v1/tasks/${taskId}/callback/${generateSecret(12)}`
|
||||
: undefined;
|
||||
|
||||
const task = await tx.task.create({
|
||||
data: {
|
||||
id: taskId,
|
||||
idempotencyKey,
|
||||
displayKey: taskBody.displayKey,
|
||||
runConnectionId: runConnection ? runConnection.id : undefined,
|
||||
icon: taskBody.icon,
|
||||
runId,
|
||||
parentId: taskBody.parentId,
|
||||
name: taskBody.name ?? "Task",
|
||||
description: taskBody.description,
|
||||
status,
|
||||
startedAt: new Date(),
|
||||
completedAt: status === "COMPLETED" || status === "CANCELED" ? new Date() : undefined,
|
||||
noop: taskBody.noop,
|
||||
delayUntil: taskBody.delayUntil,
|
||||
params: taskBody.params ?? undefined,
|
||||
properties: this.#filterProperties(taskBody.properties) ?? undefined,
|
||||
redact: taskBody.redact ?? undefined,
|
||||
operation: taskBody.operation,
|
||||
callbackUrl,
|
||||
style: taskBody.style ?? { style: "normal" },
|
||||
childExecutionMode: taskBody.parallel ? "PARALLEL" : "SEQUENTIAL",
|
||||
},
|
||||
});
|
||||
|
||||
const taskAttempt = await tx.taskAttempt.create({
|
||||
data: {
|
||||
number: 1,
|
||||
taskId: task.id,
|
||||
status: "PENDING",
|
||||
},
|
||||
});
|
||||
|
||||
if (task.status === "RUNNING" && typeof taskBody.operation === "string") {
|
||||
// We need to schedule the operation
|
||||
await taskOperationWorker.enqueue(
|
||||
"performTaskOperation",
|
||||
{
|
||||
id: task.id,
|
||||
},
|
||||
{ tx, runAt: task.delayUntil ?? undefined, jobKey: `operation:${task.id}` }
|
||||
);
|
||||
} else if (task.status === "WAITING" && callbackUrl && taskBody.callback) {
|
||||
if (taskBody.callback.timeoutInSeconds > 0) {
|
||||
// We need to schedule the callback timeout
|
||||
await workerQueue.enqueue(
|
||||
"processCallbackTimeout",
|
||||
{
|
||||
id: task.id,
|
||||
},
|
||||
{
|
||||
tx,
|
||||
runAt: new Date(Date.now() + taskBody.callback.timeoutInSeconds * 1000),
|
||||
jobKey: `process-callback:${task.id}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { task, taskAttempt };
|
||||
});
|
||||
|
||||
if (!results) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { task, taskAttempt } = results;
|
||||
|
||||
return task
|
||||
? taskWithAttemptsToServerTask({ ...task, attempts: [taskAttempt], run })
|
||||
: undefined;
|
||||
}
|
||||
|
||||
async #handleExistingTask(
|
||||
runId: string,
|
||||
idempotencyKey: string,
|
||||
taskBody: RunTaskBodyOutput,
|
||||
delayUntilInFuture: boolean,
|
||||
callbackEnabled: boolean
|
||||
) {
|
||||
const existingTask = await this.#prismaClient.task.findUnique({
|
||||
where: {
|
||||
runId_idempotencyKey: {
|
||||
runId,
|
||||
idempotencyKey,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
attempts: true,
|
||||
run: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingTask) {
|
||||
if (existingTask.status === "CANCELED") {
|
||||
const existingTaskStatus =
|
||||
delayUntilInFuture || callbackEnabled || taskBody.trigger
|
||||
? "WAITING"
|
||||
: taskBody.noop
|
||||
? "COMPLETED"
|
||||
: "RUNNING";
|
||||
|
||||
const resumedExistingTask = await this.#prismaClient.task.update({
|
||||
where: {
|
||||
id: existingTask.id,
|
||||
},
|
||||
data: {
|
||||
status: existingTaskStatus,
|
||||
startedAt: new Date(),
|
||||
completedAt: existingTaskStatus === "COMPLETED" ? new Date() : undefined,
|
||||
},
|
||||
include: {
|
||||
run: true,
|
||||
attempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
return resumedExistingTask;
|
||||
}
|
||||
|
||||
return existingTask;
|
||||
}
|
||||
}
|
||||
|
||||
#filterProperties(properties: RunTaskBodyOutput["properties"]): RunTaskBodyOutput["properties"] {
|
||||
if (!properties) return;
|
||||
|
||||
return properties.filter((property) => {
|
||||
if (!property) return false;
|
||||
|
||||
return typeof property.label === "string" && typeof property.text === "string";
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -442,11 +442,16 @@ function getExecutionWorkerQueue() {
|
||||
handler: async (payload, job) => {
|
||||
const service = new PerformRunExecutionV3Service();
|
||||
|
||||
await service.call({
|
||||
id: payload.id,
|
||||
reason: payload.reason,
|
||||
isRetry: false,
|
||||
});
|
||||
const driftInMs = Date.now() - job.run_at.getTime();
|
||||
|
||||
await service.call(
|
||||
{
|
||||
id: payload.id,
|
||||
reason: payload.reason,
|
||||
isRetry: false,
|
||||
},
|
||||
driftInMs
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -369,3 +369,8 @@ export function docsIntegrationPath(api: string) {
|
||||
export function docsCreateIntegration() {
|
||||
return `${docsRoot()}/integrations/create`;
|
||||
}
|
||||
|
||||
//api
|
||||
export function apiReferencePath(apiSlug: string) {
|
||||
return `https://trigger.dev/apis/${apiSlug}`;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,16 @@ export function sse({ request, pingInterval = 1000, updateInterval = 348, run }:
|
||||
return new Response("SSE disabled", { status: 200 });
|
||||
}
|
||||
|
||||
let pinger: NodeJS.Timer | undefined = undefined;
|
||||
let updater: NodeJS.Timer | undefined = undefined;
|
||||
let timeout: NodeJS.Timeout | undefined = undefined;
|
||||
|
||||
const abort = () => {
|
||||
clearInterval(pinger);
|
||||
clearInterval(updater);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
|
||||
return eventStream(request.signal, (send, close) => {
|
||||
const safeSend = (args: { event?: string; data: string }) => {
|
||||
try {
|
||||
@@ -49,30 +59,26 @@ export function sse({ request, pingInterval = 1000, updateInterval = 348, run }:
|
||||
}
|
||||
};
|
||||
|
||||
const pinger = setInterval(() => {
|
||||
pinger = setInterval(() => {
|
||||
if (request.signal.aborted) {
|
||||
return close();
|
||||
return abort();
|
||||
}
|
||||
|
||||
safeSend({ event: "ping", data: new Date().toISOString() });
|
||||
}, pingInterval);
|
||||
|
||||
const updater = setInterval(() => {
|
||||
updater = setInterval(() => {
|
||||
if (request.signal.aborted) {
|
||||
return close();
|
||||
return abort();
|
||||
}
|
||||
|
||||
run(safeSend, close);
|
||||
run(safeSend, abort);
|
||||
}, updateInterval);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
close();
|
||||
timeout = setTimeout(() => {
|
||||
close(); // close the connection after 1 minute of inactivity, which will refresh the connection (that's why we aren't using abort)
|
||||
}, 60 * 1000); // 1 minute
|
||||
|
||||
return () => {
|
||||
clearInterval(updater);
|
||||
clearInterval(pinger);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
return abort;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"/public/build"
|
||||
],
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sqs": "^3.445.0",
|
||||
"@codemirror/autocomplete": "^6.3.1",
|
||||
"@codemirror/commands": "^6.1.2",
|
||||
"@codemirror/lang-javascript": "^6.1.1",
|
||||
@@ -62,7 +63,7 @@
|
||||
"@remix-run/v1-meta": "^0.1.3",
|
||||
"@tabler/icons-react": "^2.39.0",
|
||||
"@team-plain/typescript-sdk": "^2.2.0",
|
||||
"@trigger.dev/companyicons": "^1.5.14",
|
||||
"@trigger.dev/companyicons": "^1.5.32",
|
||||
"@trigger.dev/core": "workspace:*",
|
||||
"@trigger.dev/core-backend": "workspace:*",
|
||||
"@trigger.dev/database": "workspace:*",
|
||||
@@ -111,6 +112,7 @@
|
||||
"simplur": "^3.0.1",
|
||||
"slug": "^6.0.0",
|
||||
"sonner": "^1.0.3",
|
||||
"sqs-consumer": "^7.4.0",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"tailwindcss-animate": "^1.0.5",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<Warning>This feature is in beta and has not yet been deployed to the Trigger.dev cloud</Warning>
|
||||
@@ -36,6 +36,12 @@
|
||||
<ParamField body="enabled" type="boolean">
|
||||
The `enabled` property is an optional property that specifies whether the Job is enabled or not. The Job will be enabled by default if you omit this property. When a job is disabled, no new runs will be triggered or resumed. In progress runs will continue to run until they are finished or delayed by using `io.wait`.
|
||||
</ParamField>
|
||||
<ParamField body="onSuccess" type="function">
|
||||
The `onSuccess` property is an optional property that specifies a callback function to run when the Job finishes successfully. The callback function receives a [Run Notification](/sdk/run-notification) object as it's only parameter.
|
||||
</ParamField>
|
||||
<ParamField body="onFailure" type="function">
|
||||
The `onFailure` property is an optional property that specifies a callback function to run when the Job fails to complete successfully. The callback function receives a [Run Notification](/sdk/run-notification) object as it's only parameter.
|
||||
</ParamField>
|
||||
<ParamField body="logLevel" type="log | error | warn | info | debug">
|
||||
The `logLevel` property is an optional property that specifies the level of
|
||||
logging for the Job. The level is inherited from the client if you omit this property.
|
||||
|
||||
@@ -68,7 +68,7 @@ client.defineJob({
|
||||
});
|
||||
```
|
||||
|
||||
## 5. Add integrations
|
||||
## 5. Add integrations (optional)
|
||||
|
||||
Integrations provide a convienent way to create and run tasks against authenticated APIs inside your Job's run function. You'll need to pass them in the `integrations` option when defining your Job.
|
||||
|
||||
@@ -165,6 +165,7 @@ client.defineJob({
|
||||
trigger: eventTrigger({
|
||||
name: "my.event",
|
||||
}),
|
||||
// Optional integration
|
||||
integrations: { slack },
|
||||
run: async (payload, io, context) => {
|
||||
await io.logger.info("Received the my.event event", { payload });
|
||||
@@ -198,6 +199,7 @@ client.defineJob({
|
||||
trigger: eventTrigger({
|
||||
name: "my.event",
|
||||
}),
|
||||
// Optional integration
|
||||
integrations: { slack },
|
||||
run: async (payload, io, context) => {
|
||||
// Use runTask with the "get-user" cacheKey, and return the user
|
||||
@@ -366,17 +368,15 @@ We recommend exploring all of the below sections to fully understand how to crea
|
||||
|
||||
</Card>
|
||||
|
||||
<Card title="Example Jobs" icon="slot-machine" href="/examples">
|
||||
View example Jobs / the example jobs repo. These are a great starting
|
||||
point for creating your own Jobs.
|
||||
</Card>
|
||||
<Card title="SDK reference" icon="code" href="/sdk">
|
||||
How to use the SDK. This includes all the available Tasks, triggers and
|
||||
actions you can use.
|
||||
</Card>
|
||||
|
||||
<Card title="Integrations" icon="grid-2" href="/integrations">
|
||||
Integrations make it easy to authenticate and use APIs.
|
||||
Learn how to use and create integrations.
|
||||
</Card>
|
||||
<Card title="Integrations" icon="grid-2" href="/integrations">
|
||||
Integrations make it easy to authenticate and use APIs. Learn how to use and create integrations.
|
||||
</Card>
|
||||
<Card title="API catalog with code samples" icon="code" href="https://trigger.dev/apis">
|
||||
Find code examples for the API you need that you can copy and paste into your
|
||||
projects.
|
||||
</Card>
|
||||
<Card title="SDK reference" icon="book-open" href="/sdk">
|
||||
How to use the SDK. This includes all the available Tasks, triggers and actions you can use.
|
||||
</Card>
|
||||
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
---
|
||||
title: Example Jobs
|
||||
description: "Jobs and projects you can use to get started."
|
||||
description: "Jobs and code examples you can use to get started."
|
||||
---
|
||||
|
||||
<Card
|
||||
title="Browse our Jobs Showcase"
|
||||
icon="rocket-launch"
|
||||
href="https://trigger.dev/showcase"
|
||||
color="#EC4899"
|
||||
>
|
||||
The showcase is our ever-growing library of Jobs. Use them as they are, right out of the box, or
|
||||
customize them to suit your needs.
|
||||
</Card>
|
||||
<CardGroup>
|
||||
<Card
|
||||
title="Browse our Jobs Showcase"
|
||||
icon="rocket-launch"
|
||||
href="https://trigger.dev/showcase"
|
||||
color="#EC4899"
|
||||
>
|
||||
The showcase is our library of Jobs. Use them as they are, right out of the box, or customize
|
||||
them to suit your needs.
|
||||
</Card>
|
||||
<Card title="API catalog with code samples" icon="code" href="https://trigger.dev/apis">
|
||||
Find code examples for the API you need that you can copy and paste into your
|
||||
projects.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Full projects
|
||||
|
||||
@@ -24,6 +30,6 @@ To run them, simply follow the instructions in the README files linked below.
|
||||
| [OpenAI text summarizer](https://github.com/triggerdotdev/examples/tree/main/openai-text-summarizer) | An app which uses OpenAI to summarize an article and then post the result to Slack. | [OpenAI](https://trigger.dev/docs/integrations/apis/openai) [Slack](https://trigger.dev/docs/integrations/apis/slack) | Trigger.dev | ✅ |
|
||||
| [Supabase onboarding emails](https://github.com/triggerdotdev/examples/tree/main/supabase-onboarding-emails) | When a user signs up and confirms their email address, they will receive 3 "onboarding" emails over 2 days using Resend.com and Trigger.dev | [Supabase](https://trigger.dev/docs/integrations/apis/supabase) [Resend](https://trigger.dev/docs/integrations/apis/resend) | Trigger.dev | ✅ |
|
||||
| [Generate presentation titles using OpenAI](https://github.com/triggerdotdev/examples/tree/main/express-vanilla) | Generate presentation titles using OpenAI background jobs with Node.js, Express and Trigger.dev | [OpenAI](https://trigger.dev/docs/integrations/apis/openai) | [lirantal](https://github.com/lirantal) | ✅ |
|
||||
| [Send a basic email with Resend](https://github.com/triggerdotdev/examples/tree/main/resend-email-form) | Send a basic email from a form with Resend | [Resend](https://trigger.dev/docs/integrations/apis/resend) | Trigger.dev | ✅ |
|
||||
| [Send a basic email with Resend](https://github.com/triggerdotdev/examples/tree/main/resend-email-form) | Send a basic email from a form with Resend | [Resend](https://trigger.dev/docs/integrations/apis/resend) | Trigger.dev | ✅ |
|
||||
| AI landing page copy generator | Copies your site and generates new copy using OpenAI | [OpenAI](https://trigger.dev/docs/integrations/apis/openai) | Trigger.dev | 🛠️ |
|
||||
| AI changelog generator | Generates a changelog from your GitHub commits using OpenAI | [OpenAI](https://trigger.dev/docs/integrations/apis/openai) [GitHub](https://trigger.dev/docs/integrations/apis/github) | Trigger.dev | 🛠️ |
|
||||
|
||||
@@ -15,9 +15,8 @@ Gets multiple records for a table. You can filter. [Official Airtable Docs](http
|
||||
|
||||
```ts example.ts
|
||||
// Gets multiple records from the table.
|
||||
// Here we only get the Status fields (columns)
|
||||
await table.getRecords("multiple records", {
|
||||
fields: ["Status"],
|
||||
fields: ["Status"], // Here we only get the Status fields (columns)
|
||||
});
|
||||
```
|
||||
|
||||
@@ -39,7 +38,6 @@ Create one or more records in a table. [Official Airtable Docs](https://airtable
|
||||
await table.createRecords("create records", [
|
||||
{
|
||||
// Define the new record to be created
|
||||
// with field values for "Launch goals" and "Status."
|
||||
fields: {
|
||||
"Launch goals": "Created from Trigger.dev",
|
||||
Status: "In progress",
|
||||
@@ -80,16 +78,22 @@ await table.deleteRecords(
|
||||
);
|
||||
```
|
||||
|
||||
### `runTask`
|
||||
|
||||
Do anything that’s possible with the official Airtable Node SDK.
|
||||
|
||||
## Example Usage
|
||||
|
||||
You can optionally add the types for your Table – this gives you nice type inference and errors when writing your code.
|
||||
In this demo we will use the Airtable to get records from a table, create a new record, update it and then delete it.
|
||||
|
||||
```ts
|
||||
// This is the type definition for my Table
|
||||
// Importing necessary modules and packages.
|
||||
import { TriggerClient, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { Airtable } from "@trigger.dev/airtable";
|
||||
import { z } from "zod";
|
||||
|
||||
// Creating an instance of the Airtable client with personal access token obtained from an environment variable.
|
||||
const airtable = new Airtable({
|
||||
id: "airtable",
|
||||
apiKey: process.env.AIRTABLE_PERSONAL_ACCESS_TOKEN!,
|
||||
});
|
||||
|
||||
type LaunchGoalsAndOkRs = {
|
||||
"Launch goals"?: string;
|
||||
DRI?: Collaborator;
|
||||
|
||||
@@ -7,20 +7,17 @@ sidebarTitle: Overview & authentication
|
||||
|
||||
Our Airtable integration allows you to easily connect to the Airtable API and perform tasks such as creating / updating / deleting single or multiple records in your tables.
|
||||
|
||||
For examples of some of the things you can do with it, check out our Jobs Showcase:
|
||||
|
||||
<Card
|
||||
title="Jobs Showcase - Airtable"
|
||||
icon="rocket"
|
||||
href="https://trigger.dev/showcase?tags=&integrations=airtable"
|
||||
href="https://trigger.dev/showcase?tags=&apis=airtable"
|
||||
>
|
||||
Check out pre-built Airtable jobs in our showcase.
|
||||
</Card>
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with the Airtable integration on Trigger.dev, you need to install the `@trigger.dev/airtable` package.
|
||||
You can do this using npm, pnpm, or yarn:
|
||||
To get started with our Airtable integration, you need to install the `@trigger.dev/airtable` packages. You can do this using `npm`, `pnpm`, or `yarn`:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
@@ -44,7 +41,7 @@ To use the Airtable API with Trigger.dev, you can either use OAuth or a Personal
|
||||
|
||||
### OAuth
|
||||
|
||||
To use OAuth you can connect to Airtable via the Trigger.dev [web app](https://cloud.trigger.dev). Click 'Integrations' in the side panel of any project, and configure Airtable with the ID you want to use in your job and the required [scopes](https://airtable.com/developers/web/api/scopes).
|
||||
To use OAuth you can connect to Airtable via the Trigger.dev [web app](https://cloud.trigger.dev). Click 'Integrations' in the side panel of any project, configure Airtable with a unique ID and the required [scopes](https://airtable.com/developers/web/api/scopes).
|
||||
|
||||
```ts
|
||||
import { Airtable } from "@trigger.dev/airtable";
|
||||
@@ -57,7 +54,7 @@ const airtable = new Airtable({
|
||||
|
||||
### Personal Access Token
|
||||
|
||||
You can create an Airtable Personal Access Token [here on their developer site](https://airtable.com/create/tokens).
|
||||
You can create an Airtable Personal Access Token [here](https://airtable.com/create/tokens).
|
||||
|
||||
```ts
|
||||
import { Airtable } from "@trigger.dev/airtable";
|
||||
@@ -73,8 +70,6 @@ const airtable = new Airtable({
|
||||
|
||||
Once you have set up a Airtable client, you can use it to create tasks.
|
||||
|
||||
<CardGroup>
|
||||
<Card title="Tasks" icon="sparkles" href="/integrations/apis/airtable-tasks">
|
||||
Perform tasks such as creating / updating / deleting single or multiple records in table.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
<Card title="Tasks" icon="sparkles" href="/integrations/apis/airtable-tasks">
|
||||
Perform tasks such as creating / updating / deleting single or multiple records in table.
|
||||
</Card>
|
||||
|
||||
@@ -5,22 +5,20 @@ sidebarTitle: Overview & authentication
|
||||
|
||||
## Overview
|
||||
|
||||
Our GitHub integration allows you to create triggers and tasks that interact with GitHub.
|
||||
|
||||
Trigger jobs when events happen, such as when a new issue is added to a repo, a commit is pushed, or a pull request is opened. You can also use the integration to perform tasks such as creating issues, getting information about a repo, adding comments, and much more.
|
||||
|
||||
For examples of some of the things you can do with it, check out our Jobs Showcase:
|
||||
Our GitHub integration allows you to create triggers and tasks that interact with GitHub. Trigger jobs when events happen, like when a new issue is added to a repo, or when a pull request is opened, etc. You can also perform tasks like creating issues, getting information about a repo, adding comments, and much more.
|
||||
|
||||
<Card
|
||||
title="Jobs Showcase - GitHub"
|
||||
icon="rocket"
|
||||
href="https://trigger.dev/showcase?tags=&integrations=github"
|
||||
href="https://trigger.dev/showcase?tags=&apis=github"
|
||||
>
|
||||
Check out pre-built GitHub jobs in our showcase.
|
||||
</Card>
|
||||
|
||||
## Installing the GitHub packages
|
||||
|
||||
To get started with our GitHub integration, you need to install the `@trigger.dev/github` packages. You can do this using `npm`, `pnpm`, or `yarn`:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash npm
|
||||
|
||||
@@ -10,7 +10,7 @@ Our OpenAI integration allows you to easily perform AI-powered tasks, such as su
|
||||
<Card
|
||||
title="Jobs Showcase - OpenAI"
|
||||
icon="rocket"
|
||||
href="https://trigger.dev/showcase?tags=&integrations=openai"
|
||||
href="https://trigger.dev/showcase?tags=&apis=openai"
|
||||
>
|
||||
Check out pre-built OpenAI jobs in our showcase.
|
||||
</Card>
|
||||
|
||||
@@ -8,11 +8,10 @@ sidebarTitle: Overview & authentication
|
||||
Plain is the customer support tool for technical teams and products.
|
||||
It aims to bring engineering and customer service teams together by creating a modern opinionated platform that's fantastic to build with.
|
||||
|
||||
|
||||
<Card
|
||||
title="Jobs Showcase - Plain"
|
||||
icon="rocket"
|
||||
href="https://trigger.dev/showcase?tags=&integrations=plain"
|
||||
href="https://trigger.dev/showcase?tags=&apis=plain"
|
||||
>
|
||||
Check out pre-built Plain jobs in our showcase.
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Resend tasks
|
||||
sidebarTitle: Tasks
|
||||
---
|
||||
|
||||
Tasks are executed after the job is triggered and are the main building blocks of a job. You can string together as many tasks as you want.
|
||||
|
||||
---
|
||||
|
||||
## All tasks
|
||||
|
||||
### `sendEmail`
|
||||
|
||||
Send an email to a recipient with a text payload. [Official Resend Docs](https://resend.com/docs/api-reference/emails/send-email)
|
||||
|
||||
```ts example.ts
|
||||
// Using the 'io.resend.sendEmail' method to send an email using Resend.
|
||||
await io.resend.sendEmail("send-email", {
|
||||
to: <recipient-email-address>, // Recipient's email address.
|
||||
subject: <email-subject>, // Email subject.
|
||||
text: <email-body>, // Plain text content of the email.
|
||||
from: "Your-Name <your-email-address>", // Sender's email address and name.
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Tasks
|
||||
|
||||
| Function Name | Description |
|
||||
| --------------- | -------------------------------- |
|
||||
| `emails.send` | Send an email |
|
||||
| `emails.create` | Create an email |
|
||||
| `emails.get` | Get an email |
|
||||
| `batch.send` | Send a batch of emails at once |
|
||||
| `batch.create` | Create a batch of emails at once |
|
||||
|
||||
## Example
|
||||
|
||||
In this example we use [Zod](/documentation/guides/zod), a TypeScript-first schema declaration and validation library.
|
||||
|
||||
```ts
|
||||
import { Resend } from "@trigger.dev/resend";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
...
|
||||
|
||||
const resend = new Resend({
|
||||
id: "resend",
|
||||
apiKey: process.env.RESEND_API_KEY!,
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "send-resend-email",
|
||||
name: "Send Resend Email",
|
||||
version: "0.1.0",
|
||||
trigger: eventTrigger({
|
||||
name: "send.email",
|
||||
schema: z.object({
|
||||
to: z.union([z.string(), z.array(z.string())]),
|
||||
subject: z.string(),
|
||||
text: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
resend,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.resend.emails.send("send-email", {
|
||||
to: payload.to,
|
||||
subject: payload.subject,
|
||||
text: payload.text,
|
||||
from: "Trigger.dev <hello@email.trigger.dev>",
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -1,10 +1,23 @@
|
||||
---
|
||||
title: Resend
|
||||
title: Resend overview & authentication
|
||||
sidebarTitle: Overview & authentication
|
||||
---
|
||||
|
||||
<Snippet file="integration-getting-started.mdx" />
|
||||
## Overview
|
||||
|
||||
## Installation
|
||||
Resend is the email API for developers. With our Resend integration you can send email campaigns, transactional emails, and automated emails (drip campaigns) from your app.
|
||||
|
||||
<Card
|
||||
title="Jobs Showcase - Resend"
|
||||
icon="rocket"
|
||||
href="https://trigger.dev/showcase?tags=&apis=resend"
|
||||
>
|
||||
Check out pre-built Resend jobs in our showcase.
|
||||
</Card>
|
||||
|
||||
## Installing the Resend packages
|
||||
|
||||
To get started with our Resend integration, you need to install the `@trigger.dev/resend` packages. You can do this using `npm`, `pnpm`, or `yarn`:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
@@ -24,7 +37,7 @@ yarn add @trigger.dev/resend@latest
|
||||
|
||||
## Authentication
|
||||
|
||||
Resend supports API Keys
|
||||
Resend supports API Keys. Official documentation [here](https://resend.com/docs/api-reference/api-keys/create-api-key).
|
||||
|
||||
```ts
|
||||
import { Resend } from "@trigger.dev/resend";
|
||||
@@ -35,54 +48,10 @@ const resend = new Resend({
|
||||
});
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
In this example we use [Zod](/documentation/guides/zod), a TypeScript-first schema declaration and validation library.
|
||||
|
||||
```ts
|
||||
import { Resend } from "@trigger.dev/resend";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
...
|
||||
|
||||
const resend = new Resend({
|
||||
id: "resend",
|
||||
apiKey: process.env.RESEND_API_KEY!,
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "send-resend-email",
|
||||
name: "Send Resend Email",
|
||||
version: "0.1.0",
|
||||
trigger: eventTrigger({
|
||||
name: "send.email",
|
||||
schema: z.object({
|
||||
to: z.union([z.string(), z.array(z.string())]),
|
||||
subject: z.string(),
|
||||
text: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
resend,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.resend.emails.send("send-email", {
|
||||
to: payload.to,
|
||||
subject: payload.subject,
|
||||
text: payload.text,
|
||||
from: "Trigger.dev <hello@email.trigger.dev>",
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Tasks
|
||||
|
||||
| Function Name | Description |
|
||||
| --------------- | -------------------------------- |
|
||||
| `emails.send` | Send an email |
|
||||
| `emails.create` | Create an email |
|
||||
| `emails.get` | Get an email |
|
||||
| `batch.send` | Send a batch of emails at once |
|
||||
| `batch.create` | Create a batch of emails at once |
|
||||
Once you have set up a Resend client, you can use it to create tasks.
|
||||
|
||||
<Card title="Tasks" icon="sparkles" href="/integrations/apis/resend-tasks">
|
||||
Send emails with Resend.
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: SendGrid tasks
|
||||
sidebarTitle: Tasks
|
||||
---
|
||||
|
||||
Tasks are executed after the job is triggered and are the main building blocks of a job. You can string together as many tasks as you want.
|
||||
|
||||
---
|
||||
|
||||
## All tasks
|
||||
|
||||
### `sendEmail`
|
||||
|
||||
Send an email with a body. [Official SendGrid Docs](https://docs.sendgrid.com/for-developers/sending-email)
|
||||
|
||||
```ts example.ts
|
||||
// Using the 'io.sendgrid.sendEmail' method to send an email using SendGrid.
|
||||
await io.sendgrid.sendEmail({
|
||||
to: <recipient-email-address>, // Recipient's email address.
|
||||
from: "Your-Name <your-email-address>", // Sender's email address and name.
|
||||
subject: <email-subject>, // Email subject.
|
||||
text: <email-body>, // Plain text content of the email.
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
In this example, we will send weekly summary emails to users who have `summariesEnabled = true`, at 4pm every Friday, and then posts the total numbers to Slack.
|
||||
|
||||
```ts example.ts
|
||||
import { TriggerClient, cronTrigger } from "@trigger.dev/sdk";
|
||||
import { SendGrid } from "@trigger.dev/sendgrid";
|
||||
import { Slack } from "@trigger.dev/slack";
|
||||
import { weeklySummaryDb } from "./mocks/db";
|
||||
import { weeklySummaryEmail } from "./mocks/emails";
|
||||
|
||||
// Creating instances of SendGrid and Slack clients.
|
||||
const sendgrid = new SendGrid({
|
||||
id: "sendgrid",
|
||||
apiKey: process.env.SENDGRID_API_KEY!,
|
||||
});
|
||||
|
||||
const slack = new Slack({ id: "slack" });
|
||||
|
||||
// Defining a job that sends a weekly summary email to users and posts total numbers to Slack.
|
||||
client.defineJob({
|
||||
id: "weekly-user-activity-summary", // Unique identifier for the job.
|
||||
name: "Weekly user activity summary", // A specific name for the job.
|
||||
version: "1.0.0", // Version number for the job.
|
||||
integrations: { sendgrid, slack }, // Integrating SendGrid and Slack clients into this job.
|
||||
trigger: cronTrigger({
|
||||
// Setting a cron schedule to run the job every Friday at 4 pm.
|
||||
cron: "0 16 * * 5",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
// Inside the 'run' function, several operations are performed to send weekly summaries and post to Slack.
|
||||
|
||||
// Retrieving a list of users from the weeklySummaryDb.
|
||||
const users = await weeklySummaryDb.getUsers();
|
||||
|
||||
let sentCount = 0;
|
||||
let notSentCount = 0;
|
||||
|
||||
// Iterating through the list of users.
|
||||
for (const user of users) {
|
||||
if (user.summariesEnabled) {
|
||||
// Sending a weekly summary email to users with summaries enabled.
|
||||
await io.sendgrid.sendEmail(`Weekly summary for ${user.id}`, {
|
||||
to: user.email,
|
||||
from: "hello@acme.inc", // The sender's email.
|
||||
subject: "Your weekly summary",
|
||||
html: weeklySummaryEmail(user), // HTML content for the email.
|
||||
});
|
||||
sentCount++;
|
||||
} else {
|
||||
notSentCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Posting a message to Slack with a summary of the sent and unsent emails.
|
||||
await io.slack.postMessage("Notify team", {
|
||||
text: `Weekly summary sent to ${sentCount} users and not sent to ${notSentCount} users`,
|
||||
channel: "YOUR_CHANNEL_ID", // Specify the Slack channel ID for posting the message.
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -1,10 +1,23 @@
|
||||
---
|
||||
title: SendGrid
|
||||
title: SendGrid overview & authentication
|
||||
sidebarTitle: Overview & authentication
|
||||
---
|
||||
|
||||
<Snippet file="integration-getting-started.mdx" />
|
||||
## Overview
|
||||
|
||||
## Installation
|
||||
SendGrid is a cloud-based SMTP provider that allows you to send email without having to maintain email servers. With our SendGrid integration you can send email campaigns, transactional emails, and automated emails (drip campaigns) from your app.
|
||||
|
||||
<Card
|
||||
title="Jobs Showcase - SendGrid"
|
||||
icon="rocket"
|
||||
href="https://trigger.dev/showcase?tags=&apis=sendgrid"
|
||||
>
|
||||
Check out pre-built SendGrid jobs in our showcase.
|
||||
</Card>
|
||||
|
||||
## Installing the SendGrid packages
|
||||
|
||||
To get started with our SendGrid integration, you need to install the `@trigger.dev/sendgrid` packages. You can do this using `npm`, `pnpm`, or `yarn`:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
@@ -24,7 +37,7 @@ yarn add @trigger.dev/sendgrid@latest
|
||||
|
||||
## Authentication
|
||||
|
||||
SendGrid integration supports API Keys. To authenticate, you'll need to create an instance of the SendGrid class and provide your API key.
|
||||
SendGrid supports API Keys. Official documentation [here](https://docs.sendgrid.com/ui/account-and-settings/api-keys).
|
||||
|
||||
```ts
|
||||
import { SendGrid } from "@trigger.dev/sendgrid";
|
||||
@@ -35,50 +48,12 @@ const sendgrid = new SendGrid({
|
||||
});
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
In this example we use [Zod](/documentation/guides/zod), a TypeScript-first schema declaration and validation library.
|
||||
|
||||
```ts
|
||||
import { SendGrid } from "@trigger.dev/sendgrid";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
// Create an instance of SendGrid
|
||||
const sendgrid = new SendGrid({
|
||||
id: "sendgrid",
|
||||
apiKey: process.env.SENDGRID_API_KEY!,
|
||||
});
|
||||
|
||||
// Define a Trigger.dev job
|
||||
client.defineJob({
|
||||
id: "send-sendgrid-email",
|
||||
name: "Send SendGrid Email",
|
||||
version: "0.1.0",
|
||||
trigger: eventTrigger({
|
||||
name: "send.email",
|
||||
schema: z.object({
|
||||
to: z.string(),
|
||||
subject: z.string(),
|
||||
text: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
sendgrid,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.sendgrid.sendEmail({
|
||||
to: payload.to,
|
||||
from: "Trigger.dev <hello@email.trigger.dev>",
|
||||
subject: payload.subject,
|
||||
text: payload.text,
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Tasks
|
||||
|
||||
| Function Name | Description |
|
||||
| ------------- | ------------- |
|
||||
| `sendEmail` | Send an email |
|
||||
Once you have set up a SendGrid client, you can use it to create tasks.
|
||||
|
||||
{" "}
|
||||
|
||||
<Card title="Tasks" icon="sparkles" href="/integrations/apis/sendgrid-tasks">
|
||||
Send emails with SendGrid.
|
||||
</Card>
|
||||
|
||||
@@ -7,12 +7,10 @@ sidebarTitle: Overview & authentication
|
||||
|
||||
Our Slack integration allows you to connect to the Slack API and post messages to Slack.
|
||||
|
||||
For examples of some of the things you can do with Slack, check out our Jobs Showcase:
|
||||
|
||||
<Card
|
||||
title="Jobs Showcase - Slack"
|
||||
icon="rocket"
|
||||
href="https://trigger.dev/showcase?tags=&integrations=slack"
|
||||
href="https://trigger.dev/showcase?tags=&apis=slack"
|
||||
>
|
||||
Check out pre-built Slack jobs in our showcase.
|
||||
</Card>
|
||||
@@ -55,8 +53,8 @@ const slack = new Slack({
|
||||
|
||||
Once you have set up a Slack client, you can use it to create tasks.
|
||||
|
||||
<CardGroup>
|
||||
<Card title="Tasks" icon="sparkles" href="/integrations/apis/slack-tasks">
|
||||
Perform tasks such as posting messages to a channel.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
{" "}
|
||||
|
||||
<Card title="Tasks" icon="sparkles" href="/integrations/apis/slack-tasks">
|
||||
Perform tasks such as posting messages to a channel.
|
||||
</Card>
|
||||
|
||||
@@ -22,8 +22,9 @@ description: "Integrations make it easy to authenticate and use APIs."
|
||||
<Card title="How to create your own Integrations" icon="paintbrush" href="/integrations/create">
|
||||
You can create your own Integrations (and optionally share them with others)
|
||||
</Card>
|
||||
<Card title="Example repo & Jobs" icon="code" href="/examples/introduction">
|
||||
Check out example Jobs using our integrations.{" "}
|
||||
<Card title="API catalog with code examples" icon="code" href="https://trigger.dev/apis">
|
||||
Use any API with an existing Node SDK or even just using fetch. Find the right API for your
|
||||
project here.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
|
||||
+19
-33
@@ -1,9 +1,7 @@
|
||||
{
|
||||
"$schema": "https://mintlify.com/schema.json",
|
||||
"name": "Trigger.dev",
|
||||
"openapi": [
|
||||
"/openapi.yml"
|
||||
],
|
||||
"openapi": ["/openapi.yml"],
|
||||
"logo": {
|
||||
"dark": "/logo/dark.png",
|
||||
"light": "/logo/light.png",
|
||||
@@ -255,10 +253,7 @@
|
||||
"pages": [
|
||||
{
|
||||
"group": "Airtable",
|
||||
"pages": [
|
||||
"integrations/apis/airtable",
|
||||
"integrations/apis/airtable-tasks"
|
||||
]
|
||||
"pages": ["integrations/apis/airtable", "integrations/apis/airtable-tasks"]
|
||||
},
|
||||
{
|
||||
"group": "GitHub",
|
||||
@@ -284,20 +279,20 @@
|
||||
},
|
||||
{
|
||||
"group": "Plain",
|
||||
"pages": [
|
||||
"integrations/apis/plain",
|
||||
"integrations/apis/plain-tasks"
|
||||
]
|
||||
"pages": ["integrations/apis/plain", "integrations/apis/plain-tasks"]
|
||||
},
|
||||
"integrations/apis/replicate",
|
||||
"integrations/apis/resend",
|
||||
"integrations/apis/sendgrid",
|
||||
{
|
||||
"group": "SendGrid",
|
||||
"pages": ["integrations/apis/sendgrid", "integrations/apis/sendgrid-tasks"]
|
||||
},
|
||||
{
|
||||
"group": "Resend",
|
||||
"pages": ["integrations/apis/resend", "integrations/apis/resend-tasks"]
|
||||
},
|
||||
{
|
||||
"group": "Slack",
|
||||
"pages": [
|
||||
"integrations/apis/slack",
|
||||
"integrations/apis/slack-tasks"
|
||||
]
|
||||
"pages": ["integrations/apis/slack", "integrations/apis/slack-tasks"]
|
||||
},
|
||||
"integrations/apis/stripe",
|
||||
{
|
||||
@@ -334,7 +329,8 @@
|
||||
"sdk/triggerclient/instancemethods/define-http-endpoint",
|
||||
"sdk/triggerclient/instancemethods/define-dynamic-trigger",
|
||||
"sdk/triggerclient/instancemethods/define-dynamic-schedule",
|
||||
"sdk/triggerclient/instancemethods/define-auth-resolver"
|
||||
"sdk/triggerclient/instancemethods/define-auth-resolver",
|
||||
"sdk/triggerclient/instancemethods/on"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -377,10 +373,7 @@
|
||||
"sdk/dynamictrigger/constructor",
|
||||
{
|
||||
"group": "Instance methods",
|
||||
"pages": [
|
||||
"sdk/dynamictrigger/register",
|
||||
"sdk/dynamictrigger/unregister"
|
||||
]
|
||||
"pages": ["sdk/dynamictrigger/register", "sdk/dynamictrigger/unregister"]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -391,10 +384,7 @@
|
||||
"sdk/dynamicschedule/constructor",
|
||||
{
|
||||
"group": "Instance methods",
|
||||
"pages": [
|
||||
"sdk/dynamicschedule/register",
|
||||
"sdk/dynamicschedule/unregister"
|
||||
]
|
||||
"pages": ["sdk/dynamicschedule/register", "sdk/dynamicschedule/unregister"]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -406,9 +396,7 @@
|
||||
},
|
||||
{
|
||||
"group": "HTTP Reference",
|
||||
"pages": [
|
||||
"sdk/api-reference/events/create-an-event"
|
||||
]
|
||||
"pages": ["sdk/api-reference/events/create-an-event"]
|
||||
},
|
||||
{
|
||||
"group": "React SDK",
|
||||
@@ -422,9 +410,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": [
|
||||
"examples/introduction"
|
||||
]
|
||||
"pages": ["examples/introduction"]
|
||||
}
|
||||
],
|
||||
"footerSocials": {
|
||||
@@ -437,4 +423,4 @@
|
||||
"apiKey": "phc_hwYmedO564b3Ik8nhA4Csrb5SueY0EwFJWCbseGwWW"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ sidebarTitle: "waitForEvent()"
|
||||
description: "`io.waitForEvent()` waits for the next event to be emitted, and returns the event data"
|
||||
---
|
||||
|
||||
<Warning>This feature is in beta and has not yet been deployed to the Trigger.dev cloud</Warning>
|
||||
|
||||
## Parameters
|
||||
|
||||
<Snippet file="stable-key-param.mdx" />
|
||||
|
||||
@@ -26,6 +26,29 @@ client.defineJob({
|
||||
});
|
||||
```
|
||||
|
||||
```ts notifications
|
||||
client.defineJob({
|
||||
id: "github-integration-on-issue",
|
||||
name: "GitHub Integration - On Issue",
|
||||
version: "0.1.0",
|
||||
trigger: github.triggers.repo({
|
||||
event: events.onIssue,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
onSuccess: async (notification) => {
|
||||
console.log("Job succeeded", notification);
|
||||
},
|
||||
onFailure: async (notification) => {
|
||||
console.log("Job failed", notification);
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</RequestExample>
|
||||
|
||||
# Constructor
|
||||
|
||||
@@ -40,6 +40,9 @@ This document describes the payload of a Run's completion webhook.
|
||||
<ResponseField name="completedAt" type="Date" required>
|
||||
When the Run started
|
||||
</ResponseField>
|
||||
<ResponseField name="isTest" type="boolean" required>
|
||||
Whether the Run was a test Run
|
||||
</ResponseField>
|
||||
<ResponseField name="executionDurationInMs" type="number" required>
|
||||
The duration of the Run in milliseconds
|
||||
</ResponseField>
|
||||
@@ -47,6 +50,24 @@ This document describes the payload of a Run's completion webhook.
|
||||
The number of individual function executions performed to complete the Run
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="invocation" type="object" required>
|
||||
Metadata about the Invocation / Run
|
||||
<Expandable title="properties">
|
||||
<ResponseField name="id" type="string" required>
|
||||
The Run ID
|
||||
</ResponseField>
|
||||
<ResponseField name="payload" type="any" required>
|
||||
The payload that was passed to the Run
|
||||
</ResponseField>
|
||||
<ResponseField name="timestamp" type="Date" required>
|
||||
The timestamp of the event/Run
|
||||
</ResponseField>
|
||||
<ResponseField name="context" type="any">
|
||||
The context that was passed to the Run
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="job" type="object" required>
|
||||
Metadata about the Job
|
||||
<Expandable title="properties">
|
||||
@@ -59,6 +80,33 @@ This document describes the payload of a Run's completion webhook.
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="task" type="object">
|
||||
Metadata about the Task that failed, only exists if the Run failed
|
||||
<Expandable title="properties">
|
||||
<ResponseField name="id" type="string" required>
|
||||
The Task ID
|
||||
</ResponseField>
|
||||
<ResponseField name="cacheKey" type="string">
|
||||
The Task's cache key
|
||||
</ResponseField>
|
||||
<ResponseField name="error" type="Error" required>
|
||||
And Error-like object that caused the Task to fail
|
||||
</ResponseField>
|
||||
<ResponseField name="status" type="string" required>
|
||||
The Task's status
|
||||
</ResponseField>
|
||||
<ResponseField name="name" type="string">
|
||||
The Task's name
|
||||
</ResponseField>
|
||||
<ResponseField name="startedAt" type="Date">
|
||||
When the Task started
|
||||
</ResponseField>
|
||||
<ResponseField name="params" type="any">
|
||||
The Task's params
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="environment" type="object" required>
|
||||
Metadata about the Environment
|
||||
<Expandable title="properties">
|
||||
|
||||
@@ -24,6 +24,29 @@ client.defineJob({
|
||||
});
|
||||
```
|
||||
|
||||
```ts notifications
|
||||
client.defineJob({
|
||||
id: "github-integration-on-issue",
|
||||
name: "GitHub Integration - On Issue",
|
||||
version: "0.1.0",
|
||||
trigger: github.triggers.repo({
|
||||
event: events.onIssue,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
onSuccess: async (notification) => {
|
||||
console.log("Job succeeded", notification);
|
||||
},
|
||||
onFailure: async (notification) => {
|
||||
console.log("Job failed", notification);
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</RequestExample>
|
||||
|
||||
## Parameters
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: "TriggerClient: on() instance method"
|
||||
sidebarTitle: "on()"
|
||||
description: "Use the `on()` method to listen for run notifications across all Jobs."
|
||||
---
|
||||
|
||||
You can subscribe to run notifications across all your Jobs using the `on()` instance method, which currently supports two events:
|
||||
|
||||
- `runSucceeded`: Triggered when a run succeeds.
|
||||
- `runFailed`: Triggered when a run fails.
|
||||
|
||||
Both events receive a [Run Notification](/sdk/run-notification) object as their only parameter.
|
||||
|
||||
You can use the notification to determine which job the run belongs to, access the run's payload, output, errors, and more.
|
||||
|
||||
If you want to subscribe to just a single Job, see the [defineJob](/sdk/triggerclient/instancemethods/define-job) docs.
|
||||
|
||||
## Parameters
|
||||
|
||||
<ParamField body="event" type="runSucceeded | runFailed" required>
|
||||
The notification event to listen for.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="callback" type="function" required>
|
||||
The callback function to run when the event is triggered. Receives a [Run
|
||||
Notification](/sdk/run-notification) object as it's only parameter.
|
||||
</ParamField>
|
||||
|
||||
<RequestExample>
|
||||
|
||||
```ts example
|
||||
export const client = new TriggerClient({
|
||||
id: "my-project",
|
||||
apiKey: process.env.TRIGGER_API_KEY,
|
||||
});
|
||||
|
||||
client.on("runSucceeeded", async (notification) => {
|
||||
console.log(`Run on job ${notification.job.id} succeeded`);
|
||||
});
|
||||
|
||||
client.on("runFailed", async (notification) => {
|
||||
console.log(`Run on job ${notification.job.id} failed`);
|
||||
});
|
||||
```
|
||||
|
||||
</RequestExample>
|
||||
@@ -83,3 +83,7 @@ The `defineDynamicSchedule()` method defines a new Dynamic Schedule.
|
||||
#### [defineAuthResolver()](/sdk/triggerclient/instancemethods/define-auth-resolver)
|
||||
|
||||
The `defineAuthResolver()` method defines a new Auth Resolver.
|
||||
|
||||
#### [on()](/sdk/triggerclient/instancemethods/on)
|
||||
|
||||
Use the `on()` method to listen for run notifications across all Jobs.
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/airtable
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/airtable",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "Trigger.dev integration for airtable",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"airtable": "^0.12.1",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/github
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/github",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "The official GitHub integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -29,8 +29,8 @@
|
||||
"@octokit/request": "^6.2.5",
|
||||
"@octokit/request-error": "^4.0.1",
|
||||
"@octokit/webhooks": "^10.4.0",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"octokit": "^2.0.14",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/linear
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/linear",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "Trigger.dev integration for @linear/sdk",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -27,8 +27,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@linear/sdk": "^8.0.0",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/slack
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/openai",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "The official OpenAI integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -31,8 +31,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"openai": "^4.16.1",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6"
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/plain
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/plain",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "The official Plain.com integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -24,8 +24,8 @@
|
||||
"build:tsup": "tsup"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"@team-plain/typescript-sdk": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/replicate
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/replicate",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "Trigger.dev integration for replicate",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"replicate": "^0.18.1",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/resend
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/resend",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "The official Resend.com integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -24,8 +24,8 @@
|
||||
"build:tsup": "tsup"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"resend": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/sendgrid
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/sendgrid",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "Trigger.dev integration for @sendgrid/mail",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -27,8 +27,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6"
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.8.0"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/slack
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/slack",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "The official Slack integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@slack/web-api": "^6.8.1",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/stripe
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/stripe",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "Trigger.dev integration for stripe",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"stripe": "^12.14.0",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/supabase
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/supabase",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "Trigger.dev integration for @supabase/supabase-js",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -27,8 +27,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.26.0",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"supabase-management-js": "^0.1.4",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/typeform
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
- @trigger.dev/integration-kit@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/typeform",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "The official Typeform integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -25,8 +25,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.6",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.2.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7",
|
||||
"@typeform/api-client": "^1.8.0",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/astro
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/sdk@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@trigger.dev/astro",
|
||||
"description": "An Astro-native integration for Trigger.dev background jobs platform",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
@@ -20,7 +20,7 @@
|
||||
"build:tsup": "tsup"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@trigger.dev/sdk": "workspace:^2.2.6"
|
||||
"@trigger.dev/sdk": "workspace:^2.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^3.0.12",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# create-trigger
|
||||
|
||||
## 2.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/core@2.2.7
|
||||
|
||||
## 2.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/cli",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"description": "The Trigger.dev CLI",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user