Add support for webhookEvent, a manual webhook trigger

This commit is contained in:
Eric Allam
2023-01-25 15:32:52 +00:00
parent 40164ee136
commit ddf42558aa
30 changed files with 764 additions and 158 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"@trigger.dev/integrations": patch
"@trigger.dev/sdk": patch
---
Added support for webhookEvent trigger
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

+8
View File
@@ -103,6 +103,14 @@
"guides/event-driven"
]
},
{
"group": "Reference",
"pages": [
"reference/event-filters",
"reference/custom-event",
"reference/webhook-event"
]
},
{
"group": "Webhook Catalog",
"pages": [
+38
View File
@@ -0,0 +1,38 @@
---
title: "customEvent Trigger"
sidebarTitle: "customEvent"
description: "Trigger a workflow when a custom event is received."
---
## Usage
```ts
new Trigger({
id: "user-created-notify-slack",
name: "User Created - Notify Slack",
on: customEvent({
name: "user.created",
schema: z.object({ id: z.string(), admin: z.boolean() }),
filter: {
admin: [false],
},
}),
run: async (event, ctx) => {},
}).listen();
```
## Options
<ParamField path="name" type="string" required={true}>
The name of the custom event to listen for.
</ParamField>
<ParamField path="filter" type="object" required={false}>
An event filter to apply to the custom event payload. See the [event filter
documentation](/reference/event-filters) for more information.
</ParamField>
<ParamField path="schema" type="Zod Schema" required={true}>
A Zod schema to validate the webhook event payload against. See our [Zod
guide](/guides/zod) for more information.
</ParamField>
+53
View File
@@ -0,0 +1,53 @@
---
title: "Event Filters"
sidebarTitle: "Event Filters"
description: "Event filters are used to filter events based on their attributes."
---
Event filters are used to filter events based on their attributes in the [`customEvent`](/reference/custom-event) and [`webhookEvent`](/reference/webhook-event) triggers.
They are declarative pattern-matching rules, modeled after [AWS EventBridge patterns](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html).
Given the following custom event payload:
```json
{
{
"uid": "jexAgaeJFJsrGfans1pxqm",
"type": "15 Min Meeting",
"price": 0,
"title": "15 Min Meeting between Eric Allam and John Doe",
"length": 15,
"status": "ACCEPTED",
"endTime": "2023-01-25T16:00:00Z",
"bookingId": 198052,
"organizer": {
"id": 32794,
"name": "Eric Allam",
"email": "eric@trigger.dev",
"language": { "locale": "en" },
"timeZone": "Europe/London"
},
}
}
```
The following event filter would match the event:
```json
{
"type": ["15 Min Meeting"],
"status": ["ACCEPTED", "REJECTED"],
"organizer": {
"name": ["Eric Allam"]
}
}
```
For an event pattern to match an event, the event must contain all the field names listed in the event pattern. The field names must also appear in the event with the same nesting structure.
The value of each field name in the event pattern must be an array of strings, numbers, or booleans. The event pattern matches the event if the value of the field name in the event is equal to any of the values in the array.
Effectively, each array is an OR condition, and the entire event pattern is an AND condition.
So the above event filter will match because `status == "ACCEPTED"`, and it would also match if `status == "REJECTED"`.
+63
View File
@@ -0,0 +1,63 @@
---
title: "webhookEvent Trigger"
sidebarTitle: "webhookEvent"
description: "Trigger a workflow when a webhook event is received"
---
## Usage
```ts
new Trigger({
id: "caldotcom-to-slack",
name: "Cal.com To Slack",
on: webhookEvent({
service: "cal.com",
eventName: "BOOKING_CREATED",
filter: {
triggerEvent: ["BOOKING_CREATED"],
},
schema: z.any(),
verifyPayload: {
enabled: true,
header: "X-Cal-Signature-256",
},
}),
run: async (event, ctx) => {},
}).listen();
```
## Options
<ParamField path="service" type="string" required={true}>
The name of the service that will be sending the webhook event.
</ParamField>
<ParamField path="name" type="string" required={true}>
The name of the event that will be sent by the service.
</ParamField>
<ParamField path="filter" type="object" required={false}>
An event filter to apply to the webhook event. See the [event filter
documentation](/reference/event-filters) for more information.
</ParamField>
<ParamField path="schema" type="Zod Schema" required={true}>
A Zod schema to validate the webhook event payload against. See our [Zod
guide](/guides/zod) for more information.
</ParamField>
<ParamField path="verifyPayload" type="object" required={false}>
Verify the payload of the webhook event. Currently only supports sha256 HMAC
signatures.
<Expandable title="properties">
<ParamField path="enabled" type="boolean" required={false}>
Whether to verify the payload of the webhook event. Defaults to `false`.
</ParamField>
<ParamField path="header" type="string" required={false}>
The name of the header that contains the signature to verify the payload
against. e.g. `X-Webhook-Signature`.
</ParamField>
</Expandable>
</ParamField>
+69 -2
View File
@@ -4,7 +4,7 @@ sidebarTitle: "Webhooks"
description: "Webhooks allow you to subscribe to events from APIs you use."
---
If you want your server to be notified when one of your users has subscribed to your product, or someone has made a change to a support ticket, then you should use webhooks.
Webhooks are a crucial part of API development, allowing for real-time reactions to various events across different systems, such as when a Stripe Payment is made, or when a GitHub issue is created.
## Advantages of using Trigger.dev for webhooks
@@ -14,9 +14,74 @@ Webhooks can be difficult to work with, especially when developing locally. We m
- They work locally during development without needing to use tunnels (e.g. Ngrok)
- We receive the webhook, then keep trying to send it to you until you receive it. If your server goes down, no problem.
## Send a Slack message when a GitHub issue is labeled as Critical
## Usage
There are two ways to use webhooks with Trigger.dev:
1. Use one of our built-in integrations, such as [GitHub](/integrations/github). We'll take care of registering the webhook for you.
2. Use our [webhookEvent](/reference/webhook-event) function to create a webhook subscription and you'll register the webhook yourself.
## Webhook integrations
We currently have built in integrations for the following webhooks:
- [GitHub](/integrations/apis/github)
Please [join our discord community](https://discord.gg/kA47vcd8P6) and let us know which integration you'd like us to add.
We've documented all the support webhooks for each integration in the sidebar, for example the GitHub [newStarEvent](/integrations/apis/github/events/new-star) webhook:
```ts
import { github } from "@trigger.dev/integrations";
new Trigger({
id: "demo",
on: github.events.newStarEvent({
repo: "triggerdotdev/trigger.dev",
}),
run: async (event, ctx) => {},
}).listen();
```
Once you've connected to Trigger.dev, and authorized your GitHub account, we'll go ahead and register the webhook in the repository you've specified and start triggering your `Trigger.run` function when new stars roll in.
## Manual Webhooks
If we haven't built out the integration you need, you can use our `webhookEvent` function to create a webhook subscription. You'll need to register the webhook yourself, but we'll take care of the rest. Here's an example for triggering events when a new booking happens in [Cal.com](https://cal.com):
```ts
new Trigger({
id: "caldotcom-to-slack",
name: "Cal.com To Slack",
on: webhookEvent({
service: "cal.com",
eventName: "BOOKING_CREATED",
filter: {
triggerEvent: ["BOOKING_CREATED"],
},
schema: z.any(),
verifyPayload: {
enabled: true,
header: "X-Cal-Signature-256",
},
}),
run: async (event, ctx) => {},
}).listen();
```
For more information on the various options for `webhookEvent`, see the [webhookEvent reference](/reference/webhook-event).
Once you connect to Trigger.dev, we will display the URL and (optionally) the secret you need to register with the webhook provider on the workflow overview page:
![Webhook URL](/images/webhook-url.png)
Copy the URL and secret, and register the webhook with the provider (in this case, Cal.com). Once you've done that, we'll start triggering your `Trigger.run` function when the webhook fires.
## Examples
<CodeGroup>
```ts Github
import { Trigger } from "@trigger.dev/sdk";
import { github, slack } from "@trigger.dev/integrations";
@@ -47,3 +112,5 @@ new Trigger({
},
}).listen();
```
</CodeGroup>
@@ -36,6 +36,7 @@ function Webhook({ webhook }: { webhook: WebhookEventTrigger }) {
</Header2>
<div className="flex flex-col gap-1">
{webhook.source &&
!webhook.manualRegistration &&
Object.entries(webhook.source).map(([key, value]) => (
<div key={key} className="flex gap-2 items-baseline">
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
@@ -47,6 +47,14 @@ export function TriggerTypeIcon({
return (
<img src={Schedule} alt={triggerLabel(type)} className={iconClasses} />
);
case "WEBHOOK":
return (
<img
src={CustomEvent}
alt={triggerLabel(type)}
className={iconClasses}
/>
);
default:
return null;
}
@@ -1,5 +1,8 @@
import { prisma } from "~/db.server";
export type { ExternalSource } from ".prisma/client";
import type { ExternalSource } from ".prisma/client";
import { env } from "process";
export type { ExternalSource };
export type ExternalSourceWithConnection = Awaited<
ReturnType<typeof findExternalSourceById>
@@ -31,3 +34,7 @@ export async function connectExternalSource({
},
});
}
export function buildExternalSourceUrl(id: string, serviceIdentifier: string) {
return `${env.APP_ORIGIN}/api/v1/internal/webhooks/${serviceIdentifier}/${id}`;
}
@@ -33,6 +33,8 @@ export function getWorkflowFromSlugs({
connection: true,
key: true,
service: true,
manualRegistration: true,
secret: true,
},
},
externalServices: {
@@ -1,6 +1,9 @@
import { Outlet } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import { TriggerMetadataSchema } from "@trigger.dev/common-schemas";
import {
ManualWebhookSourceSchema,
TriggerMetadataSchema,
} from "@trigger.dev/common-schemas";
import { typedjson } from "remix-typedjson";
import invariant from "tiny-invariant";
import { Container } from "~/components/layout/Container";
@@ -9,6 +12,7 @@ import {
WorkflowsSideMenu,
} from "~/components/navigation/SideMenu";
import { getConnectedApiConnectionsForOrganizationSlug } from "~/models/apiConnection.server";
import { buildExternalSourceUrl } from "~/models/externalSource.server";
import { getIntegrations } from "~/models/integrations.server";
import { getRuntimeEnvironmentFromRequest } from "~/models/runtimeEnvironment.server";
import { getWorkflowFromSlugs } from "~/models/workflow.server";
@@ -78,8 +82,28 @@ export const loader = async ({ request, params }: LoaderArgs) => {
}),
};
const externalSourceSecret =
workflow.externalSource &&
workflow.externalSource.manualRegistration &&
ManualWebhookSourceSchema.safeParse(workflow.externalSource.source)
.success &&
ManualWebhookSourceSchema.parse(workflow.externalSource.source)
.verifyPayload.enabled
? workflow.externalSource.secret
: undefined;
return typedjson({
workflow: { ...workflow, rules },
workflow: {
...workflow,
rules,
externalSourceUrl: workflow.externalSource
? buildExternalSourceUrl(
workflow.externalSource.id,
workflow.externalSource.service
)
: undefined,
externalSourceSecret: externalSourceSecret,
},
currentEnvironmentSlug,
connectionSlots,
});
@@ -86,9 +86,22 @@ export default function Page() {
</div>
{workflow.status === "CREATED" && (
<>
<PanelWarning className="mb-6">
This workflow requires its APIs to be connected before it can run.
</PanelWarning>
{eventRule &&
eventRule.trigger.type === "WEBHOOK" &&
eventRule.trigger.manualRegistration &&
workflow.externalSourceUrl ? (
<PanelInfo className="mb-6">
Register webhook to activate this workflow:{" "}
{workflow.externalSourceUrl}{" "}
{workflow.externalSourceSecret && (
<>and secret {workflow.externalSourceSecret}</>
)}
</PanelInfo>
) : (
<PanelWarning className="mb-6">
This workflow requires its APIs to be connected before it can run.
</PanelWarning>
)}
</>
)}
{workflow.status === "DISABLED" && (
@@ -17,7 +17,10 @@ export async function action({ request, params }: ActionArgs) {
};
}
if (externalSource.connection?.apiIdentifier !== serviceIdentifier) {
if (
!externalSource.manualRegistration &&
externalSource.connection?.apiIdentifier !== serviceIdentifier
) {
return { status: 500, body: "Service identifier does not match" };
}
@@ -34,8 +34,10 @@ export async function action({ request, params }: ActionArgs) {
);
switch (result.status) {
case "validationError":
return json({ error: result.data }, { status: 400 });
case "validationError": {
return json({ error: result.errors }, { status: 400 });
}
case "success":
return json(result.data);
}
@@ -5,6 +5,7 @@ import { prisma } from "~/db.server";
import type { RuntimeEnvironment } from "~/models/runtimeEnvironment.server";
import type { Workflow } from "~/models/workflow.server";
import { taskQueue } from "../messageBroker.server";
import { generateErrorMessage } from "zod-error";
export class DispatchEvent {
#prismaClient: PrismaClient;
@@ -41,8 +42,16 @@ export class DispatchEvent {
},
});
console.log(
`Found ${eventRules.length} event rules to check for event ${
event.id
}: ${eventRules.map((eventRule) => eventRule.id).join(", ")}`
);
const matcher = new EventMatcher(event);
console.log(`Matching event rules for event`, matcher.json);
const matchingEventRules = eventRules.filter((eventRule) => {
return matcher.matches(eventRule);
});
@@ -77,20 +86,28 @@ export class DispatchEvent {
}
class EventMatcher {
#json: any;
json: any;
constructor(event: TriggerEvent) {
this.#json = this.#createEventJsonFromEvent(event);
this.json = this.#createEventJsonFromEvent(event);
}
public matches(eventRule: EventRule) {
console.log(`Matching against event rule ${eventRule.id}`);
const filter = this.#parseFilter(eventRule);
if (!filter.success) {
console.error(
`Could not parse filter for event rule ${
eventRule.id
}, returning false: ${generateErrorMessage(filter.error.issues)}`
);
return false;
}
return patternMatches(this.#json, filter.data);
return patternMatches(this.json, filter.data);
}
#parseFilter(eventRule: EventRule) {
@@ -104,6 +121,7 @@ class EventMatcher {
event: event.name,
service: event.service,
payload: event.payload,
context: event.context,
};
}
}
@@ -165,8 +183,40 @@ export class DispatchWorkflowRun {
status: "PENDING",
isTest: event.isTest,
},
include: {
workflow: {
include: {
externalSource: true,
},
},
},
});
if (
workflowRun.workflow.externalSource &&
workflowRun.workflow.externalSource.status === "CREATED"
) {
await this.#prismaClient.externalSource.update({
where: {
id: workflowRun.workflow.externalSource.id,
},
data: {
status: "READY",
},
});
if (workflowRun.workflow.status === "CREATED") {
await this.#prismaClient.workflow.update({
where: {
id: workflowRun.workflow.id,
},
data: {
status: "READY",
},
});
}
}
console.log(
`Created workflow run ${workflowRun.id} for event rule ${eventRule.id}`
);
@@ -4,6 +4,9 @@ import { github } from "internal-integrations";
import type { ExternalSourceWithConnection } from "~/models/externalSource.server";
import type { NormalizedRequest } from "internal-integrations";
import { IngestEvent } from "../events/ingest.server";
import { ManualWebhookSourceSchema } from "@trigger.dev/common-schemas";
import { createHmac, timingSafeEqual } from "node:crypto";
import { ulid } from "ulid";
type IgnoredEventResponse = {
status: "ignored";
@@ -41,16 +44,25 @@ export class HandleExternalSource {
async #createNormalizedRequest(request: Request): Promise<NormalizedRequest> {
const requestUrl = new URL(request.url);
const rawSearchParams = requestUrl.searchParams;
const rawBody = await request.json();
const rawBody = await request.text();
const rawHeaders = Object.fromEntries(request.headers.entries());
return {
body: rawBody,
rawBody,
body: this.#safeJsonParse(rawBody),
headers: rawHeaders,
searchParams: rawSearchParams,
};
}
#safeJsonParse(json: string): any {
try {
return JSON.parse(json);
} catch (error) {
return null;
}
}
public async call(
externalSource: NonNullable<ExternalSourceWithConnection>,
serviceIdentifier: string,
@@ -123,6 +135,14 @@ export class HandleExternalSource {
serviceIdentifier: string,
request: NormalizedRequest
): Promise<HandledExternalEventResponse> {
if (externalSource.manualRegistration) {
return this.#handleManualWebhook(
externalSource,
serviceIdentifier,
request
);
}
switch (serviceIdentifier) {
case "github": {
return github.webhooks.handleWebhookRequest({
@@ -137,4 +157,48 @@ export class HandleExternalSource {
}
}
}
async #handleManualWebhook(
externalSource: NonNullable<ExternalSourceWithConnection>,
serviceIdentifier: string,
request: NormalizedRequest
): Promise<HandledExternalEventResponse> {
const source = ManualWebhookSourceSchema.parse(externalSource.source);
if (source.verifyPayload.enabled && source.verifyPayload.header) {
const hmac = createHmac("sha256", externalSource.secret!);
const digest = Buffer.from(
hmac.update(request.rawBody).digest("hex"),
"utf8"
);
const providerSigString =
request.headers[source.verifyPayload.header.toLowerCase()] || "";
const providerSig = Buffer.from(providerSigString, "utf8");
if (
digest.length !== providerSig.length ||
!timingSafeEqual(digest, providerSig)
) {
return {
status: "error",
error: "Payload signature did not match",
};
}
}
return {
status: "ok",
data: {
id: ulid(),
payload: request.body,
event: source.event,
context: {
headers: request.headers,
externalSourceId: externalSource.id,
},
},
};
}
}
@@ -5,7 +5,10 @@ import crypto from "node:crypto";
import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
import { env } from "~/env.server";
import { findExternalSourceById } from "~/models/externalSource.server";
import {
buildExternalSourceUrl,
findExternalSourceById,
} from "~/models/externalSource.server";
import { getAccessInfo } from "../accessInfo.server";
export class RegisterExternalSource {
@@ -26,6 +29,10 @@ export class RegisterExternalSource {
return true;
}
if (externalSource.manualRegistration) {
return true;
}
console.log("[RegisterExternalSource] registering external source", {
externalSource,
});
@@ -56,9 +63,13 @@ export class RegisterExternalSource {
throw new Error("No access token found for webhook");
}
const secret = crypto.randomBytes(32).toString("hex");
const secret =
externalSource.secret ?? crypto.randomBytes(32).toString("hex");
const webhookUrl = `${env.APP_ORIGIN}/api/v1/internal/webhooks/${connection.apiIdentifier}/${externalSource.id}`;
const webhookUrl = buildExternalSourceUrl(
externalSource,
connection.apiIdentifier
);
const serviceWebhook = await this.#registerWebhookWithConnection(
externalSource.service,
@@ -1,6 +1,7 @@
import { github } from "internal-integrations";
import type { WorkflowMetadata } from "internal-platform";
import { WorkflowMetadataSchema } from "internal-platform";
import crypto from "node:crypto";
import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
import type { Organization } from "~/models/organization.server";
@@ -140,48 +141,16 @@ export class RegisterWorkflow {
) {
switch (payload.trigger.type) {
case "WEBHOOK": {
if (!payload.trigger.source) {
const externalSource = await this.#upsertWebhookSource(
payload,
organization,
workflow
);
if (!externalSource) {
return;
}
const existingConnection =
await this.#findLatestExistingConnectionInOrg(
payload.trigger.service,
organization
);
const externalSource = await this.#prismaClient.externalSource.upsert({
where: {
organizationId_key: {
key: this.#keyForExternalSource(payload),
organizationId: organization.id,
},
},
update: {
source: payload.trigger.source,
},
create: {
organizationId: organization.id,
key: this.#keyForExternalSource(payload),
type: "WEBHOOK",
source: payload.trigger.source,
status: "CREATED",
connectionId: existingConnection?.id,
service: payload.trigger.service,
},
});
if (!externalSource.connectionId && existingConnection) {
await this.#prismaClient.externalSource.update({
where: {
id: externalSource.id,
},
data: {
connectionId: existingConnection.id,
},
});
}
await this.#prismaClient.workflow.update({
where: {
id: workflow.id,
@@ -235,6 +204,89 @@ export class RegisterWorkflow {
}
}
async #upsertWebhookSource(
payload: WorkflowMetadata,
organization: Organization,
workflow: Workflow
) {
if (payload.trigger.type !== "WEBHOOK") {
return;
}
if (!payload.trigger.source) {
return;
}
const secret = crypto.randomBytes(16).toString("hex");
if (payload.trigger.manualRegistration) {
const externalSource = await this.#prismaClient.externalSource.upsert({
where: {
organizationId_key: {
key: `${workflow.id}-${payload.trigger.service}`,
organizationId: organization.id,
},
},
update: {
source: payload.trigger.source,
},
create: {
organizationId: organization.id,
key: `${workflow.id}-${payload.trigger.service}`,
type: "WEBHOOK",
source: payload.trigger.source,
status: "CREATED",
service: payload.trigger.service,
manualRegistration: true,
secret,
},
});
return externalSource;
} else {
const existingConnection = await this.#findLatestExistingConnectionInOrg(
payload.trigger.service,
organization
);
const externalSource = await this.#prismaClient.externalSource.upsert({
where: {
organizationId_key: {
key: this.#keyForExternalSource(payload),
organizationId: organization.id,
},
},
update: {
source: payload.trigger.source,
},
create: {
organizationId: organization.id,
key: this.#keyForExternalSource(payload),
type: "WEBHOOK",
source: payload.trigger.source,
status: "CREATED",
connectionId: existingConnection?.id,
service: payload.trigger.service,
manualRegistration: false,
secret,
},
});
if (!externalSource.connectionId && existingConnection) {
await this.#prismaClient.externalSource.update({
where: {
id: externalSource.id,
},
data: {
connectionId: existingConnection.id,
},
});
}
return externalSource;
}
}
#keyForExternalSource(payload: WorkflowMetadata): string {
if (payload.trigger.type === "WEBHOOK") {
switch (payload.trigger.service) {
+2 -1
View File
@@ -115,7 +115,8 @@
"tiny-invariant": "^1.2.0",
"tsx": "^3.4.3",
"ulid": "^2.3.0",
"zod": "^3.20.2"
"zod": "^3.20.2",
"zod-error": "^1.1.0"
},
"devDependencies": {
"@faker-js/faker": "^7.5.0",
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "ExternalSource" ADD COLUMN "manualRegistration" BOOLEAN NOT NULL DEFAULT false;
+8 -7
View File
@@ -197,13 +197,14 @@ model ExternalSource {
service String
workflows Workflow[]
type ExternalSourceType
key String
source Json
status ExternalSourceStatus @default(CREATED)
externalData Json?
secret String?
workflows Workflow[]
type ExternalSourceType
key String
source Json
status ExternalSourceStatus @default(CREATED)
externalData Json?
secret String?
manualRegistration Boolean @default(false)
readyAt DateTime?
createdAt DateTime @default(now())
+105 -3
View File
@@ -1,9 +1,8 @@
import { Trigger, customEvent } from "@trigger.dev/sdk";
import { slack } from "@trigger.dev/integrations";
import { Trigger, customEvent, webhookEvent } from "@trigger.dev/sdk";
import { z } from "zod";
const TOKEN = "abc123";
new Trigger({
id: "fetch-playground",
name: "Fetch Playground",
@@ -54,3 +53,106 @@ new Trigger({
});
},
}).listen();
export const bookingPayloadSchema = z.object({
triggerEvent: z.string(),
createdAt: z.coerce.date(),
payload: z.object({
type: z.string(),
title: z.string(),
description: z.string(),
additionalNotes: z.string(),
customInputs: z.object({}),
startTime: z.coerce.date(),
endTime: z.coerce.date(),
organizer: z.object({
id: z.number(),
name: z.string(),
email: z.string(),
timeZone: z.string(),
language: z.object({ locale: z.string() }),
}),
attendees: z.array(
z.object({
email: z.string(),
name: z.string(),
timeZone: z.string(),
language: z.object({ locale: z.string() }),
})
),
location: z.string(),
destinationCalendar: z.object({
id: z.number(),
integration: z.string(),
externalId: z.string(),
userId: z.number(),
eventTypeId: z.null(),
credentialId: z.number(),
}),
hideCalendarNotes: z.boolean(),
requiresConfirmation: z.null(),
eventTypeId: z.number(),
seatsShowAttendees: z.boolean(),
uid: z.string(),
conferenceData: z.object({
createRequest: z.object({ requestId: z.string() }),
}),
videoCallData: z.object({
type: z.string(),
id: z.string(),
password: z.string(),
url: z.string(),
}),
appsStatus: z.array(
z.object({
appName: z.string(),
type: z.string(),
success: z.number(),
failures: z.number(),
errors: z.array(z.any()).optional(),
warnings: z.array(z.any()).optional(),
})
),
eventTitle: z.string(),
eventDescription: z.null(),
price: z.number(),
currency: z.string(),
length: z.number(),
bookingId: z.number(),
metadata: z.object({ videoCallUrl: z.string() }),
status: z.string(),
}),
});
new Trigger({
id: "caldotcom-to-slack-2",
name: "Cal.com To Slack",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: webhookEvent({
service: "cal.com",
eventName: "BOOKING_CREATED",
filter: {
triggerEvent: ["BOOKING_CREATED"],
},
schema: bookingPayloadSchema,
verifyPayload: {
enabled: true,
header: "X-Cal-Signature-256",
},
}),
run: async (event, ctx) => {
await ctx.logger.info("Received a cal.com booking", {
event,
wallTime: new Date(),
});
await slack.postMessage(`Cal.com booking yo`, {
channelName: "customers",
text: `New Booking: ${
event.payload.title
} at ${event.payload.startTime.toLocaleDateString()}`,
});
},
}).listen();
+8
View File
@@ -62,3 +62,11 @@ export const ScheduleSourceSchema = z.union([
]);
export type ScheduleSource = z.infer<typeof ScheduleSourceSchema>;
export const ManualWebhookSourceSchema = z.object({
verifyPayload: z.object({
enabled: z.boolean(),
header: z.string().optional(),
}),
event: z.string(),
});
+2 -1
View File
@@ -15,7 +15,8 @@ export const WebhookEventTriggerSchema = z.object({
service: z.string(),
name: z.string(),
filter: EventFilterSchema,
source: JsonSchema,
source: JsonSchema.optional(),
manualRegistration: z.boolean().default(false),
});
export type WebhookEventTrigger = z.infer<typeof WebhookEventTriggerSchema>;
@@ -13,6 +13,7 @@ export interface WebhookConfig {
}
export interface NormalizedRequest {
rawBody: string;
body: any;
headers: Record<string, string>;
searchParams: URLSearchParams;
@@ -26,6 +26,7 @@ export function commitCommentEvent(params: {
repo: params.repo,
events: ["commit_comment"],
}),
manualRegistration: false,
},
schema: github.schemas.commitComments.commitCommentEventSchema,
};
@@ -54,6 +55,7 @@ export function issueEvent(params: {
repo: params.repo,
events: ["issues"],
}),
manualRegistration: false,
},
schema: github.schemas.issues.issuesEventSchema,
};
@@ -82,6 +84,7 @@ export function issueCommentEvent(params: {
repo: params.repo,
events: ["issue_comment"],
}),
manualRegistration: false,
},
schema: github.schemas.issuesComments.issueCommentEventSchema,
};
@@ -110,6 +113,7 @@ export function pullRequestEvent(params: {
repo: params.repo,
events: ["pull_request"],
}),
manualRegistration: false,
},
schema: github.schemas.pullRequest.pullRequestEventSchema,
};
@@ -140,6 +144,7 @@ export function pullRequestCommentEvent(params: {
repo: params.repo,
events: ["pull_request_review_comment"],
}),
manualRegistration: false,
},
schema:
github.schemas.pullRequestComments.pullRequestReviewCommentEventSchema,
@@ -171,6 +176,7 @@ export function pullRequestReviewEvent(params: {
repo: params.repo,
events: ["pull_request_review"],
}),
manualRegistration: false,
},
schema: github.schemas.pullRequestReviews.pullRequestReviewEventSchema,
};
@@ -199,6 +205,7 @@ export function pushEvent(params: {
repo: params.repo,
events: ["push"],
}),
manualRegistration: false,
},
schema: github.schemas.push.pushEventSchema,
};
@@ -228,6 +235,7 @@ export function newStarEvent(params: {
repo: params.repo,
events: ["star"],
}),
manualRegistration: false,
},
schema: github.schemas.stars.starCreatedEventSchema,
};
+2
View File
@@ -23,6 +23,7 @@
"@types/debug": "^4.1.7",
"@types/node": "16",
"@types/node-fetch": "2.6.x",
"@types/slug": "^5.0.3",
"@types/uuid": "^9.0.0",
"@types/ws": "^8.5.3",
"internal-bridge": "workspace:*",
@@ -39,6 +40,7 @@
"debug": "^4.3.4",
"evt": "^2.4.13",
"node-fetch": "2.6.x",
"slug": "^6.0.0",
"ulid": "^2.3.0",
"uuid": "^9.0.0",
"ws": "^8.11.0",
+38 -1
View File
@@ -3,8 +3,10 @@ import {
TriggerMetadataSchema,
ScheduleSourceSchema,
ScheduledEventPayloadSchema,
EventFilter,
} from "@trigger.dev/common-schemas";
import { z } from "zod";
import slugify from "slug";
export type EventRule = z.infer<typeof EventFilterSchema>;
@@ -16,6 +18,7 @@ export type TriggerEvent<TSchema extends z.ZodTypeAny> = {
export type TriggerCustomEventOptions<TSchema extends z.ZodTypeAny> = {
name: string;
schema: TSchema;
filter?: EventFilter;
};
export function customEvent<TSchema extends z.ZodTypeAny>(
@@ -26,7 +29,7 @@ export function customEvent<TSchema extends z.ZodTypeAny>(
type: "CUSTOM_EVENT",
service: "trigger",
name: options.name,
filter: { event: [options.name] },
filter: { event: [options.name], payload: options.filter ?? {} },
},
schema: options.schema,
};
@@ -47,3 +50,37 @@ export function scheduleEvent(
schema: ScheduledEventPayloadSchema,
};
}
export type TriggerWebhookEventOptions<TSchema extends z.ZodTypeAny> = {
schema: TSchema;
service: string;
eventName: string;
filter?: EventFilter;
verifyPayload?: {
enabled: boolean;
header: string;
};
};
export function webhookEvent<TSchema extends z.ZodTypeAny>(
options: TriggerWebhookEventOptions<TSchema>
): TriggerEvent<TSchema> {
return {
metadata: {
type: "WEBHOOK",
service: slugify(options.service),
name: options.eventName,
filter: {
service: [slugify(options.service)],
payload: options.filter ?? {},
event: [options.eventName],
},
source: {
verifyPayload: options.verifyPayload ?? { enabled: false },
event: options.eventName,
},
manualRegistration: true,
},
schema: options.schema,
};
}
+58 -87
View File
@@ -176,11 +176,12 @@ importers:
vite-tsconfig-paths: ^3.5.1
vitest: ^0.23.4
zod: ^3.20.2
zod-error: ^1.1.0
dependencies:
'@aws-sdk/client-s3': 3.245.0
'@aws-sdk/s3-request-presigner': 3.245.0
'@cfworker/json-schema': 1.12.5
'@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde
'@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu
'@codemirror/commands': 6.1.3
'@codemirror/lang-javascript': 6.1.2
'@codemirror/lang-json': 6.0.1
@@ -205,7 +206,7 @@ importers:
'@tanstack/react-table': 8.7.6_biqbaboplfbrettd7655fr4n2y
'@trigger.dev/common-schemas': link:../../packages/common-schemas
'@trigger.dev/providers': link:../../packages/trigger-providers
'@uiw/react-codemirror': 4.19.5_aguurb4bmecpxzejz52amioxne
'@uiw/react-codemirror': 4.19.5_k4ec5g7vuuzzonc3d6xbjnmmle
bcryptjs: 2.4.3
classnames: 2.3.2
clsx: 1.2.1
@@ -257,6 +258,7 @@ importers:
tsx: 3.12.2
ulid: 2.3.0
zod: 3.20.2
zod-error: 1.1.0
devDependencies:
'@faker-js/faker': 7.6.0
'@remix-run/dev': 1.10.0_biqbaboplfbrettd7655fr4n2y
@@ -810,6 +812,7 @@ importers:
'@types/debug': ^4.1.7
'@types/node': '16'
'@types/node-fetch': 2.6.x
'@types/slug': ^5.0.3
'@types/uuid': ^9.0.0
'@types/ws': ^8.5.3
debug: ^4.3.4
@@ -817,6 +820,7 @@ importers:
internal-bridge: workspace:*
node-fetch: 2.6.x
rimraf: ^3.0.2
slug: ^6.0.0
tsup: ^6.5.0
tsx: ^3.12.1
ulid: ^2.3.0
@@ -828,6 +832,7 @@ importers:
debug: 4.3.4
evt: 2.4.13
node-fetch: 2.6.7
slug: 6.1.0
ulid: 2.3.0
uuid: 9.0.0
ws: 8.12.0
@@ -839,6 +844,7 @@ importers:
'@types/debug': 4.1.7
'@types/node': 16.18.11
'@types/node-fetch': 2.6.2
'@types/slug': 5.0.3
'@types/uuid': 9.0.0
'@types/ws': 8.5.4
internal-bridge: link:../internal-bridge
@@ -3471,13 +3477,12 @@ packages:
prettier: 2.8.2
dev: false
/@codemirror/autocomplete/6.4.0_eo6pz6bvsllvatnnwfprpuflde:
/@codemirror/autocomplete/6.4.0_czcfkg2f66rxeiodoti7r2gulu:
resolution: {integrity: sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==}
peerDependencies:
'@codemirror/language': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
'@lezer/common': ^1.0.0
dependencies:
'@codemirror/language': 6.3.2
'@codemirror/state': 6.2.0
@@ -3497,7 +3502,7 @@ packages:
/@codemirror/lang-javascript/6.1.2:
resolution: {integrity: sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==}
dependencies:
'@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde
'@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu
'@codemirror/language': 6.3.2
'@codemirror/lint': 6.1.0
'@codemirror/state': 6.2.0
@@ -3580,7 +3585,6 @@ packages:
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@cush/relative/1.0.0:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
@@ -4168,7 +4172,6 @@ packages:
/@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/set-array/1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
@@ -4177,7 +4180,6 @@ packages:
/@jridgewell/sourcemap-codec/1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
dev: true
/@jridgewell/trace-mapping/0.3.17:
resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
@@ -4191,7 +4193,6 @@ packages:
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@jsdevtools/ono/7.1.3:
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
@@ -4820,7 +4821,7 @@ packages:
eslint: 8.31.0
eslint-import-resolver-node: 0.3.6
eslint-import-resolver-typescript: 3.5.3_hnftvkj7qg3s6bbigj4pr6djxy
eslint-plugin-import: 2.27.4_qdjeohovcytra7xto5vgmxssaq
eslint-plugin-import: 2.27.4_2ac3tknkazjoq5fxmuugu665ny
eslint-plugin-jest: 26.9.0_ohsifnwenhmxgcp7mend4dnv74
eslint-plugin-jest-dom: 4.0.3_eslint@8.31.0
eslint-plugin-jsx-a11y: 6.7.1_eslint@8.31.0
@@ -5187,7 +5188,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-darwin-x64/1.3.26:
@@ -5196,7 +5196,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm-gnueabihf/1.3.26:
@@ -5205,7 +5204,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-gnu/1.3.26:
@@ -5214,7 +5212,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-musl/1.3.26:
@@ -5223,7 +5220,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-gnu/1.3.26:
@@ -5232,7 +5228,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-musl/1.3.26:
@@ -5241,7 +5236,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-arm64-msvc/1.3.26:
@@ -5250,7 +5244,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-ia32-msvc/1.3.26:
@@ -5259,7 +5252,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-x64-msvc/1.3.26:
@@ -5268,7 +5260,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core/1.3.26:
@@ -5286,7 +5277,6 @@ packages:
'@swc/core-win32-arm64-msvc': 1.3.26
'@swc/core-win32-ia32-msvc': 1.3.26
'@swc/core-win32-x64-msvc': 1.3.26
dev: true
/@swc/helpers/0.4.14:
resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
@@ -5314,7 +5304,7 @@ packages:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
dependencies:
mini-svg-data-uri: 1.4.4
tailwindcss: 3.1.8_postcss@8.4.21
tailwindcss: 3.1.8_aesdjsunmf4wiehhujt67my7tu
/@tailwindcss/typography/0.5.9_tailwindcss@3.1.8:
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
@@ -5325,7 +5315,7 @@ packages:
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
tailwindcss: 3.1.8_postcss@8.4.21
tailwindcss: 3.1.8_aesdjsunmf4wiehhujt67my7tu
dev: true
/@tanstack/react-table/8.7.6_biqbaboplfbrettd7655fr4n2y:
@@ -5415,19 +5405,15 @@ packages:
/@tsconfig/node10/1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
/@tsconfig/node12/1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14/1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16/1.0.3:
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
dev: true
/@types/acorn/4.0.6:
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
@@ -5950,18 +5936,17 @@ packages:
eslint-visitor-keys: 3.3.0
dev: true
/@uiw/codemirror-extensions-basic-setup/4.19.5_tbeldtdcrf45b35pezgkzq2u4e:
/@uiw/codemirror-extensions-basic-setup/4.19.5_wd2tsis3in55bkaiwnc2c46tom:
resolution: {integrity: sha512-1zt7ZPJ01xKkSW/KDy0FZNga0bngN1fC594wCVG7FBi60ehfcAucpooQ+JSPScKXopxcb+ugPKZvVLzr9/OfzA==}
peerDependencies:
'@codemirror/autocomplete': '>=6.0.0'
'@codemirror/commands': '>=6.0.0'
'@codemirror/language': '>=6.0.0'
'@codemirror/lint': '>=6.0.0'
'@codemirror/search': '>=6.0.0'
'@codemirror/state': '>=6.0.0'
'@codemirror/view': '>=6.0.0'
dependencies:
'@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde
'@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu
'@codemirror/commands': 6.1.3
'@codemirror/language': 6.3.2
'@codemirror/lint': 6.1.0
@@ -5970,14 +5955,11 @@ packages:
'@codemirror/view': 6.7.2
dev: false
/@uiw/react-codemirror/4.19.5_aguurb4bmecpxzejz52amioxne:
/@uiw/react-codemirror/4.19.5_k4ec5g7vuuzzonc3d6xbjnmmle:
resolution: {integrity: sha512-ZCHh8d7beXbF8/t7F1+yHht6A9Y6CdKeOkZq4A09lxJEnyTQrj1FMf2zvfaqc7K23KNjkTCtSlbqKKbVDgrWaw==}
peerDependencies:
'@babel/runtime': '>=7.11.0'
'@codemirror/state': '>=6.0.0'
'@codemirror/theme-one-dark': '>=6.0.0'
'@codemirror/view': '>=6.0.0'
codemirror: '>=6.0.0'
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
@@ -5986,14 +5968,13 @@ packages:
'@codemirror/state': 6.2.0
'@codemirror/theme-one-dark': 6.1.0
'@codemirror/view': 6.7.2
'@uiw/codemirror-extensions-basic-setup': 4.19.5_tbeldtdcrf45b35pezgkzq2u4e
codemirror: 6.0.1_@lezer+common@1.0.2
'@uiw/codemirror-extensions-basic-setup': 4.19.5_wd2tsis3in55bkaiwnc2c46tom
codemirror: 6.0.1
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
transitivePeerDependencies:
- '@codemirror/autocomplete'
- '@codemirror/language'
- '@codemirror/lint'
- '@codemirror/search'
dev: false
@@ -6095,7 +6076,6 @@ packages:
/acorn-walk/8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn/7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
@@ -6106,7 +6086,6 @@ packages:
resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/agent-base/6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
@@ -6272,7 +6251,6 @@ packages:
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/arg/5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
@@ -7307,18 +7285,16 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
/codemirror/6.0.1_@lezer+common@1.0.2:
/codemirror/6.0.1:
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
dependencies:
'@codemirror/autocomplete': 6.4.0_eo6pz6bvsllvatnnwfprpuflde
'@codemirror/autocomplete': 6.4.0_czcfkg2f66rxeiodoti7r2gulu
'@codemirror/commands': 6.1.3
'@codemirror/language': 6.3.2
'@codemirror/lint': 6.1.0
'@codemirror/search': 6.2.3
'@codemirror/state': 6.2.0
'@codemirror/view': 6.7.2
transitivePeerDependencies:
- '@lezer/common'
dev: false
/collection-visit/1.0.0:
@@ -7549,7 +7525,6 @@ packages:
/create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/crelt/1.0.5:
resolution: {integrity: sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==}
@@ -8045,7 +8020,6 @@ packages:
/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/diff/5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
@@ -8699,7 +8673,7 @@ packages:
debug: 4.3.4
enhanced-resolve: 5.12.0
eslint: 8.31.0
eslint-plugin-import: 2.27.4_qdjeohovcytra7xto5vgmxssaq
eslint-plugin-import: 2.27.4_2ac3tknkazjoq5fxmuugu665ny
get-tsconfig: 4.3.0
globby: 13.1.3
is-core-module: 2.11.0
@@ -8737,7 +8711,7 @@ packages:
- supports-color
dev: true
/eslint-module-utils/2.7.4_sqt5xxn4ciiurbqrzlaarm6ama:
/eslint-module-utils/2.7.4_v73lhamtbyinynmwa5fn7kpmfq:
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
engines: {node: '>=4'}
peerDependencies:
@@ -8762,6 +8736,7 @@ packages:
debug: 3.2.7
eslint: 8.31.0
eslint-import-resolver-node: 0.3.7
eslint-import-resolver-typescript: 3.5.3_hnftvkj7qg3s6bbigj4pr6djxy
transitivePeerDependencies:
- supports-color
dev: true
@@ -8786,6 +8761,39 @@ packages:
regexpp: 3.2.0
dev: true
/eslint-plugin-import/2.27.4_2ac3tknkazjoq5fxmuugu665ny:
resolution: {integrity: sha512-Z1jVt1EGKia1X9CnBCkpAOhWy8FgQ7OmJ/IblEkT82yrFU/xJaxwujaTzLWqigewwynRQ9mmHfX9MtAfhxm0sA==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.48.1_iukboom6ndih5an6iafl45j2fe
array-includes: 3.1.6
array.prototype.flat: 1.3.1
array.prototype.flatmap: 1.3.1
debug: 3.2.7
doctrine: 2.1.0
eslint: 8.31.0
eslint-import-resolver-node: 0.3.7
eslint-module-utils: 2.7.4_v73lhamtbyinynmwa5fn7kpmfq
has: 1.0.3
is-core-module: 2.11.0
is-glob: 4.0.3
minimatch: 3.1.2
object.values: 1.1.6
resolve: 1.22.1
semver: 6.3.0
tsconfig-paths: 3.14.1
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: true
/eslint-plugin-import/2.27.4_eslint@8.31.0:
resolution: {integrity: sha512-Z1jVt1EGKia1X9CnBCkpAOhWy8FgQ7OmJ/IblEkT82yrFU/xJaxwujaTzLWqigewwynRQ9mmHfX9MtAfhxm0sA==}
engines: {node: '>=4'}
@@ -8818,39 +8826,6 @@ packages:
- supports-color
dev: true
/eslint-plugin-import/2.27.4_qdjeohovcytra7xto5vgmxssaq:
resolution: {integrity: sha512-Z1jVt1EGKia1X9CnBCkpAOhWy8FgQ7OmJ/IblEkT82yrFU/xJaxwujaTzLWqigewwynRQ9mmHfX9MtAfhxm0sA==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.48.1_iukboom6ndih5an6iafl45j2fe
array-includes: 3.1.6
array.prototype.flat: 1.3.1
array.prototype.flatmap: 1.3.1
debug: 3.2.7
doctrine: 2.1.0
eslint: 8.31.0
eslint-import-resolver-node: 0.3.7
eslint-module-utils: 2.7.4_sqt5xxn4ciiurbqrzlaarm6ama
has: 1.0.3
is-core-module: 2.11.0
is-glob: 4.0.3
minimatch: 3.1.2
object.values: 1.1.6
resolve: 1.22.1
semver: 6.3.0
tsconfig-paths: 3.14.1
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: true
/eslint-plugin-jest-dom/4.0.3_eslint@8.31.0:
resolution: {integrity: sha512-9j+n8uj0+V0tmsoS7bYC7fLhQmIvjRqRYEcbDSi+TKPsTThLLXCyj5swMSSf/hTleeMktACnn+HFqXBr5gbcbA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6', yarn: '>=1'}
@@ -11712,7 +11687,6 @@ packages:
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/make-iterator/0.1.1:
resolution: {integrity: sha512-s8tbTxYqrfcXYHAPxUecPxgBnWod7yFShdSOWiV17WRM87bBH2mzr24A4tpUDv9SqebaV6JsPApwKXnisMmMBA==}
@@ -13520,7 +13494,6 @@ packages:
postcss: 8.4.21
ts-node: 10.9.1_fodzh64fuekdilycyvke2qmf2e
yaml: 1.10.2
dev: true
/postcss-load-config/3.1.4_postcss@8.4.21:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
@@ -13537,6 +13510,7 @@ packages:
lilconfig: 2.0.6
postcss: 8.4.21
yaml: 1.10.2
dev: true
/postcss-nested/5.0.6_postcss@8.4.21:
resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==}
@@ -15444,7 +15418,6 @@ packages:
resolve: 1.22.1
transitivePeerDependencies:
- ts-node
dev: true
/tailwindcss/3.1.8_postcss@8.4.21:
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
@@ -15477,6 +15450,7 @@ packages:
resolve: 1.22.1
transitivePeerDependencies:
- ts-node
dev: true
/tapable/2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
@@ -15756,7 +15730,6 @@ packages:
typescript: 4.9.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/ts-toolbelt/9.6.0:
resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==}
@@ -16354,7 +16327,6 @@ packages:
/v8-compile-cache-lib/3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/v8-to-istanbul/9.0.1:
resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==}
@@ -16878,7 +16850,6 @@ packages:
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}