Compare commits

...

29 Commits

Author SHA1 Message Date
Eric Allam 321378db4f Run pnpm i to fix lock file
🚀 Publish Trigger.dev Docker / ʦ TypeScript (push) Has been cancelled
🚀 Publish Trigger.dev Docker / Unit Tests (push) Has been cancelled
🚀 Publish Trigger.dev Docker / e2e Tests (push) Has been cancelled
🚀 Publish Trigger.dev Docker / publish (push) Has been cancelled
2023-08-09 12:40:48 +01:00
github-actions[bot] c8b10ce188 chore: Update version for release (#291)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-09 12:40:15 +01:00
Eric Allam 57ed2b2d0d Fixed cli type errors 2023-08-09 12:37:22 +01:00
Eric Allam fa3a22eb78 Added an $isNull EventFilter condition matcher 2023-08-09 12:35:47 +01:00
Dan a1bc15d18b Updated the example job (#288)
* Changed the example job to include a delay

* Improved the example job copy and added ‘in a separate terminal’ to 2.

* Create kind-eggs-pump.md

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2023-08-09 12:21:46 +01:00
Yaswanth 3f48dd0a68 [TRI-945]: Adds manual project setup guide to the docs (#286)
Co-authored-by: Crossover <crossover@MacBook-Pro.local>
2023-08-09 12:18:05 +01:00
Eric Allam 5c71868a73 Fix the examples in the supabase integration 2023-08-09 12:16:27 +01:00
Chigala 3ce6decef4 Feat/scaffold job catalog file (#283)
* feat: adds a job catalog file when an integration is created via the CLI

* added a changeset

* fix: create-integration CLI throws an error when it tries to install dependencies

* Update getUserPkgManager.ts to main

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2023-08-09 10:42:20 +01:00
Eric Allam ab007f2708 Fix the CLI typecheck error 2023-08-09 10:41:06 +01:00
Eric Allam 8412680863 Adding createImageEdit and createImageVariation tasks to openai 2023-08-09 10:39:17 +01:00
Matt Aitken a90908df6e CLI init fixes: don't ask for endpoint slug, fix for next.config and package manager artifact issue (#287)
* If an endpointId isn’t in the arguments or package, then use the project slug. Don’t ask for it.

* Should await the package artifact detection

* Catch error thrown if there’s no next.config file when detecting the type of Next project

* Create long-carrots-camp.md
2023-08-09 10:37:10 +01:00
Eric Allam a82b86210f Fix pnpm lock file for package release 2023-08-08 22:08:40 +01:00
github-actions[bot] 0367a92731 chore: Update version for release (#281)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-08 22:08:12 +01:00
Eric Allam d230cf622f Treat @trigger.dev/sdk the same as @trigger.dev/core in the webapp
🚀 Publish Trigger.dev Docker / ʦ TypeScript (push) Has been cancelled
🚀 Publish Trigger.dev Docker / Unit Tests (push) Has been cancelled
🚀 Publish Trigger.dev Docker / e2e Tests (push) Has been cancelled
🚀 Publish Trigger.dev Docker / publish (push) Has been cancelled
2023-08-08 15:52:14 +01:00
Eric Allam a1bf073b43 Trigger another release run 2023-08-08 15:18:13 +01:00
Eric Allam 5588108b97 Add unit test running to the public CI 2023-08-08 15:04:15 +01:00
Eric Allam b9bcecffe3 Added some Supabase storage and auth triggers to the job catalog 2023-08-08 15:02:07 +01:00
Eric Allam 59075f5fc2 EventFilter now supports more complex condition filters #271 2023-08-08 15:02:07 +01:00
Sai Hari fa942fc9f5 feat: 🎸 add end-to-end test harness (#253)
* feat: 🎸 add end-to-end test harness

* Address PR comments
2023-08-08 13:55:25 +01:00
D-K-P 47a8a557cf Replaced all the screenshots of the environments page 2023-08-08 11:47:25 +01:00
Yaswanth b51d0b5399 Fix:[TRI-879]-Hides help panel when endpoints exists on the Environme… (#256)
* Fix:[TRI-879]-Hides help panel when endpoints exists on the Environments & API Keys page

* fix: adds check for fully configured client

g

---------

Co-authored-by: Crossover <crossover@MacBook-Pro.local>
2023-08-08 11:15:58 +01:00
D-K-P 763c84e3b3 Replaced api key screenshot and text 2023-08-08 10:41:33 +01:00
Eric Allam dfb00e84e9 Fixing pnpm lock file 2023-08-08 07:35:25 +01:00
github-actions[bot] b5a64545bb chore: Update version for release (#273)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-08 07:32:24 +01:00
Eric Allam 3cf3eaff48 @trigger.dev/supabase: You can now trigger on multiple database events in the same job 2023-08-07 17:27:49 +01:00
Matt Aitken 4ca758a3de job-catalog: added initial setup and CLI build step
🚀 Publish Trigger.dev Docker / ʦ TypeScript (push) Has been cancelled
🚀 Publish Trigger.dev Docker / publish (push) Has been cancelled
2023-08-07 15:07:58 +01:00
Matt Aitken da81537009 Update job-catalog instructions
`pnpm run trigger:dev` should be `pnpm run dev:trigger`
2023-08-07 14:57:08 +01:00
Eric Allam 7c1b13ba3b Increase webhook delivery attempts to the max 25 attempts over 3 days 2023-08-07 14:25:23 +01:00
D-K-P 4b654e5b3b Fixed broken docs link 2023-08-07 13:29:11 +01:00
102 changed files with 5090 additions and 322 deletions
+85 -1
View File
@@ -64,8 +64,92 @@ jobs:
- name: 🔎 Type check
run: pnpm run typecheck --filter webapp
unitTests:
name: Unit Tests
runs-on: buildjet-4vcpu-ubuntu-2204
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v2.2.4
with:
version: 7.18
- name: ⎔ Setup node
uses: buildjet/setup-node@v3
with:
node-version: 18
cache: "pnpm"
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: Run Unit Tests
run: |
pnpm run test
e2e:
name: e2e Tests
runs-on: buildjet-4vcpu-ubuntu-2204
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v2.2.4
with:
version: 7.18
- name: ⎔ Setup node
uses: buildjet/setup-node@v3
with:
node-version: 18
cache: "pnpm"
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: |
# Setup environment variables
cp ./.env.example ./.env
cp ./examples/nextjs-test/.env.example ./examples/nextjs-test/.env.local
cp ./packages/database/.env.example ./packages/database/.env
# Build packages
pnpm run build --filter @examples/nextjs-test^...
pnpm --filter @trigger.dev/database generate
# Move trigger-cli bin to correct place
pnpm install --frozen-lockfile
# Execute tests
pnpm run docker
pnpm run db:migrate
pnpm run db:seed
pnpm run test:e2e
# Cleanup
pnpm run docker:stop
- name: Upload Playwright report
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
publish:
needs: [typecheck]
needs: [typecheck, unitTests, e2e]
runs-on: buildjet-4vcpu-ubuntu-2204
outputs:
version: ${{ steps.get_version.outputs.version }}
+4 -1
View File
@@ -48,4 +48,7 @@ apps/**/public/build
.sentryclirc
.buildt
**/tmp/
**/tmp/
/test-results/
/playwright-report/
/playwright/.cache/
+46
View File
@@ -169,6 +169,52 @@ pnpm exec trigger-cli dev
9. Please remember to delete the temporary project you created after you've tested the changes, and before you raise a PR.
## Running end-to-end webapp tests
To run the end-to-end tests, follow the steps below:
1. Set up environment variables (copy example envs into the correct place)
```sh
cp ./.env.example ./.env
cp ./examples/nextjs-test/.env.example ./examples/nextjs-test/.env.local
cp ./packages/database/.env.example ./packages/database/.env
```
2. Set up dependencies
```sh
# Build packages
pnpm run build --filter @examples/nextjs-test^...
pnpm --filter @trigger.dev/database generate
# Move trigger-cli bin to correct place
pnpm install --frozen-lockfile
```
3. Set up the database
```sh
pnpm run docker
pnpm run db:migrate
pnpm run db:seed
```
4. Run the end-to-end tests
```sh
pnpm run test:e2e
```
### Cleanup
The end-to-end tests use a `setup` and `teardown` script to seed the database with test data. If the test runner doesn't exit cleanly, then the database can be left in a state where the tests can't run because the `setup` script will try to create data that already exists. If this happens, you can manually delete the `users` and `organizations` from the database using prisma studio:
```sh
# With the database running (i.e. pnpm run docker)
pnpm run db:studio
```
## Add sample jobs
The [examples/jobs-starter](./examples/jobs-starter/) project defines simple jobs you can get started with.
@@ -96,6 +96,13 @@ export default function Page() {
};
}, [selected, clients]);
const isAnyClientFullyConfigured = useMemo(() => {
return clients.some((client) => {
const { DEVELOPMENT, PRODUCTION } = client.endpoints;
return PRODUCTION.state === "configured" && DEVELOPMENT.state === PRODUCTION.state;
});
}, [clients]);
const organization = useOrganization();
const project = useProject();
@@ -120,7 +127,7 @@ export default function Page() {
<PageDescription>API Keys and endpoints for your environments.</PageDescription>
</PageHeader>
<PageBody>
<Help defaultOpen>
<Help defaultOpen={!isAnyClientFullyConfigured}>
{(open) => (
<div className={cn("grid h-full gap-4", open ? "grid-cols-2" : "grid-cols-1")}>
<div>
+6
View File
@@ -1,6 +1,7 @@
import type { DeliverEmail } from "emails";
import { EmailClient } from "emails";
import type { SendEmailOptions } from "remix-auth-email-link";
import { redirect } from "remix-typedjson";
import { env } from "~/env.server";
import type { User } from "~/models/user.server";
import type { AuthUser } from "./authUser";
@@ -14,6 +15,11 @@ const client = new EmailClient({
});
export async function sendMagicLinkEmail(options: SendEmailOptions<AuthUser>): Promise<void> {
// Auto redirect when in development mode
if (env.NODE_ENV === "development") {
throw redirect(options.magicLink);
}
return client.send({
email: "magic_link",
to: options.emailAddress,
@@ -1,6 +1,6 @@
import type { EventDispatcher, EventRecord } from "@trigger.dev/database";
import type { EventFilter } from "@trigger.dev/core";
import { EventFilterSchema } from "@trigger.dev/core";
import { EventFilterSchema, eventFilterMatches } from "@trigger.dev/core";
import { $transaction, PrismaClientOrTransaction, prisma } from "~/db.server";
import { logger } from "~/services/logger.server";
import { workerQueue } from "../worker.server";
@@ -124,29 +124,6 @@ export class EventMatcher {
}
public matches(filter: EventFilter) {
return patternMatches(this.event, filter);
return eventFilterMatches(this.event, filter);
}
}
function patternMatches(payload: any, pattern: any): boolean {
for (const [patternKey, patternValue] of Object.entries(pattern)) {
const payloadValue = payload[patternKey];
if (Array.isArray(patternValue)) {
if (patternValue.length > 0 && !patternValue.includes(payloadValue)) {
return false;
}
} else if (typeof patternValue === "object") {
if (Array.isArray(payloadValue)) {
if (!payloadValue.some((item) => patternMatches(item, patternValue))) {
return false;
}
} else {
if (!patternMatches(payloadValue, patternValue)) {
return false;
}
}
}
}
return true;
}
@@ -10,11 +10,11 @@ const supabase = new SupabaseManagement({
});
new Job(client, {
id: "on-new-users",
name: "On New Users",
id: "on-new-todos",
name: "On New Todos",
version: "0.1.1",
trigger: supabase.onInsert({
table: "users",
trigger: supabase.onInserted({
table: "todos",
}),
run: async (payload, io, ctx) => {
},
@@ -33,11 +33,11 @@ const supabase = new SupabaseManagement({
});
new Job(client, {
id: "on-new-users",
name: "On New Users",
id: "on-new-todos",
name: "On New Todos",
version: "0.1.1",
trigger: supabase.onInsert({
table: "users",
trigger: supabase.onInserted({
table: "todos",
}),
run: async (payload, io, ctx) => {
},
+1 -1
View File
@@ -180,7 +180,7 @@ function getWorkerQueue() {
},
},
deliverHttpSourceRequest: {
maxAttempts: 5,
maxAttempts: 25,
handler: async (payload, job) => {
const service = new DeliverHttpSourceRequestService();
+2 -1
View File
@@ -14,6 +14,7 @@
"start": "cross-env NODE_ENV=production node --max-old-space-size=8192 ./build/server.js",
"typecheck": "tsc --noEmit",
"db:seed": "node prisma/seed.js",
"db:seed:local": "ts-node prisma/seed.ts",
"generate:sourcemaps": "remix build --sourcemap",
"clean:sourcemaps": "run-s clean:sourcemaps:*",
"clean:sourcemaps:public": "rimraf ./build/**/*.map",
@@ -61,7 +62,7 @@
"@trigger.dev/companyicons": "^1.5.14",
"@trigger.dev/database": "workspace:*",
"@trigger.dev/core": "workspace:*",
"@trigger.dev/sdk": "workspace:2.0.4",
"@trigger.dev/sdk": "workspace:*",
"@uiw/react-codemirror": "^4.19.5",
"class-variance-authority": "^0.5.2",
"clsx": "^1.2.1",
+13 -2
View File
@@ -12,8 +12,19 @@ module.exports = {
ignoredRouteFiles: ["**/.*"],
devServerPort: 8002,
serverModuleFormat: "cjs",
serverDependenciesToBundle: ["marked", "axios", "@trigger.dev/core", "emails", "highlight.run"],
serverDependenciesToBundle: [
"marked",
"axios",
"@trigger.dev/core",
"@trigger.dev/sdk",
"emails",
"highlight.run",
],
watchPaths: async () => {
return ["../../packages/core/src/**/*", "../../packages/emails/src/**/*"];
return [
"../../packages/core/src/**/*",
"../../packages/trigger-sdk/src/**/*",
"../../packages/emails/src/**/*",
];
},
};
+262
View File
@@ -0,0 +1,262 @@
---
title: Using Manual Setup
description: How to Manually Initialize Trigger.dev in your Next.js project
---
<Accordion defaultOpen title="Don't have a Next.js project yet to add Trigger.dev to? No problem, you can complete the Manual Setup using a blank Next.js project:">
Create a blank project by running the `create-next-app` command in your terminal:
```bash
npx create-next-app@latest
```
Trigger.dev works with either the Pages or App Router configuration.
</Accordion>
## Installing Required Packages
To begin, install the necessary packages in your Next.js project directory. You can choose one of the following package managers:
<CodeGroup>
```bash npm
npm i @trigger.dev/sdk @trigger-dev/nextjs
```
```bash pnpm
pnpm install @trigger.dev/sdk @trigger-dev/nextjs
```
```bash yarn
yarn add @trigger.dev/sdk @trigger-dev/nextjs
```
</CodeGroup>
<br />
<Note>Ensure that you execute this command within a Next.js project.</Note>
## Obtaining the Development API Key
To locate your development API key, login to the [Trigger.dev
dashboard](https://cloud.trigger.dev) and select the Project you want to
connect to. Then click on the Environments & API Keys tab in the left menu.
You can copy your development API Key from the field at the top of this page.
(Your development key will start with `tr_dev_`).
## Adding Environment Variables
Create a `.env.local` file at the root of your project and include your Trigger API key and URL like this:
```bash
TRIGGER_API_KEY=ENTER_YOUR_DEVELOPMENT_API_KEY_HERE
TRIGGER_API_URL=https://cloud.trigger.dev
```
Replace `ENTER_YOUR_DEVELOPMENT_API_KEY_HERE` with the actual API key obtained from the previous step.
## Configuring the Trigger Client
To set up the Trigger Client for your project, follow these steps:
1. **Create Configuration File:**
In your project directory, create a configuration file named `trigger.ts` or `trigger.js`, depending on whether your project uses TypeScript (`.ts`) or JavaScript (`.js`).
2. **Choose Directory:**
Depending on your project structure, choose the appropriate directory for the configuration file. If your project uses a `src` directory, create the file within it. Otherwise, create it directly in the project root.
3. **Add Configuration Code:**
Open the configuration file you created and add the following code:
```typescript
// trigger.ts (for TypeScript) or trigger.js (for JavaScript)
import { TriggerClient } from "@trigger.dev/sdk";
export const client = new TriggerClient({
id: "my-app",
apiKey: process.env.TRIGGER_API_KEY,
apiUrl: process.env.TRIGGER_API_URL,
});
```
Replace **"my-app"** with an appropriate identifier for your project. The **apiKey** and **apiUrl** are obtained from the environment variables you set earlier.
4. **File Location:**
Depending on your project structure, save the configuration file in the appropriate location:
- If your project uses a **src** directory, save the file within the **src** directory.
- If your project does not use a **src** directory, save the file in the project root.
**Example Directory Structure with src:**
```
project-root/
├── src/
├── trigger.ts
├── other files...
```
**Example Directory Structure without src:**
```
project-root/
├── trigger.ts
├── other files...
```
By following these steps, you'll configure the Trigger Client to work with your project, regardless of whether you have a separate **src** directory and whether you're using TypeScript or JavaScript files.
## Creating the API Route
To establish an API route for interacting with Trigger.dev, follow these steps based on your project's file type and structure
<Tabs>
<Tab title="App Directory">
1. Create a new file named `route.(ts/js)` within the `app/api/trigger/` directory.
2. Add the following code to `route.(ts/js)`:
```typescript
import { createAppRoute } from "@trigger.dev/nextjs";
import { client } from "@/trigger";
import "@/Jobs";
export const { POST, dynamic } = createAppRoute(client);
```
</Tab>
<Tab title="Pages Directory">
1. Create a new file named `trigger.(ts/js)` within the `pages/api/` directory.
2. Add the following code to `trigger.(ts/js)`:
```
import { createPagesRoute } from "@trigger.dev/nextjs";
import { client } from "@/trigger";
import "@/Jobs";
const { handler, config } = createPagesRoute(client);
export { config };
export default handler;
```
</Tab>
</Tabs>
<Warning>In the code blocks, replace "@/trigger" with the appropriate path to your Trigger Client configuration file, and adjust the path to the Jobs folder accordingly. Make sure to provide the correct paths if your project isn't utilizing the Next.js alias feature.</Warning>
## Creating the Example Job
1. Create a folder named `Jobs` alongside your `app` or `pages` directory
2. Inside the `Jobs` folder, add two files named `example.(ts/js)` and `index.(ts/js)`.
<CodeGroup>
```typescript example.(ts/js)
import { eventTrigger } from "@trigger.dev/sdk";
import { client } from "@/trigger";
// your first job
client.defineJob({
id: "example-job",
name: "Example Job",
version: "0.0.1",
trigger: eventTrigger({
name: "example.event",
}),
run: async (payload, io, ctx) => {
await io.logger.info("Hello world!", { payload });
return {
message: "Hello world!",
};
},
});
```
```typescript index.ts/index.(ts/js)
// import all your job files here
export * from "./examples"
```
</CodeGroup>
## Additonal Job Definations
You can define more job definitions by creating additional files in the `Jobs` folder and exporting them in `index` file.
For example, in `index.(ts/js)`, you can export other job files like this:
```typescript
// import all your job files here
export * from "./examples"
export * from "./other-job-file";
```
## Adding Configuration to `package.json`
Inside the `package.json` file, add the following configuration under the root object:
```json
"trigger.dev": {
"endpointId": "my-app"
}
```
Your `package.json` file might look something like this:
```json
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
// ... other dependencies
},
"trigger.dev": {
"endpointId": "my-app"
}
}
```
Replace **"my-app"** with the appropriate identifier you used during the step for creating the Trigger Client.
## Next Steps
Start your Next.js project locally locally, and then execute the `dev` CLI command to run Trigger.dev locally. You should run this command every time you want to use Trigger.dev locally.
![Your first Job](/images/cli-dev.gif)
<Warning>
Make sure your Next.js site is running locally before continuing. You must
also leave this `dev` terminal command running while you develop.
</Warning>
In a **new terminal window or tab** run:
<CodeGroup>
```bash npm
npx @trigger.dev/cli@latest dev
```
```bash pnpm
pnpm dlx @trigger.dev/cli@latest dev
```
```bash yarn
yarn dlx @trigger.dev/cli@latest dev
```
</CodeGroup>
<br />
<Note>
You can optionally pass the port if you're not running on 3000 by adding
`--port 3001` to the end
</Note>
<Tip>If your existing Next.js project utilizes middleware and you encounter any issues, such as potential conflicts with Trigger.dev, it's recommended to refer to the troubleshooting guide at [Middleware](/documentation/guides/platforms/nextjs#middleware) for assistance. This guide can help you address any concerns related to middleware conflicts and ensure the smooth functioning of your project with Trigger.dev.</Tip>
@@ -57,7 +57,7 @@ First, cd into your Next.js project, then run the `@trigger.dev/cli init` comman
npx @trigger.dev/cli@latest init -t "https://<your render app name>.onrender.com"
```
When it asks for your development API key, head over to your self-hosted Trigger.dev dashboard and select the initial project you created when signing up, and head to the `Environments & API Keys` page to copy your `dev` API key:
When it asks for your development API key, head over to your self-hosted Trigger.dev dashboard and select the initial project you created when signing up, and head to the `Environments & API Keys` page to copy your `dev` **SERVER** API key:
![api key](/images/api-key.png)
+1 -1
View File
@@ -35,7 +35,7 @@ Once you've created an account, follow the steps to:
1. Go to the "Environments & API Keys" page in your project.
![Go to the Environments & API Keys page ](/images/environments-link.png)
2. Copy the `DEV` API key.
2. Copy the `DEV` **SERVER** API key.
![API Keys](/images/api-keys.png)
## Run the CLI `init` command
Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 69 KiB

+2
View File
@@ -83,6 +83,8 @@ Tasks that are marked as "long-running" can last longer than your serverless tim
| `listModels` | Lists the available models. |
| `createEdit` | Edits a given text prompt. |
| `createImage` | Generates images from textual descriptions. |
| `createImageEdit` | Creates an edited or extended image given an original image and a prompt |
| `createImageVariation` | Creates a variation of a given image. |
| `createEmbedding` | Generates embeddings for a given text. |
| `createFile` | Uploads a file to the OpenAI API. |
| `listFiles` | Lists the uploaded files. |
+15 -23
View File
@@ -7,8 +7,8 @@ description: "Interact with your Supabase project using the Supabase JS Client."
Our `@trigger.dev/supabase` package provides an integration that wraps the [@supabase/supabase-js](https://github.com/supabase/supabase-js) package, allowing you to run tasks to interact with your Supabase project.
<Note>
If you want to trigger jobs based on changes in your Supabase database, you'll
need to use the [Supabase Management API](../management) integration
If you want to trigger jobs based on changes in your Supabase database, you'll need to use the
[Supabase Management API](/integrations/apis/supabase/management) integration
</Note>
## Usage
@@ -26,8 +26,7 @@ const supabase = new Supabase({
```
<Warning>
Never expose the `service_role` key in a browser or anywhere where a user can
see it.
Never expose the `service_role` key in a browser or anywhere where a user can see it.
</Warning>
You can then use the `supabase` integration to run tasks in your jobs:
@@ -39,21 +38,17 @@ client.defineJob({
supabase,
},
run: async (payload, io, ctx) => {
const { data: users, error } = await io.supabase.runTask(
"find-users",
async (db) => {
return db.from("users").select("*");
}
);
const { data: todos, error } = await io.supabase.runTask("find-todos", async (db) => {
return db.from("todos").select("*");
});
},
});
```
<Note>
By using `runTask` instead of the `@supabase/supabase-js` client directly
inside your job run, you'll be able to create tasks that can be run
idempotently and also retried. For more, see our guide on
[Resumability](http://localhost:3050/documentation/concepts/resumability)
By using `runTask` instead of the `@supabase/supabase-js` client directly inside your job run,
you'll be able to create tasks that can be run idempotently and also retried. For more, see our
guide on [Resumability](http://localhost:3050/documentation/concepts/resumability)
</Note>
You can also choose to throw an error if the query fails and abort the job run:
@@ -65,8 +60,8 @@ client.defineJob({
supabase,
},
run: async (payload, io, ctx) => {
const users = await io.supabase.runTask("find-users", async (db) => {
const { data, error } = await db.from("users").select("*");
const todos = await io.supabase.runTask("find-todos", async (db) => {
const { data, error } = await db.from("todos").select("*");
if (error) throw error;
@@ -83,10 +78,7 @@ The `db` object passed to the callback is an instance of the [@supabase/supabase
- [Invoking Functions](https://supabase.com/docs/reference/javascript/functions-invoke)
- [Storage](https://supabase.com/docs/reference/javascript/storage-createbucket)
<Warning>
Currently we do not support Supabase Realtime (such as subscribing to a
channel)
</Warning>
<Warning>Currently we do not support Supabase Realtime (such as subscribing to a channel)</Warning>
## Typescript Support
@@ -108,15 +100,15 @@ client.defineJob({
supabase,
},
run: async (payload, io, ctx) => {
const users = await io.supabase.runTask("find-users", async (db) => {
const { data, error } = await db.from("users").select("*");
const todos = await io.supabase.runTask("find-todos", async (db) => {
const { data, error } = await db.from("todos").select("*");
if (error) throw error;
return data;
});
// users is now typed as User[] instead of any[]
// todos is now typed as Todo[] instead of any[]
},
});
```
+28 -17
View File
@@ -126,7 +126,7 @@ client.defineJob({
id: "supabase-trigger",
name: "Supabase Trigger",
trigger: db.onInserted({
table: "users",
table: "todos",
}),
run: async (payload, io, ctx) => {
// payload is the database webhook body (see https://supabase.com/docs/guides/database/webhooks#payload)
@@ -137,20 +137,6 @@ client.defineJob({
You can add additional filters to the trigger by passing a `filter` object:
```ts
client.defineJob({
id: "supabase-trigger",
name: "Supabase Trigger",
trigger: db.onUpdated({
table: "users",
filter: {
country: ["USA", "Canada"], // This will only trigger the job if the user.country is USA or Canada
},
}),
run: async (payload, io, ctx) => {
// payload is the database webhook body (see https://supabase.com/docs/guides/database/webhooks#payload)
},
});
client.defineJob({
id: "supabase-trigger",
name: "Supabase Trigger",
@@ -172,6 +158,31 @@ client.defineJob({
});
```
You can also listen for multiple different events using the `on` trigger:
```ts
client.defineJob({
id: "supabase-trigger",
name: "Supabase Trigger",
trigger: db.on({
table: "todos",
events: ["INSERT", "UPDATE"] // Trigger on both insert and update events
filter: {
record: {
is_completed: [false],
},
},
}),
run: async (payload, io, ctx) => {
if (payload.type === "INSERT") {
// payload will be typed as the INSERT payload
} else {
// payload will be typed as the UPDATE payload
}
},
});
```
<Note>
We will only create at most 1 database webhook per table, to limit resource usage when writing to
your database. This means we cannot support scoping updated triggers to specific columns.
@@ -196,10 +207,10 @@ client.defineJob({
id: "supabase-trigger",
name: "Supabase Trigger",
trigger: db.onUpdated({
table: "users",
table: "todos",
}),
run: async (payload, io, ctx) => {
// payload.record and payload.old_record are now correctly typed to match the users table
// payload.record and payload.old_record are now correctly typed to match the todos table
},
});
```
+1
View File
@@ -103,6 +103,7 @@
]
},
"documentation/guides/cli",
"documentation/guides/manual",
"documentation/guides/running-jobs",
{
"group": "Using the Dashboard",
+11 -1
View File
@@ -2,8 +2,18 @@
This project is meant to be used to create a catalog of jobs, usually to test something in an integration or the SDK.
## Setup
You will need to create a `.env` file. You can duplicate the `.env.example` file and set your local `TRIGGER_API_KEY` value.
### Running
You need to build the CLI:
```sh
pnpm run build --filter @trigger.dev/cli
```
Each file in `src` is a separate set of jobs that can be run separately. For example, the `src/stripe.ts` file can be run with:
```sh
@@ -15,7 +25,7 @@ This will open up a local server using `express` on port 8080. Then in a new ter
```sh
cd examples/job-catalog
pnpm run trigger:dev
pnpm run dev:trigger
```
### Adding a new file
+1
View File
@@ -5,6 +5,7 @@
"scripts": {
"stripe": "nodemon --watch src/stripe.ts -r tsconfig-paths/register -r dotenv/config src/stripe.ts",
"supabase": "nodemon --watch src/supabase.ts -r tsconfig-paths/register -r dotenv/config src/supabase.ts",
"supabase:types": "npx supabase gen types typescript --project-id $SUPABASE_PROJECT_ID --schema public --schema auth --schema storage > src/supabase-types.ts",
"dev:trigger": "trigger-cli dev --port 8080"
},
"dependencies": {
+843
View File
@@ -0,0 +1,843 @@
export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json | undefined }
| Json[]
export interface Database {
auth: {
Tables: {
audit_log_entries: {
Row: {
created_at: string | null
id: string
instance_id: string | null
ip_address: string
payload: Json | null
}
Insert: {
created_at?: string | null
id: string
instance_id?: string | null
ip_address?: string
payload?: Json | null
}
Update: {
created_at?: string | null
id?: string
instance_id?: string | null
ip_address?: string
payload?: Json | null
}
Relationships: []
}
flow_state: {
Row: {
auth_code: string
authentication_method: string
code_challenge: string
code_challenge_method: Database["auth"]["Enums"]["code_challenge_method"]
created_at: string | null
id: string
provider_access_token: string | null
provider_refresh_token: string | null
provider_type: string
updated_at: string | null
user_id: string | null
}
Insert: {
auth_code: string
authentication_method: string
code_challenge: string
code_challenge_method: Database["auth"]["Enums"]["code_challenge_method"]
created_at?: string | null
id: string
provider_access_token?: string | null
provider_refresh_token?: string | null
provider_type: string
updated_at?: string | null
user_id?: string | null
}
Update: {
auth_code?: string
authentication_method?: string
code_challenge?: string
code_challenge_method?: Database["auth"]["Enums"]["code_challenge_method"]
created_at?: string | null
id?: string
provider_access_token?: string | null
provider_refresh_token?: string | null
provider_type?: string
updated_at?: string | null
user_id?: string | null
}
Relationships: []
}
identities: {
Row: {
created_at: string | null
email: string | null
id: string
identity_data: Json
last_sign_in_at: string | null
provider: string
updated_at: string | null
user_id: string
}
Insert: {
created_at?: string | null
email?: string | null
id: string
identity_data: Json
last_sign_in_at?: string | null
provider: string
updated_at?: string | null
user_id: string
}
Update: {
created_at?: string | null
email?: string | null
id?: string
identity_data?: Json
last_sign_in_at?: string | null
provider?: string
updated_at?: string | null
user_id?: string
}
Relationships: [
{
foreignKeyName: "identities_user_id_fkey"
columns: ["user_id"]
referencedRelation: "users"
referencedColumns: ["id"]
}
]
}
instances: {
Row: {
created_at: string | null
id: string
raw_base_config: string | null
updated_at: string | null
uuid: string | null
}
Insert: {
created_at?: string | null
id: string
raw_base_config?: string | null
updated_at?: string | null
uuid?: string | null
}
Update: {
created_at?: string | null
id?: string
raw_base_config?: string | null
updated_at?: string | null
uuid?: string | null
}
Relationships: []
}
mfa_amr_claims: {
Row: {
authentication_method: string
created_at: string
id: string
session_id: string
updated_at: string
}
Insert: {
authentication_method: string
created_at: string
id: string
session_id: string
updated_at: string
}
Update: {
authentication_method?: string
created_at?: string
id?: string
session_id?: string
updated_at?: string
}
Relationships: [
{
foreignKeyName: "mfa_amr_claims_session_id_fkey"
columns: ["session_id"]
referencedRelation: "sessions"
referencedColumns: ["id"]
}
]
}
mfa_challenges: {
Row: {
created_at: string
factor_id: string
id: string
ip_address: unknown
verified_at: string | null
}
Insert: {
created_at: string
factor_id: string
id: string
ip_address: unknown
verified_at?: string | null
}
Update: {
created_at?: string
factor_id?: string
id?: string
ip_address?: unknown
verified_at?: string | null
}
Relationships: [
{
foreignKeyName: "mfa_challenges_auth_factor_id_fkey"
columns: ["factor_id"]
referencedRelation: "mfa_factors"
referencedColumns: ["id"]
}
]
}
mfa_factors: {
Row: {
created_at: string
factor_type: Database["auth"]["Enums"]["factor_type"]
friendly_name: string | null
id: string
secret: string | null
status: Database["auth"]["Enums"]["factor_status"]
updated_at: string
user_id: string
}
Insert: {
created_at: string
factor_type: Database["auth"]["Enums"]["factor_type"]
friendly_name?: string | null
id: string
secret?: string | null
status: Database["auth"]["Enums"]["factor_status"]
updated_at: string
user_id: string
}
Update: {
created_at?: string
factor_type?: Database["auth"]["Enums"]["factor_type"]
friendly_name?: string | null
id?: string
secret?: string | null
status?: Database["auth"]["Enums"]["factor_status"]
updated_at?: string
user_id?: string
}
Relationships: [
{
foreignKeyName: "mfa_factors_user_id_fkey"
columns: ["user_id"]
referencedRelation: "users"
referencedColumns: ["id"]
}
]
}
refresh_tokens: {
Row: {
created_at: string | null
id: number
instance_id: string | null
parent: string | null
revoked: boolean | null
session_id: string | null
token: string | null
updated_at: string | null
user_id: string | null
}
Insert: {
created_at?: string | null
id?: number
instance_id?: string | null
parent?: string | null
revoked?: boolean | null
session_id?: string | null
token?: string | null
updated_at?: string | null
user_id?: string | null
}
Update: {
created_at?: string | null
id?: number
instance_id?: string | null
parent?: string | null
revoked?: boolean | null
session_id?: string | null
token?: string | null
updated_at?: string | null
user_id?: string | null
}
Relationships: [
{
foreignKeyName: "refresh_tokens_session_id_fkey"
columns: ["session_id"]
referencedRelation: "sessions"
referencedColumns: ["id"]
}
]
}
saml_providers: {
Row: {
attribute_mapping: Json | null
created_at: string | null
entity_id: string
id: string
metadata_url: string | null
metadata_xml: string
sso_provider_id: string
updated_at: string | null
}
Insert: {
attribute_mapping?: Json | null
created_at?: string | null
entity_id: string
id: string
metadata_url?: string | null
metadata_xml: string
sso_provider_id: string
updated_at?: string | null
}
Update: {
attribute_mapping?: Json | null
created_at?: string | null
entity_id?: string
id?: string
metadata_url?: string | null
metadata_xml?: string
sso_provider_id?: string
updated_at?: string | null
}
Relationships: [
{
foreignKeyName: "saml_providers_sso_provider_id_fkey"
columns: ["sso_provider_id"]
referencedRelation: "sso_providers"
referencedColumns: ["id"]
}
]
}
saml_relay_states: {
Row: {
created_at: string | null
for_email: string | null
from_ip_address: unknown | null
id: string
redirect_to: string | null
request_id: string
sso_provider_id: string
updated_at: string | null
}
Insert: {
created_at?: string | null
for_email?: string | null
from_ip_address?: unknown | null
id: string
redirect_to?: string | null
request_id: string
sso_provider_id: string
updated_at?: string | null
}
Update: {
created_at?: string | null
for_email?: string | null
from_ip_address?: unknown | null
id?: string
redirect_to?: string | null
request_id?: string
sso_provider_id?: string
updated_at?: string | null
}
Relationships: [
{
foreignKeyName: "saml_relay_states_sso_provider_id_fkey"
columns: ["sso_provider_id"]
referencedRelation: "sso_providers"
referencedColumns: ["id"]
}
]
}
schema_migrations: {
Row: {
version: string
}
Insert: {
version: string
}
Update: {
version?: string
}
Relationships: []
}
sessions: {
Row: {
aal: Database["auth"]["Enums"]["aal_level"] | null
created_at: string | null
factor_id: string | null
id: string
not_after: string | null
updated_at: string | null
user_id: string
}
Insert: {
aal?: Database["auth"]["Enums"]["aal_level"] | null
created_at?: string | null
factor_id?: string | null
id: string
not_after?: string | null
updated_at?: string | null
user_id: string
}
Update: {
aal?: Database["auth"]["Enums"]["aal_level"] | null
created_at?: string | null
factor_id?: string | null
id?: string
not_after?: string | null
updated_at?: string | null
user_id?: string
}
Relationships: [
{
foreignKeyName: "sessions_user_id_fkey"
columns: ["user_id"]
referencedRelation: "users"
referencedColumns: ["id"]
}
]
}
sso_domains: {
Row: {
created_at: string | null
domain: string
id: string
sso_provider_id: string
updated_at: string | null
}
Insert: {
created_at?: string | null
domain: string
id: string
sso_provider_id: string
updated_at?: string | null
}
Update: {
created_at?: string | null
domain?: string
id?: string
sso_provider_id?: string
updated_at?: string | null
}
Relationships: [
{
foreignKeyName: "sso_domains_sso_provider_id_fkey"
columns: ["sso_provider_id"]
referencedRelation: "sso_providers"
referencedColumns: ["id"]
}
]
}
sso_providers: {
Row: {
created_at: string | null
id: string
resource_id: string | null
updated_at: string | null
}
Insert: {
created_at?: string | null
id: string
resource_id?: string | null
updated_at?: string | null
}
Update: {
created_at?: string | null
id?: string
resource_id?: string | null
updated_at?: string | null
}
Relationships: []
}
users: {
Row: {
aud: string | null
banned_until: string | null
confirmation_sent_at: string | null
confirmation_token: string | null
confirmed_at: string | null
created_at: string | null
deleted_at: string | null
email: string | null
email_change: string | null
email_change_confirm_status: number | null
email_change_sent_at: string | null
email_change_token_current: string | null
email_change_token_new: string | null
email_confirmed_at: string | null
encrypted_password: string | null
id: string
instance_id: string | null
invited_at: string | null
is_sso_user: boolean
is_super_admin: boolean | null
last_sign_in_at: string | null
phone: string | null
phone_change: string | null
phone_change_sent_at: string | null
phone_change_token: string | null
phone_confirmed_at: string | null
raw_app_meta_data: Json | null
raw_user_meta_data: Json | null
reauthentication_sent_at: string | null
reauthentication_token: string | null
recovery_sent_at: string | null
recovery_token: string | null
role: string | null
updated_at: string | null
}
Insert: {
aud?: string | null
banned_until?: string | null
confirmation_sent_at?: string | null
confirmation_token?: string | null
confirmed_at?: string | null
created_at?: string | null
deleted_at?: string | null
email?: string | null
email_change?: string | null
email_change_confirm_status?: number | null
email_change_sent_at?: string | null
email_change_token_current?: string | null
email_change_token_new?: string | null
email_confirmed_at?: string | null
encrypted_password?: string | null
id: string
instance_id?: string | null
invited_at?: string | null
is_sso_user?: boolean
is_super_admin?: boolean | null
last_sign_in_at?: string | null
phone?: string | null
phone_change?: string | null
phone_change_sent_at?: string | null
phone_change_token?: string | null
phone_confirmed_at?: string | null
raw_app_meta_data?: Json | null
raw_user_meta_data?: Json | null
reauthentication_sent_at?: string | null
reauthentication_token?: string | null
recovery_sent_at?: string | null
recovery_token?: string | null
role?: string | null
updated_at?: string | null
}
Update: {
aud?: string | null
banned_until?: string | null
confirmation_sent_at?: string | null
confirmation_token?: string | null
confirmed_at?: string | null
created_at?: string | null
deleted_at?: string | null
email?: string | null
email_change?: string | null
email_change_confirm_status?: number | null
email_change_sent_at?: string | null
email_change_token_current?: string | null
email_change_token_new?: string | null
email_confirmed_at?: string | null
encrypted_password?: string | null
id?: string
instance_id?: string | null
invited_at?: string | null
is_sso_user?: boolean
is_super_admin?: boolean | null
last_sign_in_at?: string | null
phone?: string | null
phone_change?: string | null
phone_change_sent_at?: string | null
phone_change_token?: string | null
phone_confirmed_at?: string | null
raw_app_meta_data?: Json | null
raw_user_meta_data?: Json | null
reauthentication_sent_at?: string | null
reauthentication_token?: string | null
recovery_sent_at?: string | null
recovery_token?: string | null
role?: string | null
updated_at?: string | null
}
Relationships: []
}
}
Views: {
[_ in never]: never
}
Functions: {
email: {
Args: Record<PropertyKey, never>
Returns: string
}
jwt: {
Args: Record<PropertyKey, never>
Returns: Json
}
role: {
Args: Record<PropertyKey, never>
Returns: string
}
uid: {
Args: Record<PropertyKey, never>
Returns: string
}
}
Enums: {
aal_level: "aal1" | "aal2" | "aal3"
code_challenge_method: "s256" | "plain"
factor_status: "unverified" | "verified"
factor_type: "totp" | "webauthn"
}
CompositeTypes: {
[_ in never]: never
}
}
public: {
Tables: {
todos: {
Row: {
id: number
inserted_at: string
is_complete: boolean | null
task: string | null
user_id: string
}
Insert: {
id?: number
inserted_at?: string
is_complete?: boolean | null
task?: string | null
user_id: string
}
Update: {
id?: number
inserted_at?: string
is_complete?: boolean | null
task?: string | null
user_id?: string
}
Relationships: [
{
foreignKeyName: "todos_user_id_fkey"
columns: ["user_id"]
referencedRelation: "users"
referencedColumns: ["id"]
}
]
}
}
Views: {
[_ in never]: never
}
Functions: {
[_ in never]: never
}
Enums: {
[_ in never]: never
}
CompositeTypes: {
[_ in never]: never
}
}
storage: {
Tables: {
buckets: {
Row: {
allowed_mime_types: string[] | null
avif_autodetection: boolean | null
created_at: string | null
file_size_limit: number | null
id: string
name: string
owner: string | null
public: boolean | null
updated_at: string | null
}
Insert: {
allowed_mime_types?: string[] | null
avif_autodetection?: boolean | null
created_at?: string | null
file_size_limit?: number | null
id: string
name: string
owner?: string | null
public?: boolean | null
updated_at?: string | null
}
Update: {
allowed_mime_types?: string[] | null
avif_autodetection?: boolean | null
created_at?: string | null
file_size_limit?: number | null
id?: string
name?: string
owner?: string | null
public?: boolean | null
updated_at?: string | null
}
Relationships: [
{
foreignKeyName: "buckets_owner_fkey"
columns: ["owner"]
referencedRelation: "users"
referencedColumns: ["id"]
}
]
}
migrations: {
Row: {
executed_at: string | null
hash: string
id: number
name: string
}
Insert: {
executed_at?: string | null
hash: string
id: number
name: string
}
Update: {
executed_at?: string | null
hash?: string
id?: number
name?: string
}
Relationships: []
}
objects: {
Row: {
bucket_id: string | null
created_at: string | null
id: string
last_accessed_at: string | null
metadata: Json | null
name: string | null
owner: string | null
path_tokens: string[] | null
updated_at: string | null
version: string | null
}
Insert: {
bucket_id?: string | null
created_at?: string | null
id?: string
last_accessed_at?: string | null
metadata?: Json | null
name?: string | null
owner?: string | null
path_tokens?: string[] | null
updated_at?: string | null
version?: string | null
}
Update: {
bucket_id?: string | null
created_at?: string | null
id?: string
last_accessed_at?: string | null
metadata?: Json | null
name?: string | null
owner?: string | null
path_tokens?: string[] | null
updated_at?: string | null
version?: string | null
}
Relationships: [
{
foreignKeyName: "objects_bucketId_fkey"
columns: ["bucket_id"]
referencedRelation: "buckets"
referencedColumns: ["id"]
},
{
foreignKeyName: "objects_owner_fkey"
columns: ["owner"]
referencedRelation: "users"
referencedColumns: ["id"]
}
]
}
}
Views: {
[_ in never]: never
}
Functions: {
can_insert_object: {
Args: {
bucketid: string
name: string
owner: string
metadata: Json
}
Returns: undefined
}
extension: {
Args: {
name: string
}
Returns: string
}
filename: {
Args: {
name: string
}
Returns: string
}
foldername: {
Args: {
name: string
}
Returns: unknown
}
get_size_by_bucket: {
Args: Record<PropertyKey, never>
Returns: {
size: number
bucket_id: string
}[]
}
search: {
Args: {
prefix: string
bucketname: string
limits?: number
levels?: number
offsets?: number
search?: string
sortcolumn?: string
sortorder?: string
}
Returns: {
name: string
id: string
updated_at: string
created_at: string
last_accessed_at: string
metadata: Json
}[]
}
}
Enums: {
[_ in never]: never
}
CompositeTypes: {
[_ in never]: never
}
}
}
+112 -8
View File
@@ -1,14 +1,26 @@
import { TriggerClient, eventTrigger } from "@trigger.dev/sdk";
import { TriggerClient } from "@trigger.dev/sdk";
import { createExpressServer } from "@trigger.dev/express";
import { z } from "zod";
import { SupabaseManagement } from "@trigger.dev/supabase";
import { Supabase, SupabaseManagement } from "@trigger.dev/supabase";
import { OpenAI } from "@trigger.dev/openai";
import { Database } from "./supabase-types";
const supabaseManagement = new SupabaseManagement({
id: "supabase-management",
apiKey: process.env["SUPABASE_API_KEY"]!,
});
const db = supabaseManagement.db(process.env["SUPABASE_ID"]!);
const triggers = supabaseManagement.db<Database>(process.env["SUPABASE_ID"]!);
const supabase = new Supabase({
id: "supabase",
supabaseKey: process.env["SUPABASE_SERVICE_ROLE_KEY"]!,
supabaseUrl: process.env["SUPABASE_URL"]!,
});
const openai = new OpenAI({
id: "open-ai",
apiKey: process.env["OPENAI_API_KEY"]!,
});
export const client = new TriggerClient({
id: "job-catalog",
@@ -24,8 +36,8 @@ client.defineJob({
id: "supabase-management-example-1",
name: "Supabase Management Example 1",
version: "0.1.0",
trigger: db.onInserted({
table: "users",
trigger: triggers.onInserted({
table: "todos",
}),
run: async (payload, io, ctx) => {},
});
@@ -34,8 +46,100 @@ client.defineJob({
id: "supabase-management-example-2",
name: "Supabase Management Example 2",
version: "0.1.0",
trigger: db.onUpdated({
table: "users",
trigger: triggers.onUpdated({
table: "todos",
}),
run: async (payload, io, ctx) => {},
});
client.defineJob({
id: "supabase-management-example-users-auth",
name: "Supabase Management Example Users Auth",
version: "0.1.0",
trigger: triggers.onInserted({
table: "users",
schema: "auth",
}),
run: async (payload, io, ctx) => {},
});
client.defineJob({
id: "supabase-management-example-objects-storage",
name: "Supabase Management Example Object Storage",
version: "0.1.0",
trigger: triggers.onInserted({
schema: "storage",
table: "objects",
filter: {
record: {
bucket_id: ["example_bucket"],
name: [
{
$endsWith: ".png",
},
],
path_tokens: [
{
$includes: "images",
},
],
},
},
}),
integrations: {
openai,
},
run: async (payload, io, ctx) => {
if (!payload.record.name) {
return;
}
const {
data: { publicUrl },
} = supabase.native.storage.from("example_bucket").getPublicUrl(payload.record.name);
const imageVariation = await io.openai.createImageVariation("variation-image", {
image: publicUrl,
n: 2,
response_format: "url",
size: "512x512",
});
const imageEdit = await io.openai.createImageEdit("edit-image", {
image: publicUrl,
prompt:
"Fill in the background to make it seem like the cat is on the moon with a beautiful view of the earth.",
n: 2,
response_format: "url",
size: "512x512",
});
// return imageEdit;
},
});
client.defineJob({
id: "supabase-management-example-on",
name: "Supabase Management Example On",
version: "0.1.0",
trigger: triggers.on({
table: "todos",
events: ["INSERT", "UPDATE"],
}),
integrations: {
supabase,
},
run: async (payload, io, ctx) => {
const user = await io.supabase.runTask("fetch-user", async (db) => {
const { data, error } = await db.auth.admin.getUserById(payload.record.user_id);
if (error) {
throw error;
}
return data.user;
});
return user;
},
});
+3
View File
@@ -0,0 +1,3 @@
TRIGGER_API_KEY=tr_dev_test-api-key
TRIGGER_API_URL=http://localhost:3030
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
+35
View File
@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
+34
View File
@@ -0,0 +1,34 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
+7
View File
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
+4
View File
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
module.exports = nextConfig
+30
View File
@@ -0,0 +1,30 @@
{
"name": "@examples/nextjs-test",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "concurrently --kill-others \"pnpm dev:next\" \"wait-on http://localhost:3000 && pnpm dev:trigger\"",
"dev:next": "next dev",
"dev:trigger": "trigger-cli dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "13.4.12",
"react": "18.2.0",
"react-dom": "18.2.0",
"@trigger.dev/sdk": "^2.0.0-next.22",
"@trigger.dev/nextjs": "^1.0.0-next.8"
},
"trigger.dev": {
"endpointId": "nextjs-test"
},
"devDependencies": {
"@trigger.dev/cli": "workspace:*",
"concurrently": "^8.2.0",
"eslint": "8.42.0",
"eslint-config-next": "13.4.6",
"wait-on": "^7.0.1"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

@@ -0,0 +1,9 @@
import { createAppRoute } from "@trigger.dev/nextjs";
import { client } from "@/trigger";
// Replace this with your own jobs
import "@/jobs/examples";
//this route is used to send and receive data with Trigger.dev
export const { POST, dynamic } = createAppRoute(client);
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+107
View File
@@ -0,0 +1,107 @@
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
--primary-glow: conic-gradient(
from 180deg at 50% 50%,
#16abff33 0deg,
#0885ff33 55deg,
#54d6ff33 120deg,
#0071ff33 160deg,
transparent 360deg
);
--secondary-glow: radial-gradient(
rgba(255, 255, 255, 1),
rgba(255, 255, 255, 0)
);
--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
--tile-border: conic-gradient(
#00000080,
#00000040,
#00000030,
#00000020,
#00000010,
#00000010,
#00000080
);
--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
--card-rgb: 180, 185, 188;
--card-border-rgb: 131, 134, 135;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
--secondary-glow: linear-gradient(
to bottom right,
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0.3)
);
--tile-start-rgb: 2, 13, 46;
--tile-end-rgb: 2, 5, 19;
--tile-border: conic-gradient(
#ffffff80,
#ffffff40,
#ffffff30,
#ffffff20,
#ffffff10,
#ffffff10,
#ffffff80
);
--callout-rgb: 20, 20, 20;
--callout-border-rgb: 108, 108, 108;
--card-rgb: 100, 100, 100;
--card-border-rgb: 200, 200, 200;
}
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}
+17
View File
@@ -0,0 +1,17 @@
import './globals.css'
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
)
}
+95
View File
@@ -0,0 +1,95 @@
import Image from 'next/image'
import styles from './page.module.css'
export default function Home() {
return (
<main className={styles.main}>
<div className={styles.description}>
<p>
Get started by editing&nbsp;
<code className={styles.code}>src/app/page.js</code>
</p>
<div>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{' '}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className={styles.vercelLogo}
width={100}
height={24}
priority
/>
</a>
</div>
</div>
<div className={styles.center}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div className={styles.grid}>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Docs <span>-&gt;</span>
</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Learn <span>-&gt;</span>
</h2>
<p>Learn about Next.js in an interactive course with&nbsp;quizzes!</p>
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Templates <span>-&gt;</span>
</h2>
<p>Explore the Next.js 13 playground.</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Deploy <span>-&gt;</span>
</h2>
<p>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
)
}
@@ -0,0 +1,229 @@
.main {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 6rem;
min-height: 100vh;
}
.description {
display: inherit;
justify-content: inherit;
align-items: inherit;
font-size: 0.85rem;
max-width: var(--max-width);
width: 100%;
z-index: 2;
font-family: var(--font-mono);
}
.description a {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
}
.description p {
position: relative;
margin: 0;
padding: 1rem;
background-color: rgba(var(--callout-rgb), 0.5);
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
border-radius: var(--border-radius);
}
.code {
font-weight: 700;
font-family: var(--font-mono);
}
.grid {
display: grid;
grid-template-columns: repeat(4, minmax(25%, auto));
width: var(--max-width);
max-width: 100%;
}
.card {
padding: 1rem 1.2rem;
border-radius: var(--border-radius);
background: rgba(var(--card-rgb), 0);
border: 1px solid rgba(var(--card-border-rgb), 0);
transition: background 200ms, border 200ms;
}
.card span {
display: inline-block;
transition: transform 200ms;
}
.card h2 {
font-weight: 600;
margin-bottom: 0.7rem;
}
.card p {
margin: 0;
opacity: 0.6;
font-size: 0.9rem;
line-height: 1.5;
max-width: 30ch;
}
.center {
display: flex;
justify-content: center;
align-items: center;
position: relative;
padding: 4rem 0;
}
.center::before {
background: var(--secondary-glow);
border-radius: 50%;
width: 480px;
height: 360px;
margin-left: -400px;
}
.center::after {
background: var(--primary-glow);
width: 240px;
height: 180px;
z-index: -1;
}
.center::before,
.center::after {
content: '';
left: 50%;
position: absolute;
filter: blur(45px);
transform: translateZ(0);
}
.logo {
position: relative;
}
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
.card:hover {
background: rgba(var(--card-rgb), 0.1);
border: 1px solid rgba(var(--card-border-rgb), 0.15);
}
.card:hover span {
transform: translateX(4px);
}
}
@media (prefers-reduced-motion) {
.card:hover span {
transform: none;
}
}
/* Mobile */
@media (max-width: 700px) {
.content {
padding: 4rem;
}
.grid {
grid-template-columns: 1fr;
margin-bottom: 120px;
max-width: 320px;
text-align: center;
}
.card {
padding: 1rem 2.5rem;
}
.card h2 {
margin-bottom: 0.5rem;
}
.center {
padding: 8rem 0 6rem;
}
.center::before {
transform: none;
height: 300px;
}
.description {
font-size: 0.8rem;
}
.description a {
padding: 1rem;
}
.description p,
.description div {
display: flex;
justify-content: center;
position: fixed;
width: 100%;
}
.description p {
align-items: center;
inset: 0 0 auto;
padding: 2rem 1rem 1.4rem;
border-radius: 0;
border: none;
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
background: linear-gradient(
to bottom,
rgba(var(--background-start-rgb), 1),
rgba(var(--callout-rgb), 0.5)
);
background-clip: padding-box;
backdrop-filter: blur(24px);
}
.description div {
align-items: flex-end;
pointer-events: none;
inset: auto 0 0;
padding: 2rem;
height: 200px;
background: linear-gradient(
to bottom,
transparent 0%,
rgb(var(--background-end-rgb)) 40%
);
z-index: 1;
}
}
/* Tablet and Smaller Desktop */
@media (min-width: 701px) and (max-width: 1120px) {
.grid {
grid-template-columns: repeat(2, 50%);
}
}
@media (prefers-color-scheme: dark) {
.vercelLogo {
filter: invert(1);
}
.logo {
filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
}
}
@keyframes rotate {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}
+20
View File
@@ -0,0 +1,20 @@
import { eventTrigger } from "@trigger.dev/sdk";
import { client } from "@/trigger";
// your first job
client.defineJob({
id: "test-job",
name: "Test Job One",
version: "0.0.1",
trigger: eventTrigger({
name: "test.event",
}),
run: async (payload, io, ctx) => {
await io.logger.info("Hello world!", { payload });
return {
message: "Hello world!",
};
},
});
+9
View File
@@ -0,0 +1,9 @@
import { TriggerClient } from "@trigger.dev/sdk";
export const client = new TriggerClient({
id: "nextjs-test",
apiKey: process.env.TRIGGER_API_KEY,
apiUrl: process.env.TRIGGER_API_URL,
});
+22
View File
@@ -1,5 +1,27 @@
# @trigger.dev/github
## 2.0.7
### Patch Changes
- Updated dependencies [84126808]
- @trigger.dev/integration-kit@2.0.7
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/integration-kit@2.0.6
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/integration-kit@2.0.5
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/github",
"version": "2.0.4",
"version": "2.0.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/sdk": "workspace:^2.0.4",
"@trigger.dev/integration-kit": "workspace:^2.0.4",
"@trigger.dev/sdk": "workspace:^2.0.7",
"@trigger.dev/integration-kit": "workspace:^2.0.7",
"octokit": "^2.0.14",
"zod": "3.21.4"
},
+23
View File
@@ -1,5 +1,28 @@
# @trigger.dev/slack
## 2.0.7
### Patch Changes
- 84126808: Adding createImageEdit and createImageVariation tasks to openai
- Updated dependencies [84126808]
- @trigger.dev/integration-kit@2.0.7
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/integration-kit@2.0.6
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/integration-kit@2.0.5
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/openai",
"version": "2.0.4",
"version": "2.0.7",
"description": "The official OpenAI integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
},
"dependencies": {
"openai": "^3.3.0",
"@trigger.dev/sdk": "workspace:^2.0.4",
"@trigger.dev/integration-kit": "workspace:^2.0.4",
"@trigger.dev/sdk": "workspace:^2.0.7",
"@trigger.dev/integration-kit": "workspace:^2.0.7",
"zod": "3.21.4"
},
"engines": {
+154 -4
View File
@@ -10,7 +10,7 @@ import {
} from "openai";
import { OpenAIIntegrationAuth } from "./types";
import { redactString } from "@trigger.dev/sdk";
import { Prettify, fileFromString, truncate } from "@trigger.dev/integration-kit";
import { Prettify, fileFromString, fileFromUrl, truncate } from "@trigger.dev/integration-kit";
import { createTaskUsageProperties, onTaskError } from "./taskUtils";
type OpenAIClientType = InstanceType<typeof OpenAIApi>;
@@ -326,6 +326,156 @@ export const createImage: AuthenticatedTask<
},
};
export type CreateImageEditRequest = {
image: string | File;
prompt: string;
mask?: string | File;
n?: number;
size?: "256x256" | "512x512" | "1024x1024";
response_format?: "url" | "b64_json";
user?: string;
};
type CreateImageEditResponseData = Prettify<
Awaited<ReturnType<OpenAIClientType["createImageEdit"]>>["data"]
>;
export const createImageEdit: AuthenticatedTask<
OpenAIClientType,
Prettify<CreateImageEditRequest>,
CreateImageEditResponseData
> = {
onError: onTaskError,
run: async (params, client, task) => {
const file = typeof params.image === "string" ? await fileFromUrl(params.image) : params.image;
const mask = typeof params.mask === "string" ? await fileFromUrl(params.mask) : params.mask;
const response = await client.createImageEdit(
file,
params.prompt,
mask,
params.n,
params.size,
params.response_format,
params.user
);
return response.data;
},
init: (params) => {
let properties = [];
properties.push({
label: "Prompt",
text: params.prompt,
});
if (params.n) {
properties.push({
label: "Number of images",
text: params.n.toString(),
});
}
if (params.size) {
properties.push({
label: "Size",
text: params.size,
});
}
if (params.response_format) {
properties.push({
label: "Response format",
text: params.response_format,
});
}
if (typeof params.image === "string") {
properties.push({
label: "Image URL",
text: params.image,
url: params.image,
});
}
return {
name: "Create image edit",
params,
icon: "openai",
properties,
};
},
};
type CreateImageVariationResponseData = Prettify<
Awaited<ReturnType<OpenAIClientType["createImageVariation"]>>["data"]
>;
export type CreateImageVariationRequest = {
image: string | File;
n?: number;
size?: "256x256" | "512x512" | "1024x1024";
response_format?: "url" | "b64_json";
user?: string;
};
export const createImageVariation: AuthenticatedTask<
OpenAIClientType,
Prettify<CreateImageVariationRequest>,
CreateImageVariationResponseData
> = {
onError: onTaskError,
run: async (params, client, task) => {
const file = typeof params.image === "string" ? await fileFromUrl(params.image) : params.image;
const response = await client
.createImageVariation(file, params.n, params.size, params.response_format, params.user)
.then((res) => res.data);
return response;
},
init: (params) => {
let properties = [];
if (params.n) {
properties.push({
label: "Number of images",
text: params.n.toString(),
});
}
if (params.size) {
properties.push({
label: "Size",
text: params.size,
});
}
if (params.response_format) {
properties.push({
label: "Response format",
text: params.response_format,
});
}
if (typeof params.image === "string") {
properties.push({
label: "Image URL",
text: params.image,
url: params.image,
});
}
return {
name: "Create image variation",
params,
icon: "openai",
properties,
};
},
};
type CreateEmbeddingResponseData = Prettify<
Awaited<ReturnType<OpenAIClientType["createEmbedding"]>>["data"]
>;
@@ -383,7 +533,7 @@ export const createFile: AuthenticatedTask<
let file: File;
if (typeof params.file === "string") {
file = (await fileFromString(params.file, params.fileName ?? "file.txt")) as any;
file = await fileFromString(params.file, params.fileName ?? "file.txt");
} else {
file = params.file;
}
@@ -441,10 +591,10 @@ export const createFineTuneFile: AuthenticatedTask<
> = {
onError: onTaskError,
run: async (params, client) => {
const file = (await fileFromString(
const file = await fileFromString(
params.examples.map((d) => JSON.stringify(d)).join("\n"),
params.fileName
)) as any;
);
return client.createFile(file, "fine-tune").then((res) => res.data);
},
+22
View File
@@ -1,5 +1,27 @@
# @trigger.dev/plain
## 2.0.7
### Patch Changes
- Updated dependencies [84126808]
- @trigger.dev/integration-kit@2.0.7
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/integration-kit@2.0.6
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/integration-kit@2.0.5
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/plain",
"version": "2.0.4",
"version": "2.0.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.0.4",
"@trigger.dev/sdk": "workspace:^2.0.4",
"@trigger.dev/integration-kit": "workspace:^2.0.7",
"@trigger.dev/sdk": "workspace:^2.0.7",
"@team-plain/typescript-sdk": "^2.7.0"
},
"engines": {
+22
View File
@@ -1,5 +1,27 @@
# @trigger.dev/resend
## 2.0.7
### Patch Changes
- Updated dependencies [84126808]
- @trigger.dev/integration-kit@2.0.7
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/integration-kit@2.0.6
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/integration-kit@2.0.5
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/resend",
"version": "2.0.4",
"version": "2.0.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.0.4",
"@trigger.dev/sdk": "workspace:^2.0.4",
"@trigger.dev/integration-kit": "workspace:^2.0.7",
"@trigger.dev/sdk": "workspace:^2.0.7",
"resend": "^0.9.1"
},
"engines": {
+18
View File
@@ -1,5 +1,23 @@
# @trigger.dev/slack
## 2.0.7
### Patch Changes
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/slack",
"version": "2.0.4",
"version": "2.0.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.0.4",
"@trigger.dev/sdk": "workspace:^2.0.7",
"zod": "3.21.4"
},
"engines": {
+22
View File
@@ -1,5 +1,27 @@
# @trigger.dev/stripe
## 2.0.7
### Patch Changes
- Updated dependencies [84126808]
- @trigger.dev/integration-kit@2.0.7
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/integration-kit@2.0.6
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/integration-kit@2.0.5
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/stripe",
"version": "2.0.4",
"version": "2.0.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.0.4",
"@trigger.dev/sdk": "workspace:^2.0.4",
"@trigger.dev/integration-kit": "workspace:^2.0.7",
"@trigger.dev/sdk": "workspace:^2.0.7",
"stripe": "^12.14.0",
"zod": "3.21.4"
},
+23
View File
@@ -1,5 +1,28 @@
# @trigger.dev/supabase
## 2.0.7
### Patch Changes
- Updated dependencies [84126808]
- @trigger.dev/integration-kit@2.0.7
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/integration-kit@2.0.6
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- 3cf3eaff: You can now trigger on multiple database events in the same job
- @trigger.dev/integration-kit@2.0.5
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/supabase",
"version": "2.0.4",
"version": "2.0.7",
"description": "Trigger.dev integration for @supabase/supabase-js",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@supabase/supabase-js": "^2.26.0",
"@trigger.dev/integration-kit": "workspace:^2.0.4",
"@trigger.dev/sdk": "workspace:^2.0.4",
"@trigger.dev/integration-kit": "workspace:^2.0.7",
"@trigger.dev/sdk": "workspace:^2.0.7",
"supabase-management-js": "^0.1.4",
"zod": "3.21.4"
},
+183 -2
View File
@@ -34,6 +34,91 @@ class SupabaseDatabase<Database = any> {
private projectRef: string
) {}
/**
* The function `on` creates a trigger for when a record is inserted, updated, or deleted on a
* specific table in a database schema.
* @param params - The `params` parameter is an object that contains the following properties:
* @param params.table - The `table` property is a string that specifies the name of the table
* that the trigger will be created for.
* @param params.events - The `events` property is an array of events that specifies the events
* that the trigger will be called for. The events that can be specified are `INSERT`, `UPDATE`, or `DELETE`.
* By default, the trigger will be called for all events.
* @param params.schema - The `schema` property is a string that specifies the name of the schema
* that the trigger will be created for. If the schema is not specified, the default schema will
* be used. (public)
* @param params.filter - The `filter` property is an object that specifies the filter that will
* be used to determine if the trigger should be called. If the filter is not specified, the
* trigger will be called for all records.
*
* @example
*
* ```ts
* const supabase = new SupabaseManagement({ id: "supabase" });
* const database = supabase.database<Database>("https://<project-id>.supabase.co");
*
* client.defineJob({
* trigger: database.on({
* table: "todos",
* events: ["INSERTED", "UPDATED"],
* schema: "public",
* filter: {
* record: { is_completed: [false] },
* },
* }),
* })
* ```
*/
on<
SchemaName extends string & keyof Database = "public" extends keyof Database
? "public"
: string & keyof Database,
Schema extends GenericSchema = Database[SchemaName] extends GenericSchema
? Database[SchemaName]
: any,
TTableName extends string & keyof Schema["Tables"] = string & keyof Schema["Tables"],
TTable extends Schema["Tables"][TTableName] = Schema["Tables"][TTableName],
TEvents extends WebhookEvents[] = ["INSERT", "UPDATE", "DELETE"],
>(params: { table: TTableName; events?: TEvents; schema?: SchemaName; filter?: EventFilter }) {
return createTrigger<Prettify<UnionPayloads<TEvents, TTableName, SchemaName, TTable["Row"]>>>(
this.integration.source,
{
event: params.events ?? ["INSERT", "UPDATE", "DELETE"],
projectRef: this.projectRef,
...params,
}
);
}
/**
* The function `onInserted` creates a trigger for when a new record is inserted into a specific
* table in a database schema.
* @param params - The `params` parameter is an object that contains the following properties:
* @param params.table - The `table` property is a string that specifies the name of the table
* that the trigger will be created for.
* @param params.schema - The `schema` property is a string that specifies the name of the schema
* that the trigger will be created for. If the schema is not specified, the default schema will
* be used. (public)
* @param params.filter - The `filter` property is an object that specifies the filter that will
* be used to determine if the trigger should be called. If the filter is not specified, the
* trigger will be called for all records.
*
* @example
*
* ```ts
* const supabase = new SupabaseManagement({ id: "supabase" });
* const database = supabase.database<Database>("https://<project-id>.supabase.co");
*
* client.defineJob({
* trigger: database.onInserted({
* table: "todos",
* schema: "public",
* filter: {
* record: { is_completed: [false] },
* },
* }),
* })
* ```
*/
onInserted<
SchemaName extends string & keyof Database = "public" extends keyof Database
? "public"
@@ -57,6 +142,37 @@ class SupabaseDatabase<Database = any> {
});
}
/**
* The function `onUpdated` creates a trigger for when a new record is updated on a specific
* table in a database schema.
* @param params - The `params` parameter is an object that contains the following properties:
* @param params.table - The `table` property is a string that specifies the name of the table
* that the trigger will be created for.
* @param params.schema - The `schema` property is a string that specifies the name of the schema
* that the trigger will be created for. If the schema is not specified, the default schema will
* be used. (public)
* @param params.filter - The `filter` property is an object that specifies the filter that will
* be used to determine if the trigger should be called. If the filter is not specified, the
* trigger will be called for all records.
*
* @example
*
* ```ts
* const supabase = new SupabaseManagement({ id: "supabase" });
* const database = supabase.database<Database>("https://<project-id>.supabase.co");
*
* client.defineJob({
* trigger: database.onUpdated({
* table: "todos",
* schema: "public",
* filter: {
* record: { completed: [true] },
* old_record: { completed: [false] },
* },
* }),
* })
* ```
*/
onUpdated<
SchemaName extends string & keyof Database = "public" extends keyof Database
? "public"
@@ -80,6 +196,36 @@ class SupabaseDatabase<Database = any> {
});
}
/**
* The function `onDeleted` creates a trigger for when a new record is deleted from a specific
* table in a database schema.
* @param params - The `params` parameter is an object that contains the following properties:
* @param params.table - The `table` property is a string that specifies the name of the table
* that the trigger will be created for.
* @param params.schema - The `schema` property is a string that specifies the name of the schema
* that the trigger will be created for. If the schema is not specified, the default schema will
* be used. (public)
* @param params.filter - The `filter` property is an object that specifies the filter that will
* be used to determine if the trigger should be called. If the filter is not specified, the
* trigger will be called for all records.
*
* @example
*
* ```ts
* const supabase = new SupabaseManagement({ id: "supabase" });
* const database = supabase.database<Database>("https://<project-id>.supabase.co");
*
* client.defineJob({
* trigger: database.onDeleted({
* table: "todos",
* schema: "public",
* filter: {
* old_record: { is_completed: [true] },
* },
* }),
* })
* ```
*/
onDeleted<
SchemaName extends string & keyof Database = "public" extends keyof Database
? "public"
@@ -175,9 +321,44 @@ type WebhookEventSource = ReturnType<typeof createWebhookEventSource>;
type WebhookEvents = "INSERT" | "UPDATE" | "DELETE";
type WebhookEventPayloads<
TTableName extends string,
TSchemaName extends string = "public",
TRecord = any,
> = {
INSERT: {
table: TTableName;
record: Prettify<TRecord>;
type: "INSERT";
schema: TSchemaName;
old_record: null;
};
UPDATE: {
table: TTableName;
record: Prettify<TRecord>;
type: "UPDATE";
schema: TSchemaName;
old_record: Prettify<TRecord>;
};
DELETE: {
table: TTableName;
record: null;
type: "DELETE";
schema: TSchemaName;
old_record: Prettify<TRecord>;
};
};
type UnionPayloads<
T extends WebhookEvents[],
TTableName extends string,
TSchemaName extends string = "public",
TRecord = any,
> = WebhookEventPayloads<TTableName, TSchemaName, TRecord>[T[number]];
function createTrigger<TEvent extends any>(
source: WebhookEventSource,
params: { event: WebhookEvents; filter?: EventFilter } & {
params: { event: WebhookEvents | WebhookEvents[]; filter?: EventFilter } & {
projectRef: string;
table: string;
schema?: string;
@@ -190,7 +371,7 @@ function createTrigger<TEvent extends any>(
icon: "supabase",
filter: {
...params.filter,
type: [params.event],
type: typeof params.event === "string" ? [params.event] : params.event,
schema: [params.schema ?? "public"],
},
properties: [],
+22
View File
@@ -1,5 +1,27 @@
# @trigger.dev/typeform
## 2.0.7
### Patch Changes
- Updated dependencies [84126808]
- @trigger.dev/integration-kit@2.0.7
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/integration-kit@2.0.6
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/integration-kit@2.0.5
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/typeform",
"version": "2.0.4",
"version": "2.0.7",
"description": "The official Typeform integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@typeform/api-client": "^1.8.0",
"@trigger.dev/sdk": "workspace:^2.0.4",
"@trigger.dev/integration-kit": "workspace:^2.0.4",
"@trigger.dev/sdk": "workspace:^2.0.7",
"@trigger.dev/integration-kit": "workspace:^2.0.7",
"zod": "3.21.4"
},
"engines": {
+4
View File
@@ -29,6 +29,7 @@
"clean": "turbo run clean",
"clean:node_modules": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +",
"typecheck": "turbo run typecheck",
"test:e2e": "playwright test",
"test:e2e:dev": "turbo run test:e2e:dev",
"test:e2e:ci": "turbo run test:e2e:ci",
"setup": "turbo run generate db:migrate:force db:seed",
@@ -43,9 +44,12 @@
},
"devDependencies": {
"@manypkg/cli": "^0.19.2",
"@playwright/test": "^1.36.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.8",
"@trigger.dev/cli": "workspace:*",
"@trigger.dev/database": "workspace:*",
"@types/node": "18.17.1",
"autoprefixer": "^10.4.12",
"eslint-config-custom": "workspace:*",
"postcss": "^8.4.17",
+12
View File
@@ -1,5 +1,17 @@
# create-trigger
## 2.0.7
### Patch Changes
- a1bc15d1: Updated the example job
- 3ce6dece: creates a job catalog file when a new integration is created with the create-integration command
- a90908df: CLI init fixes: don't ask for endpoint slug, fix for next.config and package manager artifact issue
## 2.0.6
## 2.0.5
## 2.0.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/cli",
"version": "2.0.4",
"version": "2.0.7",
"description": "The Trigger.dev CLI",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+26 -13
View File
@@ -6,6 +6,7 @@ import { COMMAND_NAME } from "../consts.js";
import { getLatestPackageVersion } from "../utils/addDependencies.js";
import { createFile, pathExists, readJSONFile, writeJSONFile } from "../utils/fileSystem.js";
import { generateIntegrationFiles } from "../utils/generateIntegrationFiles.js";
import { getPackageName } from "../utils/getPackagName.js";
import { installDependencies } from "../utils/installDependencies.js";
import { logger } from "../utils/logger.js";
import { resolvePath } from "../utils/parseNameAndPath.js";
@@ -196,13 +197,9 @@ export default defineConfig([
// If inside the monorepo:
if (triggerMonorepoPath) {
// Add the dependency to the nextjs-example package.json
// Add the paths to the nextjs-example tsconfig paths
await updateNextJsExampleProjectWithNewIntegration(
triggerMonorepoPath,
resolvedPath,
resolvedOptions
);
//adds the integration run script to the job-catalog/package.json
//adds the path to the integration to the job-catalog/tsconfig.json
await updateJobCatalogWithNewIntegration(triggerMonorepoPath, resolvedPath, resolvedOptions);
}
// Install the dependencies
@@ -490,27 +487,44 @@ async function findGitPath(path: string): Promise<string | undefined> {
return findGitPath(parentPath);
}
async function updateNextJsExampleProjectWithNewIntegration(
async function updateJobCatalogWithNewIntegration(
monorepoPath: string,
integrationPath: string,
resolvedOptions: ResolvedCLIOptions
) {
const nextjsPath = pathModule.join(monorepoPath, "examples", "nextjs-example");
const packageName = getPackageName(resolvedOptions.packageName);
const jobCatalogPath = pathModule.join(monorepoPath, "examples", "job-catalog");
const jobCatalogSrcFolder = pathModule.join(jobCatalogPath, "src");
const integrationFile = `export {}`;
await createFileInPath(jobCatalogSrcFolder, `${packageName}.ts`, integrationFile);
const packageJsonPath = pathModule.join(jobCatalogPath, "package.json");
const packageJsonPath = pathModule.join(nextjsPath, "package.json");
const packageJson = await readJSONFile(packageJsonPath);
const newPackageJson = {
...packageJson,
scripts: {
...packageJson.scripts,
[packageName]: `nodemon --watch src/${packageName}.ts -r tsconfig-paths/register -r dotenv/config src/${packageName}.ts`,
},
dependencies: {
...packageJson.dependencies,
[resolvedOptions.packageName]: `workspace:*`,
},
};
// Move "dev:trigger script" to the last position in the scripts object
if (newPackageJson.scripts.hasOwnProperty("dev:trigger")) {
const devTriggerScript = newPackageJson.scripts["dev:trigger"];
delete newPackageJson.scripts["dev:trigger"];
newPackageJson.scripts["dev:trigger"] = devTriggerScript;
}
await writeJSONFile(packageJsonPath, newPackageJson);
const tsConfigPath = pathModule.join(nextjsPath, "tsconfig.json");
const tsConfigPath = pathModule.join(jobCatalogPath, "tsconfig.json");
const tsConfig = await readJSONFile(tsConfigPath);
const newTsConfig = {
@@ -528,6 +542,5 @@ async function updateNextJsExampleProjectWithNewIntegration(
},
},
};
await writeJSONFile(tsConfigPath, newTsConfig);
}
+50 -33
View File
@@ -7,7 +7,7 @@ import { pathToRegexp } from "path-to-regexp";
import { simpleGit } from "simple-git";
import { parse } from "tsconfck";
import { pathToFileURL } from "url";
import { promptApiKey, promptEndpointSlug, promptTriggerUrl } from "../cli/index.js";
import { promptApiKey, promptTriggerUrl } from "../cli/index.js";
import { CLOUD_API_URL, CLOUD_TRIGGER_URL, COMMAND_NAME } from "../consts.js";
import { TelemetryClient, telemetryClient } from "../telemetry/telemetry.js";
import { addDependencies } from "../utils/addDependencies.js";
@@ -67,24 +67,28 @@ export const initCommand = async (options: InitCommandOptions) => {
const isTypescriptProject = await detectTypescriptProject(resolvedPath);
telemetryClient.init.isTypescriptProject(isTypescriptProject, options);
const resolvedOptions = await resolveOptionsWithPrompts(options, resolvedPath, telemetryClient);
const apiKey = resolvedOptions.apiKey;
const optionsAfterPrompts = await resolveOptionsWithPrompts(
options,
resolvedPath,
telemetryClient
);
const apiKey = optionsAfterPrompts.apiKey;
if (!apiKey) {
logger.error("You must provide an API key to continue.");
telemetryClient.init.failed("no_api_key", resolvedOptions);
telemetryClient.init.failed("no_api_key", optionsAfterPrompts);
return;
}
const apiClient = new TriggerApi(apiKey, resolvedOptions.apiUrl);
const apiClient = new TriggerApi(apiKey, optionsAfterPrompts.apiUrl);
const authorizedKey = await apiClient.whoami(apiKey);
if (!authorizedKey) {
logger.error(
`🛑 The API key you provided is not authorized. Try visiting your dashboard at ${resolvedOptions.triggerUrl} to get a new API key.`
`🛑 The API key you provided is not authorized. Try visiting your dashboard at ${optionsAfterPrompts.triggerUrl} to get a new API key.`
);
telemetryClient.init.failed("invalid_api_key", resolvedOptions);
telemetryClient.init.failed("invalid_api_key", optionsAfterPrompts);
return;
}
@@ -94,6 +98,9 @@ export const initCommand = async (options: InitCommandOptions) => {
authorizedKey.userId
);
const endpointSlug = authorizedKey.project.slug;
const resolvedOptions: ResolvedOptions = { ...optionsAfterPrompts, endpointSlug };
await addDependencies(resolvedPath, [
{ name: "@trigger.dev/sdk", tag: "latest" },
{ name: "@trigger.dev/nextjs", tag: "latest" },
@@ -150,7 +157,7 @@ async function printNextSteps(options: ResolvedOptions, authorizedKey: WhoamiRes
logger.info("Next steps:");
logger.info(` 1. Run your Next.js project locally with 'npm run dev'`);
logger.info(
` 2. Run 'npx @trigger.dev/cli@latest dev' to watch for changes and automatically register Trigger.dev jobs`
` 2. In a separate terminal, run 'npx @trigger.dev/cli@latest dev' to watch for changes and automatically register Trigger.dev jobs`
);
logger.info(` 3. View your jobs at ${projectUrl}`);
@@ -174,11 +181,15 @@ async function addConfigurationToPackageJson(path: string, options: ResolvedOpti
logger.success(`✅ Wrote trigger.dev config to package.json`);
}
type OptionsAfterPrompts = Required<Omit<InitCommandOptions, "endpointSlug">> & {
endpointSlug: InitCommandOptions["endpointSlug"];
};
const resolveOptionsWithPrompts = async (
options: InitCommandOptions,
path: string,
telemetryClient: TelemetryClient
): Promise<ResolvedOptions> => {
): Promise<OptionsAfterPrompts> => {
const resolvedOptions: InitCommandOptions = { ...options };
try {
@@ -205,11 +216,8 @@ const resolveOptionsWithPrompts = async (
if (packageJSON && packageJSON["trigger.dev"] && packageJSON["trigger.dev"].endpointId) {
resolvedOptions.endpointSlug = packageJSON["trigger.dev"].endpointId;
} else {
resolvedOptions.endpointSlug = await promptEndpointSlug(path);
telemetryClient.init.resolvedEndpointSlug(resolvedOptions);
}
telemetryClient.init.resolvedEndpointSlug(resolvedOptions);
}
} catch (err) {
// If the user is not calling the command from an interactive terminal, inquirer will throw an error with isTTYError = true
@@ -239,7 +247,7 @@ const resolveOptionsWithPrompts = async (
}
}
return resolvedOptions as ResolvedOptions;
return resolvedOptions as OptionsAfterPrompts;
};
// Detects if there are any uncommitted git changes at path
@@ -278,7 +286,7 @@ async function detectPagesOrAppDir(
isTypescriptProject = false
): Promise<"pages" | "app"> {
const nextConfigPath = pathModule.join(path, "next.config.js");
const importedConfig = await import(pathToFileURL(nextConfigPath).toString());
const importedConfig = await import(pathToFileURL(nextConfigPath).toString()).catch(() => ({}));
if (importedConfig?.default?.experimental?.appDir) {
return "app";
@@ -437,11 +445,11 @@ async function createTriggerAppRoute(
const tsConfigPath = pathModule.join(projectPath, configFileName);
const { tsconfig } = await parse(tsConfigPath);
const extension = isTypescriptProject ? ".ts" : ".js"
const triggerFileName = `trigger${extension}`
const examplesFileName = `examples${extension}`
const examplesIndexFileName = `index${extension}`
const routeFileName = `route${extension}`
const extension = isTypescriptProject ? ".ts" : ".js";
const triggerFileName = `trigger${extension}`;
const examplesFileName = `examples${extension}`;
const examplesIndexFileName = `index${extension}`;
const routeFileName = `route${extension}`;
const pathAlias = getPathAlias(tsconfig, usesSrcDir);
const routePathPrefix = pathAlias ? pathAlias + "/" : "../../../";
@@ -473,29 +481,38 @@ export const client = new TriggerClient({
import { eventTrigger } from "@trigger.dev/sdk";
import { client } from "${jobsPathPrefix}trigger";
// your first job
// Your first job
// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline
client.defineJob({
// This is the unique identifier for your Job, it must be unique across all Jobs in your project
id: "example-job",
name: "Example Job",
name: "Example Job: a joke with a delay",
version: "0.0.1",
// This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction
trigger: eventTrigger({
name: "example.event",
}),
run: async (payload, io, ctx) => {
await io.logger.info("Hello world!", { payload });
return {
message: "Hello world!",
};
// This logs a message to the console
await io.logger.info("🧪 Example Job: a joke with a delay");
await io.logger.info("How do you comfort a JavaScript bug?");
// This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year
await io.wait("Wait 5 seconds for the punchline...", 5);
await io.logger.info("You console it! 🤦");
await io.logger.info(
"✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨"
);
// To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job
},
});
`;
const examplesIndexContent = `
// import all your job files here
export * from "./examples"
`
`;
const directories = pathModule.join(path, "app", "api", "trigger");
await fs.mkdir(directories, { recursive: true });
@@ -552,10 +569,10 @@ async function createTriggerPageRoute(
const pathAlias = getPathAlias(tsconfig, usesSrcDir);
const routePathPrefix = pathAlias ? pathAlias + "/" : "../..";
const extension = isTypescriptProject ? ".ts" : ".js"
const triggerFileName = `trigger${extension}`
const examplesFileName = `examples${extension}`
const examplesIndexFileName = `index${extension}`
const extension = isTypescriptProject ? ".ts" : ".js";
const triggerFileName = `trigger${extension}`;
const examplesFileName = `examples${extension}`;
const examplesIndexFileName = `index${extension}`;
const routeContent = `
import { createPagesRoute } from "@trigger.dev/nextjs";
@@ -608,7 +625,7 @@ client.defineJob({
// import all your job files here
export * from "./examples"
`
`;
const directories = pathModule.join(path, "pages", "api");
await fs.mkdir(directories, { recursive: true });
+4
View File
@@ -0,0 +1,4 @@
export function getPackageName(inputString: string): string {
const packageName = inputString.split("/")[1];
return packageName ?? "";
}
+1 -1
View File
@@ -5,7 +5,7 @@ export type PackageManager = "npm" | "pnpm" | "yarn";
export async function getUserPackageManager(path: string): Promise<PackageManager> {
try {
return detectPackageManagerFromArtifacts(path);
return await detectPackageManagerFromArtifacts(path);
} catch (error) {
return detectPackageManagerFromCurrentCommand();
}
+14
View File
@@ -1,5 +1,19 @@
# internal-platform
## 2.0.7
### Patch Changes
- fa3a22eb: Added an $isNull EventFilter condition matcher
## 2.0.6
### Patch Changes
- 59075f5f: EventFilter now supports more complex condition filters #271
## 2.0.5
## 2.0.4
### Patch Changes
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
moduleFileExtensions: ["ts", "tsx", "js"],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
testMatch: ["<rootDir>/test/**/*.ts?(x)", "<rootDir>/test/**/?(*.)+(spec|test).ts?(x)"],
testEnvironment: "node",
};
+8 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@trigger.dev/core",
"version": "2.0.4",
"description": "Common code used across the Trigger.dev SDK and platform",
"version": "2.0.7",
"description": "Core code used across the Trigger.dev SDK and platform",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"publishConfig": {
@@ -22,7 +22,8 @@
"clean": "rimraf dist",
"build": "npm run clean && npm run build:tsup",
"build:tsup": "tsup --dts-resolve",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"test": "jest"
},
"dependencies": {
"ulid": "^2.3.0",
@@ -31,12 +32,15 @@
},
"devDependencies": {
"@trigger.dev/tsconfig": "workspace:*",
"@types/jest": "^29.5.3",
"@types/node": "16",
"jest": "^29.6.2",
"rimraf": "^3.0.2",
"ts-jest": "^29.1.1",
"tsup": "^7.1.0",
"typescript": "^4.9.4"
},
"engines": {
"node": ">=16.8.0"
}
}
}
+194
View File
@@ -0,0 +1,194 @@
import { EventFilter } from "./schemas/eventFilter";
// EventFilter is a recursive type, where the keys are strings and the values are an array of strings, numbers, booleans, or objects.
// If the values of the array are strings, numbers, or booleans, than we are matching against the value of the payload.
// If the values of the array are objects, then we are doing content filtering
// An example would be [{ $endsWith: ".png" }, { $startsWith: "images/" } ]
export function eventFilterMatches(payload: any, filter: EventFilter): boolean {
for (const [patternKey, patternValue] of Object.entries(filter)) {
const payloadValue = payload[patternKey];
if (Array.isArray(patternValue)) {
if (patternValue.length === 0) {
continue;
}
// Check to see if all the items in the array are a string
if ((patternValue as unknown[]).every((item) => typeof item === "string")) {
if ((patternValue as string[]).includes(payloadValue)) {
continue;
}
return false;
}
// Check to see if all the items in the array are a number
if ((patternValue as unknown[]).every((item) => typeof item === "number")) {
if ((patternValue as number[]).includes(payloadValue)) {
continue;
}
return false;
}
// Check to see if all the items in the array are a boolean
if ((patternValue as unknown[]).every((item) => typeof item === "boolean")) {
if ((patternValue as boolean[]).includes(payloadValue)) {
continue;
}
return false;
}
// Now we know that all the items in the array are objects
const objectArray = patternValue as Exclude<
typeof patternValue,
number[] | string[] | boolean[]
>;
if (!contentFiltersMatches(payloadValue, objectArray)) {
return false;
}
continue;
} else if (typeof patternValue === "object") {
if (Array.isArray(payloadValue)) {
if (!payloadValue.some((item) => eventFilterMatches(item, patternValue))) {
return false;
}
} else {
if (!eventFilterMatches(payloadValue, patternValue)) {
return false;
}
}
}
}
return true;
}
type ContentFilters = Exclude<EventFilter[string], EventFilter | string[] | number[] | boolean[]>;
function contentFiltersMatches(actualValue: any, contentFilters: ContentFilters): boolean {
for (const contentFilter of contentFilters) {
if (typeof contentFilter === "object") {
const [key, value] = Object.entries(contentFilter)[0];
if (!contentFilterMatches(actualValue, contentFilter)) {
return false;
}
}
}
return true;
}
function contentFilterMatches(actualValue: any, contentFilter: ContentFilters[number]): boolean {
if ("$endsWith" in contentFilter) {
if (typeof actualValue !== "string") {
return false;
}
return actualValue.endsWith(contentFilter.$endsWith);
}
if ("$startsWith" in contentFilter) {
if (typeof actualValue !== "string") {
return false;
}
return actualValue.startsWith(contentFilter.$startsWith);
}
if ("$anythingBut" in contentFilter) {
if (Array.isArray(contentFilter.$anythingBut)) {
if ((contentFilter.$anythingBut as any[]).includes(actualValue)) {
return false;
}
}
if (contentFilter.$anythingBut === actualValue) {
return false;
}
return true;
}
if ("$exists" in contentFilter) {
if (contentFilter.$exists) {
return actualValue !== undefined;
}
return actualValue === undefined;
}
if ("$gt" in contentFilter) {
if (typeof actualValue !== "number") {
return false;
}
return actualValue > contentFilter.$gt;
}
if ("$lt" in contentFilter) {
if (typeof actualValue !== "number") {
return false;
}
return actualValue < contentFilter.$lt;
}
if ("$gte" in contentFilter) {
if (typeof actualValue !== "number") {
return false;
}
return actualValue >= contentFilter.$gte;
}
if ("$lte" in contentFilter) {
if (typeof actualValue !== "number") {
return false;
}
return actualValue <= contentFilter.$lte;
}
if ("$between" in contentFilter) {
if (typeof actualValue !== "number") {
return false;
}
return actualValue >= contentFilter.$between[0] && actualValue <= contentFilter.$between[1];
}
if ("$includes" in contentFilter) {
if (Array.isArray(actualValue)) {
return actualValue.includes(contentFilter.$includes);
}
return false;
}
// Use localCompare
if ("$ignoreCaseEquals" in contentFilter) {
if (typeof actualValue !== "string") {
return false;
}
return (
actualValue.localeCompare(contentFilter.$ignoreCaseEquals, undefined, {
sensitivity: "accent",
}) === 0
);
}
if ("$isNull" in contentFilter) {
if (contentFilter.$isNull) {
return actualValue === null;
}
return actualValue !== null;
}
return true;
}
+1
View File
@@ -5,3 +5,4 @@ export * from "./utils";
export * from "./retry";
export * from "./replacements";
export * from "./searchParams";
export * from "./eventFilterMatches";
+43
View File
@@ -7,6 +7,49 @@ const EventMatcherSchema = z.union([
z.array(z.number()),
/** Match against a boolean */
z.array(z.boolean()),
z.array(
z.union([
z.object({
$endsWith: z.string(),
}),
z.object({
$startsWith: z.string(),
}),
z.object({
$exists: z.boolean(),
}),
z.object({
$isNull: z.boolean(),
}),
z.object({
$anythingBut: z.union([z.string(), z.number(), z.boolean()]),
}),
z.object({
$anythingBut: z.union([z.array(z.string()), z.array(z.number()), z.array(z.boolean())]),
}),
z.object({
$gt: z.number(),
}),
z.object({
$lt: z.number(),
}),
z.object({
$gte: z.number(),
}),
z.object({
$lte: z.number(),
}),
z.object({
$between: z.tuple([z.number(), z.number()]),
}),
z.object({
$includes: z.union([z.string(), z.number(), z.boolean()]),
}),
z.object({
$ignoreCaseEquals: z.string(),
}),
])
),
]);
type EventMatcher = z.infer<typeof EventMatcherSchema>;
@@ -0,0 +1,612 @@
import { EventFilter } from "../src";
import { eventFilterMatches } from "../src/eventFilterMatches";
describe("eventFilterMatches", () => {
it("should return true when payload matches string filter", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
name: ["John"],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload matches boolean filter", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
isAdmin: [false],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload matches number filter", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
age: [30],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload matches $startsWith content filter", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
name: [{ $startsWith: "Jo" }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload matches $endsWith content filter", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
name: [{ $endsWith: "hn" }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload matches $startsWith and $endsWith content filters", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
name: [{ $startsWith: "Jo" }, { $endsWith: "hn" }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload does not match $anythingBut filter", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
name: [{ $anythingBut: "Jane" }],
address: {
street: [{ $anythingBut: "456 Elm St" }],
},
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload does not match $anythingBut filter with an array", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
name: [{ $anythingBut: ["Jane", "Joe"] }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload does have a key that $exists = true", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
name: [{ $exists: true }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload does NOT have a key that $exists = false", () => {
const payload = {
name: "John",
age: 30,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
foo: [{ $exists: false }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload does match numeric condition", () => {
const payload = {
name: "John",
age: 30,
score: 100,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
age: [{ $gt: 20 }, { $lt: 40 }],
score: [{ $between: [90, 110] }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload matches an includes condition", () => {
const payload = {
name: "John",
age: 30,
score: 100,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
hobbies: [{ $includes: "reading" }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload matches an ignoreCaseEquals condition", () => {
const payload = {
name: "John",
age: 30,
score: 100,
isAdmin: false,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
name: [{ $ignoreCaseEquals: "john" }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload matches an isNull condition", () => {
const payload = {
name: "John",
age: 30,
score: 100,
isAdmin: false,
confirmedAt: null,
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
confirmedAt: [{ $isNull: true }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return true when payload matches an isNull condition (flipped)", () => {
const payload = {
name: "John",
age: 30,
score: 100,
isAdmin: false,
confirmedAt: "2020-01-01T00:00:00.000Z",
hobbies: ["reading", "swimming"],
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip: "12345",
},
};
const filter: EventFilter = {
confirmedAt: [{ $isNull: false }],
};
expect(eventFilterMatches(payload, filter)).toBe(true);
});
it("should return false when payload does not match string filter", () => {
const payload = {
name: "Jane",
age: 25,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
name: ["John"],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when payload does not match string filter because it's the wrong type", () => {
const payload = {
name: "Jane",
age: 25,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
age: ["John"],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when payload does not match boolean filter", () => {
const payload = {
name: "Jane",
age: 25,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
isAdmin: [false],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when payload does not match number filter", () => {
const payload = {
name: "Jane",
age: 25,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
age: [30],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when payload does not match $startsWith content filter", () => {
const payload = {
name: "Jane",
age: 25,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
name: [{ $startsWith: "Jo" }],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when payload does not match $endsWith content filter", () => {
const payload = {
name: "Jane",
age: 25,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
name: [{ $startsWith: "Ja" }, { $endsWith: "hn" }],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when payload does match $anythingBut content filters", () => {
const payload = {
name: "Jane",
age: 25,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
name: [{ $anythingBut: "Jane" }],
address: {
street: [{ $anythingBut: "456 Elm St" }],
},
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when payload does match $anythingBut content filters with an array", () => {
const payload = {
name: "Jane",
age: 25,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
name: [{ $anythingBut: ["Jane", "John"] }],
address: {
street: [{ $anythingBut: "456 Elm St" }],
},
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when payload does not have a key that $exists = true", () => {
const payload = {
name: "Jane",
age: 25,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
foo: [{ $exists: true }],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when payload does have a key that $exists = false", () => {
const payload = {
name: "Jane",
age: 25,
score: 100,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
},
};
const filter: EventFilter = {
name: [{ $exists: false }],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when the payload does not match the numeric filters", () => {
const payload = {
name: "Jane",
age: 25,
score: 100,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "Othertown",
state: "NY",
zip: "67890",
latitude: 37.7749,
longitude: 122.4194,
},
};
const filter: EventFilter = {
age: [{ $gt: 30 }],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("Should return false when the payload does not match an includes filter", () => {
const payload = {
name: "Jane",
age: 25,
score: 100,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "San Francisco",
state: "CA",
zip: "67890",
latitude: 37.7749,
longitude: 122.4194,
},
};
const filter: EventFilter = {
hobbies: [{ $includes: "swimming" }],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("Should return false when the payload does not match any ignoreCaseEquals condition", () => {
const payload = {
name: "Jane",
age: 25,
score: 100,
isAdmin: true,
hobbies: ["running", "yoga"],
address: {
street: "456 Elm St",
city: "San Francisco",
state: "CA",
zip: "67890",
latitude: 37.7749,
longitude: 122.4194,
},
};
const filter: EventFilter = {
name: [{ $ignoreCaseEquals: "john" }],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when the payload does not match an isNull condition", () => {
const payload = {
name: "Jane",
age: 25,
score: 100,
isAdmin: true,
hobbies: ["running", "yoga"],
confirmedAt: "2020-01-01T00:00:00.000Z",
address: {
street: "456 Elm St",
city: "San Francisco",
state: "CA",
zip: "67890",
latitude: 37.7749,
longitude: 122.4194,
},
};
const filter: EventFilter = {
confirmedAt: [{ $isNull: true }],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
it("should return false when the payload does not match an isNull condition (flipped)", () => {
const payload = {
name: "Jane",
age: 25,
score: 100,
isAdmin: true,
hobbies: ["running", "yoga"],
confirmedAt: null,
address: {
street: "456 Elm St",
city: "San Francisco",
state: "CA",
zip: "67890",
latitude: 37.7749,
longitude: 122.4194,
},
};
const filter: EventFilter = {
confirmedAt: [{ $isNull: false }],
};
expect(eventFilterMatches(payload, filter)).toBe(false);
});
});
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "@trigger.dev/tsconfig/node18.json",
"include": ["src/globals.d.ts", "./src/**/*.ts", "tsup.config.ts"],
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": false,
"declarationMap": false
},
"exclude": ["node_modules"]
}
+3 -2
View File
@@ -1,11 +1,12 @@
{
"extends": "@trigger.dev/tsconfig/node18.json",
"include": ["src/globals.d.ts", "./src/**/*.ts", "tsup.config.ts"],
"include": ["src/globals.d.ts", "./src/**/*.ts", "tsup.config.ts", "./test/**/*.ts"],
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": false,
"declarationMap": false
"declarationMap": false,
"types": ["jest"]
},
"exclude": ["node_modules"]
}
+1
View File
@@ -3,6 +3,7 @@ import { defineConfig } from "tsup";
export default defineConfig([
{
name: "main",
config: "tsconfig.build.json",
entry: ["./src/index.ts"],
outDir: "./dist",
platform: "neutral",
+18
View File
@@ -1,5 +1,23 @@
# @trigger.dev/express
## 2.0.7
### Patch Changes
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/express",
"version": "2.0.4",
"version": "2.0.7",
"description": "Official Express adapter for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -18,7 +18,7 @@
"./package.json": "./package.json"
},
"devDependencies": {
"@trigger.dev/sdk": "workspace:^2.0.4",
"@trigger.dev/sdk": "workspace:^2.0.7",
"@trigger.dev/tsconfig": "workspace:*",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.13",
@@ -32,7 +32,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^2.0.4"
"@trigger.dev/sdk": "workspace:^2.0.7"
},
"dependencies": {
"@remix-run/web-fetch": "^4.3.5",
+10
View File
@@ -1,5 +1,15 @@
# @trigger.dev/integration-kit
## 2.0.7
### Patch Changes
- 84126808: Adding createImageEdit and createImageVariation tasks to openai
## 2.0.6
## 2.0.5
## 2.0.4
## 2.0.3
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/integration-kit",
"version": "2.0.4",
"version": "2.0.7",
"description": "Trigger.dev Integration Kit has helpers to make creating integrations easier",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -20,6 +20,7 @@
"devDependencies": {
"@trigger.dev/tsconfig": "workspace:*",
"@types/node": "18",
"@types/node-fetch": "2.6.x",
"@types/uuid": "^9.0.0",
"rimraf": "^3.0.2",
"tsup": "^6.5.0",
@@ -32,6 +33,7 @@
"typecheck": "tsup --dts-resolve --no-dts"
},
"dependencies": {
"node-fetch": "2.6.x",
"uuid": "^9.0.0"
},
"engines": {
+11 -2
View File
@@ -1,11 +1,20 @@
import fs, { promises } from "fs";
import path from "path";
import { v4 as uuidv4 } from "uuid";
import fetch from "node-fetch";
export async function fileFromString(contents: string, fileName: string) {
export async function fileFromString(contents: string | Buffer, fileName: string): Promise<File> {
const directory = path.join("tmp", uuidv4());
await promises.mkdir(directory, { recursive: true });
const filePath = path.join(directory, fileName);
await promises.writeFile(filePath, contents);
return fs.createReadStream(filePath);
return fs.createReadStream(filePath) as unknown as File;
}
export async function fileFromUrl(url: string) {
const response = await fetch(url);
const content = await response.buffer();
const fileName = path.basename(url);
return fileFromString(content, fileName);
}
+18
View File
@@ -1,5 +1,23 @@
# @trigger.dev/nextjs
## 2.0.7
### Patch Changes
- @trigger.dev/sdk@2.0.7
## 2.0.6
### Patch Changes
- @trigger.dev/sdk@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/sdk@2.0.5
## 2.0.4
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/nextjs",
"version": "2.0.4",
"version": "2.0.7",
"description": "Trigger.dev Next.js integration",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -32,7 +32,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^2.0.4",
"@trigger.dev/sdk": "workspace:^2.0.7",
"next": ">=12.0.0 <14.0.0"
},
"dependencies": {
+20
View File
@@ -1,5 +1,25 @@
# @trigger.dev/react
## 2.0.7
### Patch Changes
- Updated dependencies [fa3a22eb]
- @trigger.dev/core@2.0.7
## 2.0.6
### Patch Changes
- Updated dependencies [59075f5f]
- @trigger.dev/core@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/core@2.0.5
## 2.0.4
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/react",
"version": "2.0.4",
"version": "2.0.7",
"description": "Trigger.dev React SDK",
"types": "dist/index.d.ts",
"main": "dist/index.cjs",
@@ -26,7 +26,7 @@
},
"dependencies": {
"@tanstack/react-query": "5.0.0-beta.2",
"@trigger.dev/core": "workspace:^2.0.4",
"@trigger.dev/core": "workspace:^2.0.7",
"debug": "^4.3.4",
"zod": "3.21.4"
},
+20
View File
@@ -1,5 +1,25 @@
# @trigger.dev/sdk
## 2.0.7
### Patch Changes
- Updated dependencies [fa3a22eb]
- @trigger.dev/core@2.0.7
## 2.0.6
### Patch Changes
- Updated dependencies [59075f5f]
- @trigger.dev/core@2.0.6
## 2.0.5
### Patch Changes
- @trigger.dev/core@2.0.5
## 2.0.4
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sdk",
"version": "2.0.4",
"version": "2.0.7",
"description": "trigger.dev Node.JS SDK",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,7 +24,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/core": "workspace:^2.0.4",
"@trigger.dev/core": "workspace:^2.0.7",
"chalk": "^5.2.0",
"cronstrue": "^2.21.0",
"debug": "^4.3.4",
+65
View File
@@ -0,0 +1,65 @@
import { defineConfig, devices } from "@playwright/test";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests/e2e",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
baseURL: "http://localhost:3030",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
globalSetup: require.resolve("./tests/global.setup.ts"),
globalTeardown: require.resolve("./tests/global.teardown.ts"),
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
],
/* Run your local dev server before starting the tests */
webServer: [
{
command: "pnpm run --filter webapp dev",
url: "http://localhost:3030",
reuseExistingServer: !process.env.CI,
},
{
command: "pnpm run --filter @examples/nextjs-test dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
],
});
+1157 -136
View File
File diff suppressed because it is too large Load Diff
+41
View File
@@ -0,0 +1,41 @@
import { test, expect } from "@playwright/test";
test("Create an account", async ({ page }) => {
await page.goto("/login");
await page.getByRole("link", { name: "Continue with Email" }).click();
await page.getByPlaceholder("Email Address").type(`test_${Math.random()}@test.com`);
await page.getByRole("button", { name: "Send a magic link" }).click();
await expect(page.getByText("Welcome to Trigger.dev")).toBeVisible();
await page.getByLabel("Full name").type("John Doe");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Organization name").type("Test Org");
await page.getByLabel("Project name").type("Test Project");
await page.getByRole("button", { name: "Create" }).click();
await expect(page.locator("h1").filter({ hasText: /^Jobs$/ })).toBeVisible();
});
test("Verify jobs from the test nextjs project", async ({ page }) => {
await page.goto("/");
await page.getByRole("link", { name: "Continue with Email" }).click();
await page.getByPlaceholder("Email Address").type("test-user@test.com");
await page.getByRole("button", { name: "Send a magic link" }).click();
await page.getByRole("link", { name: /Test Project/ }).click();
await expect(page.locator("h1").filter({ hasText: /^Jobs$/ })).toBeVisible();
await page.getByRole("link", { name: "Environments & API Keys" }).click();
await expect(page.locator("h1").filter({ hasText: "Environments & API Keys" })).toBeVisible();
await expect(
page.locator("h3").filter({ hasText: "nextjs-test" })
// Set the timeout high to allow the cli to register jobs
).toBeVisible({ timeout: 15000 });
await page.getByRole("link", { name: "Jobs" }).click();
await expect(page.locator("h1").filter({ hasText: /^Jobs$/ })).toBeVisible();
await expect(page.getByRole("link", { name: /Test Job One/ })).toBeVisible();
});
+99
View File
@@ -0,0 +1,99 @@
import { setDB } from "./utils";
const setup = async () => {
await setDB(async (prisma) => {
// Create test user
const user = await prisma.user.create({
data: {
email: "test-user@test.com",
name: "Test User",
authenticationMethod: "MAGIC_LINK",
confirmedBasicDetails: true
},
});
// Create test organization
const organization = await prisma.organization.create({
data: {
title: "Test Organization",
slug: "test-org",
members: {
create: {
userId: user.id,
role: "ADMIN",
},
},
},
include: {
members: true,
},
});
// Create test project
const project = await prisma.project.create({
data: {
name: "Test Project",
slug: "test-project",
organization: {
connect: {
slug: organization.slug,
},
},
},
include: {
organization: {
include: {
members: true,
},
},
},
});
// Create test environment
await prisma.runtimeEnvironment.create({
data: {
slug: "dev",
// Defined in @examples/nextjs-test
apiKey: "tr_dev_test-api-key",
pkApiKey: "tr_dev_pk_test-api-key",
autoEnableInternalSources: false,
organization: {
connect: {
id: organization.id,
},
},
project: {
connect: {
id: project.id,
},
},
orgMember: { connect: { id: project.organization.members[0].id } },
type: "DEVELOPMENT",
},
});
await prisma.runtimeEnvironment.create({
data: {
slug: "prod",
// Defined in @examples/nextjs-test
apiKey: "tr_prod_test-api-key",
pkApiKey: "tr_prod_pk_test-api-key",
autoEnableInternalSources: false,
organization: {
connect: {
id: organization.id,
},
},
project: {
connect: {
id: project.id,
},
},
orgMember: { connect: { id: project.organization.members[0].id } },
type: "PRODUCTION",
},
});
});
};
export default setup;

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