diff --git a/docs/examples/dall-e3-generate-image.mdx b/docs/examples/dall-e3-generate-image.mdx index abcab2cf78..5846be703f 100644 --- a/docs/examples/dall-e3-generate-image.mdx +++ b/docs/examples/dall-e3-generate-image.mdx @@ -29,7 +29,6 @@ export const generateContent = task({ maxAttempts: 3, // Retry up to 3 times }, run: async ({ theme, description }: Payload) => { - // Generate text const textResult = await openai.chat.completions.create({ model: "gpt-4o", @@ -64,4 +63,15 @@ function generateTextPrompt(theme: string, description: string): any { function generateImagePrompt(theme: string, description: string): any { return `Theme: ${theme}\n\nDescription: ${description}`; } -``` \ No newline at end of file +``` + +## Testing your task + +To test this task in the dashboard, you can use the following payload: + +```json +{ + "theme": "A beautiful sunset", + "description": "A sunset over the ocean with a tiny yacht in the distance." +} +``` diff --git a/docs/examples/ffmpeg-video-processing.mdx b/docs/examples/ffmpeg-video-processing.mdx index 33665b77d3..a5a33a0100 100644 --- a/docs/examples/ffmpeg-video-processing.mdx +++ b/docs/examples/ffmpeg-video-processing.mdx @@ -98,14 +98,13 @@ export const ffmpegCompressVideo = task({ // Read the compressed video const compressedVideo = await fs.readFile(outputPath); - const compressedSize = compressedVideo.length; // Log compression results logger.log(`Compressed video size: ${compressedSize} bytes`); - logger.log(`Compressed video saved at: ${outputPath}`); + logger.log(`Temporary compressed video file created`, { outputPath }); - // Upload the compressed video to S3, replacing slashes with underscores + // Create the r2Key for the extracted audio, using the base name of the output path const r2Key = `processed-videos/${path.basename(outputPath)}`; const uploadParams = { @@ -116,22 +115,31 @@ export const ffmpegCompressVideo = task({ // Upload the video to R2 and get the URL await s3Client.send(new PutObjectCommand(uploadParams)); - const r2Url = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}/${r2Key}`; - logger.log("Compressed video uploaded to R2", { url: r2Url }); + logger.log(`Compressed video saved to your r2 bucket`, { r2Key }); // Delete the temporary compressed video file await fs.unlink(outputPath); + logger.log(`Temporary compressed video file deleted`, { outputPath }); - // Return the compressed video file path, compressed size, and S3 URL + // Return the compressed video buffer and r2 key return { - compressedVideoPath: outputPath, - compressedSize, - r2Url, + Bucket: process.env.R2_BUCKET, + r2Key, }; }, }); ``` +### Testing: + +To test this task, use this payload structure: + +```json +{ + "videoUrl": "" +} +``` + ## Extract audio from a video using FFmpeg This task demonstrates how to use FFmpeg to extract audio from a video, convert it to WAV format, and upload it to R2 storage. @@ -145,11 +153,6 @@ This task demonstrates how to use FFmpeg to extract audio from a video, convert ### Task code - - When testing, make sure to provide a video URL that contains audio. If the video does not have - audio, the task will fail. - - ```ts trigger/ffmpeg-extract-audio.ts import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; import { logger, task } from "@trigger.dev/sdk/v3"; @@ -176,63 +179,81 @@ export const ffmpegExtractAudio = task({ run: async (payload: { videoUrl: string }) => { const { videoUrl } = payload; - // Generate temporary and output file names + // Generate temporary file names const tempDirectory = os.tmpdir(); - const outputPath = path.join(tempDirectory, `output_${Date.now()}.wav`); + const outputPath = path.join(tempDirectory, `audio_${Date.now()}.wav`); // Fetch the video const response = await fetch(videoUrl); - // Convert the video to WAV + // Extract the audio await new Promise((resolve, reject) => { if (!response.body) { return reject(new Error("Failed to fetch video")); } + ffmpeg(Readable.from(response.body)) - .toFormat("wav") - .save(outputPath) - .on("end", () => { - logger.log(`WAV file saved to ${outputPath}`); - resolve(outputPath); - }) - .on("error", (err) => { - reject(err); - }); + .outputOptions([ + "-vn", // Disable video output + "-acodec pcm_s16le", // Use PCM 16-bit little-endian encoding + "-ar 44100", // Set audio sample rate to 44.1 kHz + "-ac 2", // Set audio channels to stereo + ]) + .output(outputPath) + .on("end", resolve) + .on("error", reject) + .run(); }); - // Read the WAV file - const wavBuffer = await fs.readFile(outputPath); + // Read the extracted audio + const audioBuffer = await fs.readFile(outputPath); + const audioSize = audioBuffer.length; - // Log the output file path - logger.log(`Converted video saved at: ${outputPath}`); + // Log audio extraction results + logger.log(`Extracted audio size: ${audioSize} bytes`); + logger.log(`Temporary audio file created`, { outputPath }); - // Upload the compressed video to S3, replacing slashes with underscores - const r2Key = `processed-audio/${path.basename(outputPath)}`; + // Create the r2Key for the extracted audio, using the base name of the output path + const r2Key = `extracted-audio/${path.basename(outputPath)}`; const uploadParams = { Bucket: process.env.R2_BUCKET, Key: r2Key, - Body: wavBuffer, + Body: audioBuffer, }; // Upload the audio to R2 and get the URL await s3Client.send(new PutObjectCommand(uploadParams)); - const r2Url = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}/${r2Key}`; - logger.log("Extracted audio uploaded to R2", { url: r2Url }); + logger.log(`Extracted audio saved to your R2 bucket`, { r2Key }); - // Delete the temporary file + // Delete the temporary audio file await fs.unlink(outputPath); + logger.log(`Temporary audio file deleted`, { outputPath }); - // Return the WAV buffer and file path + // Return the audio file path, size, and R2 URL return { - wavBuffer, - wavFilePath: outputPath, - r2Url, + Bucket: process.env.R2_BUCKET, + r2Key, }; }, }); ``` +### Testing: + +To test this task, use this payload structure: + + + Make sure to provide a video URL that contains audio. If the video does not have audio, the task + will fail. + + +```json +{ + "videoUrl": "" +} +``` + ## Generate a thumbnail from a video using FFmpeg This task demonstrates how to use FFmpeg to generate a thumbnail from a video at a specific time and upload the generated thumbnail to R2 storage. @@ -298,7 +319,7 @@ export const ffmpegGenerateThumbnail = task({ // Read the generated thumbnail const thumbnail = await fs.readFile(outputPath); - // Upload the compressed video to S3, replacing slashes with underscores + // Create the r2Key for the extracted audio, using the base name of the output path const r2Key = `thumbnails/${path.basename(outputPath)}`; const uploadParams = { @@ -318,7 +339,7 @@ export const ffmpegGenerateThumbnail = task({ // Log thumbnail generation results logger.log(`Thumbnail uploaded to S3: ${r2Url}`); - // Return the thumbnail buffer, file path, sizes, and S3 URL + // Return the thumbnail buffer, path, and R2 URL return { thumbnailBuffer: thumbnail, thumbnailPath: outputPath, @@ -327,3 +348,13 @@ export const ffmpegGenerateThumbnail = task({ }, }); ``` + +## Testing your task + +To test this task in the dashboard, you can use the following payload: + +```json +{ + "videoUrl": "" +} +``` diff --git a/docs/examples/intro.mdx b/docs/examples/intro.mdx new file mode 100644 index 0000000000..e9881ebdf3 --- /dev/null +++ b/docs/examples/intro.mdx @@ -0,0 +1,15 @@ +--- +title: "Introduction" +sidebarTitle: "Introduction" +description: "Learn how to use Trigger.dev with these practical task examples." +--- + +| Example task | Description | +| :------------------------------------------------------------ | :-------------------------------------------------------------------------- | +| [DALL·E 3 image generation](/examples/dall-e3-generate-image) | Use OpenAI's GPT-4o and DALL·E 3 to generate an image and text. | +| [FFmpeg video processing](/examples/ffmpeg-video-processing) | Use FFmpeg to process a video in various ways and save it to Cloudflare R2. | +| [OpenAI with retrying](/examples/open-ai-with-retrying) | Create a reusable OpenAI task with custom retry options. | +| [React to PDF](/examples/react-pdf) | Use `react-pdf` to generate a PDF and save it to Cloudflare R2. | +| [Resend email sequence](/examples/resend-email-sequence) | Send a sequence of emails over several days using Resend with Trigger.dev. | +| [Sharp image processing](/examples/sharp-image-processing) | Use Sharp to process an image and save it to Cloudflare R2. | +| [Vercel AI SDK](/examples/vercel-ai-sdk) | Use Vercel AI SDK to generate text using OpenAI. | diff --git a/docs/examples/open-ai-with-retrying.mdx b/docs/examples/open-ai-with-retrying.mdx index c89ca4bdc8..2a0ae1f6be 100644 --- a/docs/examples/open-ai-with-retrying.mdx +++ b/docs/examples/open-ai-with-retrying.mdx @@ -43,5 +43,14 @@ export const openaiTask = task({ return chatCompletion.choices[0].message.content; }, }); +``` -``` \ No newline at end of file +## Testing your task + +To test this task in the dashboard, you can use the following payload: + +```json +{ + "prompt": "What is the meaning of life?" +} +``` diff --git a/docs/examples/react-pdf.mdx b/docs/examples/react-pdf.mdx index 165e938476..cfda52faa7 100644 --- a/docs/examples/react-pdf.mdx +++ b/docs/examples/react-pdf.mdx @@ -10,16 +10,18 @@ This example demonstrates how to use Trigger.dev to generate a PDF using `react- ## Task code -```ts trigger/generateResumePDF.ts + This example must be a .tsx file to use React components. + +```ts trigger/generateResumePDF.tsx import { logger, task } from "@trigger.dev/sdk/v3"; -import { Document, Page, Text, View } from "@react-pdf/renderer"; -import { renderToBuffer } from "@react-pdf/renderer"; +import { renderToBuffer, Document, Page, Text, View } from "@react-pdf/renderer"; import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; -// Initialize S3 client -const s3Client = new S3Client({ +// Initialize R2 client +const r2Client = new S3Client({ + // How to authenticate to R2: https://developers.cloudflare.com/r2/api/s3/tokens/ region: "auto", - endpoint: process.env.S3_ENDPOINT, + endpoint: process.env.R2_ENDPOINT, credentials: { accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "", secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "", @@ -29,6 +31,7 @@ const s3Client = new S3Client({ export const generateResumePDF = task({ id: "generate-resume-pdf", run: async (payload: { text: string }) => { + // Log the payload logger.log("Generating PDF resume", payload); // Render the ResumeDocument component to a PDF buffer @@ -42,28 +45,41 @@ export const generateResumePDF = task({ ); - // Generate a unique filename - const filename = `${payload.text - .replace(/\s+/g, "-") - .toLowerCase()}-${Date.now()}.pdf`; + // Generate a unique filename based on the text and current timestamp + const filename = `${payload.text.replace(/\s+/g, "-").toLowerCase()}-${Date.now()}.pdf`; + + // Set the R2 key for the PDF file + const r2Key = `resumes/${filename}`; - // Upload to R2 - const s3Key = `resumes/${filename}`; + // Set the upload parameters for R2 const uploadParams = { - Bucket: process.env.S3_BUCKET, - Key: s3Key, + Bucket: process.env.R2_BUCKET, + Key: r2Key, Body: pdfBuffer, ContentType: "application/pdf", }; + // Log the upload parameters logger.log("Uploading to R2 with params", uploadParams); - // Upload the PDF to R2 and return the URL. - await s3Client.send(new PutObjectCommand(uploadParams)); - const s3Url = `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${s3Key}`; - logger.log("PDF uploaded to R2", { url: s3Url }); - return { pdfUrl: s3Url }; + // Upload the PDF to R2 + await r2Client.send(new PutObjectCommand(uploadParams)); + + // Return the Bucket and R2 key for the uploaded PDF + return { + Bucket: process.env.R2_BUCKET, + Key: r2Key, + }; }, }); +``` + +## Testing your task + +To test this task in the dashboard, you can use the following payload: -``` \ No newline at end of file +```json +{ + "text": "Hello, world!" +} +``` diff --git a/docs/examples/resend-email-sequence.mdx b/docs/examples/resend-email-sequence.mdx index 522aaa2be7..2efe8b802e 100644 --- a/docs/examples/resend-email-sequence.mdx +++ b/docs/examples/resend-email-sequence.mdx @@ -22,7 +22,7 @@ export const emailSequence = task({ run: async (payload: { userId: string; email: string; name: string }) => { console.log(`Start email sequence for user ${payload.userId}`, payload); - //send the first email immediately + // Send the first email immediately const firstEmailResult = await retry.onThrow( async ({ attempt }) => { const { data, error } = await resend.emails.send({ @@ -33,7 +33,7 @@ export const emailSequence = task({ }); if (error) { - //throwing an error will trigger a retry of this block + // Throwing an error will trigger a retry of this block throw error; } @@ -42,10 +42,10 @@ export const emailSequence = task({ { maxAttempts: 3 } ); - //then wait 3 days + // Then wait 3 days await wait.for({ days: 3 }); - //send the second email + // Send the second email const secondEmailResult = await retry.onThrow( async ({ attempt }) => { const { data, error } = await resend.emails.send({ @@ -56,7 +56,7 @@ export const emailSequence = task({ }); if (error) { - //throwing an error will trigger a retry of this block + // Throwing an error will trigger a retry of this block throw error; } @@ -68,5 +68,16 @@ export const emailSequence = task({ //etc... }, }); +``` -``` \ No newline at end of file +## Testing your task + +To test this task in the dashboard, you can use the following payload: + +```json +{ + "userId": "123", + "email": "", // Replace with your test email + "name": "Alice Testington" +} +``` diff --git a/docs/examples/sharp-image-processing.mdx b/docs/examples/sharp-image-processing.mdx index ba4bf58567..76d781f6ba 100644 --- a/docs/examples/sharp-image-processing.mdx +++ b/docs/examples/sharp-image-processing.mdx @@ -119,3 +119,14 @@ export const sharpProcessImage = task({ }, }); ``` + +## Testing your task + +To test this task in the dashboard, you can use the following payload: + +```json +{ + "imageUrl": "", // Replace with a URL to a JPEG image + "watermarkUrl": "" // Replace with a URL to a PNG watermark image +} +``` diff --git a/docs/examples/vercel-ai-sdk.mdx b/docs/examples/vercel-ai-sdk.mdx index f7ed7b5fbd..755b87859a 100644 --- a/docs/examples/vercel-ai-sdk.mdx +++ b/docs/examples/vercel-ai-sdk.mdx @@ -41,3 +41,13 @@ export const openaiTask = task({ }, }); ``` + +## Testing your task + +To test this task in the dashboard, you can use the following payload: + +```json +{ + "prompt": "What is the meaning of life?" +} +``` diff --git a/docs/guides/bun.mdx b/docs/guides/frameworks/bun.mdx similarity index 100% rename from docs/guides/bun.mdx rename to docs/guides/frameworks/bun.mdx diff --git a/docs/guides/frameworks/introduction.mdx b/docs/guides/frameworks/introduction.mdx new file mode 100644 index 0000000000..eb74771e78 --- /dev/null +++ b/docs/guides/frameworks/introduction.mdx @@ -0,0 +1,20 @@ +--- +title: "Introduction" +sidebarTitle: "Introduction" +description: "Get started with Trigger.dev in your favorite framework." +icon: "grid-2" +--- + +import CardBun from "/snippets/card-bun.mdx"; +import CardNodejs from "/snippets/card-nodejs.mdx"; +import CardNextjs from "/snippets/card-nextjs.mdx"; +import CardRemix from "/snippets/card-remix.mdx"; +import CardSupabase from "/snippets/card-supabase.mdx"; + + + + + + + + diff --git a/docs/guides/frameworks/supabase-guides-overview.mdx b/docs/guides/frameworks/supabase-guides-overview.mdx new file mode 100644 index 0000000000..74405a4f3d --- /dev/null +++ b/docs/guides/frameworks/supabase-guides-overview.mdx @@ -0,0 +1,22 @@ +--- +title: "Supabase guides" +sidebarTitle: "Overview" +description: "Guides for using Supabase with Trigger.dev." +--- + + + + Learn how to trigger a task from a Supabase edge function when a URL is visited. + + + Learn how to trigger a task from a Supabase edge function when an event occurs in your database. + + diff --git a/docs/mint.json b/docs/mint.json index 06ff7d7337..2521f8ec97 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -245,15 +245,17 @@ { "group": "Frameworks", "pages": [ - "guides/frameworks/nodejs", - "guides/bun", + "guides/frameworks/introduction", + "guides/frameworks/bun", "guides/frameworks/nextjs", + "guides/frameworks/nodejs", "guides/frameworks/remix", { "group": "Supabase", "icon": "bolt", "iconType": "solid", "pages": [ + "guides/frameworks/supabase-guides-overview", "guides/frameworks/supabase-edge-functions-basic", "guides/frameworks/supabase-edge-functions-database-webhooks" ] @@ -271,6 +273,7 @@ { "group": "Examples", "pages": [ + "examples/intro", "examples/dall-e3-generate-image", "examples/ffmpeg-video-processing", "examples/open-ai-with-retrying", diff --git a/docs/snippets/card-bun.mdx b/docs/snippets/card-bun.mdx new file mode 100644 index 0000000000..720dc03156 --- /dev/null +++ b/docs/snippets/card-bun.mdx @@ -0,0 +1,10 @@ + + + + +} +href="/guides/frameworks/bun" + +/> diff --git a/docs/snippets/card-nextjs.mdx b/docs/snippets/card-nextjs.mdx new file mode 100644 index 0000000000..043fe53cd3 --- /dev/null +++ b/docs/snippets/card-nextjs.mdx @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + +} + href="/guides/frameworks/nextjs" + +/> diff --git a/docs/snippets/card-nodejs.mdx b/docs/snippets/card-nodejs.mdx new file mode 100644 index 0000000000..f9d18a3d2e --- /dev/null +++ b/docs/snippets/card-nodejs.mdx @@ -0,0 +1,11 @@ + + + + + +} + href="/guides/frameworks/nodejs" + +/> diff --git a/docs/snippets/card-remix.mdx b/docs/snippets/card-remix.mdx new file mode 100644 index 0000000000..c5d0c4398d --- /dev/null +++ b/docs/snippets/card-remix.mdx @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + href="/guides/frameworks/remix" +/> diff --git a/docs/snippets/card-supabase.mdx b/docs/snippets/card-supabase.mdx new file mode 100644 index 0000000000..628ca74d4b --- /dev/null +++ b/docs/snippets/card-supabase.mdx @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} +href="/guides/frameworks/supabase-guides-overview" +/>