New integration: @trigger.dev/supabase (#203)
* WIP supabase integration * supabase oauth working * Supabase database triggers * Specify postgres:14 * Limit refreshOAuthToken jobs to 10 attempts * Better displaying types and removing onChange for now * WIP on the supabase db client * Finishing the supabase-js integration * Adding changeset * Added supabase to the integration catalogs, and added an optional icon to Integrations * Reworking how we handle types for the triggers (wip) * Update fully over to the new way to define supabase triggers * Go back to using the type for the event name * Add back in the icon to the JobListPresenter since it was moved from the ProjectPresenter * Remove unused import
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@trigger.dev/supabase": patch
|
||||
"@trigger.dev/sdk": patch
|
||||
---
|
||||
|
||||
Added io.integration.runTask and initial @trigger.dev/supabase integration
|
||||
@@ -28,12 +28,14 @@ export function ConnectToIntegrationSheet({
|
||||
button,
|
||||
className,
|
||||
callbackUrl,
|
||||
icon,
|
||||
}: {
|
||||
integration: Integration;
|
||||
organizationId: string;
|
||||
button: React.ReactNode;
|
||||
callbackUrl: string;
|
||||
className?: string;
|
||||
icon?: string;
|
||||
}) {
|
||||
const [integrationMethod, setIntegrationMethod] = useState<
|
||||
IntegrationMethod | undefined
|
||||
@@ -48,7 +50,10 @@ export function ConnectToIntegrationSheet({
|
||||
<SheetTrigger className={className}>{button}</SheetTrigger>
|
||||
<SheetContent size="lg" className="relative">
|
||||
<SheetHeader>
|
||||
<NamedIconInBox name={integration.identifier} className="h-9 w-9" />
|
||||
<NamedIconInBox
|
||||
name={icon ?? integration.identifier}
|
||||
className="h-9 w-9"
|
||||
/>
|
||||
<div className="grow">
|
||||
<Header1>{integration.name}</Header1>
|
||||
{integration.description && (
|
||||
|
||||
@@ -60,6 +60,7 @@ export function ConnectToOAuthForm({
|
||||
},
|
||||
] = useForm({
|
||||
lastSubmission: fetcher.data,
|
||||
shouldRevalidate: "onSubmit",
|
||||
onValidate({ formData }) {
|
||||
return parse(formData, {
|
||||
// Create the schema without any constraint defined
|
||||
@@ -187,15 +188,16 @@ export function ConnectToOAuthForm({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Header2>Scopes</Header2>
|
||||
<Paragraph variant="small" className="mb-4">
|
||||
Select the scopes you want to grant to {integration.name} in order
|
||||
for it to access your data. Note: If you try and perform an action
|
||||
in a Job that requires a scope you haven’t granted, that task will
|
||||
fail.
|
||||
</Paragraph>
|
||||
{/* <Header3 className="mb-2">
|
||||
{authMethod.scopes.length > 0 && (
|
||||
<div>
|
||||
<Header2>Scopes</Header2>
|
||||
<Paragraph variant="small" className="mb-4">
|
||||
Select the scopes you want to grant to {integration.name} in order
|
||||
for it to access your data. Note: If you try and perform an action
|
||||
in a Job that requires a scope you haven’t granted, that task will
|
||||
fail.
|
||||
</Paragraph>
|
||||
{/* <Header3 className="mb-2">
|
||||
Select from popular scope collections
|
||||
</Header3>
|
||||
<fieldset>
|
||||
@@ -205,60 +207,63 @@ export function ConnectToOAuthForm({
|
||||
variant="button/small"
|
||||
/>
|
||||
</fieldset> */}
|
||||
<div className="mb-2 mt-4 flex items-center justify-between">
|
||||
<Header3>Select {integration.name} scopes</Header3>
|
||||
<Paragraph variant="small" className="text-slate-500">
|
||||
{simplur`${selectedScopes.size} scope[|s] selected`}
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Input
|
||||
placeholder="Search scopes"
|
||||
className="mb-2"
|
||||
variant="medium"
|
||||
icon="search"
|
||||
fullWidth={true}
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
/>
|
||||
<div className="mb-28 flex flex-col gap-y-0.5 overflow-hidden rounded-md">
|
||||
{filteredItems.length === 0 && (
|
||||
<Paragraph variant="small" className="p-4">
|
||||
No scopes match {filterText}. Try a different search query.
|
||||
<div className="mb-2 mt-4 flex items-center justify-between">
|
||||
<Header3>Select {integration.name} scopes</Header3>
|
||||
<Paragraph variant="small" className="text-slate-500">
|
||||
{simplur`${selectedScopes.size} scope[|s] selected`}
|
||||
</Paragraph>
|
||||
)}
|
||||
{authMethod.scopes.map((s) => {
|
||||
return (
|
||||
<Checkbox
|
||||
key={s.name}
|
||||
id={s.name}
|
||||
value={s.name}
|
||||
name="scopes"
|
||||
label={s.name}
|
||||
defaultChecked={s.defaultChecked ?? false}
|
||||
badges={s.annotations?.map((a) => a.label)}
|
||||
description={s.description}
|
||||
variant="description"
|
||||
className={cn(
|
||||
filteredItems.find((f) => f.name === s.name) ? "" : "hidden"
|
||||
)}
|
||||
onChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setSelectedScopes((selected) => {
|
||||
selected.add(s.name);
|
||||
return new Set(selected);
|
||||
});
|
||||
} else {
|
||||
setSelectedScopes((selected) => {
|
||||
selected.delete(s.name);
|
||||
return new Set(selected);
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Input
|
||||
placeholder="Search scopes"
|
||||
className="mb-2"
|
||||
variant="medium"
|
||||
icon="search"
|
||||
fullWidth={true}
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
/>
|
||||
<div className="mb-28 flex flex-col gap-y-0.5 overflow-hidden rounded-md">
|
||||
{filteredItems.length === 0 && (
|
||||
<Paragraph variant="small" className="p-4">
|
||||
No scopes match {filterText}. Try a different search query.
|
||||
</Paragraph>
|
||||
)}
|
||||
{authMethod.scopes.map((s) => {
|
||||
return (
|
||||
<Checkbox
|
||||
key={s.name}
|
||||
id={s.name}
|
||||
value={s.name}
|
||||
name="scopes"
|
||||
label={s.name}
|
||||
defaultChecked={s.defaultChecked ?? false}
|
||||
badges={s.annotations?.map((a) => a.label)}
|
||||
description={s.description}
|
||||
variant="description"
|
||||
className={cn(
|
||||
filteredItems.find((f) => f.name === s.name)
|
||||
? ""
|
||||
: "hidden"
|
||||
)}
|
||||
onChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setSelectedScopes((selected) => {
|
||||
selected.add(s.name);
|
||||
return new Set(selected);
|
||||
});
|
||||
} else {
|
||||
setSelectedScopes((selected) => {
|
||||
selected.delete(s.name);
|
||||
return new Set(selected);
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fieldset>
|
||||
|
||||
<div className="absolute bottom-0 left-0 flex w-full items-center justify-end gap-x-4 rounded-b-md border-t border-slate-800 bg-midnight-900 p-4">
|
||||
@@ -268,7 +273,7 @@ export function ConnectToOAuthForm({
|
||||
className="flex gap-2"
|
||||
disabled={transition.state !== "idle"}
|
||||
variant="primary/medium"
|
||||
LeadingIcon={integration.identifier}
|
||||
LeadingIcon={integration.icon ?? integration.identifier}
|
||||
>
|
||||
Connect to {integration.name}
|
||||
</Button>
|
||||
|
||||
@@ -172,15 +172,16 @@ export function UpdateOAuthForm({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Header2>Scopes</Header2>
|
||||
<Paragraph variant="small" className="mb-4">
|
||||
Select the scopes you want to grant to {integration.name} in order
|
||||
for it to access your data. Note: If you try and perform an action
|
||||
in a Job that requires a scope you haven’t granted, that task will
|
||||
fail.
|
||||
</Paragraph>
|
||||
{/* <Header3 className="mb-2">
|
||||
{authMethod.scopes.length > 0 && (
|
||||
<div>
|
||||
<Header2>Scopes</Header2>
|
||||
<Paragraph variant="small" className="mb-4">
|
||||
Select the scopes you want to grant to {integration.name} in order
|
||||
for it to access your data. Note: If you try and perform an action
|
||||
in a Job that requires a scope you haven’t granted, that task will
|
||||
fail.
|
||||
</Paragraph>
|
||||
{/* <Header3 className="mb-2">
|
||||
Select from popular scope collections
|
||||
</Header3>
|
||||
<fieldset>
|
||||
@@ -190,60 +191,63 @@ export function UpdateOAuthForm({
|
||||
variant="button/small"
|
||||
/>
|
||||
</fieldset> */}
|
||||
<div className="mb-2 mt-4 flex items-center justify-between">
|
||||
<Header3>Select {integration.name} scopes</Header3>
|
||||
<Paragraph variant="small" className="text-slate-500">
|
||||
{simplur`${selectedScopes.size} scope[|s] selected`}
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Input
|
||||
placeholder="Search scopes"
|
||||
className="mb-2"
|
||||
variant="medium"
|
||||
icon="search"
|
||||
fullWidth={true}
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
/>
|
||||
<div className="mb-28 flex flex-col gap-y-0.5 overflow-hidden rounded-md">
|
||||
{filteredItems.length === 0 && (
|
||||
<Paragraph variant="small" className="p-4">
|
||||
No scopes match {filterText}. Try a different search query.
|
||||
<div className="mb-2 mt-4 flex items-center justify-between">
|
||||
<Header3>Select {integration.name} scopes</Header3>
|
||||
<Paragraph variant="small" className="text-slate-500">
|
||||
{simplur`${selectedScopes.size} scope[|s] selected`}
|
||||
</Paragraph>
|
||||
)}
|
||||
{authMethod.scopes.map((s) => {
|
||||
return (
|
||||
<Checkbox
|
||||
key={s.name}
|
||||
id={s.name}
|
||||
value={s.name}
|
||||
name="scopes"
|
||||
label={s.name}
|
||||
defaultChecked={s.defaultChecked ?? false}
|
||||
badges={s.annotations?.map((a) => a.label)}
|
||||
description={s.description}
|
||||
variant="description"
|
||||
className={cn(
|
||||
filteredItems.find((f) => f.name === s.name) ? "" : "hidden"
|
||||
)}
|
||||
onChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setSelectedScopes((selected) => {
|
||||
selected.add(s.name);
|
||||
return new Set(selected);
|
||||
});
|
||||
} else {
|
||||
setSelectedScopes((selected) => {
|
||||
selected.delete(s.name);
|
||||
return new Set(selected);
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Input
|
||||
placeholder="Search scopes"
|
||||
className="mb-2"
|
||||
variant="medium"
|
||||
icon="search"
|
||||
fullWidth={true}
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
/>
|
||||
<div className="mb-28 flex flex-col gap-y-0.5 overflow-hidden rounded-md">
|
||||
{filteredItems.length === 0 && (
|
||||
<Paragraph variant="small" className="p-4">
|
||||
No scopes match {filterText}. Try a different search query.
|
||||
</Paragraph>
|
||||
)}
|
||||
{authMethod.scopes.map((s) => {
|
||||
return (
|
||||
<Checkbox
|
||||
key={s.name}
|
||||
id={s.name}
|
||||
value={s.name}
|
||||
name="scopes"
|
||||
label={s.name}
|
||||
defaultChecked={s.defaultChecked ?? false}
|
||||
badges={s.annotations?.map((a) => a.label)}
|
||||
description={s.description}
|
||||
variant="description"
|
||||
className={cn(
|
||||
filteredItems.find((f) => f.name === s.name)
|
||||
? ""
|
||||
: "hidden"
|
||||
)}
|
||||
onChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setSelectedScopes((selected) => {
|
||||
selected.add(s.name);
|
||||
return new Set(selected);
|
||||
});
|
||||
} else {
|
||||
setSelectedScopes((selected) => {
|
||||
selected.delete(s.name);
|
||||
return new Set(selected);
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fieldset>
|
||||
|
||||
<div className="absolute bottom-0 left-0 flex w-full items-center justify-end gap-x-4 rounded-b-md border-t border-slate-800 bg-midnight-900 p-4">
|
||||
|
||||
@@ -124,7 +124,10 @@ export function TaskCard({
|
||||
)}
|
||||
{connection && (
|
||||
<RunPanelIconProperty
|
||||
icon={connection.integration.definitionId}
|
||||
icon={
|
||||
connection.integration.definition.icon ??
|
||||
connection.integration.definitionId
|
||||
}
|
||||
label="Connection"
|
||||
value={connection.integration.slug}
|
||||
/>
|
||||
|
||||
@@ -48,6 +48,7 @@ export class IntegrationClientPresenter {
|
||||
id: true,
|
||||
name: true,
|
||||
packageName: true,
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
connectionType: true,
|
||||
@@ -119,6 +120,7 @@ export class IntegrationClientPresenter {
|
||||
identifier: integration.definition.id,
|
||||
name: integration.definition.name,
|
||||
packageName: integration.definition.packageName,
|
||||
icon: integration.definition.icon,
|
||||
},
|
||||
authMethod: {
|
||||
type: integration.authMethod?.type ?? "local",
|
||||
|
||||
@@ -51,6 +51,7 @@ export class IntegrationsPresenter {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
authSource: true,
|
||||
@@ -111,6 +112,7 @@ export class IntegrationsPresenter {
|
||||
return {
|
||||
id: c.id,
|
||||
title: c.title ?? c.slug,
|
||||
icon: c.definition.icon ?? c.definition.id,
|
||||
slug: c.slug,
|
||||
integrationIdentifier: c.definition.id,
|
||||
description: c.description,
|
||||
|
||||
@@ -158,7 +158,9 @@ export class JobListPresenter {
|
||||
const integrations = alias.version.integrations.map((integration) => ({
|
||||
key: integration.key,
|
||||
title: integration.integration.slug,
|
||||
icon: integration.integration.definition.id,
|
||||
icon:
|
||||
integration.integration.definition.icon ??
|
||||
integration.integration.definition.id,
|
||||
setupStatus: integration.integration.setupStatus,
|
||||
}));
|
||||
|
||||
|
||||
@@ -46,6 +46,11 @@ const taskSelect = {
|
||||
definitionId: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
definition: {
|
||||
select: {
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -41,6 +41,11 @@ export class TriggerSourcePresenter {
|
||||
slug: true,
|
||||
definitionId: true,
|
||||
setupStatus: true,
|
||||
definition: {
|
||||
select: {
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
|
||||
@@ -36,6 +36,11 @@ export class TriggersPresenter {
|
||||
slug: true,
|
||||
definitionId: true,
|
||||
setupStatus: true,
|
||||
definition: {
|
||||
select: {
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
|
||||
+6
-5
@@ -185,10 +185,12 @@ function PossibleIntegrationsList({
|
||||
integration={option}
|
||||
organizationId={organizationId}
|
||||
callbackUrl={callbackUrl}
|
||||
icon={option.icon}
|
||||
button={
|
||||
<AddIntegrationConnection
|
||||
identifier={option.identifier}
|
||||
name={option.name}
|
||||
icon={option.icon}
|
||||
isIntegration
|
||||
/>
|
||||
}
|
||||
@@ -330,10 +332,7 @@ function ConnectedIntegrationsList({
|
||||
<TableCell to={path}>{client.title}</TableCell>
|
||||
<TableCell to={path}>
|
||||
<span className="flex items-center gap-1">
|
||||
<NamedIcon
|
||||
name={client.integrationIdentifier}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
<NamedIcon name={client.icon} className="h-5 w-5" />
|
||||
{client.integration.name}
|
||||
</span>
|
||||
</TableCell>
|
||||
@@ -503,15 +502,17 @@ function AddIntegrationConnection({
|
||||
identifier,
|
||||
name,
|
||||
isIntegration,
|
||||
icon,
|
||||
}: {
|
||||
identifier: string;
|
||||
name: string;
|
||||
isIntegration: boolean;
|
||||
icon?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="group flex h-11 w-full items-center gap-3 rounded-md p-1 pr-3 transition hover:bg-slate-850">
|
||||
<NamedIconInBox
|
||||
name={identifier}
|
||||
name={icon ?? identifier}
|
||||
className="h-9 w-9 flex-none transition group-hover:border-slate-750"
|
||||
/>
|
||||
<Paragraph
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ export default function Integrations() {
|
||||
}
|
||||
/>
|
||||
<PageInfoProperty
|
||||
icon={client.integration.identifier}
|
||||
icon={client.integration.icon ?? client.integration.identifier}
|
||||
label="API"
|
||||
value={client.integration.name}
|
||||
/>
|
||||
|
||||
+4
-1
@@ -87,7 +87,10 @@ export default function Integrations() {
|
||||
<TableCell to={path}>
|
||||
<div className="flex items-center gap-1">
|
||||
<NamedIcon
|
||||
name={t.integration.definitionId}
|
||||
name={
|
||||
t.integration.definition.icon ??
|
||||
t.integration.definitionId
|
||||
}
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
<LabelValueStack
|
||||
|
||||
+4
-1
@@ -170,7 +170,10 @@ export default function Page() {
|
||||
<PageInfoRow>
|
||||
<PageInfoGroup>
|
||||
<PageInfoProperty
|
||||
icon={trigger.integration.definitionId}
|
||||
icon={
|
||||
trigger.integration.definition.icon ??
|
||||
trigger.integration.definitionId
|
||||
}
|
||||
label={trigger.integration.title ?? ""}
|
||||
value={trigger.integration.slug}
|
||||
/>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { prisma } from "~/db.server";
|
||||
import { env } from "~/env.server";
|
||||
import { integrationAuthRepository } from "~/services/externalApis/integrationAuthRepository.server";
|
||||
import { OAuthClient, OAuthClientSchema } from "~/services/externalApis/types";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { getSecretStore } from "~/services/secrets/secretStore.server";
|
||||
import { requestUrl } from "~/utils/requestUrl.server";
|
||||
|
||||
@@ -22,6 +23,7 @@ export async function loader({ request }: LoaderArgs) {
|
||||
}
|
||||
|
||||
const url = requestUrl(request);
|
||||
|
||||
const parsedParams = ParamsSchema.safeParse(
|
||||
Object.fromEntries(url.searchParams)
|
||||
);
|
||||
|
||||
@@ -75,11 +75,7 @@ export function createSchema(
|
||||
redirectTo: z.string(),
|
||||
scopes: z.preprocess(
|
||||
(data) => (typeof data === "string" ? [data] : data),
|
||||
z
|
||||
.array(z.string(), {
|
||||
required_error: "You must select at least one scope",
|
||||
})
|
||||
.nonempty("You must select at least one scope")
|
||||
z.array(z.string()).default([])
|
||||
),
|
||||
})
|
||||
.refine(
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import type {
|
||||
SecretReference,
|
||||
ExternalAccount,
|
||||
IntegrationConnection,
|
||||
ConnectionType,
|
||||
Integration,
|
||||
ConnectionAttempt,
|
||||
ConnectionType,
|
||||
ExternalAccount,
|
||||
Integration,
|
||||
IntegrationAuthMethod,
|
||||
IntegrationConnection,
|
||||
IntegrationDefinition,
|
||||
SecretReference,
|
||||
} from "@trigger.dev/database";
|
||||
import jsonpointer from "jsonpointer";
|
||||
import { customAlphabet } from "nanoid";
|
||||
import * as crypto from "node:crypto";
|
||||
import createSlug from "slug";
|
||||
import type {
|
||||
PrismaClient,
|
||||
PrismaClientOrTransaction,
|
||||
@@ -31,7 +30,7 @@ import {
|
||||
} from "./oauth2.server";
|
||||
import {
|
||||
AccessToken,
|
||||
ApiAuthenticationMethod,
|
||||
AccessTokenSchema,
|
||||
ApiAuthenticationMethodOAuth2,
|
||||
ConnectionMetadata,
|
||||
GrantTokenParams,
|
||||
@@ -39,7 +38,7 @@ import {
|
||||
OAuthClientSchema,
|
||||
RefreshTokenParams,
|
||||
} from "./types";
|
||||
import { AccessTokenSchema } from "./types";
|
||||
import { logger } from "../logger.server";
|
||||
|
||||
export type ConnectionWithSecretReference = IntegrationConnection & {
|
||||
dataReference: SecretReference;
|
||||
@@ -127,6 +126,16 @@ export class IntegrationAuthRepository {
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug("Creating Integration", {
|
||||
id,
|
||||
clientType,
|
||||
scopes,
|
||||
title,
|
||||
slug,
|
||||
integrationIdentifier,
|
||||
integrationAuthMethod,
|
||||
});
|
||||
|
||||
const client = await tx.integration.create({
|
||||
data: {
|
||||
id,
|
||||
@@ -391,6 +400,8 @@ export class IntegrationAuthRepository {
|
||||
expiresInPointer:
|
||||
authMethod.config.token.expiresInPointer ?? "/expires_in",
|
||||
scopePointer: authMethod.config.token.scopePointer ?? "/scope",
|
||||
authorizationMethod: authMethod.config.token.authorizationMethod,
|
||||
bodyFormat: authMethod.config.token.bodyFormat,
|
||||
};
|
||||
|
||||
const token = await grantOAuth2Token(
|
||||
@@ -662,6 +673,9 @@ export class IntegrationAuthRepository {
|
||||
expiresInPointer:
|
||||
authMethod.config.token.expiresInPointer ?? "/expires_in",
|
||||
scopePointer: authMethod.config.token.scopePointer ?? "/scope",
|
||||
authorizationMethod: authMethod.config.token.authorizationMethod,
|
||||
bodyFormat: authMethod.config.token.bodyFormat,
|
||||
skipScopes: authMethod.config.refresh.skipScopes,
|
||||
};
|
||||
|
||||
//todo do we need pkce here?
|
||||
|
||||
@@ -3,6 +3,7 @@ import { openai } from "./integrations/openai";
|
||||
import { plain } from "./integrations/plain";
|
||||
import { resend } from "./integrations/resend";
|
||||
import { slack } from "./integrations/slack";
|
||||
import { supabaseManagement, supabase } from "./integrations/supabase";
|
||||
import { typeform } from "./integrations/typeform";
|
||||
import type { Integration } from "./types";
|
||||
|
||||
@@ -35,4 +36,6 @@ export const integrationCatalog = new IntegrationCatalog({
|
||||
resend,
|
||||
slack,
|
||||
typeform,
|
||||
supabaseManagement,
|
||||
supabase,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import type { HelpSample, Integration } from "../types";
|
||||
|
||||
const managementUsageSample: HelpSample = {
|
||||
title: "Using the client",
|
||||
code: `
|
||||
import { SupabaseManagement } from "@trigger.dev/supabase";
|
||||
|
||||
const supabase = new SupabaseManagement({
|
||||
id: "__SLUG__",
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "on-new-users",
|
||||
name: "On New Users",
|
||||
version: "0.1.1",
|
||||
trigger: supabase.onInsert({
|
||||
table: "users",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
},
|
||||
});
|
||||
`,
|
||||
};
|
||||
|
||||
const managementApiKeyUsageSample: HelpSample = {
|
||||
title: "Using the client",
|
||||
code: `
|
||||
import { SupabaseManagement } from "@trigger.dev/supabase";
|
||||
|
||||
const supabase = new SupabaseManagement({
|
||||
id: "__SLUG__",
|
||||
apiKey: process.env.SUPABASE_API_KEY!,
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "on-new-users",
|
||||
name: "On New Users",
|
||||
version: "0.1.1",
|
||||
trigger: supabase.onInsert({
|
||||
table: "users",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
},
|
||||
});
|
||||
`,
|
||||
};
|
||||
|
||||
export const supabaseManagement: Integration = {
|
||||
identifier: "supabase-management",
|
||||
icon: "supabase",
|
||||
name: "Supabase Management",
|
||||
packageName: "@trigger.dev/supabase",
|
||||
authenticationMethods: {
|
||||
apikey: {
|
||||
type: "apikey",
|
||||
help: {
|
||||
samples: [
|
||||
{
|
||||
title: "Creating the client",
|
||||
code: `
|
||||
import { SupabaseManagement } from "@trigger.dev/supabase";
|
||||
|
||||
const supabase = new SupabaseManagement({
|
||||
id: "__SLUG__"
|
||||
apiKey: process.env.SUPABASE_API_KEY!,
|
||||
});
|
||||
`,
|
||||
},
|
||||
managementApiKeyUsageSample,
|
||||
],
|
||||
},
|
||||
},
|
||||
oauth2: {
|
||||
name: "OAuth",
|
||||
type: "oauth2",
|
||||
client: {
|
||||
id: {
|
||||
envName: "CLOUD_SUPABASE_CLIENT_ID",
|
||||
},
|
||||
secret: {
|
||||
envName: "CLOUD_SUPABASE_CLIENT_SECRET",
|
||||
},
|
||||
},
|
||||
config: {
|
||||
authorization: {
|
||||
url: "https://api.supabase.com/v1/oauth/authorize",
|
||||
scopeSeparator: " ",
|
||||
},
|
||||
token: {
|
||||
url: "https://api.supabase.com/v1/oauth/token",
|
||||
metadata: { accountPointer: "/team/name" },
|
||||
authorizationMethod: "body",
|
||||
},
|
||||
refresh: {
|
||||
url: "https://api.supabase.com/v1/oauth/token",
|
||||
skipScopes: true,
|
||||
},
|
||||
},
|
||||
scopes: [
|
||||
{
|
||||
name: "all",
|
||||
description:
|
||||
"Grants full access to all resources available in the Supabase Management API.",
|
||||
defaultChecked: true,
|
||||
},
|
||||
],
|
||||
help: {
|
||||
samples: [
|
||||
{
|
||||
title: "Creating the client",
|
||||
code: `
|
||||
import { SupabaseManagement } from "@trigger.dev/supabase";
|
||||
|
||||
const supabase = new SupabaseManagement({
|
||||
id: "__SLUG__"
|
||||
});
|
||||
`,
|
||||
},
|
||||
managementUsageSample,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const supabaseUsageSample: HelpSample = {
|
||||
title: "Using the client",
|
||||
code: `
|
||||
import { Supabase } from "@trigger.dev/supabase";
|
||||
import { Database } from "@/supabase.types";
|
||||
|
||||
const supabase = new Supabase<Database>({
|
||||
id: "__SLUG__",
|
||||
projectId: process.env.SUPABASE_ID!,
|
||||
supabaseKey: process.env.SUPABASE_API_KEY!,
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "on-new-users",
|
||||
name: "On New Users",
|
||||
version: "0.1.1",
|
||||
trigger: eventTrigger({
|
||||
name: "foo.bar
|
||||
}),
|
||||
integrations: {
|
||||
supabase
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.supabase.runTask("get-users", async (db) => {
|
||||
return await db.from("users").select("*");
|
||||
});
|
||||
},
|
||||
});
|
||||
`,
|
||||
};
|
||||
|
||||
export const supabase: Integration = {
|
||||
identifier: "supabase",
|
||||
icon: "supabase",
|
||||
name: "Supabase",
|
||||
packageName: "@trigger.dev/supabase",
|
||||
authenticationMethods: {
|
||||
apikey: {
|
||||
type: "apikey",
|
||||
help: {
|
||||
samples: [
|
||||
{
|
||||
title: "Creating the client",
|
||||
code: `
|
||||
import { Supabase } from "@trigger.dev/supabase";
|
||||
|
||||
const supabase = new Supabase({
|
||||
id: "__SLUG__"
|
||||
projectId: process.env.SUPABASE_ID!,
|
||||
supabaseKey: process.env.SUPABASE_KEY!,
|
||||
});
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import simpleOauth2 from "simple-oauth2";
|
||||
import jsonpointer from "jsonpointer";
|
||||
import * as crypto from "node:crypto";
|
||||
import simpleOauth2 from "simple-oauth2";
|
||||
import type {
|
||||
AccessToken,
|
||||
CreateUrlParams,
|
||||
@@ -7,7 +8,6 @@ import type {
|
||||
OAuthClient,
|
||||
RefreshTokenParams,
|
||||
} from "./types";
|
||||
import jsonpointer from "jsonpointer";
|
||||
|
||||
export function getClientConfig({
|
||||
env,
|
||||
@@ -126,6 +126,8 @@ export async function grantOAuth2Token(
|
||||
expiresInPointer,
|
||||
scopePointer,
|
||||
pkceCode,
|
||||
authorizationMethod,
|
||||
bodyFormat,
|
||||
}: GrantTokenParams,
|
||||
strategy?: string
|
||||
): Promise<AccessToken> {
|
||||
@@ -145,7 +147,11 @@ export async function grantOAuth2Token(
|
||||
tokenHost: `${tokenUrlObj.protocol}//${tokenUrlObj.host}`,
|
||||
tokenPath: tokenUrlObj.pathname,
|
||||
},
|
||||
};
|
||||
options: {
|
||||
authorizationMethod,
|
||||
bodyFormat,
|
||||
},
|
||||
} as const;
|
||||
|
||||
const simpleOAuthClient = new simpleOauth2.AuthorizationCode(clientConfig);
|
||||
|
||||
@@ -161,7 +167,6 @@ export async function grantOAuth2Token(
|
||||
const token = await simpleOAuthClient.getToken({
|
||||
code,
|
||||
redirect_uri: callbackUrl,
|
||||
scope: requestedScopes.join(scopeSeparator),
|
||||
...pkceParams,
|
||||
});
|
||||
|
||||
@@ -188,6 +193,9 @@ export async function refreshOAuth2Token(
|
||||
refreshTokenPointer,
|
||||
expiresInPointer,
|
||||
scopePointer,
|
||||
authorizationMethod,
|
||||
bodyFormat,
|
||||
skipScopes,
|
||||
}: RefreshTokenParams,
|
||||
strategy?: string
|
||||
) {
|
||||
@@ -208,6 +216,10 @@ export async function refreshOAuth2Token(
|
||||
tokenPath: tokenUrlObj.pathname,
|
||||
refreshPath: tokenUrlObj.pathname,
|
||||
},
|
||||
options: {
|
||||
authorizationMethod,
|
||||
bodyFormat,
|
||||
},
|
||||
};
|
||||
|
||||
const simpleOAuthClient = new simpleOauth2.AuthorizationCode(clientConfig);
|
||||
@@ -220,7 +232,7 @@ export async function refreshOAuth2Token(
|
||||
});
|
||||
|
||||
const newToken = await oldToken.refresh({
|
||||
scope: requestedScopes.join(scopeSeparator),
|
||||
...(skipScopes ? {} : { scope: requestedScopes.join(scopeSeparator) }),
|
||||
});
|
||||
|
||||
return convertToken({
|
||||
|
||||
@@ -3,6 +3,8 @@ import { z } from "zod";
|
||||
export type Integration = {
|
||||
/** Used to uniquely identify an integration */
|
||||
identifier: string;
|
||||
/** identifier is used by default as the icon name, but you can specify a different one using icon */
|
||||
icon?: string;
|
||||
/** The name of the integration */
|
||||
name: string;
|
||||
/** The description of the integration */
|
||||
@@ -93,10 +95,20 @@ export type ApiAuthenticationMethodOAuth2 = {
|
||||
scopePointer?: string;
|
||||
/** Some APIs have strange granting logic, this allows total control to deal with that */
|
||||
grantTokenStrategy?: string;
|
||||
/** Format of data sent in the request body. Defaults to form. */
|
||||
bodyFormat?: "form" | "json";
|
||||
/**
|
||||
* Indicates the method used to send the client.id/client.secret authorization params at the token request.
|
||||
* If set to body, the bodyFormat option will be used to format the credentials.
|
||||
* Defaults to header
|
||||
*/
|
||||
authorizationMethod?: "header" | "body";
|
||||
};
|
||||
/** Refresh is how a token is refreshed */
|
||||
refresh: {
|
||||
url: string;
|
||||
/** Skip including scopes with the refresh_token request */
|
||||
skipScopes?: boolean;
|
||||
/** Some APIs have strange refreshing logic, this allows total control to deal with that */
|
||||
refreshTokenStrategy?: string;
|
||||
};
|
||||
@@ -141,6 +153,8 @@ export type GrantTokenParams = {
|
||||
expiresInPointer: string;
|
||||
scopePointer: string;
|
||||
pkceCode?: string;
|
||||
authorizationMethod?: "header" | "body";
|
||||
bodyFormat?: "form" | "json";
|
||||
};
|
||||
|
||||
export type RefreshTokenParams = {
|
||||
@@ -154,6 +168,9 @@ export type RefreshTokenParams = {
|
||||
refreshTokenPointer: string;
|
||||
expiresInPointer: string;
|
||||
scopePointer: string;
|
||||
authorizationMethod?: "header" | "body";
|
||||
bodyFormat?: "form" | "json";
|
||||
skipScopes?: boolean;
|
||||
};
|
||||
|
||||
type AdditionalField = {
|
||||
|
||||
@@ -216,6 +216,7 @@ function getWorkerQueue() {
|
||||
},
|
||||
refreshOAuthToken: {
|
||||
queueName: "internal-queue",
|
||||
maxAttempts: 10,
|
||||
handler: async (payload, job) => {
|
||||
await integrationAuthRepository.refreshConnection({
|
||||
connectionId: payload.connectionId,
|
||||
|
||||
@@ -5,16 +5,17 @@ import { seedCloud } from "./seedCloud";
|
||||
import { prisma } from "../app/db.server";
|
||||
|
||||
async function seedIntegrationAuthMethods() {
|
||||
for (const [identifier, integration] of Object.entries(
|
||||
for (const [_, integration] of Object.entries(
|
||||
integrationCatalog.getIntegrations()
|
||||
)) {
|
||||
await prisma.integrationDefinition.upsert({
|
||||
where: {
|
||||
id: identifier,
|
||||
id: integration.identifier,
|
||||
},
|
||||
create: {
|
||||
id: identifier,
|
||||
id: integration.identifier,
|
||||
name: integration.name,
|
||||
icon: integration.icon ?? integration.identifier,
|
||||
instructions: "Instructions go here",
|
||||
description: integration.description,
|
||||
packageName: integration.packageName,
|
||||
@@ -23,6 +24,7 @@ async function seedIntegrationAuthMethods() {
|
||||
name: integration.name,
|
||||
description: integration.description,
|
||||
packageName: integration.packageName,
|
||||
icon: integration.icon ?? integration.identifier,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -30,12 +32,12 @@ async function seedIntegrationAuthMethods() {
|
||||
integration.authenticationMethods
|
||||
)) {
|
||||
if (authMethod.type === "oauth2") {
|
||||
console.log(`Upserting auth method ${identifier}.${key}`);
|
||||
console.log(`Upserting auth method ${integration.identifier}.${key}`);
|
||||
|
||||
await prisma.integrationAuthMethod.upsert({
|
||||
where: {
|
||||
definitionId_key: {
|
||||
definitionId: identifier,
|
||||
definitionId: integration.identifier,
|
||||
key,
|
||||
},
|
||||
},
|
||||
@@ -49,7 +51,7 @@ async function seedIntegrationAuthMethods() {
|
||||
scopes: authMethod.scopes,
|
||||
definition: {
|
||||
connect: {
|
||||
id: identifier,
|
||||
id: integration.identifier,
|
||||
},
|
||||
},
|
||||
help: authMethod.help,
|
||||
|
||||
@@ -10,7 +10,7 @@ networks:
|
||||
services:
|
||||
database:
|
||||
container_name: database
|
||||
image: postgres:latest
|
||||
image: postgres:14
|
||||
restart: always
|
||||
volumes:
|
||||
- database-data:/var/lib/postgresql/data/
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"generate:types": "npx supabase gen types typescript --project-id axtbanoixaztvdntngew --schema public --schema public_2 > src/supabase.types.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/github": "workspace:*",
|
||||
@@ -25,7 +26,8 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"typescript": "5.0.4",
|
||||
"zod": "3.21.4"
|
||||
"zod": "3.21.4",
|
||||
"@trigger.dev/supabase": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "8.42.0",
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
import { Database } from "@/supabase.types";
|
||||
import { client } from "@/trigger";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { SupabaseManagement, Supabase } from "@trigger.dev/supabase";
|
||||
import { z } from "zod";
|
||||
|
||||
const supabase = new SupabaseManagement({
|
||||
id: "supabase",
|
||||
});
|
||||
|
||||
const db = supabase.db<Database>(process.env.SUPABASE_ID!);
|
||||
|
||||
const dbNoTypes = supabase.db(process.env.SUPABASE_ID!);
|
||||
|
||||
const supabaseManagementKey = new SupabaseManagement({
|
||||
id: "supabase-management-key",
|
||||
apiKey: process.env.SUPABASE_API_KEY!,
|
||||
});
|
||||
|
||||
const dbKey = supabase.db<Database>(process.env.SUPABASE_ID!);
|
||||
|
||||
const supabaseDB = new Supabase<Database>({
|
||||
id: "supabase-db",
|
||||
supabaseUrl: `https://${process.env.SUPABASE_ID}.supabase.co`,
|
||||
supabaseKey: process.env.SUPABASE_KEY!,
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-playground",
|
||||
name: "Supabase Playground",
|
||||
version: "0.1.1",
|
||||
trigger: eventTrigger({
|
||||
name: "supabase.playground",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
supabaseDB,
|
||||
supabaseManagementKey,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.supabaseManagementKey.getPGConfig("get-pg-config", {
|
||||
ref: payload.ref,
|
||||
});
|
||||
|
||||
await io.supabase.getOrganizations("get-orgs");
|
||||
await io.supabase.getProjects("get-projects");
|
||||
|
||||
await io.supabase.listFunctions("list-functions", {
|
||||
ref: payload.ref,
|
||||
});
|
||||
|
||||
await io.supabase.runQuery("run-query", {
|
||||
ref: payload.ref,
|
||||
query: "SELECT * FROM users",
|
||||
});
|
||||
|
||||
await io.supabase.getTypescriptTypes("get-typescript-types", {
|
||||
ref: payload.ref,
|
||||
});
|
||||
|
||||
const users = await io.supabaseDB.runTask(
|
||||
"fetch-users",
|
||||
async (db) => {
|
||||
const { data, error } = await db.from("users").select("*");
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return data;
|
||||
},
|
||||
{ name: "Fetch Users" }
|
||||
);
|
||||
|
||||
const newUser = await io.supabaseDB.runTask(
|
||||
"create-user",
|
||||
async (db) => {
|
||||
return await db
|
||||
.from("users")
|
||||
.insert({
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email_address: "john@trigger.dev",
|
||||
})
|
||||
.select();
|
||||
},
|
||||
{ name: "New Users" }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-create-todo",
|
||||
name: "Supabase Create Todo",
|
||||
version: "0.1.1",
|
||||
trigger: eventTrigger({
|
||||
name: "supabase.create-todo",
|
||||
schema: z.object({
|
||||
contents: z.string(),
|
||||
user_id: z.number(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
supabaseDB,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
const newTodo = await io.supabaseDB.runTask(
|
||||
"create-todo",
|
||||
async (db) => {
|
||||
const { data, error } = await db
|
||||
.from("todos")
|
||||
.insert({
|
||||
contents: payload.contents,
|
||||
user_id: payload.user_id,
|
||||
is_complete: false,
|
||||
})
|
||||
.select();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return data;
|
||||
},
|
||||
{
|
||||
name: "Create Todo",
|
||||
properties: [{ label: "Contents", text: payload.contents }],
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-create-project",
|
||||
name: "Supabase Create Project",
|
||||
version: "0.1.1",
|
||||
trigger: eventTrigger({
|
||||
name: "supabase.create",
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
organization_id: z.string(),
|
||||
plan: z.enum(["free", "pro"]),
|
||||
region: z.enum(["us-east-1", "us-west-1"]),
|
||||
password: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.supabase.createProject("create-project", {
|
||||
name: payload.name,
|
||||
organization_id: payload.organization_id,
|
||||
plan: payload.plan,
|
||||
region: payload.region,
|
||||
kps_enabled: true,
|
||||
db_pass: payload.password,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-user-insert",
|
||||
name: "Supabase On User Insert",
|
||||
version: "0.1.1",
|
||||
trigger: db.onInserted({
|
||||
table: "users",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-user-insert-2",
|
||||
name: "Supabase On User Insert 2",
|
||||
version: "0.1.1",
|
||||
trigger: db.onInserted({
|
||||
table: "users",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-user-email-changed",
|
||||
name: "Supabase On User Email Changed",
|
||||
version: "0.1.1",
|
||||
trigger: db.onUpdated({
|
||||
table: "users",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-user-deleted",
|
||||
name: "Supabase On User Deleted",
|
||||
version: "0.1.1",
|
||||
trigger: db.onDeleted({
|
||||
table: "users",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-todo-created",
|
||||
name: "Supabase On TODO created",
|
||||
version: "0.1.1",
|
||||
trigger: dbKey.onInserted({
|
||||
table: "todos",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-todo-created",
|
||||
name: "Supabase On TODO created",
|
||||
version: "0.1.1",
|
||||
trigger: dbKey.onInserted({
|
||||
table: "todos",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-todo-completed",
|
||||
name: "Supabase On TODO completed",
|
||||
version: "0.1.1",
|
||||
trigger: dbKey.onUpdated({
|
||||
table: "todos",
|
||||
filter: {
|
||||
old_record: {
|
||||
is_complete: [false],
|
||||
},
|
||||
record: {
|
||||
is_complete: [true],
|
||||
},
|
||||
},
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.log("Todo Completed", { payload });
|
||||
},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-tweet-created-or-deleted",
|
||||
name: "Supabase On Tweet Created or Deleted",
|
||||
version: "0.1.1",
|
||||
trigger: dbKey.onDeleted({
|
||||
schema: "public_2",
|
||||
table: "tweets",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-todo-created-no-types",
|
||||
name: "Supabase On TODO created",
|
||||
version: "0.1.1",
|
||||
trigger: dbNoTypes.onInserted({
|
||||
table: "todos",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import "@/jobs/schedules";
|
||||
import "@/jobs/slack";
|
||||
import "@/jobs/typeform";
|
||||
import "@/jobs/edgeCases";
|
||||
import "@/jobs/supabase";
|
||||
|
||||
import { createPagesRoute } from "@trigger.dev/nextjs";
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
export type Json =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| { [key: string]: Json | undefined }
|
||||
| Json[]
|
||||
|
||||
export interface Database {
|
||||
public: {
|
||||
Tables: {
|
||||
todos: {
|
||||
Row: {
|
||||
contents: string | null
|
||||
created_at: string | null
|
||||
id: number
|
||||
is_complete: boolean | null
|
||||
user_id: number | null
|
||||
}
|
||||
Insert: {
|
||||
contents?: string | null
|
||||
created_at?: string | null
|
||||
id?: number
|
||||
is_complete?: boolean | null
|
||||
user_id?: number | null
|
||||
}
|
||||
Update: {
|
||||
contents?: string | null
|
||||
created_at?: string | null
|
||||
id?: number
|
||||
is_complete?: boolean | null
|
||||
user_id?: number | null
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "todos_user_id_fkey"
|
||||
columns: ["user_id"]
|
||||
referencedRelation: "users"
|
||||
referencedColumns: ["id"]
|
||||
}
|
||||
]
|
||||
}
|
||||
users: {
|
||||
Row: {
|
||||
created_at: string | null
|
||||
email_address: string | null
|
||||
first_name: string | null
|
||||
id: number
|
||||
last_name: string | null
|
||||
}
|
||||
Insert: {
|
||||
created_at?: string | null
|
||||
email_address?: string | null
|
||||
first_name?: string | null
|
||||
id?: number
|
||||
last_name?: string | null
|
||||
}
|
||||
Update: {
|
||||
created_at?: string | null
|
||||
email_address?: string | null
|
||||
first_name?: string | null
|
||||
id?: number
|
||||
last_name?: string | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
}
|
||||
Views: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Functions: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Enums: {
|
||||
[_ in never]: never
|
||||
}
|
||||
CompositeTypes: {
|
||||
[_ in never]: never
|
||||
}
|
||||
}
|
||||
public_2: {
|
||||
Tables: {
|
||||
tweets: {
|
||||
Row: {
|
||||
content: string
|
||||
created_at: string | null
|
||||
id: number
|
||||
tweet_id: string
|
||||
}
|
||||
Insert: {
|
||||
content: string
|
||||
created_at?: string | null
|
||||
id?: number
|
||||
tweet_id: string
|
||||
}
|
||||
Update: {
|
||||
content?: string
|
||||
created_at?: string | null
|
||||
id?: number
|
||||
tweet_id?: string
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
}
|
||||
Views: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Functions: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Enums: {
|
||||
[_ in never]: never
|
||||
}
|
||||
CompositeTypes: {
|
||||
[_ in never]: never
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -15,31 +19,75 @@
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"],
|
||||
"@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"],
|
||||
"@trigger.dev/nextjs": ["../../packages/nextjs/src/index"],
|
||||
"@trigger.dev/nextjs/*": ["../../packages/nextjs/src/*"],
|
||||
"@trigger.dev/internal": ["../../packages/internal/src/index"],
|
||||
"@trigger.dev/internal/*": ["../../packages/internal/src/*"],
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@trigger.dev/sdk": [
|
||||
"../../packages/trigger-sdk/src/index"
|
||||
],
|
||||
"@trigger.dev/sdk/*": [
|
||||
"../../packages/trigger-sdk/src/*"
|
||||
],
|
||||
"@trigger.dev/nextjs": [
|
||||
"../../packages/nextjs/src/index"
|
||||
],
|
||||
"@trigger.dev/nextjs/*": [
|
||||
"../../packages/nextjs/src/*"
|
||||
],
|
||||
"@trigger.dev/internal": [
|
||||
"../../packages/internal/src/index"
|
||||
],
|
||||
"@trigger.dev/internal/*": [
|
||||
"../../packages/internal/src/*"
|
||||
],
|
||||
"@trigger.dev/integration-kit": [
|
||||
"../../packages/integration-kit/src/index"
|
||||
],
|
||||
"@trigger.dev/integration-kit/*": [
|
||||
"../../packages/integration-kit/src/*"
|
||||
],
|
||||
"@trigger.dev/github": ["../../integrations/github/src/index"],
|
||||
"@trigger.dev/github/*": ["../../integrations/github/src/*"],
|
||||
"@trigger.dev/slack": ["../../integrations/slack/src/index"],
|
||||
"@trigger.dev/slack/*": ["../../integrations/slack/src/*"],
|
||||
"@trigger.dev/openai": ["../../integrations/openai/src/index"],
|
||||
"@trigger.dev/openai/*": ["../../integrations/openai/src/*"],
|
||||
"@trigger.dev/resend": ["../../integrations/resend/src/index"],
|
||||
"@trigger.dev/resend/*": ["../../integrations/resend/src/*"],
|
||||
"@trigger.dev/typeform": ["../../integrations/typeform/src/index"],
|
||||
"@trigger.dev/typeform/*": ["../../integrations/typeform/src/*"],
|
||||
"@trigger.dev/plain": ["../../integrations/plain/src/index"],
|
||||
"@trigger.dev/plain/*": ["../../integrations/plain/src/*"]
|
||||
"@trigger.dev/github": [
|
||||
"../../integrations/github/src/index"
|
||||
],
|
||||
"@trigger.dev/github/*": [
|
||||
"../../integrations/github/src/*"
|
||||
],
|
||||
"@trigger.dev/slack": [
|
||||
"../../integrations/slack/src/index"
|
||||
],
|
||||
"@trigger.dev/slack/*": [
|
||||
"../../integrations/slack/src/*"
|
||||
],
|
||||
"@trigger.dev/openai": [
|
||||
"../../integrations/openai/src/index"
|
||||
],
|
||||
"@trigger.dev/openai/*": [
|
||||
"../../integrations/openai/src/*"
|
||||
],
|
||||
"@trigger.dev/resend": [
|
||||
"../../integrations/resend/src/index"
|
||||
],
|
||||
"@trigger.dev/resend/*": [
|
||||
"../../integrations/resend/src/*"
|
||||
],
|
||||
"@trigger.dev/typeform": [
|
||||
"../../integrations/typeform/src/index"
|
||||
],
|
||||
"@trigger.dev/typeform/*": [
|
||||
"../../integrations/typeform/src/*"
|
||||
],
|
||||
"@trigger.dev/plain": [
|
||||
"../../integrations/plain/src/index"
|
||||
],
|
||||
"@trigger.dev/plain/*": [
|
||||
"../../integrations/plain/src/*"
|
||||
],
|
||||
"@trigger.dev/supabase": [
|
||||
"../../integrations/supabase/src/index"
|
||||
],
|
||||
"@trigger.dev/supabase/*": [
|
||||
"../../integrations/supabase/src/*"
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
@@ -47,6 +95,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# @trigger.dev/supabase
|
||||
|
||||
The official Supabase integration for [Trigger.dev](https://trigger.dev). See our [docs](https://trigger.dev/docs/integrations/apis/supabase) for more info.
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@trigger.dev/supabase",
|
||||
"version": "0.1.0",
|
||||
"description": "Trigger.dev integration for @supabase/supabase-js",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist/index.js",
|
||||
"dist/index.d.ts",
|
||||
"dist/index.js.map"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "18.x",
|
||||
"rimraf": "^3.0.2",
|
||||
"tsup": "7.1.x",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"build": "npm run clean && npm run build:tsup",
|
||||
"build:tsup": "tsup",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.26.0",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.0-next.5",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.0-next.16",
|
||||
"supabase-management-js": "^0.1.2",
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
SupabaseClient,
|
||||
SupabaseClientOptions,
|
||||
createClient,
|
||||
} from "@supabase/supabase-js";
|
||||
import { IntegrationClient, TriggerIntegration } from "@trigger.dev/sdk";
|
||||
import { GenericSchema } from "./types";
|
||||
|
||||
const tasks = {};
|
||||
|
||||
export type SupabaseIntegrationOptions<TSchema extends string> =
|
||||
| {
|
||||
/** The unique ID for this integration */
|
||||
id: string;
|
||||
/** The Supabase project url (e.g. "https://<project-id>.supabase.co") */
|
||||
supabaseUrl: string;
|
||||
/** The Supabase service account API Key (found in your Supabase Project Settings -> API -> service_role) */
|
||||
supabaseKey: string;
|
||||
/** Options that are passed through to the call the createClient */
|
||||
options?: SupabaseClientOptions<TSchema>;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
projectId: string;
|
||||
supabaseKey: string;
|
||||
options?: SupabaseClientOptions<TSchema>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Trigger Integration for Supabase
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Supabase } from "@trigger.dev/supabase";
|
||||
* import { Database } from "@/supabase.types";
|
||||
*
|
||||
* const supabase = new Supabase<Database>({
|
||||
* id: "my-supabase",
|
||||
* projectId: process.env.SUPABASE_ID!,
|
||||
* supabaseKey: process.env.SUPABASE_API_KEY!,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class Supabase<
|
||||
Database = any,
|
||||
SchemaName extends string & keyof Database = "public" extends keyof Database
|
||||
? "public"
|
||||
: string & keyof Database,
|
||||
Schema extends GenericSchema = Database[SchemaName] extends GenericSchema
|
||||
? Database[SchemaName]
|
||||
: any
|
||||
> implements
|
||||
TriggerIntegration<
|
||||
IntegrationClient<
|
||||
SupabaseClient<Database, SchemaName, Schema>,
|
||||
typeof tasks
|
||||
>
|
||||
>
|
||||
{
|
||||
client: IntegrationClient<
|
||||
SupabaseClient<Database, SchemaName, Schema>,
|
||||
typeof tasks
|
||||
>;
|
||||
|
||||
constructor(private options: SupabaseIntegrationOptions<SchemaName>) {
|
||||
const supabaseOptions = options.options || {};
|
||||
|
||||
const supabaseUrl =
|
||||
"projectId" in options
|
||||
? `https://${options.projectId}.supabase.co`
|
||||
: options.supabaseUrl;
|
||||
|
||||
const supabaseClient = createClient(supabaseUrl, options.supabaseKey, {
|
||||
...supabaseOptions,
|
||||
auth: {
|
||||
...supabaseOptions.auth,
|
||||
persistSession: false,
|
||||
},
|
||||
});
|
||||
|
||||
this.client = {
|
||||
tasks,
|
||||
usesLocalAuth: true,
|
||||
client: supabaseClient,
|
||||
auth: {
|
||||
supabaseUrl,
|
||||
supabaseKey: options.supabaseKey,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.options.id;
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
return { id: "supabase", name: "Supabase" };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export type GenericTable = {
|
||||
Row: Record<string, unknown>;
|
||||
Insert: Record<string, unknown>;
|
||||
Update: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type GenericUpdatableView = {
|
||||
Row: Record<string, unknown>;
|
||||
Insert: Record<string, unknown>;
|
||||
Update: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type GenericNonUpdatableView = {
|
||||
Row: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type GenericView = GenericUpdatableView | GenericNonUpdatableView;
|
||||
|
||||
export type GenericFunction = {
|
||||
Args: Record<string, unknown>;
|
||||
Returns: unknown;
|
||||
};
|
||||
|
||||
export type GenericSchema = {
|
||||
Tables: Record<string, GenericTable>;
|
||||
Views: Record<string, GenericView>;
|
||||
Functions: Record<string, GenericFunction>;
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./management";
|
||||
export * from "./database";
|
||||
@@ -0,0 +1,431 @@
|
||||
import {
|
||||
EventFilter,
|
||||
EventSpecification,
|
||||
ExternalSource,
|
||||
ExternalSourceTrigger,
|
||||
HandlerEvent,
|
||||
IntegrationClient,
|
||||
Logger,
|
||||
TriggerIntegration,
|
||||
} from "@trigger.dev/sdk";
|
||||
import { SupabaseManagementAPI } from "supabase-management-js";
|
||||
import { z } from "zod";
|
||||
import { Prettify, safeParseBody } from "@trigger.dev/integration-kit";
|
||||
import * as tasks from "./tasks";
|
||||
import { randomUUID } from "crypto";
|
||||
import { GenericSchema } from "../database/types";
|
||||
|
||||
export type SupabaseManagementIntegrationOptions =
|
||||
| {
|
||||
id: string;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
type SupabaseManagementIntegrationClient = IntegrationClient<
|
||||
SupabaseManagementAPI,
|
||||
typeof tasks
|
||||
>;
|
||||
type SupabaseManagementIntegration =
|
||||
TriggerIntegration<SupabaseManagementIntegrationClient>;
|
||||
|
||||
class SupabaseDatabase<Database = any> {
|
||||
constructor(
|
||||
private integration: SupabaseManagement,
|
||||
private projectRef: string
|
||||
) {}
|
||||
|
||||
onInserted<
|
||||
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]
|
||||
>(params: { table: TTableName; schema?: SchemaName; filter?: EventFilter }) {
|
||||
return createTrigger<{
|
||||
table: TTableName;
|
||||
record: Prettify<TTable["Row"]>;
|
||||
type: "INSERT";
|
||||
schema: SchemaName;
|
||||
old_record: null;
|
||||
}>(this.integration.source, {
|
||||
event: "INSERT",
|
||||
projectRef: this.projectRef,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
onUpdated<
|
||||
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]
|
||||
>(params: { table: TTableName; schema?: SchemaName; filter?: EventFilter }) {
|
||||
return createTrigger<{
|
||||
table: TTableName;
|
||||
record: Prettify<TTable["Row"]>;
|
||||
type: "UPDATE";
|
||||
schema: SchemaName;
|
||||
old_record: Prettify<TTable["Row"]>;
|
||||
}>(this.integration.source, {
|
||||
event: "UPDATE",
|
||||
projectRef: this.projectRef,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
onDeleted<
|
||||
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]
|
||||
>(params: { table: TTableName; schema?: SchemaName; filter?: EventFilter }) {
|
||||
return createTrigger<{
|
||||
table: TTableName;
|
||||
record: null;
|
||||
type: "DELETE";
|
||||
schema: SchemaName;
|
||||
old_record: Prettify<TTable["Row"]>;
|
||||
}>(this.integration.source, {
|
||||
event: "DELETE",
|
||||
projectRef: this.projectRef,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class SupabaseManagement implements SupabaseManagementIntegration {
|
||||
client: SupabaseManagementIntegrationClient;
|
||||
|
||||
constructor(private options: SupabaseManagementIntegrationOptions) {
|
||||
if ("apiKey" in options) {
|
||||
if (!options.apiKey || options.apiKey === "") {
|
||||
throw `Can't create SupabaseManagement integration (${options.id}) as apiKey is undefined`;
|
||||
}
|
||||
|
||||
this.client = {
|
||||
tasks,
|
||||
usesLocalAuth: true,
|
||||
client: new SupabaseManagementAPI({ accessToken: options.apiKey }),
|
||||
auth: {
|
||||
apiKey: options.apiKey,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
this.client = {
|
||||
tasks,
|
||||
usesLocalAuth: false,
|
||||
clientFactory: (auth) => {
|
||||
return new SupabaseManagementAPI({ accessToken: auth.accessToken });
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.options.id;
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
return { id: "supabase-management", name: "Supabase Management API" };
|
||||
}
|
||||
|
||||
get source(): WebhookEventSource {
|
||||
return createWebhookEventSource(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new database instance that can be used to listen to changes in the database.
|
||||
*
|
||||
* @param projectIdOrUrl The project ID or URL of the Supabase project (e.g. `https://<project-id>.supabase.co`)
|
||||
* @param options Options for the database instance
|
||||
*/
|
||||
db<Database = any>(projectIdOrUrl: string) {
|
||||
const projectRef = getProjectRef(projectIdOrUrl);
|
||||
|
||||
return new SupabaseDatabase<Database>(this, projectRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param projectIdOrUrl The project ID or URL of the Supabase project (e.g. `https://<project-id>.supabase.co`)
|
||||
* @returns The project reference of the Supabase project (e.g. `<project-id>`)
|
||||
*/
|
||||
function getProjectRef(projectIdOrUrl: string) {
|
||||
if (projectIdOrUrl.startsWith("http")) {
|
||||
const url = new URL(projectIdOrUrl);
|
||||
return url.hostname.split(".")[0];
|
||||
}
|
||||
|
||||
return projectIdOrUrl;
|
||||
}
|
||||
|
||||
type WebhookEventSource = ReturnType<typeof createWebhookEventSource>;
|
||||
|
||||
type WebhookEvents = "INSERT" | "UPDATE" | "DELETE";
|
||||
|
||||
function createTrigger<TEvent extends any>(
|
||||
source: WebhookEventSource,
|
||||
params: { event: WebhookEvents; filter?: EventFilter } & {
|
||||
projectRef: string;
|
||||
table: string;
|
||||
schema?: string;
|
||||
}
|
||||
): ExternalSourceTrigger<EventSpecification<TEvent>, WebhookEventSource> {
|
||||
const eventSpecification = {
|
||||
name: params.event,
|
||||
title: "Supabase DB Webhook",
|
||||
source: "supabase",
|
||||
icon: "supabase",
|
||||
filter: {
|
||||
...params.filter,
|
||||
type: [params.event],
|
||||
schema: [params.schema ?? "public"],
|
||||
},
|
||||
properties: [],
|
||||
parsePayload: (payload: any) => payload as TEvent,
|
||||
};
|
||||
|
||||
return new ExternalSourceTrigger({
|
||||
event: eventSpecification,
|
||||
params: {
|
||||
projectRef: params.projectRef,
|
||||
table: params.table,
|
||||
schema: params.schema ?? "public",
|
||||
},
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
const WebhookSchema = z.object({
|
||||
projectRef: z.string(),
|
||||
table: z.string(),
|
||||
schema: z.string(),
|
||||
});
|
||||
|
||||
const WebhookData = z.object({
|
||||
triggerName: z.string(),
|
||||
table: z.string(),
|
||||
schema: z.string(),
|
||||
});
|
||||
|
||||
export function createWebhookEventSource(
|
||||
integration: SupabaseManagementIntegration
|
||||
): ExternalSource<
|
||||
SupabaseManagementIntegration,
|
||||
{
|
||||
projectRef: string;
|
||||
table: string;
|
||||
schema: string;
|
||||
},
|
||||
"HTTP"
|
||||
> {
|
||||
return new ExternalSource("HTTP", {
|
||||
id: "supabase.webhook",
|
||||
schema: WebhookSchema,
|
||||
version: "0.1.1",
|
||||
integration,
|
||||
filter: (params) => {
|
||||
return {
|
||||
table: [params.table],
|
||||
};
|
||||
},
|
||||
key: (params) => `${params.projectRef}-${params.schema}-${params.table}`,
|
||||
properties: (params) => [
|
||||
{
|
||||
label: "Project Ref",
|
||||
text: params.projectRef,
|
||||
},
|
||||
{
|
||||
label: "Table",
|
||||
text: `${params.schema}.${params.table}`,
|
||||
},
|
||||
],
|
||||
handler: webhookHandler,
|
||||
register: async (event, io, ctx) => {
|
||||
const { params, source: httpSource, events, missingEvents } = event;
|
||||
|
||||
const webhookData = WebhookData.safeParse(httpSource.data);
|
||||
|
||||
if (httpSource.active && webhookData.success) {
|
||||
const { triggerName, table, schema } = webhookData.data;
|
||||
|
||||
const allEvents = new Set<string>([
|
||||
...events,
|
||||
...missingEvents,
|
||||
]) as Set<WebhookEvents>;
|
||||
|
||||
const condition = createTriggerCondition(Array.from(allEvents));
|
||||
|
||||
const query = createTriggerQuery({
|
||||
triggerName,
|
||||
condition,
|
||||
schema: params.schema,
|
||||
table: params.table,
|
||||
url: httpSource.url,
|
||||
secret: httpSource.secret,
|
||||
});
|
||||
|
||||
const queryResults = await io.integration.runQuery("update-trigger", {
|
||||
ref: params.projectRef,
|
||||
query,
|
||||
});
|
||||
|
||||
await io.logger.debug("Query results", { queryResults });
|
||||
|
||||
return {
|
||||
data: {
|
||||
triggerName: triggerName,
|
||||
table: table,
|
||||
schema: schema,
|
||||
},
|
||||
registeredEvents: Array.from(allEvents),
|
||||
};
|
||||
}
|
||||
|
||||
const url = new URL(httpSource.url);
|
||||
const id = url.pathname.split("/").pop() ?? randomUUID();
|
||||
|
||||
// Create the trigger name using the last 12 characters of the id
|
||||
const triggerName = `tr_${id.slice(-12)}`;
|
||||
|
||||
const condition = createTriggerCondition(events as WebhookEvents[]);
|
||||
|
||||
const query = createTriggerQuery({
|
||||
triggerName,
|
||||
condition,
|
||||
schema: params.schema,
|
||||
table: params.table,
|
||||
url: httpSource.url,
|
||||
secret: httpSource.secret,
|
||||
});
|
||||
|
||||
const queryResults = await io.integration.runQuery("create-trigger", {
|
||||
ref: params.projectRef,
|
||||
query,
|
||||
});
|
||||
|
||||
await io.logger.debug("Query results", { queryResults });
|
||||
|
||||
return {
|
||||
data: {
|
||||
triggerName: triggerName,
|
||||
table: params.table,
|
||||
schema: params.schema,
|
||||
},
|
||||
registeredEvents: events,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createTriggerQuery({
|
||||
triggerName,
|
||||
condition,
|
||||
schema,
|
||||
table,
|
||||
url,
|
||||
secret,
|
||||
}: {
|
||||
triggerName: string;
|
||||
condition: string;
|
||||
schema: string;
|
||||
table: string;
|
||||
url: string;
|
||||
secret: string;
|
||||
}): string {
|
||||
return `
|
||||
CREATE OR REPLACE TRIGGER ${triggerName}
|
||||
AFTER ${condition} on "${schema}"."${table}"
|
||||
FOR EACH ROW EXECUTE FUNCTION supabase_functions.http_request('${url}', 'POST', '{"Content-type":"application/json", "Authorization": "Bearer ${secret}" }', '{}', '1000')
|
||||
`;
|
||||
}
|
||||
|
||||
function createTriggerCondition(events: WebhookEvents[]): string {
|
||||
return events.join(" OR ");
|
||||
}
|
||||
|
||||
async function webhookHandler(event: HandlerEvent<"HTTP">, logger: Logger) {
|
||||
logger.debug(
|
||||
"[inside supabase management integration] Handling webhook handler"
|
||||
);
|
||||
|
||||
const { rawEvent: request, source } = event;
|
||||
|
||||
if (!request.body) {
|
||||
logger.debug("[inside supabase management integration] No body found");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the Bearer token matches the secret
|
||||
const authHeader = request.headers.get("Authorization");
|
||||
|
||||
if (!authHeader) {
|
||||
logger.debug(
|
||||
"[inside supabase management integration] No Authorization header found"
|
||||
);
|
||||
|
||||
return { events: [] };
|
||||
}
|
||||
|
||||
const authHeaderParts = authHeader.split(" ");
|
||||
|
||||
if (authHeaderParts.length !== 2) {
|
||||
logger.debug(
|
||||
"[inside supabase management integration] Authorization header is not in the correct format"
|
||||
);
|
||||
|
||||
return { events: [] };
|
||||
}
|
||||
|
||||
const token = authHeaderParts[1];
|
||||
|
||||
if (token !== source.secret) {
|
||||
logger.debug(
|
||||
"[inside supabase management integration] Authorization header does not match the secret"
|
||||
);
|
||||
|
||||
return { events: [] };
|
||||
}
|
||||
|
||||
const rawBody = await request.text();
|
||||
|
||||
const payload = safeParseBody(rawBody);
|
||||
|
||||
if (!payload) {
|
||||
return { events: [] };
|
||||
}
|
||||
|
||||
// Generate a unique ID for the event
|
||||
const id = randomUUID();
|
||||
|
||||
return {
|
||||
events: [
|
||||
{
|
||||
id,
|
||||
name: payload.type,
|
||||
source: "supabase",
|
||||
payload,
|
||||
context: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import { AuthenticatedTask } from "@trigger.dev/sdk";
|
||||
import type {
|
||||
CreateProjectRequestBody,
|
||||
CreateProjectResponseData,
|
||||
GetOrganizationsResponseData,
|
||||
GetPostgRESTConfigResponseData,
|
||||
GetProjectPGConfigResponseData,
|
||||
GetProjectsResponseData,
|
||||
GetTypescriptTypesResponseData,
|
||||
ListFunctionsResponseData,
|
||||
RunQueryResponseData,
|
||||
SupabaseManagementAPI,
|
||||
} from "supabase-management-js";
|
||||
|
||||
export const getOrganizations: AuthenticatedTask<
|
||||
SupabaseManagementAPI,
|
||||
void,
|
||||
GetOrganizationsResponseData
|
||||
> = {
|
||||
run: async (params, client) => {
|
||||
return client.getOrganizations();
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
name: "Get Organizations",
|
||||
params,
|
||||
icon: "supabase",
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const getProjects: AuthenticatedTask<
|
||||
SupabaseManagementAPI,
|
||||
void,
|
||||
GetProjectsResponseData
|
||||
> = {
|
||||
run: async (params, client) => {
|
||||
return client.getProjects();
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
name: "Get Projects",
|
||||
params,
|
||||
icon: "supabase",
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const createProject: AuthenticatedTask<
|
||||
SupabaseManagementAPI,
|
||||
CreateProjectRequestBody,
|
||||
CreateProjectResponseData
|
||||
> = {
|
||||
run: async (params, client) => {
|
||||
return client.createProject(params);
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
name: "Create Project",
|
||||
params,
|
||||
icon: "supabase",
|
||||
properties: [
|
||||
{ label: "Name", text: params.name },
|
||||
{ label: "Org", text: params.organization_id },
|
||||
{ label: "Region", text: params.region },
|
||||
{ label: "Plan", text: params.plan },
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const listFunctions: AuthenticatedTask<
|
||||
SupabaseManagementAPI,
|
||||
{ ref: string },
|
||||
ListFunctionsResponseData
|
||||
> = {
|
||||
run: async (params, client) => {
|
||||
return client.listFunctions(params.ref);
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
name: "List Functions",
|
||||
params,
|
||||
icon: "supabase",
|
||||
properties: [
|
||||
{
|
||||
label: "Project",
|
||||
text: params.ref,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const runQuery: AuthenticatedTask<
|
||||
SupabaseManagementAPI,
|
||||
{ ref: string; query: string },
|
||||
RunQueryResponseData
|
||||
> = {
|
||||
run: async (params, client) => {
|
||||
return client.runQuery(params.ref, params.query);
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
name: "Run Query",
|
||||
params,
|
||||
icon: "supabase",
|
||||
properties: [
|
||||
{
|
||||
label: "Project",
|
||||
text: params.ref,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const getTypescriptTypes: AuthenticatedTask<
|
||||
SupabaseManagementAPI,
|
||||
{ ref: string },
|
||||
GetTypescriptTypesResponseData
|
||||
> = {
|
||||
run: async (params, client) => {
|
||||
return client.getTypescriptTypes(params.ref);
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
name: "Get Typescript Types",
|
||||
params,
|
||||
icon: "supabase",
|
||||
properties: [
|
||||
{
|
||||
label: "Project",
|
||||
text: params.ref,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const getPostgRESTConfig: AuthenticatedTask<
|
||||
SupabaseManagementAPI,
|
||||
{ ref: string },
|
||||
GetPostgRESTConfigResponseData
|
||||
> = {
|
||||
run: async (params, client) => {
|
||||
return client.getPostgRESTConfig(params.ref);
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
name: "Get PostgREST Config",
|
||||
params,
|
||||
icon: "supabase",
|
||||
properties: [
|
||||
{
|
||||
label: "Project",
|
||||
text: params.ref,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const getPGConfig: AuthenticatedTask<
|
||||
SupabaseManagementAPI,
|
||||
{ ref: string },
|
||||
GetProjectPGConfigResponseData
|
||||
> = {
|
||||
run: async (params, client) => {
|
||||
return client.getPGConfig(params.ref);
|
||||
},
|
||||
init: (params) => {
|
||||
return {
|
||||
name: "Get PG Config",
|
||||
params,
|
||||
icon: "supabase",
|
||||
properties: [
|
||||
{
|
||||
label: "Project",
|
||||
text: params.ref,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSources": false,
|
||||
"isolatedModules": true,
|
||||
"moduleResolution": "node",
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"preserveWatchOutput": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"lib": ["es2019", "dom"],
|
||||
"module": "CommonJS",
|
||||
"target": "es2021"
|
||||
},
|
||||
"include": ["./src/**/*.ts", "tsup.config.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
name: "main",
|
||||
entry: ["./src/index.ts"],
|
||||
outDir: "./dist",
|
||||
platform: "node",
|
||||
format: ["cjs"],
|
||||
legacyOutput: true,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
bundle: true,
|
||||
splitting: false,
|
||||
dts: true,
|
||||
treeshake: {
|
||||
preset: "smallest",
|
||||
},
|
||||
esbuildPlugins: [],
|
||||
noExternal: [],
|
||||
external: ["http", "https", "util", "events", "tty", "os", "timers"],
|
||||
},
|
||||
]);
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "IntegrationDefinition" ADD COLUMN "icon" TEXT;
|
||||
@@ -135,6 +135,7 @@ model IntegrationDefinition {
|
||||
name String
|
||||
instructions String?
|
||||
description String?
|
||||
icon String?
|
||||
packageName String @default("")
|
||||
|
||||
authMethods IntegrationAuthMethod[]
|
||||
|
||||
@@ -4,7 +4,13 @@ import {
|
||||
RunTaskOptions,
|
||||
ServerTask,
|
||||
} from "@trigger.dev/internal";
|
||||
import { IO } from "./io";
|
||||
import { IO, IOTask } from "./io";
|
||||
|
||||
type IntegrationRunTaskFunction<TClient> = <TResult>(
|
||||
key: string | any[],
|
||||
callback: (client: TClient, task: IOTask, io: IO) => Promise<TResult>,
|
||||
options?: RunTaskOptions
|
||||
) => Promise<TResult>;
|
||||
|
||||
export type ClientFactory<TClient> = (auth: ConnectionAuth) => TClient;
|
||||
|
||||
@@ -88,12 +94,18 @@ type ExtractIntegrationClientClient<
|
||||
usesLocalAuth: true;
|
||||
client: infer TClient;
|
||||
}
|
||||
? { client: TClient }
|
||||
? {
|
||||
client: TClient;
|
||||
runTask: IntegrationRunTaskFunction<TClient>;
|
||||
}
|
||||
: TIntegrationClient extends {
|
||||
usesLocalAuth: false;
|
||||
clientFactory: ClientFactory<infer TClient>;
|
||||
}
|
||||
? { client: TClient }
|
||||
? {
|
||||
client: TClient;
|
||||
runTask: IntegrationRunTaskFunction<TClient>;
|
||||
}
|
||||
: never;
|
||||
|
||||
type ExtractIntegrationClient<
|
||||
|
||||
@@ -236,13 +236,14 @@ export class IO {
|
||||
key,
|
||||
{
|
||||
name: "Update Source",
|
||||
description: `Update Source ${options.key}`,
|
||||
description: "Update Source",
|
||||
properties: [
|
||||
{
|
||||
label: "key",
|
||||
text: options.key,
|
||||
},
|
||||
],
|
||||
params: options,
|
||||
redact: {
|
||||
paths: ["secret"],
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConnectionAuth } from "@trigger.dev/internal";
|
||||
import { ConnectionAuth, RunTaskOptions } from "@trigger.dev/internal";
|
||||
import {
|
||||
AuthenticatedTask,
|
||||
IOWithIntegrations,
|
||||
@@ -41,6 +41,20 @@ export function createIOWithIntegrations<
|
||||
client,
|
||||
} as any;
|
||||
|
||||
ioConnection.runTask = async (
|
||||
key: string | any[],
|
||||
callback: (client: any, task: any, io: IO) => Promise<any>,
|
||||
options?: RunTaskOptions
|
||||
) => {
|
||||
return await io.runTask(
|
||||
key,
|
||||
{ name: "Task", icon: integration.metadata.id, ...options },
|
||||
async (ioTask) => {
|
||||
return await callback(client, ioTask, io);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
if (integration.client.tasks) {
|
||||
const tasks: Record<
|
||||
string,
|
||||
|
||||
Generated
+211
-2
@@ -414,6 +414,7 @@ importers:
|
||||
'@trigger.dev/resend': workspace:*
|
||||
'@trigger.dev/sdk': workspace:*
|
||||
'@trigger.dev/slack': workspace:*
|
||||
'@trigger.dev/supabase': workspace:*
|
||||
'@trigger.dev/typeform': workspace:*
|
||||
'@types/node': 18.15.13
|
||||
'@types/node-fetch': 2.6.x
|
||||
@@ -437,6 +438,7 @@ importers:
|
||||
'@trigger.dev/resend': link:../../integrations/resend
|
||||
'@trigger.dev/sdk': link:../../packages/trigger-sdk
|
||||
'@trigger.dev/slack': link:../../integrations/slack
|
||||
'@trigger.dev/supabase': link:../../integrations/supabase
|
||||
'@trigger.dev/typeform': link:../../integrations/typeform
|
||||
'@types/node': 18.15.13
|
||||
'@types/react': 18.0.26
|
||||
@@ -564,6 +566,29 @@ importers:
|
||||
rimraf: 3.0.2
|
||||
tsup: 6.6.3
|
||||
|
||||
integrations/supabase:
|
||||
specifiers:
|
||||
'@supabase/supabase-js': ^2.26.0
|
||||
'@trigger.dev/integration-kit': workspace:^2.0.0-next.5
|
||||
'@trigger.dev/sdk': workspace:^2.0.0-next.16
|
||||
'@types/node': 18.x
|
||||
rimraf: ^3.0.2
|
||||
supabase-management-js: ^0.1.2
|
||||
tsup: 7.1.x
|
||||
typescript: 4.9.4
|
||||
zod: 3.21.4
|
||||
dependencies:
|
||||
'@supabase/supabase-js': 2.31.0
|
||||
'@trigger.dev/integration-kit': link:../../packages/integration-kit
|
||||
'@trigger.dev/sdk': link:../../packages/trigger-sdk
|
||||
supabase-management-js: 0.1.2
|
||||
zod: 3.21.4
|
||||
devDependencies:
|
||||
'@types/node': 18.15.13
|
||||
rimraf: 3.0.2
|
||||
tsup: 7.1.0_typescript@4.9.4
|
||||
typescript: 4.9.4
|
||||
|
||||
integrations/typeform:
|
||||
specifiers:
|
||||
'@trigger.dev/integration-kit': workspace:^2.0.0-next.5
|
||||
@@ -9128,7 +9153,7 @@ packages:
|
||||
globby: 11.1.0
|
||||
ip: 2.0.0
|
||||
lodash: 4.17.21
|
||||
node-fetch: 2.6.11
|
||||
node-fetch: 2.6.12
|
||||
open: 8.4.0
|
||||
pretty-hrtime: 1.0.3
|
||||
prompts: 2.4.2
|
||||
@@ -9656,6 +9681,62 @@ packages:
|
||||
file-system-cache: 2.1.1
|
||||
dev: true
|
||||
|
||||
/@supabase/functions-js/2.1.2:
|
||||
resolution: {integrity: sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==}
|
||||
dependencies:
|
||||
cross-fetch: 3.1.8
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@supabase/gotrue-js/2.46.1:
|
||||
resolution: {integrity: sha512-tebFX3XvPqEJKHOVgkXTN20g9iUhLx6tebIYQvTggYTrqOT2af8oTpSBdgYzbwJ291G6P6CSpR6KY0cT9ade5A==}
|
||||
dependencies:
|
||||
cross-fetch: 3.1.8
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@supabase/postgrest-js/1.7.2:
|
||||
resolution: {integrity: sha512-GK80JpRq8l6Qll85erICypAfQCied8tdlXfsDN14W844HqXCSOisk8AaE01DAwGJanieaoN5fuqhzA2yKxDvEQ==}
|
||||
dependencies:
|
||||
cross-fetch: 3.1.8
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@supabase/realtime-js/2.7.3:
|
||||
resolution: {integrity: sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw==}
|
||||
dependencies:
|
||||
'@types/phoenix': 1.6.0
|
||||
'@types/websocket': 1.0.5
|
||||
websocket: 1.0.34
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@supabase/storage-js/2.5.1:
|
||||
resolution: {integrity: sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==}
|
||||
dependencies:
|
||||
cross-fetch: 3.1.8
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@supabase/supabase-js/2.31.0:
|
||||
resolution: {integrity: sha512-W9/4s+KnSUX67wJKBn/3yLq+ieycnMzVjK3nNTLX5Wko3ypNT/081l2iFYrf+nsLQ1CiT4mA92I3dxCy6CmxTg==}
|
||||
dependencies:
|
||||
'@supabase/functions-js': 2.1.2
|
||||
'@supabase/gotrue-js': 2.46.1
|
||||
'@supabase/postgrest-js': 1.7.2
|
||||
'@supabase/realtime-js': 2.7.3
|
||||
'@supabase/storage-js': 2.5.1
|
||||
cross-fetch: 3.1.8
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@swc/core-darwin-arm64/1.3.26:
|
||||
resolution: {integrity: sha512-FWWflBfKRYrUJtko2xiedC5XCa31O75IZZqnTWuLpe9g3C5tnUuF3M8LSXZS/dn6wprome1MhtG9GMPkSYkhkg==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -10445,6 +10526,10 @@ packages:
|
||||
pg-types: 2.2.0
|
||||
dev: false
|
||||
|
||||
/@types/phoenix/1.6.0:
|
||||
resolution: {integrity: sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g==}
|
||||
dev: false
|
||||
|
||||
/@types/pretty-hrtime/1.0.1:
|
||||
resolution: {integrity: sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==}
|
||||
dev: true
|
||||
@@ -10551,6 +10636,12 @@ packages:
|
||||
resolution: {integrity: sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==}
|
||||
dev: true
|
||||
|
||||
/@types/websocket/1.0.5:
|
||||
resolution: {integrity: sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==}
|
||||
dependencies:
|
||||
'@types/node': 20.4.2
|
||||
dev: false
|
||||
|
||||
/@types/ws/8.5.4:
|
||||
resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
|
||||
dependencies:
|
||||
@@ -12051,6 +12142,14 @@ packages:
|
||||
engines: {node: '>=0.2.0'}
|
||||
dev: false
|
||||
|
||||
/bufferutil/4.0.7:
|
||||
resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==}
|
||||
engines: {node: '>=6.14.2'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
node-gyp-build: 4.6.0
|
||||
dev: false
|
||||
|
||||
/bufrw/1.3.0:
|
||||
resolution: {integrity: sha512-jzQnSbdJqhIltU9O5KUiTtljP9ccw2u5ix59McQy4pV2xGhVLhRZIndY8GIrgh5HjXa6+QJ9AQhOd2QWQizJFQ==}
|
||||
engines: {node: '>= 0.10.x'}
|
||||
@@ -12804,6 +12903,14 @@ packages:
|
||||
cross-spawn: 7.0.3
|
||||
dev: false
|
||||
|
||||
/cross-fetch/3.1.8:
|
||||
resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==}
|
||||
dependencies:
|
||||
node-fetch: 2.6.12
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/cross-spawn/5.1.0:
|
||||
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
|
||||
dependencies:
|
||||
@@ -12939,6 +13046,13 @@ packages:
|
||||
resolution: {integrity: sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==}
|
||||
dev: false
|
||||
|
||||
/d/1.0.1:
|
||||
resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==}
|
||||
dependencies:
|
||||
es5-ext: 0.10.62
|
||||
type: 1.2.0
|
||||
dev: false
|
||||
|
||||
/damerau-levenshtein/1.0.8:
|
||||
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
||||
|
||||
@@ -13587,10 +13701,35 @@ packages:
|
||||
is-date-object: 1.0.5
|
||||
is-symbol: 1.0.4
|
||||
|
||||
/es5-ext/0.10.62:
|
||||
resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==}
|
||||
engines: {node: '>=0.10'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
es6-iterator: 2.0.3
|
||||
es6-symbol: 3.1.3
|
||||
next-tick: 1.1.0
|
||||
dev: false
|
||||
|
||||
/es6-iterator/2.0.3:
|
||||
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
|
||||
dependencies:
|
||||
d: 1.0.1
|
||||
es5-ext: 0.10.62
|
||||
es6-symbol: 3.1.3
|
||||
dev: false
|
||||
|
||||
/es6-object-assign/1.1.0:
|
||||
resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==}
|
||||
dev: true
|
||||
|
||||
/es6-symbol/3.1.3:
|
||||
resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==}
|
||||
dependencies:
|
||||
d: 1.0.1
|
||||
ext: 1.7.0
|
||||
dev: false
|
||||
|
||||
/esbuild-android-64/0.15.18:
|
||||
resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -15305,6 +15444,12 @@ packages:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/ext/1.7.0:
|
||||
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
|
||||
dependencies:
|
||||
type: 2.7.2
|
||||
dev: false
|
||||
|
||||
/extend-shallow/2.0.1:
|
||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -17117,6 +17262,10 @@ packages:
|
||||
gopd: 1.0.1
|
||||
has-tostringtag: 1.0.0
|
||||
|
||||
/is-typedarray/1.0.0:
|
||||
resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
|
||||
dev: false
|
||||
|
||||
/is-unicode-supported/0.1.0:
|
||||
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -18796,6 +18945,10 @@ packages:
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dev: true
|
||||
|
||||
/next-tick/1.1.0:
|
||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||
dev: false
|
||||
|
||||
/next/12.3.4_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-VcyMJUtLZBGzLKo3oMxrEF0stxh8HwuW976pAzlHhI3t8qJ4SROjCrSh1T24bhrbjw55wfZXAbXPGwPt5FLRfQ==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
@@ -19043,7 +19196,6 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
dev: true
|
||||
|
||||
/node-fetch/2.6.7:
|
||||
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
|
||||
@@ -19065,6 +19217,11 @@ packages:
|
||||
formdata-polyfill: 4.0.10
|
||||
dev: false
|
||||
|
||||
/node-gyp-build/4.6.0:
|
||||
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/node-int64/0.4.0:
|
||||
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
||||
|
||||
@@ -19343,6 +19500,10 @@ packages:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/openapi-fetch/0.6.2:
|
||||
resolution: {integrity: sha512-Faj29Kzh7oCbt1bz6vAGNKtRJlV/GolOQTx87eYUnfCK7eVXdN9jQVojroc7tcJ5OQgyhbeOqD7LS/8UtGBnMQ==}
|
||||
dev: false
|
||||
|
||||
/opentracing/0.14.7:
|
||||
resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==}
|
||||
engines: {node: '>=0.10'}
|
||||
@@ -22396,6 +22557,13 @@ packages:
|
||||
pirates: 4.0.5
|
||||
ts-interface-checker: 0.1.13
|
||||
|
||||
/supabase-management-js/0.1.2:
|
||||
resolution: {integrity: sha512-2sdCM1Gc7XI6zMVbOxdG0hsn1RQOqdD7pGfQwrgnLtampLe9mq7FTeK6a0LS/UhsDYl7jEVYK9BINP18zDxj6Q==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dependencies:
|
||||
openapi-fetch: 0.6.2
|
||||
dev: false
|
||||
|
||||
/supports-color/5.5.0:
|
||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -23303,6 +23471,14 @@ packages:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
/type/1.2.0:
|
||||
resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==}
|
||||
dev: false
|
||||
|
||||
/type/2.7.2:
|
||||
resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==}
|
||||
dev: false
|
||||
|
||||
/typed-array-length/1.0.4:
|
||||
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
|
||||
dependencies:
|
||||
@@ -23310,6 +23486,12 @@ packages:
|
||||
for-each: 0.3.3
|
||||
is-typed-array: 1.1.10
|
||||
|
||||
/typedarray-to-buffer/3.1.5:
|
||||
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
|
||||
dependencies:
|
||||
is-typedarray: 1.0.0
|
||||
dev: false
|
||||
|
||||
/typedarray/0.0.6:
|
||||
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
||||
dev: true
|
||||
@@ -23675,6 +23857,14 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/utf-8-validate/5.0.10:
|
||||
resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
|
||||
engines: {node: '>=6.14.2'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
node-gyp-build: 4.6.0
|
||||
dev: false
|
||||
|
||||
/util-deprecate/1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
@@ -24131,6 +24321,20 @@ packages:
|
||||
- uglify-js
|
||||
dev: true
|
||||
|
||||
/websocket/1.0.34:
|
||||
resolution: {integrity: sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dependencies:
|
||||
bufferutil: 4.0.7
|
||||
debug: 2.6.9
|
||||
es5-ext: 0.10.62
|
||||
typedarray-to-buffer: 3.1.5
|
||||
utf-8-validate: 5.0.10
|
||||
yaeti: 0.0.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/whatwg-url/5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
dependencies:
|
||||
@@ -24362,6 +24566,11 @@ packages:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/yaeti/0.0.6:
|
||||
resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==}
|
||||
engines: {node: '>=0.10.32'}
|
||||
dev: false
|
||||
|
||||
/yallist/2.1.2:
|
||||
resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user