From 83455e3ad4ca222c252172ca3e7a812f1a5e7535 Mon Sep 17 00:00:00 2001 From: Hemachandar <132386067+hmacr@users.noreply.github.com> Date: Thu, 26 Oct 2023 17:45:50 +0530 Subject: [PATCH 01/95] feat: implement API key regeneration (#649) --- .../environments/RegenerateApiKeyModal.tsx | 107 ++++++++++++++++++ apps/webapp/app/models/api-key.server.ts | 101 +++++++++++++++++ apps/webapp/app/models/organization.server.ts | 26 +---- .../route.tsx | 17 ++- ...ents.$environmentId.regenerate-api-key.tsx | 39 +++++++ 5 files changed, 260 insertions(+), 30 deletions(-) create mode 100644 apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx create mode 100644 apps/webapp/app/models/api-key.server.ts create mode 100644 apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx diff --git a/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx b/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx new file mode 100644 index 000000000..f94e01cfc --- /dev/null +++ b/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx @@ -0,0 +1,107 @@ +import { Button } from "../primitives/Buttons"; +import { Dialog, DialogContent, DialogTrigger } from "~/components/primitives/Dialog"; +import { Header1, Header2 } from "../primitives/Headers"; +import { Input } from "../primitives/Input"; +import { NamedIcon } from "../primitives/NamedIcon"; +import { Paragraph } from "../primitives/Paragraph"; +import { Spinner } from "../primitives/Spinner"; +import { useState } from "react"; +import { useFetcher } from "@remix-run/react"; +import { generateTwoRandomWords } from "~/utils/randomWords"; + +type ModalProps = { + id: string; + title: string; +}; + +type ModalContentProps = ModalProps & { + randomWord: string; + closeModal: () => void; +}; + +export function RegenerateApiKeyModal({ id, title }: ModalProps) { + const randomWord = generateTwoRandomWords(); + const [open, setOpen] = useState(false); + return ( + + + + + + setOpen(false)} + /> + + + ); +} + +const RegenerateApiKeyModalContent = ({ id, title, randomWord, closeModal }: ModalContentProps) => { + const [confirmationText, setConfirmationText] = useState(""); + const fetcher = useFetcher(); + const isSubmitting = fetcher.state === "submitting"; + + // form submission completed + if (fetcher.state === "loading") { + closeModal(); + } + + return ( +
+
+ {title} Environment +
+ + Are you sure you want to regenerate the keys
+ for this environment? +
+ + <> + This will temporarily break any live jobs in the + {title} Environment until the new API keys + are set in the relevant environment variables. + + + + + To confirm, please enter the text:{" "} + {randomWord} + + setConfirmationText(e.target.value)} + /> + + +
+ ); +}; diff --git a/apps/webapp/app/models/api-key.server.ts b/apps/webapp/app/models/api-key.server.ts new file mode 100644 index 000000000..a54fdae9a --- /dev/null +++ b/apps/webapp/app/models/api-key.server.ts @@ -0,0 +1,101 @@ +import { RuntimeEnvironmentType, type RuntimeEnvironment } from "@trigger.dev/database"; +import { prisma } from "~/db.server"; +import { customAlphabet } from "nanoid"; + +const apiKeyId = customAlphabet( + "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + 12 +); + +type RegenerateAPIKeyInput = { + userId: string; + environmentId: string; +}; + +export async function regenerateApiKey({ userId, environmentId }: RegenerateAPIKeyInput) { + const environment = await prisma.runtimeEnvironment.findUnique({ + where: { + id: environmentId, + }, + include: { + organization: true, + project: true, + }, + }); + + if (!environment) { + throw new Error("Environment does not exist"); + } + + // check if the user is part of the org + const organization = await prisma.organization.findFirst({ + where: { + id: environment.organization.id, + members: { some: { userId } }, + }, + }); + + if (!organization) { + throw new Error("User does not have permission to regenerate API key"); + } + + // check if it is the user's dev environment + if (environment.type === RuntimeEnvironmentType.DEVELOPMENT) { + if (!environment.orgMemberId) { + throw new Error("User does not have permission to regenerate API key"); + } + + const orgMember = await prisma.orgMember.findFirst({ + where: { + organizationId: organization.id, + userId: userId, + id: environment.orgMemberId, + }, + }); + + if (!orgMember) { + throw new Error("User does not have permission to regenerate API key"); + } + } + + // generate and store new keys + const newApiKey = createApiKeyForEnv(environment.type); + const newPkApiKey = createPkApiKeyForEnv(environment.type); + + const updatedEnviroment = await prisma.runtimeEnvironment.update({ + data: { + apiKey: newApiKey, + pkApiKey: newPkApiKey, + }, + where: { + id: environmentId, + }, + }); + + return updatedEnviroment; +} + +export function createApiKeyForEnv(envType: RuntimeEnvironment["type"]) { + return `tr_${envSlug(envType)}_${apiKeyId(20)}`; +} + +export function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) { + return `pk_${envSlug(envType)}_${apiKeyId(20)}`; +} + +export function envSlug(environmentType: RuntimeEnvironment["type"]) { + switch (environmentType) { + case "DEVELOPMENT": { + return "dev"; + } + case "PRODUCTION": { + return "prod"; + } + case "STAGING": { + return "stg"; + } + case "PREVIEW": { + return "prev"; + } + } +} diff --git a/apps/webapp/app/models/organization.server.ts b/apps/webapp/app/models/organization.server.ts index 2daf0791a..df0475fbe 100644 --- a/apps/webapp/app/models/organization.server.ts +++ b/apps/webapp/app/models/organization.server.ts @@ -9,6 +9,7 @@ import { customAlphabet } from "nanoid"; import slug from "slug"; import { prisma, PrismaClientOrTransaction } from "~/db.server"; import { createProject } from "./project.server"; +import { createApiKeyForEnv, createPkApiKeyForEnv, envSlug } from "./api-key.server"; export type { Organization }; @@ -157,28 +158,3 @@ export async function createEnvironment( }, }); } - -function createApiKeyForEnv(envType: RuntimeEnvironment["type"]) { - return `tr_${envSlug(envType)}_${apiKeyId(20)}`; -} - -function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) { - return `pk_${envSlug(envType)}_${apiKeyId(20)}`; -} - -function envSlug(environmentType: RuntimeEnvironment["type"]) { - switch (environmentType) { - case "DEVELOPMENT": { - return "dev"; - } - case "PRODUCTION": { - return "prod"; - } - case "STAGING": { - return "stg"; - } - case "PREVIEW": { - return "prev"; - } - } -} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx index 521fb993a..0f22d6869 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx @@ -44,6 +44,7 @@ import { requestUrl } from "~/utils/requestUrl.server"; import { RuntimeEnvironmentType } from "../../../../../packages/database/src"; import { ConfigureEndpointSheet } from "./ConfigureEndpointSheet"; import { FirstEndpointSheet } from "./FirstEndpointSheet"; +import { RegenerateApiKeyModal } from "~/components/environments/RegenerateApiKeyModal"; export const loader = async ({ request, params }: LoaderArgs) => { const userId = await requireUserId(request); @@ -148,11 +149,17 @@ export default function Page() {
{environments.map((environment) => ( -
- - Environment - -
+
+
+ + Environment + + +
+
Date: Thu, 26 Oct 2023 17:48:28 +0530 Subject: [PATCH 02/95] =?UTF-8?q?Added=20warning=20=E2=9A=A0=EF=B8=8F=20-?= =?UTF-8?q?=20No=20support=20for=20long-running=20servers=20(#669)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/commands/init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 8bd7b0fbb..1978b90a0 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -165,7 +165,7 @@ async function printNextSteps( framework: Framework ) { logger.success(`✔ Successfully initialized Trigger.dev!`); - + logger.warn(`⚠️ Warning: We don't currently support long-running servers! For more details, check out https://github.com/triggerdotdev/trigger.dev/issues/244.`); logger.info("Next steps:"); logger.info(` 1. Run your ${framework.name} project locally with '${packageManager} run dev'`); logger.info( From 3c3a5e1f280399ade16e0e30e9f743ab6c67b462 Mon Sep 17 00:00:00 2001 From: ROMIT PAL <85341342+romitp4l@users.noreply.github.com> Date: Thu, 26 Oct 2023 18:00:14 +0530 Subject: [PATCH 03/95] added a missing CODE OF CONDUCT file (#677) Incorporating a Code of Conduct in your GitHub repository is crucial for promoting an inclusive, respectful community. It establishes guidelines, minimizes conflicts, safeguards participants, and adheres to legal standards. Additionally, it demonstrates professionalism and aligns with GitHub's best practices, contributing to a welcoming and harmonious environment, which, in turn, enhances collaboration and the project's overall success. --- CODE_OF_CONDUCT.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..32c9b3a9b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,43 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct that could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). From 1be25d76393d856012b36bd6581624d6d724b947 Mon Sep 17 00:00:00 2001 From: NIXEY <107938013+NXKED@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:35:15 +0200 Subject: [PATCH 04/95] BlankRow centered (#678) --- .../route.tsx | 2 +- .../route.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx index bf8fd8670..e9258e41f 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx @@ -138,7 +138,7 @@ export default function Integrations() { ); }) ) : ( - + No External triggers )} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx index fe1199439..3cd289c2c 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx @@ -143,7 +143,7 @@ export default function Integrations() { ); }) ) : ( - + No Scheduled triggers )} From e80aa2826d3197b67f2ac1e73683d7908b0725af Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 26 Oct 2023 13:42:20 +0100 Subject: [PATCH 05/95] @trigger.dev/express: Remove web-fetch dependency and use native node Request/Response --- .changeset/new-llamas-call.md | 5 +++++ packages/express/package.json | 1 - packages/express/src/index.ts | 8 ++++---- packages/express/tsconfig.json | 3 ++- pnpm-lock.yaml | 34 ---------------------------------- 5 files changed, 11 insertions(+), 40 deletions(-) create mode 100644 .changeset/new-llamas-call.md diff --git a/.changeset/new-llamas-call.md b/.changeset/new-llamas-call.md new file mode 100644 index 000000000..439125c5e --- /dev/null +++ b/.changeset/new-llamas-call.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/express": patch +--- + +Remove web-fetch dependency and use native node Request/Response diff --git a/packages/express/package.json b/packages/express/package.json index c26c2cf02..12931daed 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -36,7 +36,6 @@ "@trigger.dev/sdk": "workspace:^2.2.2" }, "dependencies": { - "@remix-run/web-fetch": "^4.3.5", "debug": "^4.3.4", "express": "^4.18.2" }, diff --git a/packages/express/src/index.ts b/packages/express/src/index.ts index bd69a54fc..cade700a3 100644 --- a/packages/express/src/index.ts +++ b/packages/express/src/index.ts @@ -1,6 +1,5 @@ import express, { Express } from "express"; import { TriggerClient } from "@trigger.dev/sdk"; -import { Request as StandardRequest, Headers as StandardHeaders } from "@remix-run/web-fetch"; /** * This is a convenience function to create an express server for the TriggerClient. If you want to use Trigger.dev with an existing express server, use `createMiddleware` instead. @@ -92,20 +91,21 @@ export function createMiddleware(client: TriggerClient, path: string = "/api/tri }; } -function convertToStandardRequest(req: express.Request): StandardRequest { +function convertToStandardRequest(req: express.Request): Request { const { headers: nextHeaders, method } = req; - const headers = new StandardHeaders(); + const headers = new Headers(); Object.entries(nextHeaders).forEach(([key, value]) => { headers.set(key, value as string); }); // Create a new Request object (hardcode the url because it doesn't really matter what it is) - return new StandardRequest("https://express.js/api/trigger", { + return new Request("https://express.js/api/trigger", { headers, method, // @ts-ignore body: req.body ? JSON.stringify(req.body) : req, + duplex: "half", }); } diff --git a/packages/express/tsconfig.json b/packages/express/tsconfig.json index 1eea77ed9..a64442248 100644 --- a/packages/express/tsconfig.json +++ b/packages/express/tsconfig.json @@ -5,7 +5,8 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "declaration": false, - "declarationMap": false + "declarationMap": false, + "lib": ["DOM"] }, "exclude": ["node_modules"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2c50aff1..c29a9c76f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -858,7 +858,6 @@ importers: packages/express: specifiers: - '@remix-run/web-fetch': ^4.3.5 '@trigger.dev/sdk': workspace:^2.2.2 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 @@ -869,7 +868,6 @@ importers: tsup: ^6.5.0 tsx: ^3.12.1 dependencies: - '@remix-run/web-fetch': 4.3.5 debug: 4.3.4 express: 4.18.2 devDependencies: @@ -10875,32 +10873,12 @@ packages: - react-dom dev: true - /@remix-run/web-blob/3.0.4: - resolution: {integrity: sha512-AfegzZvSSDc+LwnXV+SwROTrDtoLiPxeFW+jxgvtDAnkuCX1rrzmVJ6CzqZ1Ai0bVfmJadkG5GxtAfYclpPmgw==} - dependencies: - '@remix-run/web-stream': 1.0.4 - web-encoding: 1.1.5 - dev: false - /@remix-run/web-blob/3.0.5: resolution: {integrity: sha512-Mungj3erqCrq0+5zU/34NkeC2g+U7K6Uwa8uNiZgANvw0Wc64wKglk4MPQJZA0Y2tgPYXyrRn7uw4q75j6Hhww==} dependencies: '@remix-run/web-stream': 1.0.4 web-encoding: 1.1.5 - /@remix-run/web-fetch/4.3.5: - resolution: {integrity: sha512-cLLeNLvLRyFRhJLulzS98bb07kJ+ENkGaqUkBisdG4FNEoZF6tXtrTGLWJNJa1nAP/wFkMKEDxIP77LgAPyeow==} - engines: {node: ^10.17 || >=12.3} - dependencies: - '@remix-run/web-blob': 3.0.4 - '@remix-run/web-form-data': 3.0.4 - '@remix-run/web-stream': 1.0.3 - '@web3-storage/multipart-parser': 1.0.0 - abort-controller: 3.0.0 - data-uri-to-buffer: 3.0.1 - mrmime: 1.0.1 - dev: false - /@remix-run/web-fetch/4.3.6: resolution: {integrity: sha512-ifadyJS+/7W6LhKyA8tIR9fBIPwLCFVpl1YCYg5i0ikykiXxE3IWtPVB1G51AJFghc1YgR7rq8BRJLsJeUbE5Q==} engines: {node: ^10.17 || >=12.3} @@ -10918,23 +10896,11 @@ packages: dependencies: '@remix-run/web-blob': 3.0.5 - /@remix-run/web-form-data/3.0.4: - resolution: {integrity: sha512-UMF1jg9Vu9CLOf8iHBdY74Mm3PUvMW8G/XZRJE56SxKaOFWGSWlfxfG+/a3boAgHFLTkP7K4H1PxlRugy1iQtw==} - dependencies: - web-encoding: 1.1.5 - dev: false - /@remix-run/web-form-data/3.0.5: resolution: {integrity: sha512-txXJDzjDuTxF8MFvEp9AA2HF3oPcvmlE1I/6HIxeGpX3vpBtrCPw5KQ/nzgBZNuAxyxEm8ps6Ds/UZwoDyfGsQ==} dependencies: web-encoding: 1.1.5 - /@remix-run/web-stream/1.0.3: - resolution: {integrity: sha512-wlezlJaA5NF6SsNMiwQnnAW6tnPzQ5I8qk0Y0pSohm0eHKa2FQ1QhEKLVVcDDu02TmkfHgnux0igNfeYhDOXiA==} - dependencies: - web-streams-polyfill: 3.2.1 - dev: false - /@remix-run/web-stream/1.0.4: resolution: {integrity: sha512-SVO42pH21I1sAhksGEM8ZBV/jc1mz6knZSg6Qo/2HPy9JTvtUykm3QMHtF2OMCTUXxdRW+4E/rphkPRyGc8WKw==} dependencies: From c0f9f10b075f479f1d1429f6302cefc370a76510 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 26 Oct 2023 15:54:21 +0100 Subject: [PATCH 06/95] Upgrade to Remix v2.1.0 --- apps/webapp/app/components/Feedback.tsx | 3 +- .../integrations/ConnectToOAuthForm.tsx | 3 +- .../integrations/UpdateOAuthForm.tsx | 3 +- .../app/components/navigation/Breadcrumb.tsx | 7 +- .../app/components/navigation/JobsMenu.tsx | 4 +- .../components/navigation/ProjectsMenu.tsx | 4 +- .../webapp/app/components/run/RunOverview.tsx | 6 +- apps/webapp/app/components/run/TaskDetail.tsx | 2 +- apps/webapp/app/entry.server.tsx | 136 +- apps/webapp/app/hooks/useEnvironments.ts | 8 +- .../webapp/app/hooks/useIntegrationClient.tsx | 6 +- .../webapp/app/hooks/useIsProjectChildPage.ts | 4 +- apps/webapp/app/hooks/useJob.tsx | 6 +- apps/webapp/app/hooks/useJobs.tsx | 6 +- apps/webapp/app/hooks/useOrganizations.ts | 12 +- apps/webapp/app/hooks/useProject.tsx | 6 +- .../app/hooks/useProjectSetupComplete.ts | 2 +- apps/webapp/app/hooks/useRun.ts | 6 +- apps/webapp/app/hooks/useTypedMatchData.ts | 8 +- apps/webapp/app/hooks/useUser.ts | 6 +- apps/webapp/app/root.tsx | 23 +- .../routes/_app._orgaccount._index/route.tsx | 4 +- .../routes/_app._orgaccount.account/route.tsx | 3 +- .../route.tsx | 4 +- .../route.tsx | 3 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../ConfigureEndpointSheet.tsx | 14 +- .../FirstEndpointSheet.tsx | 3 +- .../route.tsx | 6 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 6 +- .../route.tsx | 7 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 9 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 6 +- .../route.tsx | 4 +- .../route.tsx | 6 +- .../route.tsx | 7 +- .../_app.orgs.$organizationSlug/route.tsx | 4 +- .../webapp/app/routes/_app.orgs.new/route.tsx | 3 +- apps/webapp/app/routes/_app/route.tsx | 4 +- apps/webapp/app/routes/admin._index.tsx | 4 +- .../app/routes/admin.api.v1.simulate.ts | 4 +- apps/webapp/app/routes/admin.tsx | 4 +- .../routes/api.internal.stripe_webhooks.ts | 4 +- .../routes/api.v1.$endpointSlug.auth.$id.ts | 4 +- ...ntSlug.schedules.$id.registrations.$key.ts | 4 +- ...ndpointSlug.schedules.$id.registrations.ts | 4 +- .../api.v1.$endpointSlug.sources.$id.ts | 4 +- ...intSlug.triggers.$id.registrations.$key.ts | 4 +- ...endpointSlug.triggers.$id.registrations.ts | 4 +- ...unts.$accountId.connections.$clientSlug.ts | 4 +- .../routes/api.v1.endpointindex.$indexId.ts | 4 +- .../api.v1.endpoints.$endpointSlug.index.ts | 4 +- ...endpointSlug.index.$indexHookIdentifier.ts | 6 +- apps/webapp/app/routes/api.v1.endpoints.ts | 4 +- .../api.v1.events.$eventId.cancel-runs.ts | 4 +- .../routes/api.v1.events.$eventId.cancel.ts | 4 +- .../app/routes/api.v1.events.$eventId.ts | 4 +- apps/webapp/app/routes/api.v1.events.ts | 4 +- ...tegrationSlug.connections.$connectionId.ts | 4 +- .../app/routes/api.v1.jobs.$jobSlug.runs.ts | 4 +- .../app/routes/api.v1.runs.$runId.cancel.ts | 4 +- .../app/routes/api.v1.runs.$runId.logs.ts | 4 +- .../routes/api.v1.runs.$runId.statuses.$id.ts | 4 +- .../app/routes/api.v1.runs.$runId.statuses.ts | 4 +- ....runs.$runId.tasks.$id.callback.$secret.ts | 4 +- .../api.v1.runs.$runId.tasks.$id.complete.ts | 4 +- .../api.v1.runs.$runId.tasks.$id.fail.ts | 4 +- .../app/routes/api.v1.runs.$runId.tasks.ts | 4 +- apps/webapp/app/routes/api.v1.runs.$runId.ts | 4 +- .../app/routes/api.v1.sources.http.$id.ts | 6 +- apps/webapp/app/routes/api.v1.whoami.ts | 4 +- .../api.v2.$endpointSlug.sources.$id.ts | 4 +- ...intSlug.triggers.$id.registrations.$key.ts | 4 +- .../app/routes/confirm-basic-details.tsx | 3 +- apps/webapp/app/routes/invite-accept.tsx | 4 +- apps/webapp/app/routes/invites.tsx | 7 +- apps/webapp/app/routes/login._index/route.tsx | 29 +- apps/webapp/app/routes/login.magic/route.tsx | 26 +- apps/webapp/app/routes/magic.tsx | 4 +- apps/webapp/app/routes/oauth2.callback.ts | 4 +- .../routes/resources.apivote.$identifier.ts | 4 +- ...n.$organizationId.oauth2.$integrationId.ts | 4 +- ...urces.connection.$organizationId.oauth2.ts | 4 +- ...ents.$environmentId.regenerate-api-key.tsx | 4 +- ...nvironmentParam.endpoint.$endpointParam.ts | 4 +- ...ments.$environmentParam.endpoint.stream.ts | 4 +- ...environments.$environmentParam.endpoint.ts | 4 +- apps/webapp/app/routes/resources.feedback.ts | 4 +- .../app/routes/resources.impersonation.ts | 4 +- .../resources.projects.$projectId.endpoint.ts | 4 +- ...sources.projects.$projectId.jobs.stream.ts | 4 +- apps/webapp/app/routes/tests.sse.stream.ts | 4 +- apps/webapp/app/routes/tests.sse.tsx | 6 +- apps/webapp/app/utils.ts | 9 +- apps/webapp/app/utils/apiCors.ts | 2 +- apps/webapp/app/utils/handle.ts | 2 +- apps/webapp/app/utils/sse.ts | 2 +- apps/webapp/package.json | 27 +- apps/webapp/remix.config.js | 10 +- apps/webapp/tsconfig.json | 2 +- docs/documentation/guides/react-hooks.mdx | 2 +- pnpm-lock.yaml | 1099 +++++++++-------- 118 files changed, 1020 insertions(+), 828 deletions(-) diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index 72843908a..8ebf2af9d 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -36,7 +36,8 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) { const [form, { path, feedbackType, message }] = useForm({ id: "accept-invite", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema }); }, diff --git a/apps/webapp/app/components/integrations/ConnectToOAuthForm.tsx b/apps/webapp/app/components/integrations/ConnectToOAuthForm.tsx index a997ab2c3..ae7a7f728 100644 --- a/apps/webapp/app/components/integrations/ConnectToOAuthForm.tsx +++ b/apps/webapp/app/components/integrations/ConnectToOAuthForm.tsx @@ -46,7 +46,8 @@ export function ConnectToOAuthForm({ const [form, { title, slug, scopes, hasCustomClient, customClientId, customClientSecret }] = useForm({ - lastSubmission: fetcher.data, + // TODO: type this + lastSubmission: fetcher.data as any, shouldRevalidate: "onSubmit", onValidate({ formData }) { return parse(formData, { diff --git a/apps/webapp/app/components/integrations/UpdateOAuthForm.tsx b/apps/webapp/app/components/integrations/UpdateOAuthForm.tsx index 85db2c6de..aeadde9ea 100644 --- a/apps/webapp/app/components/integrations/UpdateOAuthForm.tsx +++ b/apps/webapp/app/components/integrations/UpdateOAuthForm.tsx @@ -45,7 +45,8 @@ export function UpdateOAuthForm({ const { isManagedCloud } = useFeatures(); const [form, { title, scopes, hasCustomClient, customClientId, customClientSecret }] = useForm({ - lastSubmission: fetcher.data, + // TODO: type this + lastSubmission: fetcher.data as any, onValidate({ formData }) { return parse(formData, { schema, diff --git a/apps/webapp/app/components/navigation/Breadcrumb.tsx b/apps/webapp/app/components/navigation/Breadcrumb.tsx index 64980c9e9..b2d5fcd2d 100644 --- a/apps/webapp/app/components/navigation/Breadcrumb.tsx +++ b/apps/webapp/app/components/navigation/Breadcrumb.tsx @@ -1,11 +1,12 @@ -import { RouteMatch, useMatches } from "@remix-run/react"; +import { UIMatch, useMatches } from "@remix-run/react"; import { Fragment, ReactNode } from "react"; import { BreadcrumbIcon } from "../primitives/BreadcrumbIcon"; +import { Handle } from "~/utils/handle"; -export type BreadcrumbItem = (match: RouteMatch, allMatches: RouteMatch[]) => ReactNode; +export type BreadcrumbItem = (match: UIMatch, allMatches: UIMatch[]) => ReactNode; export function Breadcrumb() { - const matches = useMatches(); + const matches = useMatches() as UIMatch[]; return (
diff --git a/apps/webapp/app/components/navigation/JobsMenu.tsx b/apps/webapp/app/components/navigation/JobsMenu.tsx index 351f62bb2..506910e88 100644 --- a/apps/webapp/app/components/navigation/JobsMenu.tsx +++ b/apps/webapp/app/components/navigation/JobsMenu.tsx @@ -1,4 +1,4 @@ -import { Link, RouteMatch } from "@remix-run/react"; +import { Link, UIMatch } from "@remix-run/react"; import { useState } from "react"; import { useJob } from "~/hooks/useJob"; import { useOrganization } from "~/hooks/useOrganizations"; @@ -15,7 +15,7 @@ import { PopoverSectionHeader, } from "../primitives/Popover"; -export function JobsMenu({ matches }: { matches: RouteMatch[] }) { +export function JobsMenu({ matches }: { matches: UIMatch[] }) { const [isOpen, setIsOpen] = useState(false); const organization = useOrganization(matches); const project = useProject(matches); diff --git a/apps/webapp/app/components/navigation/ProjectsMenu.tsx b/apps/webapp/app/components/navigation/ProjectsMenu.tsx index bd9d98850..eae72eb49 100644 --- a/apps/webapp/app/components/navigation/ProjectsMenu.tsx +++ b/apps/webapp/app/components/navigation/ProjectsMenu.tsx @@ -1,4 +1,4 @@ -import { RouteMatch } from "@remix-run/react"; +import { UIMatch } from "@remix-run/react"; import { Fragment, useState } from "react"; import simplur from "simplur"; import { Badge } from "~/components/primitives/Badge"; @@ -13,7 +13,7 @@ import { PopoverSectionHeader, } from "../primitives/Popover"; -export function ProjectsMenu({ matches }: { matches: RouteMatch[] }) { +export function ProjectsMenu({ matches }: { matches: UIMatch[] }) { const [isOpen, setIsOpen] = useState(false); const organizations = useOrganizations(matches); const isNewOrgPage = useIsNewOrganizationPage(matches); diff --git a/apps/webapp/app/components/run/RunOverview.tsx b/apps/webapp/app/components/run/RunOverview.tsx index f9086d3c6..19da10d26 100644 --- a/apps/webapp/app/components/run/RunOverview.tsx +++ b/apps/webapp/app/components/run/RunOverview.tsx @@ -313,7 +313,8 @@ function RerunPopover({ const [form, { successRedirect }] = useForm({ id: "rerun", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema }); }, @@ -386,7 +387,8 @@ export function CancelRun({ runId }: { runId: string }) { const [form, { redirectUrl }] = useForm({ id: "cancel-run", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema: cancelSchema }); }, diff --git a/apps/webapp/app/components/run/TaskDetail.tsx b/apps/webapp/app/components/run/TaskDetail.tsx index 9a85df317..cd19f077b 100644 --- a/apps/webapp/app/components/run/TaskDetail.tsx +++ b/apps/webapp/app/components/run/TaskDetail.tsx @@ -28,7 +28,7 @@ import { } from "../primitives/Table"; import { TaskAttemptStatusLabel } from "./TaskAttemptStatus"; import { TaskStatusIcon } from "./TaskStatus"; -import { ClientOnly } from "remix-utils"; +import { ClientOnly } from "remix-utils/client-only"; import { Spinner } from "../primitives/Spinner"; import type { DetailedTask } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route"; diff --git a/apps/webapp/app/entry.server.tsx b/apps/webapp/app/entry.server.tsx index c27092669..20ae4628e 100644 --- a/apps/webapp/app/entry.server.tsx +++ b/apps/webapp/app/entry.server.tsx @@ -1,6 +1,9 @@ import { H } from "@highlight-run/node"; -import type { DataFunctionArgs, EntryContext, Headers } from "@remix-run/node"; // or cloudflare/deno -import { Response } from "@remix-run/node"; // or cloudflare/deno +import { + createReadableStreamFromReadable, + type DataFunctionArgs, + type EntryContext, +} from "@remix-run/node"; // or cloudflare/deno import { RemixServer } from "@remix-run/react"; import { parseAcceptLanguage } from "intl-parse-accept-language"; import isbot from "isbot"; @@ -36,7 +39,7 @@ export default function handleRequest( // response to render before sending it to the client. This // ensures that bots can see the full page content. if (isbot(request.headers.get("user-agent"))) { - return serveTheBots( + return handleBotRequest( request, responseStatusCode, responseHeaders, @@ -46,7 +49,7 @@ export default function handleRequest( ); } - return serveBrowsers( + return handleBrowserRequest( request, responseStatusCode, responseHeaders, @@ -56,7 +59,7 @@ export default function handleRequest( ); } -function serveTheBots( +function handleBotRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, @@ -65,76 +68,97 @@ function serveTheBots( platform: OperatingSystemPlatform ) { return new Promise((resolve, reject) => { + let shellRendered = false; const { pipe, abort } = renderToPipeableStream( - + , , { - // Use onAllReady to wait for the entire document to be ready onAllReady() { - responseHeaders.set("Content-Type", "text/html; charset=utf-8"); - let body = new PassThrough(); - pipe(body); - resolve( - new Response(body, { - status: responseStatusCode, - headers: responseHeaders, - }) - ); - }, - onShellError(err: unknown) { - reject(err); - }, - } - ); - setTimeout(abort, ABORT_DELAY); - }); -} + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set("Content-Type", "text/html"); -function serveBrowsers( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, - locales: string[], - platform: OperatingSystemPlatform -) { - return new Promise((resolve, reject) => { - let didError = false; - let shellReady = false; - const { pipe, abort } = renderToPipeableStream( - - - - - , - { - // use onShellReady to wait until a suspense boundary is triggered - onShellReady() { - shellReady = true; - responseHeaders.set("Content-Type", "text/html; charset=utf-8"); - let body = new PassThrough(); - pipe(body); resolve( - new Response(body, { - status: didError ? 500 : responseStatusCode, + new Response(stream, { headers: responseHeaders, + status: responseStatusCode, }) ); + + pipe(body); }, - onShellError(err: unknown) { - reject(err); + onShellError(error: unknown) { + reject(error); }, onError(error: unknown) { - didError = true; - if (shellReady) { - logError(error, request); + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); } }, } ); + + setTimeout(abort, ABORT_DELAY); + }); +} + +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + locales: string[], + platform: OperatingSystemPlatform +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + + + + + , + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + } + ); + setTimeout(abort, ABORT_DELAY); }); } diff --git a/apps/webapp/app/hooks/useEnvironments.ts b/apps/webapp/app/hooks/useEnvironments.ts index 20601bcd9..08ebaf457 100644 --- a/apps/webapp/app/hooks/useEnvironments.ts +++ b/apps/webapp/app/hooks/useEnvironments.ts @@ -1,17 +1,17 @@ -import { RouteMatch } from "@remix-run/react"; +import { UIMatch } from "@remix-run/react"; import { MatchedProject, useOptionalProject } from "./useProject"; import { useUser } from "./useUser"; export type ProjectJobEnvironment = MatchedProject["environments"][number]; -export function useEnvironments(matches?: RouteMatch[]) { +export function useEnvironments(matches?: UIMatch[]) { const project = useOptionalProject(matches); if (!project) return; return project.environments; } -export function useDevEnvironment(matches?: RouteMatch[]) { +export function useDevEnvironment(matches?: UIMatch[]) { const user = useUser(); const environments = useEnvironments(matches); if (!environments) return; @@ -21,7 +21,7 @@ export function useDevEnvironment(matches?: RouteMatch[]) { ); } -export function useProdEnvironment(matches?: RouteMatch[]) { +export function useProdEnvironment(matches?: UIMatch[]) { const environments = useEnvironments(matches); if (!environments) return; diff --git a/apps/webapp/app/hooks/useIntegrationClient.tsx b/apps/webapp/app/hooks/useIntegrationClient.tsx index 0540f2619..e7c5d0b5f 100644 --- a/apps/webapp/app/hooks/useIntegrationClient.tsx +++ b/apps/webapp/app/hooks/useIntegrationClient.tsx @@ -1,4 +1,4 @@ -import { RouteMatch } from "@remix-run/react"; +import { UIMatch } from "@remix-run/react"; import { UseDataFunctionReturn } from "remix-typedjson"; import invariant from "tiny-invariant"; import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route"; @@ -6,7 +6,7 @@ import { useTypedMatchesData } from "./useTypedMatchData"; export type MatchedClient = UseDataFunctionReturn["client"]; -export function useOptionalIntegrationClient(matches?: RouteMatch[]) { +export function useOptionalIntegrationClient(matches?: UIMatch[]) { const routeMatch = useTypedMatchesData({ id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam", matches, @@ -15,7 +15,7 @@ export function useOptionalIntegrationClient(matches?: RouteMatch[]) { return routeMatch?.client; } -export function useIntegrationClient(matches?: RouteMatch[]) { +export function useIntegrationClient(matches?: UIMatch[]) { const integration = useOptionalIntegrationClient(matches); invariant(integration, "Integration must be defined"); return integration; diff --git a/apps/webapp/app/hooks/useIsProjectChildPage.ts b/apps/webapp/app/hooks/useIsProjectChildPage.ts index a21692a28..cd9cf2f8a 100644 --- a/apps/webapp/app/hooks/useIsProjectChildPage.ts +++ b/apps/webapp/app/hooks/useIsProjectChildPage.ts @@ -1,6 +1,6 @@ -import { RouteMatch, useMatches } from "@remix-run/react"; +import { UIMatch, useMatches } from "@remix-run/react"; -export function useIsProjectChildPage(matches?: RouteMatch[]) { +export function useIsProjectChildPage(matches?: UIMatch[]) { if (!matches) { matches = useMatches(); } diff --git a/apps/webapp/app/hooks/useJob.tsx b/apps/webapp/app/hooks/useJob.tsx index e381d1535..32c6e682e 100644 --- a/apps/webapp/app/hooks/useJob.tsx +++ b/apps/webapp/app/hooks/useJob.tsx @@ -2,14 +2,14 @@ import { UseDataFunctionReturn } from "remix-typedjson"; import invariant from "tiny-invariant"; import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route"; import { useChanged } from "./useChanged"; -import { RouteMatch } from "@remix-run/react"; +import { UIMatch } from "@remix-run/react"; import { useTypedMatchesData } from "./useTypedMatchData"; export type MatchedJob = UseDataFunctionReturn["job"]; export const jobMatchId = "routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam"; -export function useOptionalJob(matches?: RouteMatch[]) { +export function useOptionalJob(matches?: UIMatch[]) { const routeMatch = useTypedMatchesData({ id: jobMatchId, matches, @@ -22,7 +22,7 @@ export function useOptionalJob(matches?: RouteMatch[]) { return routeMatch.projectJobs.find((j) => j.id === routeMatch.job.id); } -export function useJob(matches?: RouteMatch[]) { +export function useJob(matches?: UIMatch[]) { const job = useOptionalJob(matches); invariant(job, "Job must be defined"); return job; diff --git a/apps/webapp/app/hooks/useJobs.tsx b/apps/webapp/app/hooks/useJobs.tsx index 80115f12e..8c9821cc4 100644 --- a/apps/webapp/app/hooks/useJobs.tsx +++ b/apps/webapp/app/hooks/useJobs.tsx @@ -1,7 +1,7 @@ import { UseDataFunctionReturn } from "remix-typedjson"; import invariant from "tiny-invariant"; import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route"; -import { RouteMatch } from "@remix-run/react"; +import { UIMatch } from "@remix-run/react"; import { useTypedMatchesData } from "./useTypedMatchData"; export type ProjectJob = UseDataFunctionReturn["projectJobs"][number]; @@ -12,7 +12,7 @@ export const jobsMatchId = // This is only used in the JobsMenu component, which is the breadcrumb job list dropdown. // This dropdown is only shown once you have selected a job, so we can assume that // the route above has loaded and we can use the data from it. -export function useOptionalJobs(matches?: RouteMatch[]) { +export function useOptionalJobs(matches?: UIMatch[]) { const routeMatch = useTypedMatchesData({ id: jobsMatchId, matches, @@ -21,7 +21,7 @@ export function useOptionalJobs(matches?: RouteMatch[]) { return routeMatch?.projectJobs; } -export function useJobs(matches?: RouteMatch[]) { +export function useJobs(matches?: UIMatch[]) { const jobs = useOptionalJobs(matches); invariant(jobs, "Jobs must be defined"); return jobs; diff --git a/apps/webapp/app/hooks/useOrganizations.ts b/apps/webapp/app/hooks/useOrganizations.ts index 6a42d60e3..35a79d335 100644 --- a/apps/webapp/app/hooks/useOrganizations.ts +++ b/apps/webapp/app/hooks/useOrganizations.ts @@ -4,12 +4,12 @@ import type { loader as orgLoader } from "~/routes/_app.orgs.$organizationSlug/r import type { loader as appLoader } from "~/routes/_app/route"; import { hydrateObject, useMatchesData } from "~/utils"; import { useChanged } from "./useChanged"; -import { RouteMatch } from "@remix-run/react"; +import { UIMatch } from "@remix-run/react"; import { useTypedMatchesData } from "./useTypedMatchData"; export type MatchedOrganization = UseDataFunctionReturn["organizations"][number]; -export function useOptionalOrganizations(matches?: RouteMatch[]) { +export function useOptionalOrganizations(matches?: UIMatch[]) { const data = useTypedMatchesData({ id: "routes/_app", matches, @@ -17,13 +17,13 @@ export function useOptionalOrganizations(matches?: RouteMatch[]) { return data?.organizations; } -export function useOrganizations(matches?: RouteMatch[]) { +export function useOrganizations(matches?: UIMatch[]) { const orgs = useOptionalOrganizations(matches); invariant(orgs, "No organizations found in loader."); return orgs; } -export function useOptionalOrganization(matches?: RouteMatch[]) { +export function useOptionalOrganization(matches?: UIMatch[]) { const orgs = useOptionalOrganizations(matches); const org = useTypedMatchesData({ id: "routes/_app.orgs.$organizationSlug", @@ -37,13 +37,13 @@ export function useOptionalOrganization(matches?: RouteMatch[]) { return orgs.find((o) => o.id === org.organization.id); } -export function useOrganization(matches?: RouteMatch[]) { +export function useOrganization(matches?: UIMatch[]) { const org = useOptionalOrganization(matches); invariant(org, "No organization found in loader."); return org; } -export function useIsNewOrganizationPage(matches?: RouteMatch[]): boolean { +export function useIsNewOrganizationPage(matches?: UIMatch[]): boolean { const data = useTypedMatchesData({ id: "routes/_app.orgs.new", matches, diff --git a/apps/webapp/app/hooks/useProject.tsx b/apps/webapp/app/hooks/useProject.tsx index 58125ee1d..c223b8a95 100644 --- a/apps/webapp/app/hooks/useProject.tsx +++ b/apps/webapp/app/hooks/useProject.tsx @@ -1,4 +1,4 @@ -import { RouteMatch } from "@remix-run/react"; +import { UIMatch } from "@remix-run/react"; import { UseDataFunctionReturn } from "remix-typedjson"; import invariant from "tiny-invariant"; import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam/route"; @@ -9,7 +9,7 @@ export type MatchedProject = UseDataFunctionReturn["project"]; export const projectMatchId = "routes/_app.orgs.$organizationSlug.projects.$projectParam"; -export function useOptionalProject(matches?: RouteMatch[]) { +export function useOptionalProject(matches?: UIMatch[]) { const routeMatch = useTypedMatchesData({ id: projectMatchId, matches, @@ -18,7 +18,7 @@ export function useOptionalProject(matches?: RouteMatch[]) { return routeMatch?.project; } -export function useProject(matches?: RouteMatch[]) { +export function useProject(matches?: UIMatch[]) { const project = useOptionalProject(matches); invariant(project, "Project must be defined"); return project; diff --git a/apps/webapp/app/hooks/useProjectSetupComplete.ts b/apps/webapp/app/hooks/useProjectSetupComplete.ts index 7f0e64ab9..9969444f2 100644 --- a/apps/webapp/app/hooks/useProjectSetupComplete.ts +++ b/apps/webapp/app/hooks/useProjectSetupComplete.ts @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { useEventSource } from "remix-utils"; +import { useEventSource } from "remix-utils/sse/react"; import { projectPath, projectStreamingPath } from "~/utils/pathBuilder"; import { useProject } from "./useProject"; import { useOrganization } from "./useOrganizations"; diff --git a/apps/webapp/app/hooks/useRun.ts b/apps/webapp/app/hooks/useRun.ts index 2032c2f39..40c2516c9 100644 --- a/apps/webapp/app/hooks/useRun.ts +++ b/apps/webapp/app/hooks/useRun.ts @@ -1,4 +1,4 @@ -import { RouteMatch } from "@remix-run/react"; +import { UIMatch } from "@remix-run/react"; import { UseDataFunctionReturn } from "remix-typedjson"; import invariant from "tiny-invariant"; import type { loader as runLoader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route"; @@ -7,7 +7,7 @@ import { useTypedMatchesData } from "./useTypedMatchData"; export type MatchedRun = UseDataFunctionReturn["run"]; -export function useOptionalRun(matches?: RouteMatch[]) { +export function useOptionalRun(matches?: UIMatch[]) { const project = useOptionalProject(matches); const routeMatch = useTypedMatchesData({ id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam", @@ -21,7 +21,7 @@ export function useOptionalRun(matches?: RouteMatch[]) { return routeMatch.run; } -export function useRun(matches?: RouteMatch[]) { +export function useRun(matches?: UIMatch[]) { const run = useOptionalRun(matches); invariant(run, "Run must be present"); return run; diff --git a/apps/webapp/app/hooks/useTypedMatchData.ts b/apps/webapp/app/hooks/useTypedMatchData.ts index f0c3e6b14..fe5077717 100644 --- a/apps/webapp/app/hooks/useTypedMatchData.ts +++ b/apps/webapp/app/hooks/useTypedMatchData.ts @@ -1,4 +1,4 @@ -import { RouteMatch, useMatches } from "@remix-run/react"; +import { UIMatch, useMatches } from "@remix-run/react"; import { RemixSerializedType, UseDataFunctionReturn, deserializeRemix } from "remix-typedjson"; type AppData = any; @@ -8,7 +8,7 @@ function useTypedDataFromMatches({ matches, }: { id: string; - matches: RouteMatch[]; + matches: UIMatch[]; }): UseDataFunctionReturn | undefined { const match = matches.find((m) => m.id === id); return useTypedMatchData(match); @@ -19,7 +19,7 @@ export function useTypedMatchesData({ matches, }: { id: string; - matches?: RouteMatch[]; + matches?: UIMatch[]; }): UseDataFunctionReturn | undefined { if (!matches) { matches = useMatches(); @@ -29,7 +29,7 @@ export function useTypedMatchesData({ } export function useTypedMatchData( - match: RouteMatch | undefined + match: UIMatch | undefined ): UseDataFunctionReturn | undefined { if (!match) { return undefined; diff --git a/apps/webapp/app/hooks/useUser.ts b/apps/webapp/app/hooks/useUser.ts index ccd942778..98ad40fc0 100644 --- a/apps/webapp/app/hooks/useUser.ts +++ b/apps/webapp/app/hooks/useUser.ts @@ -1,11 +1,11 @@ import type { User } from "~/models/user.server"; import { useMatchesData } from "~/utils"; import { useChanged } from "./useChanged"; -import { RouteMatch } from "@remix-run/react"; +import { UIMatch } from "@remix-run/react"; import { useTypedMatchesData } from "./useTypedMatchData"; import { loader } from "~/root"; -export function useOptionalUser(matches?: RouteMatch[]): User | undefined { +export function useOptionalUser(matches?: UIMatch[]): User | undefined { const routeMatch = useTypedMatchesData({ id: "root", matches, @@ -14,7 +14,7 @@ export function useOptionalUser(matches?: RouteMatch[]): User | undefined { return routeMatch?.user ?? undefined; } -export function useUser(matches?: RouteMatch[]): User { +export function useUser(matches?: UIMatch[]): User { const maybeUser = useOptionalUser(matches); if (!maybeUser) { throw new Error( diff --git a/apps/webapp/app/root.tsx b/apps/webapp/app/root.tsx index 9819685cb..91eb125d0 100644 --- a/apps/webapp/app/root.tsx +++ b/apps/webapp/app/root.tsx @@ -1,7 +1,10 @@ -import type { LinksFunction, LoaderArgs } from "@remix-run/node"; +import { ErrorBoundary as HighlightErrorBoundary } from "@highlight-run/react"; +import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node"; import type { ShouldRevalidateFunction } from "@remix-run/react"; import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react"; +import { metaV1 } from "@remix-run/v1-meta"; import { TypedMetaFunction, typedjson, useTypedLoaderData } from "remix-typedjson"; +import { ExternalScripts } from "remix-utils/external-scripts"; import type { ToastMessage } from "~/models/message.server"; import { commitSession, getSession } from "~/models/message.server"; import tailwindStylesheetUrl from "~/tailwind.css"; @@ -11,24 +14,24 @@ import { AppContainer, MainCenteredContainer } from "./components/layout/AppLayo import { Toast } from "./components/primitives/Toast"; import { env } from "./env.server"; import { featuresForRequest } from "./features.server"; +import { useHighlight } from "./hooks/useHighlight"; import { usePostHog } from "./hooks/usePostHog"; import { getUser } from "./services/session.server"; import { appEnvTitleTag } from "./utils"; -import { ErrorBoundary as HighlightErrorBoundary } from "@highlight-run/react"; -import { useHighlight } from "./hooks/useHighlight"; -import { ExternalScripts } from "remix-utils"; export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: tailwindStylesheetUrl }]; }; -export const meta: TypedMetaFunction = ({ data }) => ({ - title: `Trigger.dev${appEnvTitleTag(data?.appEnv)}`, - charset: "utf-8", - viewport: "width=1024, initial-scale=1", -}); +export const meta: TypedMetaFunction = (args) => { + return metaV1(args, { + title: `Trigger.dev${appEnvTitleTag(args.data.appEnv)}`, + charset: "utf-8", + viewport: "width=1024, initial-scale=1", + }); +}; -export const loader = async ({ request }: LoaderArgs) => { +export const loader = async ({ request }: LoaderFunctionArgs) => { const session = await getSession(request.headers.get("cookie")); const toastMessage = session.get("toastMessage") as ToastMessage; const posthogProjectKey = env.POSTHOG_PROJECT_KEY; diff --git a/apps/webapp/app/routes/_app._orgaccount._index/route.tsx b/apps/webapp/app/routes/_app._orgaccount._index/route.tsx index 69a9269f9..637b97851 100644 --- a/apps/webapp/app/routes/_app._orgaccount._index/route.tsx +++ b/apps/webapp/app/routes/_app._orgaccount._index/route.tsx @@ -1,4 +1,4 @@ -import { LoaderArgs, json } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { redirect } from "remix-typedjson"; import { LinkButton } from "~/components/primitives/Buttons"; import { useOptionalOrganizations } from "~/hooks/useOrganizations"; @@ -11,7 +11,7 @@ import { Link } from "@remix-run/react"; import { NamedIcon } from "~/components/primitives/NamedIcon"; import { Paragraph } from "~/components/primitives/Paragraph"; -export const loader = async ({ request }: LoaderArgs) => { +export const loader = async ({ request }: LoaderFunctionArgs) => { const user = await requireUser(request); //if there are invites then we should redirect to the invites page diff --git a/apps/webapp/app/routes/_app._orgaccount.account/route.tsx b/apps/webapp/app/routes/_app._orgaccount.account/route.tsx index 76e704d34..4a3a018e1 100644 --- a/apps/webapp/app/routes/_app._orgaccount.account/route.tsx +++ b/apps/webapp/app/routes/_app._orgaccount.account/route.tsx @@ -113,7 +113,8 @@ export default function Page() { const [form, { name, email, marketingEmails }] = useForm({ id: "account", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema: createSchema() }); }, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx index e0ac76fe1..e0a38a6e6 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx @@ -14,12 +14,12 @@ import { useOrganization } from "~/hooks/useOrganizations"; import { OrganizationParamsSchema, jobPath, organizationTeamPath } from "~/utils/pathBuilder"; import { OrgAdminHeader } from "../_app.orgs.$organizationSlug._index/OrgAdminHeader"; import { Link } from "@remix-run/react/dist/components"; -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { OrgUsagePresenter } from "~/presenters/OrgUsagePresenter.server"; import { requireUserId } from "~/services/session.server"; -export async function loader({ params, request }: LoaderArgs) { +export async function loader({ params, request }: LoaderFunctionArgs) { const userId = await requireUserId(request); const { organizationSlug } = OrganizationParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx index d89daaea7..8430ff60d 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx @@ -91,7 +91,8 @@ export default function Page() { const [form, { emails }] = useForm({ id: "invite-members", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema }); }, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx index 88ce334c3..d3503b8f2 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx @@ -1,5 +1,5 @@ import { ArrowUpIcon } from "@heroicons/react/24/solid"; -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { FrameworkSelector } from "~/components/frameworks/FrameworkSelector"; import { JobsTable } from "~/components/jobs/JobsTable"; @@ -34,7 +34,7 @@ import { trimTrailingSlash, } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { projectParam } = ProjectParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments.stream/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments.stream/route.tsx index d04597fff..0cf19ac24 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments.stream/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments.stream/route.tsx @@ -1,9 +1,9 @@ -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { EnvironmentsStreamPresenter } from "~/presenters/EnvironmentsStreamPresenter.server"; import { requireUserId } from "~/services/session.server"; import { ProjectParamSchema } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { projectParam } = ProjectParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/ConfigureEndpointSheet.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/ConfigureEndpointSheet.tsx index 171761967..3ca22e070 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/ConfigureEndpointSheet.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/ConfigureEndpointSheet.tsx @@ -2,8 +2,12 @@ import { conform, useForm } from "@conform-to/react"; import { parse } from "@conform-to/zod"; import { useFetcher, useRevalidator } from "@remix-run/react"; import { useEffect } from "react"; -import { useEventSource } from "remix-utils"; +import { useEventSource } from "remix-utils/sse/react"; import { InlineCode } from "~/components/code/InlineCode"; +import { + EndpointIndexStatusIcon, + EndpointIndexStatusLabel, +} from "~/components/environments/EndpointIndexStatus"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; import { Button } from "~/components/primitives/Buttons"; import { Callout, CalloutVariant } from "~/components/primitives/Callout"; @@ -20,12 +24,6 @@ import { ClientEndpoint } from "~/presenters/EnvironmentsPresenter.server"; import { endpointStreamingPath } from "~/utils/pathBuilder"; import { EndpointIndexStatus, RuntimeEnvironmentType } from "../../../../../packages/database/src"; import { bodySchema } from "../resources.environments.$environmentParam.endpoint"; -import { - EndpointIndexStatusIcon, - EndpointIndexStatusLabel, - endpointIndexStatusTitle, -} from "~/components/environments/EndpointIndexStatus"; -import { CodeBlock } from "~/components/code/CodeBlock"; type ConfigureEndpointSheetProps = { slug: string; @@ -39,7 +37,7 @@ export function ConfigureEndpointSheet({ slug, endpoint, onClose }: ConfigureEnd const [form, { url, clientSlug }] = useForm({ id: "endpoint-url", - lastSubmission: setEndpointUrlFetcher.data, + lastSubmission: setEndpointUrlFetcher.data as any, onValidate({ formData }) { return parse(formData, { schema: bodySchema }); }, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx index fa24a3fae..5e3831689 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx @@ -40,7 +40,8 @@ export function FirstEndpointSheet({ projectId, environments }: FirstEndpointShe const setEndpointUrlFetcher = useFetcher(); const [form, { url, environmentId }] = useForm({ id: "new-endpoint-url", - lastSubmission: setEndpointUrlFetcher.data, + // TODO: type this + lastSubmission: setEndpointUrlFetcher.data as any, onValidate({ formData }) { return parse(formData, { schema: bodySchema }); }, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx index 0f22d6869..8be34a8d7 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx @@ -1,8 +1,8 @@ import { useRevalidator } from "@remix-run/react"; -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { useEffect, useMemo, useState } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; -import { useEventSource } from "remix-utils"; +import { useEventSource } from "remix-utils/sse/react"; import { EndpointIndexStatusIcon, EndpointIndexStatusLabel, @@ -46,7 +46,7 @@ import { ConfigureEndpointSheet } from "./ConfigureEndpointSheet"; import { FirstEndpointSheet } from "./FirstEndpointSheet"; import { RegenerateApiKeyModal } from "~/components/environments/RegenerateApiKeyModal"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { projectParam } = ProjectParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route.tsx index f450aef11..8a9a1c476 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route.tsx @@ -1,5 +1,5 @@ import { ChevronRightIcon } from "@heroicons/react/24/solid"; -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { useCallback, useState } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { Feedback } from "~/components/Feedback"; @@ -56,7 +56,7 @@ import { integrationClientPath, } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); const { organizationSlug, projectParam } = ProjectParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam._index/route.tsx index 35590ffa6..46bcdb351 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam._index/route.tsx @@ -1,4 +1,4 @@ -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { HowToUseThisIntegration } from "~/components/helpContent/HelpContentText"; import { JobSkeleton } from "~/components/jobs/JobSkeleton"; @@ -21,7 +21,7 @@ import { trimTrailingSlash, } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { organizationSlug, projectParam, clientParam } = IntegrationClientParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.connections/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.connections/route.tsx index 7498ae1af..b94e29e1d 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.connections/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.connections/route.tsx @@ -1,4 +1,4 @@ -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { connectionType } from "~/components/integrations/connectionType"; import { BreadcrumbLink } from "~/components/navigation/NavBar"; @@ -18,7 +18,7 @@ import { requireUserId } from "~/services/session.server"; import { Handle } from "~/utils/handle"; import { IntegrationClientParamSchema, trimTrailingSlash } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { organizationSlug, projectParam, clientParam } = IntegrationClientParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.scopes/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.scopes/route.tsx index 58d64fcdc..32a3ed0f3 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.scopes/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.scopes/route.tsx @@ -1,4 +1,4 @@ -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { BreadcrumbLink } from "~/components/navigation/NavBar"; import { Paragraph } from "~/components/primitives/Paragraph"; @@ -7,7 +7,7 @@ import { requireUserId } from "~/services/session.server"; import { Handle } from "~/utils/handle"; import { IntegrationClientParamSchema, trimTrailingSlash } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { organizationSlug, projectParam, clientParam } = IntegrationClientParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route.tsx index d38c3b48a..f78cb2818 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route.tsx @@ -1,5 +1,5 @@ import { Outlet } from "@remix-run/react"; -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { connectionType } from "~/components/integrations/connectionType"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; @@ -29,7 +29,7 @@ import { projectIntegrationsPath, } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); const { organizationSlug, clientParam, projectParam } = IntegrationClientParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx index d41446388..17dcd7ab4 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx @@ -1,5 +1,5 @@ import { useNavigation } from "@remix-run/react"; -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { HowToRunYourJob } from "~/components/helpContent/HelpContentText"; @@ -30,7 +30,7 @@ export const RunListSearchSchema = z.object({ direction: DirectionSchema.optional(), }); -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { jobParam, projectParam, organizationSlug } = JobParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.stream/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.stream/route.tsx index 8a69484ab..287ba86a4 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.stream/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.stream/route.tsx @@ -1,9 +1,9 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server"; import { requireUserId } from "~/services/session.server"; -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { await requireUserId(request); const { runParam } = z.object({ runParam: z.string() }).parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route.tsx index a02bf3ace..52538a357 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route.tsx @@ -1,5 +1,5 @@ import { Await, useLoaderData } from "@remix-run/react"; -import { LoaderArgs, SerializeFrom, defer } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs, SerializeFrom, defer } from "@remix-run/server-runtime"; import { Suspense } from "react"; import { Spinner } from "~/components/primitives/Spinner"; import { TaskDetail } from "~/components/run/TaskDetail"; @@ -7,7 +7,7 @@ import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server"; import { requireUserId } from "~/services/session.server"; import { TaskParamsSchema } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { taskParam } = TaskParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.trigger/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.trigger/route.tsx index dcdd2b7e6..7f86d26b7 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.trigger/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.trigger/route.tsx @@ -1,4 +1,4 @@ -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { TriggerDetail } from "~/components/run/TriggerDetail"; import { useJob } from "~/hooks/useJob"; @@ -6,7 +6,7 @@ import { useRun } from "~/hooks/useRun"; import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server"; import { RunParamsSchema } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const { runParam } = RunParamsSchema.parse(params); const presenter = new TriggerDetailsPresenter(); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx index 230a3b857..a2bdb1fd2 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx @@ -1,8 +1,8 @@ import { useRevalidator } from "@remix-run/react"; -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { Fragment, useEffect } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; -import { useEventSource } from "remix-utils"; +import { useEventSource } from "remix-utils/sse/react"; import { BreadcrumbLink } from "~/components/navigation/NavBar"; import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon"; import { RunOverview } from "~/components/run/RunOverview"; @@ -22,7 +22,7 @@ import { trimTrailingSlash, } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { runParam } = RunParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx index d109230a3..73640b5a1 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx @@ -3,7 +3,7 @@ import { parse } from "@conform-to/zod"; import { ClipboardIcon } from "@heroicons/react/20/solid"; import { ClockIcon, CodeBracketIcon } from "@heroicons/react/24/outline"; import { Form, useActionData, useSubmit } from "@remix-run/react"; -import { ActionFunction, LoaderArgs, json } from "@remix-run/server-runtime"; +import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { useCallback, useRef, useState } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; @@ -38,7 +38,7 @@ import { Handle } from "~/utils/handle"; import { isValidIcon } from "~/utils/icon"; import { JobParamsSchema, jobRunDashboardPath, trimTrailingSlash } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { organizationSlug, projectParam, jobParam } = JobParamsSchema.parse(params); @@ -166,7 +166,8 @@ export default function Page() { const [form, { environmentId, payload, accountId }] = useForm({ id: "test-job", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema }); }, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx index 166ab1e74..72af5ee25 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx @@ -1,5 +1,5 @@ import { Outlet, useLocation } from "@remix-run/react"; -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { Fragment } from "react"; import { typedjson } from "remix-typedjson"; import { JobStatusBadge } from "~/components/jobs/JobStatusBadge"; @@ -37,7 +37,7 @@ import { trimTrailingSlash, } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { jobParam, projectParam, organizationSlug } = JobParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx index e9258e41f..c1c27be1e 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx @@ -1,5 +1,5 @@ import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid"; -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; import { BreadcrumbLink } from "~/components/navigation/NavBar"; @@ -25,7 +25,7 @@ import { cn } from "~/utils/cn"; import { Handle } from "~/utils/handle"; import { ProjectParamSchema, externalTriggerPath, trimTrailingSlash } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); const { organizationSlug, projectParam } = ProjectParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx index 3cd289c2c..02cfaa55d 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx @@ -1,6 +1,6 @@ import { NoSymbolIcon } from "@heroicons/react/20/solid"; import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid"; -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; import { BreadcrumbLink } from "~/components/navigation/NavBar"; @@ -25,7 +25,7 @@ import { requireUser } from "~/services/session.server"; import { Handle } from "~/utils/handle"; import { ProjectParamSchema, docsPath, trimTrailingSlash } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); const { organizationSlug, projectParam } = ProjectParamSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx index ee1d51135..fcfef7cb7 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx @@ -1,5 +1,5 @@ -import { Response, json } from "@remix-run/node"; -import type { ActionFunction, LoaderArgs } from "@remix-run/server-runtime"; +import { json } from "@remix-run/node"; +import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/server-runtime"; import { Fragment } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; @@ -45,7 +45,7 @@ import { ActivateSourceService } from "~/services/sources/activateSource.server" import { redirectWithSuccessMessage } from "~/models/message.server"; import { nanoid } from "nanoid"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); const { organizationSlug, projectParam, triggerParam } = TriggerSourceParamSchema.parse(params); @@ -135,7 +135,8 @@ export default function Page() { const [form, { jobId }] = useForm({ id: "trigger-registration-retry", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema }); }, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.stream/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.stream/route.tsx index 8a69484ab..287ba86a4 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.stream/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.stream/route.tsx @@ -1,9 +1,9 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server"; import { requireUserId } from "~/services/session.server"; -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { await requireUserId(request); const { runParam } = z.object({ runParam: z.string() }).parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx index bac7cb824..1e07e6deb 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.tasks.$taskParam/route.tsx @@ -1,5 +1,5 @@ import { Await, useLoaderData } from "@remix-run/react"; -import { LoaderArgs, defer } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs, defer } from "@remix-run/server-runtime"; import { Suspense } from "react"; import { Spinner } from "~/components/primitives/Spinner"; import { TaskDetail } from "~/components/run/TaskDetail"; @@ -7,7 +7,7 @@ import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server"; import { requireUserId } from "~/services/session.server"; import { TriggerSourceRunTaskParamsSchema } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { taskParam } = TriggerSourceRunTaskParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.trigger/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.trigger/route.tsx index 89298f9f5..4dc25127d 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.trigger/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam.trigger/route.tsx @@ -1,10 +1,10 @@ -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { TriggerDetail } from "~/components/run/TriggerDetail"; import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server"; import { TriggerSourceRunParamsSchema } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const { runParam } = TriggerSourceRunParamsSchema.parse(params); const presenter = new TriggerDetailsPresenter(); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx index 557b674c9..0e14db661 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx @@ -1,8 +1,8 @@ import { useRevalidator } from "@remix-run/react"; -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { Fragment, useEffect } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; -import { useEventSource } from "remix-utils"; +import { useEventSource } from "remix-utils/sse/react"; import { BreadcrumbLink } from "~/components/navigation/NavBar"; import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon"; import { RunOverview } from "~/components/run/RunOverview"; @@ -23,7 +23,7 @@ import { trimTrailingSlash, } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { runParam, triggerParam } = TriggerSourceRunParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx index 4a697ff3f..45802ba25 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx @@ -1,5 +1,5 @@ import { Outlet } from "@remix-run/react"; -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson } from "remix-typedjson"; import invariant from "tiny-invariant"; import { RouteErrorDisplay } from "~/components/ErrorDisplay"; @@ -13,7 +13,7 @@ import { requireUserId } from "~/services/session.server"; import { Handle } from "~/utils/handle"; import { projectPath } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { projectParam } = params; invariant(projectParam, "projectParam not found"); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.new/route.tsx index 199d16b2c..4c54bfd00 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.new/route.tsx @@ -1,7 +1,7 @@ import { conform, useForm } from "@conform-to/react"; import { parse } from "@conform-to/zod"; import type { ActionFunction } from "@remix-run/node"; -import { json, redirect } from "@remix-run/node"; +import { json } from "@remix-run/node"; import { Form, useActionData } from "@remix-run/react"; import invariant from "tiny-invariant"; import { z } from "zod"; @@ -11,7 +11,6 @@ import { Fieldset } from "~/components/primitives/Fieldset"; import { FormButtons } from "~/components/primitives/FormButtons"; import { FormError } from "~/components/primitives/FormError"; import { FormTitle } from "~/components/primitives/FormTitle"; -import { Hint } from "~/components/primitives/Hint"; import { Input } from "~/components/primitives/Input"; import { InputGroup } from "~/components/primitives/InputGroup"; import { Label } from "~/components/primitives/Label"; @@ -60,7 +59,8 @@ export default function NewOrganizationPage() { const [form, { projectName }] = useForm({ id: "create-project", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema }); }, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx index 8d19ed9a4..56d23cb92 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx @@ -2,7 +2,7 @@ import { useForm } from "@conform-to/react"; import { parse } from "@conform-to/zod"; import { UserPlusIcon } from "@heroicons/react/20/solid"; import { Form, useActionData } from "@remix-run/react"; -import { ActionFunction, LoaderArgs, json } from "@remix-run/server-runtime"; +import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { useState } from "react"; import { UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson"; import invariant from "tiny-invariant"; @@ -33,7 +33,7 @@ import { inviteTeamMemberPath, organizationTeamPath, resendInvitePath } from "~/ import { OrgAdminHeader } from "../_app.orgs.$organizationSlug._index/OrgAdminHeader"; import { DateTime } from "~/components/primitives/DateTime"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { organizationSlug } = params; invariant(organizationSlug, "organizationSlug not found"); @@ -235,7 +235,8 @@ function LeaveTeamModal({ const [form, { memberId }] = useForm({ id: "remove-member", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema }); }, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug/route.tsx index 48877d70e..8e71cdbf2 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug/route.tsx @@ -1,6 +1,6 @@ import type { ShouldRevalidateFunction } from "@remix-run/react"; import { Outlet } from "@remix-run/react"; -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson } from "remix-typedjson"; import invariant from "tiny-invariant"; import { RouteErrorDisplay } from "~/components/ErrorDisplay"; @@ -11,7 +11,7 @@ import { commitCurrentOrgSession, setCurrentOrg } from "~/services/currentOrgani import { requireUserId } from "~/services/session.server"; import { organizationPath } from "~/utils/pathBuilder"; -export const loader = async ({ request, params }: LoaderArgs) => { +export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { organizationSlug } = params; invariant(organizationSlug, "organizationSlug not found"); diff --git a/apps/webapp/app/routes/_app.orgs.new/route.tsx b/apps/webapp/app/routes/_app.orgs.new/route.tsx index 9442bacdc..82db15a5d 100644 --- a/apps/webapp/app/routes/_app.orgs.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.new/route.tsx @@ -56,7 +56,8 @@ export default function NewOrganizationPage() { const [form, { orgName, projectName }] = useForm({ id: "create-organization", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema }); }, diff --git a/apps/webapp/app/routes/_app/route.tsx b/apps/webapp/app/routes/_app/route.tsx index 582008cb4..d945f5118 100644 --- a/apps/webapp/app/routes/_app/route.tsx +++ b/apps/webapp/app/routes/_app/route.tsx @@ -1,5 +1,5 @@ import { Outlet, ShouldRevalidateFunction } from "@remix-run/react"; -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson"; import { RouteErrorDisplay } from "~/components/ErrorDisplay"; import { ImpersonationBanner } from "~/components/ImpersonationBanner"; @@ -13,7 +13,7 @@ import { clearRedirectTo, commitSession } from "~/services/redirectTo.server"; import { requireUser } from "~/services/session.server"; import { confirmBasicDetailsPath } from "~/utils/pathBuilder"; -export const loader = async ({ request }: LoaderArgs) => { +export const loader = async ({ request }: LoaderFunctionArgs) => { const user = await requireUser(request); const organizations = await getOrganizations({ userId: user.id }); const impersonationId = await getImpersonationId(request); diff --git a/apps/webapp/app/routes/admin._index.tsx b/apps/webapp/app/routes/admin._index.tsx index 419a5e755..6b382c8d3 100644 --- a/apps/webapp/app/routes/admin._index.tsx +++ b/apps/webapp/app/routes/admin._index.tsx @@ -1,5 +1,5 @@ import { Form } from "@remix-run/react"; -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { redirect } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; @@ -15,7 +15,7 @@ export async function loader() { const FormSchema = z.object({ id: z.string() }); -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { if (request.method.toLowerCase() !== "post") { return new Response("Method not allowed", { status: 405 }); } diff --git a/apps/webapp/app/routes/admin.api.v1.simulate.ts b/apps/webapp/app/routes/admin.api.v1.simulate.ts index 2b7887175..8ab1bf482 100644 --- a/apps/webapp/app/routes/admin.api.v1.simulate.ts +++ b/apps/webapp/app/routes/admin.api.v1.simulate.ts @@ -1,9 +1,9 @@ -import { ActionArgs, json, redirect } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json, redirect } from "@remix-run/server-runtime"; import { prisma } from "~/db.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; import { workerQueue } from "~/services/worker.server"; -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { // Next authenticate the request const authenticationResult = await authenticateApiRequest(request); diff --git a/apps/webapp/app/routes/admin.tsx b/apps/webapp/app/routes/admin.tsx index 97091a28d..ba22cba05 100644 --- a/apps/webapp/app/routes/admin.tsx +++ b/apps/webapp/app/routes/admin.tsx @@ -1,10 +1,10 @@ import { HomeIcon } from "@heroicons/react/24/outline"; import { Outlet } from "@remix-run/react"; -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson"; import { getUser, requireUserId } from "~/services/session.server"; -export async function loader({ request }: LoaderArgs) { +export async function loader({ request }: LoaderFunctionArgs) { await requireUserId(request); const user = await getUser(request); if (user == null) { diff --git a/apps/webapp/app/routes/api.internal.stripe_webhooks.ts b/apps/webapp/app/routes/api.internal.stripe_webhooks.ts index 53c579821..3515409dc 100644 --- a/apps/webapp/app/routes/api.internal.stripe_webhooks.ts +++ b/apps/webapp/app/routes/api.internal.stripe_webhooks.ts @@ -1,4 +1,4 @@ -import { ActionArgs } from "@remix-run/server-runtime"; +import { ActionFunctionArgs } from "@remix-run/server-runtime"; /* To use this route, use the stripe CLI to forward events to this route: @@ -9,7 +9,7 @@ import { ActionArgs } from "@remix-run/server-runtime"; stripe trigger price.created */ -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { const body: any = await request.json(); const response = await fetch("https://jsonhero.io/api/create.json", { diff --git a/apps/webapp/app/routes/api.v1.$endpointSlug.auth.$id.ts b/apps/webapp/app/routes/api.v1.$endpointSlug.auth.$id.ts index 140c1940a..c90d61795 100644 --- a/apps/webapp/app/routes/api.v1.$endpointSlug.auth.$id.ts +++ b/apps/webapp/app/routes/api.v1.$endpointSlug.auth.$id.ts @@ -1,4 +1,4 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; @@ -11,7 +11,7 @@ const ParamsSchema = z.object({ id: z.string(), }); -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { logger.info("Fetching auth", { url: request.url }); const parsedParams = ParamsSchema.safeParse(params); diff --git a/apps/webapp/app/routes/api.v1.$endpointSlug.schedules.$id.registrations.$key.ts b/apps/webapp/app/routes/api.v1.$endpointSlug.schedules.$id.registrations.$key.ts index 1db73fb53..54925ac77 100644 --- a/apps/webapp/app/routes/api.v1.$endpointSlug.schedules.$id.registrations.$key.ts +++ b/apps/webapp/app/routes/api.v1.$endpointSlug.schedules.$id.registrations.$key.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { authenticateApiRequest } from "~/services/apiAuth.server"; @@ -11,7 +11,7 @@ const ParamsSchema = z.object({ key: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { logger.info("Unregistering schedule", { url: request.url }); // Ensure this is a POST request diff --git a/apps/webapp/app/routes/api.v1.$endpointSlug.schedules.$id.registrations.ts b/apps/webapp/app/routes/api.v1.$endpointSlug.schedules.$id.registrations.ts index fbd2163ff..65f0979fa 100644 --- a/apps/webapp/app/routes/api.v1.$endpointSlug.schedules.$id.registrations.ts +++ b/apps/webapp/app/routes/api.v1.$endpointSlug.schedules.$id.registrations.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { RegisterScheduleBodySchema, RegisterScheduleResponseBodySchema } from "@trigger.dev/core"; import { z } from "zod"; @@ -11,7 +11,7 @@ const ParamsSchema = z.object({ id: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { logger.info("Initializing schedule", { url: request.url }); // Ensure this is a POST request diff --git a/apps/webapp/app/routes/api.v1.$endpointSlug.sources.$id.ts b/apps/webapp/app/routes/api.v1.$endpointSlug.sources.$id.ts index 7f115f3b4..5b7072640 100644 --- a/apps/webapp/app/routes/api.v1.$endpointSlug.sources.$id.ts +++ b/apps/webapp/app/routes/api.v1.$endpointSlug.sources.$id.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { UpdateTriggerSourceBodyV1Schema } from "@trigger.dev/core"; import { z } from "zod"; @@ -11,7 +11,7 @@ const ParamsSchema = z.object({ id: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { logger.info("Updating source", { url: request.url }); // Ensure this is a POST request diff --git a/apps/webapp/app/routes/api.v1.$endpointSlug.triggers.$id.registrations.$key.ts b/apps/webapp/app/routes/api.v1.$endpointSlug.triggers.$id.registrations.$key.ts index 3b758a6d6..c021d3b28 100644 --- a/apps/webapp/app/routes/api.v1.$endpointSlug.triggers.$id.registrations.$key.ts +++ b/apps/webapp/app/routes/api.v1.$endpointSlug.triggers.$id.registrations.$key.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { RegisterTriggerBodySchemaV1 } from "@trigger.dev/core"; import { z } from "zod"; @@ -12,7 +12,7 @@ const ParamsSchema = z.object({ key: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { logger.info("Registering trigger", { url: request.url }); // Ensure this is a POST request diff --git a/apps/webapp/app/routes/api.v1.$endpointSlug.triggers.$id.registrations.ts b/apps/webapp/app/routes/api.v1.$endpointSlug.triggers.$id.registrations.ts index bb7f19681..cd707a5c8 100644 --- a/apps/webapp/app/routes/api.v1.$endpointSlug.triggers.$id.registrations.ts +++ b/apps/webapp/app/routes/api.v1.$endpointSlug.triggers.$id.registrations.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { InitializeTriggerBodySchema } from "@trigger.dev/core"; import { z } from "zod"; @@ -11,7 +11,7 @@ const ParamsSchema = z.object({ id: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { logger.info("Initializing trigger", { url: request.url }); // Ensure this is a POST request diff --git a/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug.ts b/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug.ts index e9f8d39b6..42a155e54 100644 --- a/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug.ts +++ b/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { CreateExternalConnectionBody, @@ -16,7 +16,7 @@ const ParamsSchema = z.object({ clientSlug: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.endpointindex.$indexId.ts b/apps/webapp/app/routes/api.v1.endpointindex.$indexId.ts index fcf271b21..92c2d34ee 100644 --- a/apps/webapp/app/routes/api.v1.endpointindex.$indexId.ts +++ b/apps/webapp/app/routes/api.v1.endpointindex.$indexId.ts @@ -1,4 +1,4 @@ -import { ActionArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { EndpointIndexErrorSchema, GetEndpointIndexResponse, @@ -14,7 +14,7 @@ const ParamsSchema = z.object({ indexId: z.string(), }); -export async function loader({ request, params }: ActionArgs) { +export async function loader({ request, params }: ActionFunctionArgs) { if (request.method.toUpperCase() !== "GET") { return { status: 405, body: "Method Not Allowed" }; } diff --git a/apps/webapp/app/routes/api.v1.endpoints.$endpointSlug.index.ts b/apps/webapp/app/routes/api.v1.endpoints.$endpointSlug.index.ts index 5371b3361..7eb97ef42 100644 --- a/apps/webapp/app/routes/api.v1.endpoints.$endpointSlug.index.ts +++ b/apps/webapp/app/routes/api.v1.endpoints.$endpointSlug.index.ts @@ -1,4 +1,4 @@ -import { ActionArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; @@ -14,7 +14,7 @@ const BodySchema = z.object({ data: z.any().optional(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts index 0ccd1ed99..cfd9ac7fa 100644 --- a/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts +++ b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts @@ -1,4 +1,4 @@ -import { ActionArgs, LoaderArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { RuntimeEnvironmentType } from "@trigger.dev/database"; import { z } from "zod"; import { $transaction, PrismaClient, prisma } from "~/db.server"; @@ -12,7 +12,7 @@ const ParamsSchema = z.object({ indexHookIdentifier: z.string(), }); -export async function loader({ params }: LoaderArgs) { +export async function loader({ params }: LoaderFunctionArgs) { const parsedParams = ParamsSchema.safeParse(params); if (!parsedParams.success) { @@ -39,7 +39,7 @@ export async function loader({ params }: LoaderArgs) { }); } -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { const parsedParams = ParamsSchema.safeParse(params); if (!parsedParams.success) { diff --git a/apps/webapp/app/routes/api.v1.endpoints.ts b/apps/webapp/app/routes/api.v1.endpoints.ts index 7141279dd..3c3505b45 100644 --- a/apps/webapp/app/routes/api.v1.endpoints.ts +++ b/apps/webapp/app/routes/api.v1.endpoints.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { generateErrorMessage } from "zod-error"; @@ -11,7 +11,7 @@ const BodySchema = z.object({ id: z.string(), }); -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { logger.info("action", { url: request.url }); // Ensure this is a POST request diff --git a/apps/webapp/app/routes/api.v1.events.$eventId.cancel-runs.ts b/apps/webapp/app/routes/api.v1.events.$eventId.cancel-runs.ts index ad47ab183..da968bb3e 100644 --- a/apps/webapp/app/routes/api.v1.events.$eventId.cancel-runs.ts +++ b/apps/webapp/app/routes/api.v1.events.$eventId.cancel-runs.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { authenticateApiRequest } from "~/services/apiAuth.server"; @@ -10,7 +10,7 @@ const ParamsSchema = z.object({ eventId: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.events.$eventId.cancel.ts b/apps/webapp/app/routes/api.v1.events.$eventId.cancel.ts index 28dcdf1ca..87a74de47 100644 --- a/apps/webapp/app/routes/api.v1.events.$eventId.cancel.ts +++ b/apps/webapp/app/routes/api.v1.events.$eventId.cancel.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { eventRecordToApiJson } from "~/api.server"; @@ -10,7 +10,7 @@ const ParamsSchema = z.object({ eventId: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.events.$eventId.ts b/apps/webapp/app/routes/api.v1.events.$eventId.ts index da29947dd..213ca8626 100644 --- a/apps/webapp/app/routes/api.v1.events.$eventId.ts +++ b/apps/webapp/app/routes/api.v1.events.$eventId.ts @@ -1,4 +1,4 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { GetEvent } from "@trigger.dev/core"; import { z } from "zod"; @@ -10,7 +10,7 @@ const ParamsSchema = z.object({ eventId: z.string(), }); -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { if (request.method.toUpperCase() === "OPTIONS") { return apiCors(request, json({})); } diff --git a/apps/webapp/app/routes/api.v1.events.ts b/apps/webapp/app/routes/api.v1.events.ts index 403138bc7..5fe2b960c 100644 --- a/apps/webapp/app/routes/api.v1.events.ts +++ b/apps/webapp/app/routes/api.v1.events.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { SendEventBodySchema } from "@trigger.dev/core"; import { generateErrorMessage } from "zod-error"; @@ -6,7 +6,7 @@ import { authenticateApiRequest } from "~/services/apiAuth.server"; import { IngestSendEvent } from "~/services/events/ingestSendEvent.server"; import { eventRecordToApiJson } from "~/api.server"; -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.integrations.$integrationSlug.connections.$connectionId.ts b/apps/webapp/app/routes/api.v1.integrations.$integrationSlug.connections.$connectionId.ts index e2f514423..4ad3ec535 100644 --- a/apps/webapp/app/routes/api.v1.integrations.$integrationSlug.connections.$connectionId.ts +++ b/apps/webapp/app/routes/api.v1.integrations.$integrationSlug.connections.$connectionId.ts @@ -1,4 +1,4 @@ -import { LoaderArgs, json } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; import { resolveApiConnection } from "~/models/runConnection.server"; @@ -10,7 +10,7 @@ const ParamsSchema = z.object({ connectionId: z.string(), }); -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { const authenticationResult = await authenticateApiRequest(request); if (!authenticationResult) { return json({ error: "Invalid or Missing API key" }, { status: 401 }); diff --git a/apps/webapp/app/routes/api.v1.jobs.$jobSlug.runs.ts b/apps/webapp/app/routes/api.v1.jobs.$jobSlug.runs.ts index dd79a3a41..99b028454 100644 --- a/apps/webapp/app/routes/api.v1.jobs.$jobSlug.runs.ts +++ b/apps/webapp/app/routes/api.v1.jobs.$jobSlug.runs.ts @@ -1,4 +1,4 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; @@ -14,7 +14,7 @@ const SearchQuerySchema = z.object({ take: z.coerce.number().default(20), }); -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { if (request.method.toUpperCase() === "OPTIONS") { return apiCors(request, json({})); } diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.cancel.ts b/apps/webapp/app/routes/api.v1.runs.$runId.cancel.ts index 876d22d84..0371bcefc 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.cancel.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.cancel.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { PrismaErrorSchema } from "~/db.server"; import { z } from "zod"; @@ -10,7 +10,7 @@ const ParamsSchema = z.object({ runId: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.logs.ts b/apps/webapp/app/routes/api.v1.runs.$runId.logs.ts index 04c687c9d..364efe511 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.logs.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.logs.ts @@ -1,5 +1,5 @@ import type { Organization, RuntimeEnvironment } from "@trigger.dev/database"; -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import type { LogMessage } from "@trigger.dev/core"; import { LogMessageSchema } from "@trigger.dev/core"; @@ -13,7 +13,7 @@ const ParamsSchema = z.object({ runId: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts index ca8bf9fb2..585ec5733 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { JobRunStatusRecordSchema, @@ -19,7 +19,7 @@ const ParamsSchema = z.object({ id: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "PUT") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts index f7e6351de..c3dfada96 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts @@ -1,4 +1,4 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { JobRunStatusRecordSchema } from "@trigger.dev/core"; import { z } from "zod"; @@ -13,7 +13,7 @@ const ParamsSchema = z.object({ const RecordsSchema = z.array(JobRunStatusRecordSchema); -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { if (request.method.toUpperCase() === "OPTIONS") { return apiCors(request, json({})); } diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts index 5c1425e2a..9acf5e3ea 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { RuntimeEnvironmentType } from "@trigger.dev/database"; import { z } from "zod"; @@ -12,7 +12,7 @@ const ParamsSchema = z.object({ secret: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts index c84a7af3d..0f26f46e4 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import type { CompleteTaskBodyOutput, ServerTask } from "@trigger.dev/core"; import { CompleteTaskBodyInputSchema } from "@trigger.dev/core"; @@ -14,7 +14,7 @@ const ParamsSchema = z.object({ id: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail.ts index 56bef530e..a9ac8ca02 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { FailTaskBodyInput, FailTaskBodyInputSchema, ServerTask } from "@trigger.dev/core"; import { z } from "zod"; @@ -14,7 +14,7 @@ const ParamsSchema = z.object({ id: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts index e19796b18..47d11db10 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { TaskStatus } from "@trigger.dev/database"; import { @@ -28,7 +28,7 @@ const HeadersSchema = z.object({ "x-cached-tasks-cursor": z.string().optional().nullable(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.ts b/apps/webapp/app/routes/api.v1.runs.$runId.ts index 12fbaffc9..ebdfbf365 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.ts @@ -1,4 +1,4 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { z } from "zod"; import { ApiRunPresenter } from "~/presenters/ApiRunPresenter.server"; @@ -17,7 +17,7 @@ const SearchQuerySchema = z.object({ taskdetails: z.coerce.boolean().default(false), }); -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { if (request.method.toUpperCase() === "OPTIONS") { return apiCors(request, json({})); } diff --git a/apps/webapp/app/routes/api.v1.sources.http.$id.ts b/apps/webapp/app/routes/api.v1.sources.http.$id.ts index c89a0a891..920a3bef0 100644 --- a/apps/webapp/app/routes/api.v1.sources.http.$id.ts +++ b/apps/webapp/app/routes/api.v1.sources.http.$id.ts @@ -1,9 +1,9 @@ -import type { ActionArgs, LoaderArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { logger } from "~/services/logger.server"; import { HandleHttpSourceService } from "~/services/sources/handleHttpSource.server"; -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { logger.info("Handling http source", { url: request.url }); try { @@ -26,7 +26,7 @@ export async function action({ request, params }: ActionArgs) { } } -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { logger.info("Handling http source", { url: request.url }); const { id } = z.object({ id: z.string() }).parse(params); diff --git a/apps/webapp/app/routes/api.v1.whoami.ts b/apps/webapp/app/routes/api.v1.whoami.ts index 623e356af..0ebb70b44 100644 --- a/apps/webapp/app/routes/api.v1.whoami.ts +++ b/apps/webapp/app/routes/api.v1.whoami.ts @@ -1,9 +1,9 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { prisma } from "~/db.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; -export async function loader({ request }: LoaderArgs) { +export async function loader({ request }: LoaderFunctionArgs) { // Next authenticate the request const authenticationResult = await authenticateApiRequest(request); diff --git a/apps/webapp/app/routes/api.v2.$endpointSlug.sources.$id.ts b/apps/webapp/app/routes/api.v2.$endpointSlug.sources.$id.ts index 323e3d5ed..40d576111 100644 --- a/apps/webapp/app/routes/api.v2.$endpointSlug.sources.$id.ts +++ b/apps/webapp/app/routes/api.v2.$endpointSlug.sources.$id.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { UpdateTriggerSourceBodyV2Schema } from "@trigger.dev/core"; import { z } from "zod"; @@ -11,7 +11,7 @@ const ParamsSchema = z.object({ id: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { logger.info("Updating source", { url: request.url }); // Ensure this is a POST request diff --git a/apps/webapp/app/routes/api.v2.$endpointSlug.triggers.$id.registrations.$key.ts b/apps/webapp/app/routes/api.v2.$endpointSlug.triggers.$id.registrations.$key.ts index 207f6dcce..2cb689e77 100644 --- a/apps/webapp/app/routes/api.v2.$endpointSlug.triggers.$id.registrations.$key.ts +++ b/apps/webapp/app/routes/api.v2.$endpointSlug.triggers.$id.registrations.$key.ts @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import { REGISTER_SOURCE_EVENT_V2, @@ -17,7 +17,7 @@ const ParamsSchema = z.object({ key: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { logger.info("Registering trigger", { url: request.url }); // Ensure this is a POST request diff --git a/apps/webapp/app/routes/confirm-basic-details.tsx b/apps/webapp/app/routes/confirm-basic-details.tsx index 305151354..14c9c7d31 100644 --- a/apps/webapp/app/routes/confirm-basic-details.tsx +++ b/apps/webapp/app/routes/confirm-basic-details.tsx @@ -127,7 +127,8 @@ export default function Page() { const [form, { name, email, confirmEmail }] = useForm({ id: "confirm-basic-details", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema: createSchema() }); }, diff --git a/apps/webapp/app/routes/invite-accept.tsx b/apps/webapp/app/routes/invite-accept.tsx index 69851316a..57c1ddff0 100644 --- a/apps/webapp/app/routes/invite-accept.tsx +++ b/apps/webapp/app/routes/invite-accept.tsx @@ -1,9 +1,9 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { getInviteFromToken } from "~/models/member.server"; import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; import { getUser } from "~/services/session.server"; -export async function loader({ request }: LoaderArgs) { +export async function loader({ request }: LoaderFunctionArgs) { const user = await getUser(request); const url = new URL(request.url); diff --git a/apps/webapp/app/routes/invites.tsx b/apps/webapp/app/routes/invites.tsx index 43006e992..36be94d78 100644 --- a/apps/webapp/app/routes/invites.tsx +++ b/apps/webapp/app/routes/invites.tsx @@ -1,6 +1,6 @@ import { conform, useForm } from "@conform-to/react"; import { parse } from "@conform-to/zod"; -import { ActionFunction, LoaderArgs, json, redirect } from "@remix-run/node"; +import { ActionFunction, LoaderFunctionArgs, json, redirect } from "@remix-run/node"; import { Form, useActionData } from "@remix-run/react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; @@ -18,7 +18,7 @@ import { redirectWithSuccessMessage } from "~/models/message.server"; import { requireUser, requireUserId } from "~/services/session.server"; import { invitesPath, organizationsPath } from "~/utils/pathBuilder"; -export const loader = async ({ request }: LoaderArgs) => { +export const loader = async ({ request }: LoaderFunctionArgs) => { const user = await requireUser(request); //if there are no invites left we should redirect to the orgs page @@ -94,7 +94,8 @@ export default function Page() { const [form, { inviteId }] = useForm({ id: "accept-invite", - lastSubmission, + // TODO: type this + lastSubmission: lastSubmission as any, onValidate({ formData }) { return parse(formData, { schema }); }, diff --git a/apps/webapp/app/routes/login._index/route.tsx b/apps/webapp/app/routes/login._index/route.tsx index ed85ab189..43934cde9 100644 --- a/apps/webapp/app/routes/login._index/route.tsx +++ b/apps/webapp/app/routes/login._index/route.tsx @@ -1,6 +1,15 @@ -import type { LoaderArgs } from "@remix-run/node"; +import type { DataFunctionArgs, LoaderFunctionArgs, SerializeFrom } from "@remix-run/node"; import { Form } from "@remix-run/react"; -import { TypedMetaFunction, redirect, typedjson, useTypedLoaderData } from "remix-typedjson"; +import { ServerRuntimeMetaArgs, ServerRuntimeMetaDescriptor } from "@remix-run/server-runtime"; +import { getMatchesData, metaV1 } from "@remix-run/v1-meta"; +import { + TypedJsonResponse, + TypedMetaFunction, + UseDataFunctionReturn, + redirect, + typedjson, + useTypedLoaderData, +} from "remix-typedjson"; import { LogoIcon } from "~/components/LogoIcon"; import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout"; import { Button, LinkButton } from "~/components/primitives/Buttons"; @@ -16,11 +25,19 @@ import { getUserId } from "~/services/session.server"; import { appEnvTitleTag } from "~/utils"; import { requestUrl } from "~/utils/requestUrl.server"; -export const meta: TypedMetaFunction = ({ parentsData }) => ({ - title: `Login to Trigger.dev${appEnvTitleTag(parentsData?.root.appEnv)}`, -}); +export const meta: TypedMetaFunction = (args) => { + const matchesData = getMatchesData(args) as { root: UseDataFunctionReturn }; -export async function loader({ request }: LoaderArgs) { + return metaV1(args, { + title: `Login to Trigger.dev${appEnvTitleTag(matchesData.root.appEnv)}`, + }); +}; + +export type PromiseReturnType Promise> = Awaited< + ReturnType +>; + +export async function loader({ request }: LoaderFunctionArgs) { const userId = await getUserId(request); if (userId) return redirect("/"); diff --git a/apps/webapp/app/routes/login.magic/route.tsx b/apps/webapp/app/routes/login.magic/route.tsx index 5bcb380b3..6358ada71 100644 --- a/apps/webapp/app/routes/login.magic/route.tsx +++ b/apps/webapp/app/routes/login.magic/route.tsx @@ -1,7 +1,12 @@ -import type { ActionArgs, LoaderArgs } from "@remix-run/node"; +import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node"; import { redirect } from "@remix-run/node"; -import { Form, useNavigation, useTransition } from "@remix-run/react"; -import { TypedMetaFunction, typedjson, useTypedLoaderData } from "remix-typedjson"; +import { Form, useNavigation } from "@remix-run/react"; +import { + TypedMetaFunction, + UseDataFunctionReturn, + typedjson, + useTypedLoaderData, +} from "remix-typedjson"; import { z } from "zod"; import { LogoIcon } from "~/components/LogoIcon"; import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout"; @@ -21,12 +26,17 @@ import type { LoaderType as RootLoader } from "~/root"; import { appEnvTitleTag } from "~/utils"; import { TextLink } from "~/components/primitives/TextLink"; import { FormError } from "~/components/primitives/FormError"; +import { getMatchesData, metaV1 } from "@remix-run/v1-meta"; -export const meta: TypedMetaFunction = ({ parentsData }) => ({ - title: `Login to Trigger.dev${appEnvTitleTag(parentsData?.root.appEnv)}`, -}); +export const meta: TypedMetaFunction = (args) => { + const matchesData = getMatchesData(args) as { root: UseDataFunctionReturn }; -export async function loader({ request }: LoaderArgs) { + return metaV1(args, { + title: `Login to Trigger.dev${appEnvTitleTag(matchesData.root.appEnv)}`, + }); +}; + +export async function loader({ request }: LoaderFunctionArgs) { await authenticator.isAuthenticated(request, { successRedirect: "/", }); @@ -54,7 +64,7 @@ export async function loader({ request }: LoaderArgs) { ); } -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { const clonedRequest = request.clone(); const payload = Object.fromEntries(await clonedRequest.formData()); diff --git a/apps/webapp/app/routes/magic.tsx b/apps/webapp/app/routes/magic.tsx index 6a65b5cc9..d41575357 100644 --- a/apps/webapp/app/routes/magic.tsx +++ b/apps/webapp/app/routes/magic.tsx @@ -1,8 +1,8 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { authenticator } from "~/services/auth.server"; import { getRedirectTo } from "~/services/redirectTo.server"; -export async function loader({ request }: LoaderArgs) { +export async function loader({ request }: LoaderFunctionArgs) { const redirectTo = await getRedirectTo(request); await authenticator.authenticate("email-link", request, { diff --git a/apps/webapp/app/routes/oauth2.callback.ts b/apps/webapp/app/routes/oauth2.callback.ts index 5245501c2..f53d0bfd1 100644 --- a/apps/webapp/app/routes/oauth2.callback.ts +++ b/apps/webapp/app/routes/oauth2.callback.ts @@ -1,4 +1,4 @@ -import type { LoaderArgs } from "@remix-run/server-runtime"; +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { redirect } from "@remix-run/server-runtime"; import z from "zod"; import { prisma } from "~/db.server"; @@ -17,7 +17,7 @@ const ParamsSchema = z }) .passthrough(); -export async function loader({ request }: LoaderArgs) { +export async function loader({ request }: LoaderFunctionArgs) { if (request.method.toUpperCase() !== "GET") { return { status: 405, body: "Method Not Allowed" }; } diff --git a/apps/webapp/app/routes/resources.apivote.$identifier.ts b/apps/webapp/app/routes/resources.apivote.$identifier.ts index a1a1cd5a5..1a471f6e5 100644 --- a/apps/webapp/app/routes/resources.apivote.$identifier.ts +++ b/apps/webapp/app/routes/resources.apivote.$identifier.ts @@ -1,4 +1,4 @@ -import { ActionArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { ApiVoteService } from "~/services/apiVote.server"; import { requireUserId } from "~/services/session.server"; @@ -7,7 +7,7 @@ const ParamsSchema = z.object({ identifier: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { const userId = await requireUserId(request); const { identifier } = ParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/resources.connection.$organizationId.oauth2.$integrationId.ts b/apps/webapp/app/routes/resources.connection.$organizationId.oauth2.$integrationId.ts index 179460218..396b8f575 100644 --- a/apps/webapp/app/routes/resources.connection.$organizationId.oauth2.$integrationId.ts +++ b/apps/webapp/app/routes/resources.connection.$organizationId.oauth2.$integrationId.ts @@ -1,6 +1,6 @@ import { conform } from "@conform-to/react"; import { parse } from "@conform-to/zod"; -import { ActionArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { redirect, typedjson } from "remix-typedjson"; import z from "zod"; import { prisma } from "~/db.server"; @@ -52,7 +52,7 @@ const ParamsSchema = z.object({ integrationId: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { const userId = await requireUserId(request); if (request.method.toUpperCase() !== "PUT") { diff --git a/apps/webapp/app/routes/resources.connection.$organizationId.oauth2.ts b/apps/webapp/app/routes/resources.connection.$organizationId.oauth2.ts index b1f0d5090..a0d6bb132 100644 --- a/apps/webapp/app/routes/resources.connection.$organizationId.oauth2.ts +++ b/apps/webapp/app/routes/resources.connection.$organizationId.oauth2.ts @@ -1,6 +1,6 @@ import { conform } from "@conform-to/react"; import { parse } from "@conform-to/zod"; -import { ActionArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { redirect, typedjson } from "remix-typedjson"; import z from "zod"; import { prisma } from "~/db.server"; @@ -102,7 +102,7 @@ const ParamsSchema = z.object({ organizationId: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { const userId = await requireUserId(request); if (request.method.toUpperCase() !== "POST") { diff --git a/apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx b/apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx index d712e83ff..ea79616a4 100644 --- a/apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx +++ b/apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx @@ -1,4 +1,4 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { regenerateApiKey } from "~/models/api-key.server"; import { requireUserId } from "~/services/session.server"; @@ -9,7 +9,7 @@ const ParamsSchema = z.object({ environmentId: z.string(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { return { status: 405, body: "Method Not Allowed" }; diff --git a/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.$endpointParam.ts b/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.$endpointParam.ts index b4033d9dd..3b7ecbcd5 100644 --- a/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.$endpointParam.ts +++ b/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.$endpointParam.ts @@ -1,4 +1,4 @@ -import { ActionArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { IndexEndpointService } from "~/services/endpoints/indexEndpoint.server"; import { workerQueue } from "~/services/worker.server"; @@ -8,7 +8,7 @@ const ParamsSchema = z.object({ endpointParam: z.string(), }); -export async function action({ params }: ActionArgs) { +export async function action({ params }: ActionFunctionArgs) { const { endpointParam } = ParamsSchema.parse(params); try { diff --git a/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.stream.ts b/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.stream.ts index 24033db09..077bd2c22 100644 --- a/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.stream.ts +++ b/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.stream.ts @@ -1,10 +1,10 @@ -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; import { requireUserId } from "~/services/session.server"; import { sse } from "~/utils/sse"; -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { await requireUserId(request); const { environmentParam } = z.object({ environmentParam: z.string() }).parse(params); diff --git a/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.ts b/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.ts index 5acb60120..c95ce9c3c 100644 --- a/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.ts +++ b/apps/webapp/app/routes/resources.environments.$environmentParam.endpoint.ts @@ -1,5 +1,5 @@ import { parse } from "@conform-to/zod"; -import { ActionArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; import { @@ -17,7 +17,7 @@ export const bodySchema = z.object({ url: z.string().url("Must be a valid URL"), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { const userId = await requireUserId(request); const { environmentParam } = ParamsSchema.parse(params); diff --git a/apps/webapp/app/routes/resources.feedback.ts b/apps/webapp/app/routes/resources.feedback.ts index 7ac567436..1b32b2356 100644 --- a/apps/webapp/app/routes/resources.feedback.ts +++ b/apps/webapp/app/routes/resources.feedback.ts @@ -1,5 +1,5 @@ import { parse } from "@conform-to/zod"; -import { ActionArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { ComponentDividerSpacingSize, ComponentSpacerSize, @@ -40,7 +40,7 @@ export const schema = z.object({ message: z.string().min(1, "Must be at least 1 character"), }); -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { const user = await requireUser(request); const formData = await request.formData(); diff --git a/apps/webapp/app/routes/resources.impersonation.ts b/apps/webapp/app/routes/resources.impersonation.ts index 57f032bf4..0b671cc43 100644 --- a/apps/webapp/app/routes/resources.impersonation.ts +++ b/apps/webapp/app/routes/resources.impersonation.ts @@ -1,8 +1,8 @@ -import type { ActionArgs } from "@remix-run/server-runtime"; +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { redirect } from "remix-typedjson"; import { clearImpersonationId, commitImpersonationSession } from "~/services/impersonation.server"; -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { const session = await clearImpersonationId(request); return redirect("/admin", { diff --git a/apps/webapp/app/routes/resources.projects.$projectId.endpoint.ts b/apps/webapp/app/routes/resources.projects.$projectId.endpoint.ts index ece220cb7..262add726 100644 --- a/apps/webapp/app/routes/resources.projects.$projectId.endpoint.ts +++ b/apps/webapp/app/routes/resources.projects.$projectId.endpoint.ts @@ -1,5 +1,5 @@ import { parse } from "@conform-to/zod"; -import { ActionArgs, json } from "@remix-run/server-runtime"; +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; import { CreateEndpointError } from "~/services/endpoints/createEndpoint.server"; @@ -10,7 +10,7 @@ export const bodySchema = z.object({ url: z.string().url("Must be a valid URL"), }); -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const submission = parse(formData, { schema: bodySchema }); diff --git a/apps/webapp/app/routes/resources.projects.$projectId.jobs.stream.ts b/apps/webapp/app/routes/resources.projects.$projectId.jobs.stream.ts index 574f77bd0..d1cf39856 100644 --- a/apps/webapp/app/routes/resources.projects.$projectId.jobs.stream.ts +++ b/apps/webapp/app/routes/resources.projects.$projectId.jobs.stream.ts @@ -1,10 +1,10 @@ -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { prisma } from "~/db.server"; import { requireUserId } from "~/services/session.server"; import { sse } from "~/utils/sse"; -export async function loader({ request, params }: LoaderArgs) { +export async function loader({ request, params }: LoaderFunctionArgs) { await requireUserId(request); const { projectId } = z.object({ projectId: z.string() }).parse(params); diff --git a/apps/webapp/app/routes/tests.sse.stream.ts b/apps/webapp/app/routes/tests.sse.stream.ts index 727ea949b..9a2118759 100644 --- a/apps/webapp/app/routes/tests.sse.stream.ts +++ b/apps/webapp/app/routes/tests.sse.stream.ts @@ -1,9 +1,9 @@ -import { LoaderArgs } from "@remix-run/server-runtime"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { logger } from "~/services/logger.server"; import { sse } from "~/utils/sse"; -export async function loader({ request }: LoaderArgs) { +export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const searchParams = Object.fromEntries(url.searchParams.entries()); diff --git a/apps/webapp/app/routes/tests.sse.tsx b/apps/webapp/app/routes/tests.sse.tsx index 973b1edbf..8ee412f6c 100644 --- a/apps/webapp/app/routes/tests.sse.tsx +++ b/apps/webapp/app/routes/tests.sse.tsx @@ -1,9 +1,9 @@ import { useLoaderData } from "@remix-run/react"; -import { LoaderArgs } from "@remix-run/server-runtime"; -import { useEventSource } from "remix-utils"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; +import { useEventSource } from "remix-utils/sse/react"; import { z } from "zod"; -export async function loader({ request }: LoaderArgs) { +export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const params = Object.fromEntries(url.searchParams.entries()); diff --git a/apps/webapp/app/utils.ts b/apps/webapp/app/utils.ts index cde12d89c..5a50c6d84 100644 --- a/apps/webapp/app/utils.ts +++ b/apps/webapp/app/utils.ts @@ -1,4 +1,4 @@ -import type { RouteMatch } from "@remix-run/react"; +import type { UIMatch } from "@remix-run/react"; import { useMatches } from "@remix-run/react"; import humanizeDuration from "humanize-duration"; @@ -32,10 +32,7 @@ export function safeRedirect( * @param {string} id The route id * @returns {JSON|undefined} The router data or undefined if not found */ -export function useMatchesData( - id: string | string[], - debug: boolean = false -): RouteMatch | undefined { +export function useMatchesData(id: string | string[], debug: boolean = false): UIMatch | undefined { const matchingRoutes = useMatches(); if (debug) { @@ -48,7 +45,7 @@ export function useMatchesData( const route = paths.reduce((acc, path) => { if (acc) return acc; return matchingRoutes.find((route) => route.id === path); - }, undefined as RouteMatch | undefined); + }, undefined as UIMatch | undefined); return route; } diff --git a/apps/webapp/app/utils/apiCors.ts b/apps/webapp/app/utils/apiCors.ts index 52507b9a8..4280f1d96 100644 --- a/apps/webapp/app/utils/apiCors.ts +++ b/apps/webapp/app/utils/apiCors.ts @@ -1,4 +1,4 @@ -import { cors } from "remix-utils"; +import { cors } from "remix-utils/cors"; type CorsMethod = "GET" | "HEAD" | "PUT" | "PATCH" | "POST" | "DELETE"; diff --git a/apps/webapp/app/utils/handle.ts b/apps/webapp/app/utils/handle.ts index 3613b5c67..cd2e80c2e 100644 --- a/apps/webapp/app/utils/handle.ts +++ b/apps/webapp/app/utils/handle.ts @@ -1,4 +1,4 @@ -import { ExternalScriptsFunction } from "remix-utils"; +import { ExternalScriptsFunction } from "remix-utils/external-scripts"; import { BreadcrumbItem } from "~/components/navigation/Breadcrumb"; export type Handle = { diff --git a/apps/webapp/app/utils/sse.ts b/apps/webapp/app/utils/sse.ts index 105911d7f..ce3574a13 100644 --- a/apps/webapp/app/utils/sse.ts +++ b/apps/webapp/app/utils/sse.ts @@ -1,4 +1,4 @@ -import { eventStream } from "remix-utils"; +import { eventStream } from "remix-utils/sse/server"; import { logger } from "~/services/logger.server"; type SseProps = { diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 675b49e86..54a59d4cd 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -44,7 +44,7 @@ "@heroicons/react": "^2.0.12", "@highlight-run/node": "^3.1.0", "@highlight-run/react": "^3.2.0", - "@lezer/highlight": "^1.1.2", + "@lezer/highlight": "^1.1.6", "@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.3", "@radix-ui/react-label": "^2.0.1", @@ -54,11 +54,12 @@ "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.5", - "@remix-run/express": "1.19.2", - "@remix-run/node": "1.19.2", - "@remix-run/react": "1.19.2", - "@remix-run/serve": "1.19.2", - "@remix-run/server-runtime": "1.19.2", + "@remix-run/express": "2.1.0", + "@remix-run/node": "2.1.0", + "@remix-run/react": "2.1.0", + "@remix-run/serve": "2.1.0", + "@remix-run/server-runtime": "2.1.0", + "@remix-run/v1-meta": "^0.1.3", "@team-plain/typescript-sdk": "^2.2.0", "@trigger.dev/companyicons": "^1.5.14", "@trigger.dev/core": "workspace:*", @@ -99,8 +100,8 @@ "remix-auth": "^3.2.2", "remix-auth-email-link": "^1.4.2", "remix-auth-github": "^1.1.1", - "remix-typedjson": "~0.1.7", - "remix-utils": "^6.0.0", + "remix-typedjson": "0.3.1", + "remix-utils": "^7.1.0", "semver": "^7.5.0", "simple-oauth2": "^5.0.0", "simplur": "^3.0.1", @@ -116,9 +117,9 @@ "zod-validation-error": "^1.5.0" }, "devDependencies": { - "@remix-run/dev": "1.19.2", - "@remix-run/eslint-config": "1.19.2", - "@remix-run/testing": "^1.19.2", + "@remix-run/dev": "2.1.0", + "@remix-run/eslint-config": "2.1.0", + "@remix-run/testing": "^2.1.0", "@storybook/addon-backgrounds": "^7.0.7", "@storybook/addon-docs": "^7.0.12", "@storybook/addon-essentials": "^7.0.7", @@ -174,9 +175,9 @@ "tailwindcss": "3.3.2", "ts-node": "^10.7.0", "tsconfig-paths": "^3.14.1", - "typescript": "^4.8.4" + "typescript": "^5.1.6" }, "engines": { "node": ">=16.0.0" } -} +} \ No newline at end of file diff --git a/apps/webapp/remix.config.js b/apps/webapp/remix.config.js index 2a305281a..4885a2485 100644 --- a/apps/webapp/remix.config.js +++ b/apps/webapp/remix.config.js @@ -1,18 +1,14 @@ /** @type {import('@remix-run/dev').AppConfig} */ module.exports = { - future: { - v2_dev: true, - v2_routeConvention: true, - v2_normalizeFormMethod: true, - v2_errorBoundary: true, - // v2_meta: true, + dev: { + port: 8002, }, tailwind: true, cacheDirectory: "./node_modules/.cache/remix", ignoredRouteFiles: ["**/.*"], - devServerPort: 8002, serverModuleFormat: "cjs", serverDependenciesToBundle: [ + /^remix-utils.*/, "marked", "axios", "@trigger.dev/core", diff --git a/apps/webapp/tsconfig.json b/apps/webapp/tsconfig.json index a57cd2e32..9ca1b6269 100644 --- a/apps/webapp/tsconfig.json +++ b/apps/webapp/tsconfig.json @@ -6,7 +6,7 @@ "isolatedModules": true, "esModuleInterop": true, "jsx": "react-jsx", - "moduleResolution": "node", + "moduleResolution": "Bundler", "resolveJsonModule": true, "target": "ES2019", "strict": true, diff --git a/docs/documentation/guides/react-hooks.mdx b/docs/documentation/guides/react-hooks.mdx index 0132daae7..a7580ecb5 100644 --- a/docs/documentation/guides/react-hooks.mdx +++ b/docs/documentation/guides/react-hooks.mdx @@ -112,7 +112,7 @@ This guide assumes that your project is already setup and you have a Job running ```tsx app/root.tsx //return the public key env var from the loader so it's available in the browser - export const loader = async ({ request }: LoaderArgs) => { + export const loader = async ({ request }: LoaderFunctionArgs) => { //...other code const triggerPublicApiKey = env.TRIGGER_PUBLIC_API_KEY!; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c29a9c76f..67dac69aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,7 +69,7 @@ importers: '@heroicons/react': ^2.0.12 '@highlight-run/node': ^3.1.0 '@highlight-run/react': ^3.2.0 - '@lezer/highlight': ^1.1.2 + '@lezer/highlight': ^1.1.6 '@radix-ui/react-alert-dialog': ^1.0.4 '@radix-ui/react-dialog': ^1.0.3 '@radix-ui/react-label': ^2.0.1 @@ -79,14 +79,15 @@ importers: '@radix-ui/react-switch': ^1.0.3 '@radix-ui/react-tabs': ^1.0.3 '@radix-ui/react-tooltip': ^1.0.5 - '@remix-run/dev': 1.19.2 - '@remix-run/eslint-config': 1.19.2 - '@remix-run/express': 1.19.2 - '@remix-run/node': 1.19.2 - '@remix-run/react': 1.19.2 - '@remix-run/serve': 1.19.2 - '@remix-run/server-runtime': 1.19.2 - '@remix-run/testing': ^1.19.2 + '@remix-run/dev': 2.1.0 + '@remix-run/eslint-config': 2.1.0 + '@remix-run/express': 2.1.0 + '@remix-run/node': 2.1.0 + '@remix-run/react': 2.1.0 + '@remix-run/serve': 2.1.0 + '@remix-run/server-runtime': 2.1.0 + '@remix-run/testing': ^2.1.0 + '@remix-run/v1-meta': ^0.1.3 '@storybook/addon-backgrounds': ^7.0.7 '@storybook/addon-docs': ^7.0.12 '@storybook/addon-essentials': ^7.0.7 @@ -174,8 +175,8 @@ importers: remix-auth: ^3.2.2 remix-auth-email-link: ^1.4.2 remix-auth-github: ^1.1.1 - remix-typedjson: ~0.1.7 - remix-utils: ^6.0.0 + remix-typedjson: 0.3.1 + remix-utils: ^7.1.0 rimraf: ^3.0.2 semver: ^7.5.0 simple-oauth2: ^5.0.0 @@ -193,7 +194,7 @@ importers: tiny-invariant: ^1.2.0 ts-node: ^10.7.0 tsconfig-paths: ^3.14.1 - typescript: ^4.8.4 + typescript: ^5.1.6 ulid: ^2.3.0 zod: 3.22.3 zod-error: 1.5.0 @@ -214,7 +215,7 @@ importers: '@heroicons/react': 2.0.13_react@18.2.0 '@highlight-run/node': 3.1.0 '@highlight-run/react': 3.2.0_biqbaboplfbrettd7655fr4n2y - '@lezer/highlight': 1.1.3 + '@lezer/highlight': 1.1.6 '@radix-ui/react-alert-dialog': 1.0.4_daadhm4ohxobgnrt365as5bhny '@radix-ui/react-dialog': 1.0.3_bwbutfx4xj25dewzmxso6o3wga '@radix-ui/react-label': 2.0.1_biqbaboplfbrettd7655fr4n2y @@ -224,18 +225,19 @@ importers: '@radix-ui/react-switch': 1.0.3_daadhm4ohxobgnrt365as5bhny '@radix-ui/react-tabs': 1.0.3_biqbaboplfbrettd7655fr4n2y '@radix-ui/react-tooltip': 1.0.5_bwbutfx4xj25dewzmxso6o3wga - '@remix-run/express': 1.19.2_express@4.18.2 - '@remix-run/node': 1.19.2 - '@remix-run/react': 1.19.2_biqbaboplfbrettd7655fr4n2y - '@remix-run/serve': 1.19.2 - '@remix-run/server-runtime': 1.19.2 + '@remix-run/express': 2.1.0_fgkcria6ebvizt7e6qzdl6ei7a + '@remix-run/node': 2.1.0_typescript@5.2.2 + '@remix-run/react': 2.1.0_vegmnuvoswdxuttpuhvewcem44 + '@remix-run/serve': 2.1.0_typescript@5.2.2 + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 + '@remix-run/v1-meta': 0.1.3_ybjp5xbtg4zthziheradfmei64 '@team-plain/typescript-sdk': 2.2.0 '@trigger.dev/companyicons': 1.5.14_biqbaboplfbrettd7655fr4n2y '@trigger.dev/core': link:../../packages/core '@trigger.dev/database': link:../../packages/database '@trigger.dev/sdk': link:../../packages/trigger-sdk '@uiw/react-codemirror': 4.19.5_th22fcplkuhrqjnlojwclcaim4 - class-variance-authority: 0.5.2_typescript@4.9.4 + class-variance-authority: 0.5.2_typescript@5.2.2 clsx: 1.2.1 compression: 1.7.4 cron-parser: 4.7.1 @@ -266,11 +268,11 @@ importers: react-hotkeys-hook: 4.4.1_biqbaboplfbrettd7655fr4n2y react-use: 17.4.0_biqbaboplfbrettd7655fr4n2y recharts: 2.8.0_v2m5e27vhdewzwhryxwfaorcca - remix-auth: 3.4.0_ya7xgmpozdkweczbsj2asvumvm - remix-auth-email-link: 1.5.2_3kyxpfn573t2hovk377zjamxda - remix-auth-github: 1.3.0_3kyxpfn573t2hovk377zjamxda - remix-typedjson: 0.1.7_uyy3oyfmtzakxsapatosuzqil4 - remix-utils: 6.0.0_jtxmz5feu54a7fcxnhi2w5jyki + remix-auth: 3.4.0_ybjp5xbtg4zthziheradfmei64 + remix-auth-email-link: 1.5.2_7ym4eehrzvo4xt5yocgqipdaji + remix-auth-github: 1.3.0_7ym4eehrzvo4xt5yocgqipdaji + remix-typedjson: 0.3.1_7t5z2pmrpxrx2dh2jpp55scw5q + remix-utils: 7.1.0_nb5lmxjinjv74o775xrkc35gga semver: 7.5.0 simple-oauth2: 5.0.0 simplur: 3.0.1 @@ -285,9 +287,9 @@ importers: zod-error: 1.5.0 zod-validation-error: 1.5.0_zod@3.22.3 devDependencies: - '@remix-run/dev': 1.19.2_uvzmt2w75nvrltrkqo4r7vh6eq - '@remix-run/eslint-config': 1.19.2_ol4nhuzbuflsbzk2mijpqykzba - '@remix-run/testing': 1.19.3_biqbaboplfbrettd7655fr4n2y + '@remix-run/dev': 2.1.0_h4yxzoqkgkdcmxrorkwi6rxqz4 + '@remix-run/eslint-config': 2.1.0_tivk2qwcsqyhxoqj22f6pjbp7e + '@remix-run/testing': 2.1.0_vegmnuvoswdxuttpuhvewcem44 '@storybook/addon-backgrounds': 7.0.9_biqbaboplfbrettd7655fr4n2y '@storybook/addon-docs': 7.0.12_biqbaboplfbrettd7655fr4n2y '@storybook/addon-essentials': 7.0.9_biqbaboplfbrettd7655fr4n2y @@ -296,8 +298,8 @@ importers: '@storybook/addon-styling': 1.0.6_gjpyg5imi5mt2gbvkmn3a4dr7m '@storybook/addon-viewport': 7.0.9_biqbaboplfbrettd7655fr4n2y '@storybook/blocks': 7.0.9_biqbaboplfbrettd7655fr4n2y - '@storybook/react': 7.0.9_o4scbtliisanygemawej7x2d6i - '@storybook/react-webpack5': 7.0.9_nof6wriz76uonkhaqbbs3zfxk4 + '@storybook/react': 7.0.9_vegmnuvoswdxuttpuhvewcem44 + '@storybook/react-webpack5': 7.0.9_eunppc2ebbpop5kss6ctsxsd44 '@storybook/testing-library': 0.0.14-next.2 '@swc/core': 1.3.26 '@swc/helpers': 0.4.14 @@ -324,8 +326,8 @@ importers: '@types/simple-oauth2': 5.0.4 '@types/slug': 5.0.3 '@types/tar': 6.1.4 - '@typescript-eslint/eslint-plugin': 5.59.6_bhjpu5ld5qmewts22wsycix4mi - '@typescript-eslint/parser': 5.59.6_iukboom6ndih5an6iafl45j2fe + '@typescript-eslint/eslint-plugin': 5.59.6_7rqy3acf7ittvjpliyki2mhtka + '@typescript-eslint/parser': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq autoprefixer: 10.4.13_postcss@8.4.21 esbuild: 0.15.18 eslint: 8.31.0 @@ -341,9 +343,9 @@ importers: storybook-addon-variants: 0.2.0_biqbaboplfbrettd7655fr4n2y tailwind-scrollbar: 3.0.1_tailwindcss@3.3.2 tailwindcss: 3.3.2_ts-node@10.9.1 - ts-node: 10.9.1_fodzh64fuekdilycyvke2qmf2e + ts-node: 10.9.1_v2fyojhutw2zqp3xtxirvaa4jq tsconfig-paths: 3.14.1 - typescript: 4.9.4 + typescript: 5.2.2 config-packages/eslint-config-custom: specifiers: @@ -2026,11 +2028,6 @@ packages: - supports-color dev: true - /@babel/helper-environment-visitor/7.21.5: - resolution: {integrity: sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-environment-visitor/7.22.5: resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} engines: {node: '>=6.9.0'} @@ -2042,14 +2039,6 @@ packages: '@babel/types': 7.22.17 dev: true - /@babel/helper-function-name/7.21.0: - resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.22.15 - '@babel/types': 7.22.17 - dev: true - /@babel/helper-function-name/7.22.5: resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} engines: {node: '>=6.9.0'} @@ -2057,13 +2046,6 @@ packages: '@babel/template': 7.22.15 '@babel/types': 7.22.17 - /@babel/helper-hoist-variables/7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.17 - dev: true - /@babel/helper-hoist-variables/7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} @@ -2235,13 +2217,6 @@ packages: '@babel/types': 7.22.17 dev: true - /@babel/helper-split-export-declaration/7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.17 - dev: true - /@babel/helper-split-export-declaration/7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} @@ -3082,6 +3057,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-decorators/7.22.10_@babel+core@7.22.17: + resolution: {integrity: sha512-z1KTVemBjnz+kSEilAsI4lbkPOl5TvJH7YDSY1CTIzvLWJ+KHXp+mRe8VPmfnyvqOPqar1V2gid2PleKzRUstQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.17 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.20.12: resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} peerDependencies: @@ -5134,10 +5119,10 @@ packages: dependencies: '@babel/code-frame': 7.22.13 '@babel/generator': 7.22.15 - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-function-name': 7.21.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.22.16 '@babel/types': 7.22.17 debug: 4.3.4 @@ -5458,7 +5443,7 @@ packages: '@codemirror/state': 6.2.0 '@codemirror/view': 6.7.2 '@lezer/common': 1.0.2 - '@lezer/highlight': 1.1.3 + '@lezer/highlight': 1.1.6 '@lezer/lr': 1.3.0 style-mod: 4.0.0 dev: false @@ -5489,7 +5474,7 @@ packages: '@codemirror/language': 6.3.2 '@codemirror/state': 6.2.0 '@codemirror/view': 6.7.2 - '@lezer/highlight': 1.1.3 + '@lezer/highlight': 1.1.6 dev: false /@codemirror/view/6.7.2: @@ -7116,7 +7101,6 @@ packages: strip-ansi-cjs: /strip-ansi/6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi/7.0.0 - dev: false /@istanbuljs/load-nyc-config/1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -7462,8 +7446,8 @@ packages: resolution: {integrity: sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==} dev: false - /@lezer/highlight/1.1.3: - resolution: {integrity: sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==} + /@lezer/highlight/1.1.6: + resolution: {integrity: sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==} dependencies: '@lezer/common': 1.0.2 dev: false @@ -7471,14 +7455,14 @@ packages: /@lezer/javascript/1.4.1: resolution: {integrity: sha512-Hqx36DJeYhKtdpc7wBYPR0XF56ZzIp0IkMO/zNNj80xcaFOV4Oj/P7TQc/8k2TxNhzl7tV5tXS8ZOCPbT4L3nA==} dependencies: - '@lezer/highlight': 1.1.3 + '@lezer/highlight': 1.1.6 '@lezer/lr': 1.3.0 dev: false /@lezer/json/1.0.0: resolution: {integrity: sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==} dependencies: - '@lezer/highlight': 1.1.3 + '@lezer/highlight': 1.1.6 '@lezer/lr': 1.3.0 dev: false @@ -7555,6 +7539,30 @@ packages: globby: 11.1.0 read-yaml-file: 1.1.0 + /@mdx-js/mdx/2.3.0: + resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} + dependencies: + '@types/estree-jsx': 1.0.0 + '@types/mdx': 2.0.5 + estree-util-build-jsx: 2.2.2 + estree-util-is-identifier-name: 2.0.1 + estree-util-to-js: 1.2.0 + estree-walker: 3.0.3 + hast-util-to-estree: 2.1.0 + markdown-extensions: 1.1.1 + periscopic: 3.1.0 + remark-mdx: 2.3.0 + remark-parse: 10.0.2 + remark-rehype: 10.1.0 + unified: 10.1.2 + unist-util-position-from-estree: 1.1.1 + unist-util-stringify-position: 3.0.2 + unist-util-visit: 4.1.2 + vfile: 5.3.7 + transitivePeerDependencies: + - supports-color + dev: true + /@mdx-js/react/2.3.0_react@18.2.0: resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==} peerDependencies: @@ -8006,7 +8014,6 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: semver: 7.5.4 - dev: false /@npmcli/git/4.1.0: resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==} @@ -8022,7 +8029,6 @@ packages: which: 3.0.1 transitivePeerDependencies: - bluebird - dev: false /@npmcli/installed-package-contents/2.0.2: resolution: {integrity: sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==} @@ -8054,12 +8060,26 @@ packages: json-parse-even-better-errors: 2.3.1 dev: true + /@npmcli/package-json/4.0.1: + resolution: {integrity: sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + '@npmcli/git': 4.1.0 + glob: 10.3.3 + hosted-git-info: 6.1.1 + json-parse-even-better-errors: 3.0.0 + normalize-package-data: 5.0.0 + proc-log: 3.0.0 + semver: 7.5.4 + transitivePeerDependencies: + - bluebird + dev: true + /@npmcli/promise-spawn/6.0.2: resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: which: 3.0.1 - dev: false /@npmcli/run-script/6.0.2: resolution: {integrity: sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==} @@ -9305,7 +9325,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: false optional: true /@pkgr/utils/2.3.1: @@ -10475,87 +10494,6 @@ packages: engines: {node: '>=14.0.0'} dev: false - /@remix-run/dev/1.19.2_uvzmt2w75nvrltrkqo4r7vh6eq: - resolution: {integrity: sha512-wAY3AN0xPJB01wi9RqHG1mV3W0M915XgudcLtJlWDbVppY3kGr7gP0/1sAz5onyEX8Z/+1vvVVU9jn59G8PIcA==} - engines: {node: '>=14.0.0'} - hasBin: true - peerDependencies: - '@remix-run/serve': ^1.19.2 - peerDependenciesMeta: - '@remix-run/serve': - optional: true - dependencies: - '@babel/core': 7.22.17 - '@babel/generator': 7.22.15 - '@babel/parser': 7.22.16 - '@babel/plugin-syntax-jsx': 7.22.5_@babel+core@7.22.17 - '@babel/plugin-syntax-typescript': 7.21.4_@babel+core@7.22.17 - '@babel/preset-env': 7.21.5_@babel+core@7.22.17 - '@babel/preset-typescript': 7.21.5_@babel+core@7.22.17 - '@babel/traverse': 7.22.17 - '@babel/types': 7.22.17 - '@npmcli/package-json': 2.0.0 - '@remix-run/serve': 1.19.2 - '@remix-run/server-runtime': 1.19.2 - '@vanilla-extract/integration': 6.2.1_@types+node@18.11.18 - arg: 5.0.2 - cacache: 15.3.0 - chalk: 4.1.2 - chokidar: 3.5.3 - dotenv: 16.3.1 - esbuild: 0.17.6 - esbuild-plugins-node-modules-polyfill: 1.3.0_esbuild@0.17.6 - execa: 5.1.1 - exit-hook: 2.2.1 - express: 4.18.2 - fast-glob: 3.2.11 - fs-extra: 10.1.0 - get-port: 5.1.1 - gunzip-maybe: 1.4.2 - inquirer: 8.2.6 - jsesc: 3.0.2 - json5: 2.2.3 - lodash: 4.17.21 - lodash.debounce: 4.0.8 - minimatch: 9.0.3 - node-fetch: 2.6.12 - ora: 5.4.1 - picocolors: 1.0.0 - picomatch: 2.3.1 - pidtree: 0.6.0 - postcss: 8.4.29 - postcss-discard-duplicates: 5.1.0_postcss@8.4.29 - postcss-load-config: 4.0.1_ntcifew6f27c4l7dh5q6i7owpq - postcss-modules: 6.0.0_postcss@8.4.29 - prettier: 2.8.8 - pretty-ms: 7.0.1 - proxy-agent: 6.3.0 - react-refresh: 0.14.0 - recast: 0.21.5 - remark-frontmatter: 4.0.1 - remark-mdx-frontmatter: 1.1.1 - semver: 7.5.4 - sort-package-json: 1.57.0 - tar-fs: 2.1.1 - tsconfig-paths: 4.2.0 - ws: 7.5.9 - xdm: 2.1.0 - transitivePeerDependencies: - - '@types/node' - - bluebird - - bufferutil - - encoding - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - ts-node - - utf-8-validate - dev: true - /@remix-run/dev/1.19.3_@remix-run+serve@1.19.3: resolution: {integrity: sha512-Yh733OI0AxR7QbPaJbocujxSF1S5CToDmfZnmv5SlTTIXEw5KfnbCceHy9qhUp0nrkz2YT7pd1zbTEVYIi/Vug==} engines: {node: '>=14.0.0'} @@ -10593,7 +10531,7 @@ packages: fs-extra: 10.1.0 get-port: 5.1.1 gunzip-maybe: 1.4.2 - inquirer: 8.2.5 + inquirer: 8.2.6 jsesc: 3.0.2 json5: 2.2.3 lodash: 4.17.21 @@ -10637,40 +10575,83 @@ packages: - utf-8-validate dev: true - /@remix-run/eslint-config/1.19.2_ol4nhuzbuflsbzk2mijpqykzba: - resolution: {integrity: sha512-YvB3uulbSdwsvkcQSUPXtZG2AbVq1v49jVf5IsPj0xApNcRMtkk4Y/6cethbtHJOIpHhbAnJyPtoHOVvlRWQHQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@remix-run/dev/2.1.0_h4yxzoqkgkdcmxrorkwi6rxqz4: + resolution: {integrity: sha512-Hn5lw46F+a48dp5uHKe68ckaHgdStW4+PmLod+LMFEqrMbkF0j4XD1ousebxlv989o0Uy/OLgfRMgMy4cBOvHg==} + engines: {node: '>=18.0.0'} + hasBin: true peerDependencies: - eslint: ^8.0.0 - react: ^17.0.0 || ^18.0.0 - typescript: ^4.0.0 || ^5.0.0 + '@remix-run/serve': ^2.1.0 + typescript: ^5.1.0 peerDependenciesMeta: + '@remix-run/serve': + optional: true typescript: optional: true dependencies: '@babel/core': 7.22.17 - '@babel/eslint-parser': 7.21.8_6ukzja5aifaykyuthozjethhji - '@babel/preset-react': 7.18.6_@babel+core@7.22.17 - '@rushstack/eslint-patch': 1.2.0 - '@typescript-eslint/eslint-plugin': 5.59.6_bhjpu5ld5qmewts22wsycix4mi - '@typescript-eslint/parser': 5.59.6_iukboom6ndih5an6iafl45j2fe - eslint: 8.31.0 - eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.5_xfsrrykzxa74jzzdrhgdfm2jz4 - eslint-plugin-import: 2.27.5_4nmwrqeucdooykidvg7oplsu44 - eslint-plugin-jest: 26.9.0_x6daaykndggqmizjmaubfxsrba - eslint-plugin-jest-dom: 4.0.3_eslint@8.31.0 - eslint-plugin-jsx-a11y: 6.7.1_eslint@8.31.0 - eslint-plugin-node: 11.1.0_eslint@8.31.0 - eslint-plugin-react: 7.32.2_eslint@8.31.0 - eslint-plugin-react-hooks: 4.6.0_eslint@8.31.0 - eslint-plugin-testing-library: 5.11.0_iukboom6ndih5an6iafl45j2fe - react: 18.2.0 - typescript: 4.9.4 + '@babel/generator': 7.22.15 + '@babel/parser': 7.22.16 + '@babel/plugin-syntax-decorators': 7.22.10_@babel+core@7.22.17 + '@babel/plugin-syntax-jsx': 7.22.5_@babel+core@7.22.17 + '@babel/preset-typescript': 7.21.5_@babel+core@7.22.17 + '@babel/traverse': 7.22.17 + '@mdx-js/mdx': 2.3.0 + '@npmcli/package-json': 4.0.1 + '@remix-run/serve': 2.1.0_typescript@5.2.2 + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 + '@types/mdx': 2.0.5 + '@vanilla-extract/integration': 6.2.1_@types+node@18.11.18 + arg: 5.0.2 + cacache: 17.1.4 + chalk: 4.1.2 + chokidar: 3.5.3 + dotenv: 16.3.1 + esbuild: 0.17.6 + esbuild-plugins-node-modules-polyfill: 1.6.1_esbuild@0.17.6 + execa: 5.1.1 + exit-hook: 2.2.1 + express: 4.18.2 + fs-extra: 10.1.0 + get-port: 5.1.1 + gunzip-maybe: 1.4.2 + jsesc: 3.0.2 + json5: 2.2.3 + lodash: 4.17.21 + lodash.debounce: 4.0.8 + minimatch: 9.0.3 + node-fetch: 2.6.12 + ora: 5.4.1 + picocolors: 1.0.0 + picomatch: 2.3.1 + pidtree: 0.6.0 + postcss: 8.4.29 + postcss-discard-duplicates: 5.1.0_postcss@8.4.29 + postcss-load-config: 4.0.1_ntcifew6f27c4l7dh5q6i7owpq + postcss-modules: 6.0.0_postcss@8.4.29 + prettier: 2.8.8 + pretty-ms: 7.0.1 + react-refresh: 0.14.0 + remark-frontmatter: 4.0.1 + remark-mdx-frontmatter: 1.1.1 + semver: 7.5.4 + tar-fs: 2.1.1 + tsconfig-paths: 4.2.0 + typescript: 5.2.2 + ws: 7.5.9 transitivePeerDependencies: - - eslint-import-resolver-webpack - - jest + - '@types/node' + - bluebird + - bufferutil + - encoding + - less + - lightningcss + - sass + - stylus + - sugarss - supports-color + - terser + - ts-node + - utf-8-validate dev: true /@remix-run/eslint-config/1.19.3_iyrjzpeh7xaqnslyy6ezqi7i5u: @@ -10709,14 +10690,41 @@ packages: - supports-color dev: true - /@remix-run/express/1.19.2_express@4.18.2: - resolution: {integrity: sha512-uaKv2Yxfl545QmE2Ou/EYkGUycKYJDZ98kCgI0+bBgX591IE2rzhmSeExCerLXI5rDTKO4YikPTyxiUqZncPiA==} - engines: {node: '>=14.0.0'} + /@remix-run/eslint-config/2.1.0_tivk2qwcsqyhxoqj22f6pjbp7e: + resolution: {integrity: sha512-yfeUnHpUG+XveujMi6QODKMGhs5CvKWCKzASU397BPXiPWbMv6r2acfODSWK64ZdBMu9hcLbOb42GBFydVQeHA==} + engines: {node: '>=18.0.0'} peerDependencies: - express: ^4.17.1 + eslint: ^8.0.0 + react: ^18.0.0 + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@remix-run/node': 1.19.2 - express: 4.18.2 + '@babel/core': 7.22.17 + '@babel/eslint-parser': 7.21.8_6ukzja5aifaykyuthozjethhji + '@babel/preset-react': 7.18.6_@babel+core@7.22.17 + '@rushstack/eslint-patch': 1.2.0 + '@typescript-eslint/eslint-plugin': 5.59.6_7rqy3acf7ittvjpliyki2mhtka + '@typescript-eslint/parser': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq + eslint: 8.31.0 + eslint-import-resolver-node: 0.3.7 + eslint-import-resolver-typescript: 3.5.5_xfsrrykzxa74jzzdrhgdfm2jz4 + eslint-plugin-import: 2.27.5_4nmwrqeucdooykidvg7oplsu44 + eslint-plugin-jest: 26.9.0_qzdjua55qjokohvdpltmadfz3i + eslint-plugin-jest-dom: 4.0.3_eslint@8.31.0 + eslint-plugin-jsx-a11y: 6.7.1_eslint@8.31.0 + eslint-plugin-node: 11.1.0_eslint@8.31.0 + eslint-plugin-react: 7.32.2_eslint@8.31.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.31.0 + eslint-plugin-testing-library: 5.11.0_a3vgt4pcadxos4gox5ryckz5rq + react: 18.2.0 + typescript: 5.2.2 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - jest + - supports-color + dev: true /@remix-run/express/1.19.3_express@4.18.2: resolution: {integrity: sha512-jeWZ9xFyaarKSbpN/sQWx53QApGs16IiB8XC7CkOAEVDtLfOhXkJ9jOZNScOFUn6JXPx2oAwBBRRdbwOmryScQ==} @@ -10727,19 +10735,19 @@ packages: '@remix-run/node': 1.19.3 express: 4.18.2 - /@remix-run/node/1.19.2: - resolution: {integrity: sha512-ceoDsnG4SQHxqV6aq6ZTxiBr90mnAsQBL3D7kfNswsgPS/d0gERV1hIC9svDW2NuDeMI/U1CXCM08XZYrqLfRQ==} - engines: {node: '>=14.0.0'} + /@remix-run/express/2.1.0_fgkcria6ebvizt7e6qzdl6ei7a: + resolution: {integrity: sha512-R5myPowQx6LYWY3+EqP42q19MOCT3+ZGwb2f0UKNs9a34R8U3nFpGWL7saXryC+To+EasujEScc8rTQw5Pftog==} + engines: {node: '>=18.0.0'} + peerDependencies: + express: ^4.17.1 + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@remix-run/server-runtime': 1.19.2 - '@remix-run/web-fetch': 4.3.6 - '@remix-run/web-file': 3.0.3 - '@remix-run/web-stream': 1.0.4 - '@web3-storage/multipart-parser': 1.0.0 - abort-controller: 3.0.0 - cookie-signature: 1.2.0 - source-map-support: 0.5.21 - stream-slice: 0.1.2 + '@remix-run/node': 2.1.0_typescript@5.2.2 + express: 4.18.2 + typescript: 5.2.2 /@remix-run/node/1.19.3: resolution: {integrity: sha512-z5qrVL65xLXIUpU4mkR4MKlMeKARLepgHAk4W5YY3IBXOreRqOGUC70POViYmY7x38c2Ia1NwqL80H+0h7jbMw==} @@ -10755,18 +10763,24 @@ packages: source-map-support: 0.5.21 stream-slice: 0.1.2 - /@remix-run/react/1.19.2_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-Q3abYOR02+8wg3mNGYHafGqZUwVv19Et+2ti/8CKTOvY+/rIRppEhDqPCYaiAvNJ7kMcvTdCYKbCi6bU9ElV0g==} - engines: {node: '>=14.0.0'} + /@remix-run/node/2.1.0_typescript@5.2.2: + resolution: {integrity: sha512-TeSgjXnZUUlmw5FVpBVnXY7MLpracjdnwFNwoJE5NQkiUEFnGD/Yhvk4F2fOCkszqc2Z25KRclc5noweyiFu6Q==} + engines: {node: '>=18.0.0'} peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@remix-run/router': 1.7.2 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - react-router-dom: 6.14.2_biqbaboplfbrettd7655fr4n2y - dev: false + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 + '@remix-run/web-fetch': 4.4.1 + '@remix-run/web-file': 3.1.0 + '@remix-run/web-stream': 1.1.0 + '@web3-storage/multipart-parser': 1.0.0 + cookie-signature: 1.2.0 + source-map-support: 0.5.21 + stream-slice: 0.1.2 + typescript: 5.2.2 /@remix-run/react/1.19.3_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-iP37MZ+oG1n4kv4rX77pKT/knra51lNwKo5tinPPF0SuNJhF3+XjWo5nwEjvisKTXLZ/OHeicinhgX2JHHdDvA==} @@ -10779,6 +10793,29 @@ packages: react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-router-dom: 6.14.2_biqbaboplfbrettd7655fr4n2y + dev: false + + /@remix-run/react/2.1.0_vegmnuvoswdxuttpuhvewcem44: + resolution: {integrity: sha512-DeYgfsvNxHqNn29sGA3XsZCciMKo2EFTQ9hHkuVPTsJXC4ipHr6Dja1j6UzZYPe/ZuKppiuTjueWCQlE2jOe1w==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@remix-run/router': 1.10.0 + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-router-dom: 6.17.0_biqbaboplfbrettd7655fr4n2y + typescript: 5.2.2 + + /@remix-run/router/1.10.0: + resolution: {integrity: sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==} + engines: {node: '>=14.0.0'} /@remix-run/router/1.7.2: resolution: {integrity: sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==} @@ -10789,20 +10826,6 @@ packages: engines: {node: '>=14.0.0'} dev: true - /@remix-run/serve/1.19.2: - resolution: {integrity: sha512-8ypS7zdK8hlAyjD+I8MNwqHV3m+4qek2UryqCFAJU6/nfjRmCNo1TTItR9NuQSqNiWzfW2J6TyA47NYtwGb7Dw==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - '@remix-run/express': 1.19.2_express@4.18.2 - '@remix-run/node': 1.19.2 - compression: 1.7.4 - express: 4.18.2 - morgan: 1.10.0 - source-map-support: 0.5.21 - transitivePeerDependencies: - - supports-color - /@remix-run/serve/1.19.3: resolution: {integrity: sha512-RUVEy7EBYvHO1hIuyVQJyIYZeFfXecC+Uaw6GvZscLywIGnhcAj+5dpeK6HVayNuV4kKLvBp0F4ZGHbwW1oWaA==} engines: {node: '>=14.0.0'} @@ -10817,16 +10840,22 @@ packages: transitivePeerDependencies: - supports-color - /@remix-run/server-runtime/1.19.2: - resolution: {integrity: sha512-d+oOaQlBPFHPum1nstJCntRgwgi4fzijW51yPCo3ljUxIrjlDjutX2GCQ72Apd6Ffpd11/6nEq+B90EY+Ala9Q==} - engines: {node: '>=14.0.0'} + /@remix-run/serve/2.1.0_typescript@5.2.2: + resolution: {integrity: sha512-XHI+vPYz217qrg1QcV38TTPlEBTzMJzAt0SImPutyF0S2IBrZGZIFMEsspI0i0wNvdcdQz1IqmSx+mTghzW8eQ==} + engines: {node: '>=18.0.0'} + hasBin: true dependencies: - '@remix-run/router': 1.7.2 - '@types/cookie': 0.4.1 - '@web3-storage/multipart-parser': 1.0.0 - cookie: 0.4.2 - set-cookie-parser: 2.6.0 - source-map: 0.7.4 + '@remix-run/express': 2.1.0_fgkcria6ebvizt7e6qzdl6ei7a + '@remix-run/node': 2.1.0_typescript@5.2.2 + chokidar: 3.5.3 + compression: 1.7.4 + express: 4.18.2 + get-port: 5.1.1 + morgan: 1.10.0 + source-map-support: 0.5.21 + transitivePeerDependencies: + - supports-color + - typescript /@remix-run/server-runtime/1.19.3: resolution: {integrity: sha512-KzQ+htUsKqpBgKE2tWo7kIIGy3MyHP58Io/itUPvV+weDjApwr9tQr9PZDPA3yAY6rAzLax7BU0NMSYCXWFY5A==} @@ -10858,27 +10887,65 @@ packages: typescript: 4.9.5 dev: true - /@remix-run/testing/1.19.3_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-qO485TWDxHUnWuWX8PotBdq4xa5W3WUQ+iYvoWNCyILw6iJG0WeBTgd2qN44GFEOE1Hd80RqDXVuAlTkG33rCA==} - engines: {node: '>=14.0.0'} + /@remix-run/server-runtime/2.1.0_typescript@5.2.2: + resolution: {integrity: sha512-Uz69yF4Gu6F3VYQub3JgDo9godN8eDMeZclkadBTAWN7bYLonu0ChR/GlFxS35OLeF7BDgudxOSZob0nE1WHNg==} + engines: {node: '>=18.0.0'} peerDependencies: - react: ^17.0.0 || ^18.0.0 + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@remix-run/node': 1.19.3 - '@remix-run/react': 1.19.3_biqbaboplfbrettd7655fr4n2y - '@remix-run/router': 1.7.2 + '@remix-run/router': 1.10.0 + '@types/cookie': 0.4.1 + '@web3-storage/multipart-parser': 1.0.0 + cookie: 0.4.2 + set-cookie-parser: 2.6.0 + source-map: 0.7.4 + typescript: 5.2.2 + + /@remix-run/testing/2.1.0_vegmnuvoswdxuttpuhvewcem44: + resolution: {integrity: sha512-eLPx4Bmjt243kyRpQTong1eFo6nkvSfCr65bb5PfoF172DKnsSSCYWAmBmB72VwtAPESHxBm3g6AUbhwphkU6A==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0.0 + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@remix-run/node': 2.1.0_typescript@5.2.2 + '@remix-run/react': 2.1.0_vegmnuvoswdxuttpuhvewcem44 + '@remix-run/router': 1.10.0 react: 18.2.0 - react-router-dom: 6.14.2_biqbaboplfbrettd7655fr4n2y + react-router-dom: 6.17.0_biqbaboplfbrettd7655fr4n2y + typescript: 5.2.2 transitivePeerDependencies: - react-dom dev: true + /@remix-run/v1-meta/0.1.3_ybjp5xbtg4zthziheradfmei64: + resolution: {integrity: sha512-pYTJSrT8ouipv9gL7sqk4hjgVrfj6Jcp9KDnjZKPFUF+91S1J5PNjipAHE1aXh0LyHo2rDbTW/OjtFeJ6BKhYg==} + peerDependencies: + '@remix-run/react': ^1.15.0 || ^2.0.0 + '@remix-run/server-runtime': ^1.15.0 || ^2.0.0 + dependencies: + '@remix-run/react': 2.1.0_vegmnuvoswdxuttpuhvewcem44 + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 + dev: false + /@remix-run/web-blob/3.0.5: resolution: {integrity: sha512-Mungj3erqCrq0+5zU/34NkeC2g+U7K6Uwa8uNiZgANvw0Wc64wKglk4MPQJZA0Y2tgPYXyrRn7uw4q75j6Hhww==} dependencies: '@remix-run/web-stream': 1.0.4 web-encoding: 1.1.5 + /@remix-run/web-blob/3.1.0: + resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==} + dependencies: + '@remix-run/web-stream': 1.1.0 + web-encoding: 1.1.5 + /@remix-run/web-fetch/4.3.6: resolution: {integrity: sha512-ifadyJS+/7W6LhKyA8tIR9fBIPwLCFVpl1YCYg5i0ikykiXxE3IWtPVB1G51AJFghc1YgR7rq8BRJLsJeUbE5Q==} engines: {node: ^10.17 || >=12.3} @@ -10891,21 +10958,49 @@ packages: data-uri-to-buffer: 3.0.1 mrmime: 1.0.1 + /@remix-run/web-fetch/4.4.1: + resolution: {integrity: sha512-xMceEGn2kvfeWS91nHSOhEQHPGgjFnmDVpWFZrbWPVdiTByMZIn421/tdSF6Kd1RsNsY+5Iwt3JFEKZHAcMQHw==} + engines: {node: ^10.17 || >=12.3} + dependencies: + '@remix-run/web-blob': 3.1.0 + '@remix-run/web-file': 3.1.0 + '@remix-run/web-form-data': 3.1.0 + '@remix-run/web-stream': 1.1.0 + '@web3-storage/multipart-parser': 1.0.0 + abort-controller: 3.0.0 + data-uri-to-buffer: 3.0.1 + mrmime: 1.0.1 + /@remix-run/web-file/3.0.3: resolution: {integrity: sha512-yPf6MSXNcaQ4H1vkT/TSgImnqqfvfVKZzjd0vz3wvR0MM1NmrYfLbSbwfFLXdESFnQpXItbyKsgYGeAUEawgBg==} dependencies: '@remix-run/web-blob': 3.0.5 + /@remix-run/web-file/3.1.0: + resolution: {integrity: sha512-dW2MNGwoiEYhlspOAXFBasmLeYshyAyhIdrlXBi06Duex5tDr3ut2LFKVj7tyHLmn8nnNwFf1BjNbkQpygC2aQ==} + dependencies: + '@remix-run/web-blob': 3.1.0 + /@remix-run/web-form-data/3.0.5: resolution: {integrity: sha512-txXJDzjDuTxF8MFvEp9AA2HF3oPcvmlE1I/6HIxeGpX3vpBtrCPw5KQ/nzgBZNuAxyxEm8ps6Ds/UZwoDyfGsQ==} dependencies: web-encoding: 1.1.5 + /@remix-run/web-form-data/3.1.0: + resolution: {integrity: sha512-NdeohLMdrb+pHxMQ/Geuzdp0eqPbea+Ieo8M8Jx2lGC6TBHsgHzYcBvr0LyPdPVycNRDEpWpiDdCOdCryo3f9A==} + dependencies: + web-encoding: 1.1.5 + /@remix-run/web-stream/1.0.4: resolution: {integrity: sha512-SVO42pH21I1sAhksGEM8ZBV/jc1mz6knZSg6Qo/2HPy9JTvtUykm3QMHtF2OMCTUXxdRW+4E/rphkPRyGc8WKw==} dependencies: web-streams-polyfill: 3.2.1 + /@remix-run/web-stream/1.1.0: + resolution: {integrity: sha512-KRJtwrjRV5Bb+pM7zxcTJkhIqWWSy+MYsIxHK+0m5atcznsf15YwUBWHWulZerV2+vvHH1Lp1DD7pw6qKW8SgA==} + dependencies: + web-streams-polyfill: 3.2.1 + /@rollup/pluginutils/4.2.1: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -11541,7 +11636,7 @@ packages: - supports-color dev: true - /@storybook/builder-webpack5/7.0.9_nof6wriz76uonkhaqbbs3zfxk4: + /@storybook/builder-webpack5/7.0.9_eunppc2ebbpop5kss6ctsxsd44: resolution: {integrity: sha512-a2482TEsk3K+FKC0rma3Z6oOvedUMXMCLrPJ5Zxl5BbtK8kAjJhlAxCZVNiKqckFW3CoN4v3zdOGs96I1mORLA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -11579,7 +11674,7 @@ packages: case-sensitive-paths-webpack-plugin: 2.4.0 css-loader: 6.7.3_webpack@5.88.2 express: 4.18.2 - fork-ts-checker-webpack-plugin: 7.3.0_vf3ejk3u7gfag4p4x6gqje5yuq + fork-ts-checker-webpack-plugin: 7.3.0_dtthwp2bsqb7yvb7hoeealjg4i fs-extra: 11.1.0 html-webpack-plugin: 5.5.1_webpack@5.88.2 path-browserify: 1.0.1 @@ -11590,7 +11685,7 @@ packages: style-loader: 3.3.2_webpack@5.88.2 terser-webpack-plugin: 5.3.7_nww33inhqu3uc3cp573wawoccu ts-dedent: 2.2.0 - typescript: 4.9.4 + typescript: 5.2.2 util: 0.12.5 util-deprecate: 1.0.2 webpack: 5.88.2_uhpfu7q6noim4yjdo6qt2aajgu @@ -12086,7 +12181,7 @@ packages: resolution: {integrity: sha512-KUcHqRDC7wANt9dE8gpymzKcqXMSFUPDC/EJSc8mhbuRBW9/DUmkFBgRDj+h3a+orWNzhFTDGr+CnXezV/0X+A==} dev: true - /@storybook/preset-react-webpack/7.0.9_nof6wriz76uonkhaqbbs3zfxk4: + /@storybook/preset-react-webpack/7.0.9_eunppc2ebbpop5kss6ctsxsd44: resolution: {integrity: sha512-LNXej/lyYKDxjQicpShCjC37Ew+RSeNISrFpA1kKGR8ub9IUSZvCHUT0qC6B++bfeUbNiFesltja6/xi+VjWQA==} engines: {node: '>=16.0.0'} peerDependencies: @@ -12106,8 +12201,8 @@ packages: '@storybook/core-webpack': 7.0.9 '@storybook/docs-tools': 7.0.9 '@storybook/node-logger': 7.0.9 - '@storybook/react': 7.0.9_o4scbtliisanygemawej7x2d6i - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0_vf3ejk3u7gfag4p4x6gqje5yuq + '@storybook/react': 7.0.9_vegmnuvoswdxuttpuhvewcem44 + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0_dtthwp2bsqb7yvb7hoeealjg4i '@types/node': 16.18.11 '@types/semver': 7.3.13 babel-plugin-add-react-displayname: 0.0.5 @@ -12117,7 +12212,7 @@ packages: react-dom: 18.2.0_react@18.2.0 react-refresh: 0.11.0 semver: 7.5.4 - typescript: 4.9.4 + typescript: 5.2.2 webpack: 5.88.2_uhpfu7q6noim4yjdo6qt2aajgu transitivePeerDependencies: - '@swc/core' @@ -12177,7 +12272,7 @@ packages: resolution: {integrity: sha512-09tD+rBWMqBAdVqKhyotO6bTTJlCbVX9uVmc8la4jBoLL1JdE3qkBBmDivEsMDK5AoVaM5Zg2maDO4jm2HyZFw==} dev: true - /@storybook/react-docgen-typescript-plugin/1.0.6--canary.9.0c3f3b7.0_vf3ejk3u7gfag4p4x6gqje5yuq: + /@storybook/react-docgen-typescript-plugin/1.0.6--canary.9.0c3f3b7.0_dtthwp2bsqb7yvb7hoeealjg4i: resolution: {integrity: sha512-KUqXC3oa9JuQ0kZJLBhVdS4lOneKTOopnNBK4tUAgoxWQ3u/IjzdueZjFr7gyBrXMoU6duutk3RQR9u8ZpYJ4Q==} peerDependencies: typescript: '>= 4.x' @@ -12188,9 +12283,9 @@ packages: find-cache-dir: 3.3.2 flat-cache: 3.0.4 micromatch: 4.0.5 - react-docgen-typescript: 2.2.2_typescript@4.9.4 + react-docgen-typescript: 2.2.2_typescript@5.2.2 tslib: 2.6.2 - typescript: 4.9.4 + typescript: 5.2.2 webpack: 5.88.2_uhpfu7q6noim4yjdo6qt2aajgu transitivePeerDependencies: - supports-color @@ -12216,7 +12311,7 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: true - /@storybook/react-webpack5/7.0.9_nof6wriz76uonkhaqbbs3zfxk4: + /@storybook/react-webpack5/7.0.9_eunppc2ebbpop5kss6ctsxsd44: resolution: {integrity: sha512-DaUMrM99CGb0HTfcLP1H/qH89f3Y1YvOcqOW7HJFSor2GXqOh4hH6Pkjrk0fO5ShpbX7ApHMLejReNZ1oZLY5g==} engines: {node: '>=16.0.0'} peerDependencies: @@ -12230,13 +12325,13 @@ packages: typescript: optional: true dependencies: - '@storybook/builder-webpack5': 7.0.9_nof6wriz76uonkhaqbbs3zfxk4 - '@storybook/preset-react-webpack': 7.0.9_nof6wriz76uonkhaqbbs3zfxk4 - '@storybook/react': 7.0.9_o4scbtliisanygemawej7x2d6i + '@storybook/builder-webpack5': 7.0.9_eunppc2ebbpop5kss6ctsxsd44 + '@storybook/preset-react-webpack': 7.0.9_eunppc2ebbpop5kss6ctsxsd44 + '@storybook/react': 7.0.9_vegmnuvoswdxuttpuhvewcem44 '@types/node': 16.18.11 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - typescript: 4.9.4 + typescript: 5.2.2 transitivePeerDependencies: - '@swc/core' - '@types/webpack' @@ -12252,7 +12347,7 @@ packages: - webpack-plugin-serve dev: true - /@storybook/react/7.0.9_o4scbtliisanygemawej7x2d6i: + /@storybook/react/7.0.9_vegmnuvoswdxuttpuhvewcem44: resolution: {integrity: sha512-wYrtyk6qVqgqODgBh/vWO31SrjzFER0NfnrZy6FvwR4AAHhM0u2qMs8ZXCJFXFIgwjkgGwpD9/EwjRuYHUqDTQ==} engines: {node: '>=16.0.0'} peerDependencies: @@ -12285,7 +12380,7 @@ packages: react-element-to-jsx-string: 15.0.0_biqbaboplfbrettd7655fr4n2y ts-dedent: 2.2.0 type-fest: 2.19.0 - typescript: 4.9.4 + typescript: 5.2.2 util-deprecate: 1.0.2 transitivePeerDependencies: - supports-color @@ -13776,7 +13871,7 @@ packages: dev: false optional: true - /@typescript-eslint/eslint-plugin/5.59.6_bhjpu5ld5qmewts22wsycix4mi: + /@typescript-eslint/eslint-plugin/5.59.6_7rqy3acf7ittvjpliyki2mhtka: resolution: {integrity: sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -13788,18 +13883,18 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 5.59.6_iukboom6ndih5an6iafl45j2fe + '@typescript-eslint/parser': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq '@typescript-eslint/scope-manager': 5.59.6 - '@typescript-eslint/type-utils': 5.59.6_iukboom6ndih5an6iafl45j2fe - '@typescript-eslint/utils': 5.59.6_iukboom6ndih5an6iafl45j2fe + '@typescript-eslint/type-utils': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq + '@typescript-eslint/utils': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq debug: 4.3.4 eslint: 8.31.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 semver: 7.5.0 - tsutils: 3.21.0_typescript@4.9.4 - typescript: 4.9.4 + tsutils: 3.21.0_typescript@5.2.2 + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true @@ -13889,6 +13984,26 @@ packages: - supports-color dev: true + /@typescript-eslint/parser/5.59.6_a3vgt4pcadxos4gox5ryckz5rq: + resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.59.6 + '@typescript-eslint/types': 5.59.6 + '@typescript-eslint/typescript-estree': 5.59.6_typescript@5.2.2 + debug: 4.3.4 + eslint: 8.31.0 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser/5.59.6_binxsscxvozjxebftqdoazsxm4: resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -13928,26 +14043,6 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.59.6_iukboom6ndih5an6iafl45j2fe: - resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 5.59.6 - '@typescript-eslint/types': 5.59.6 - '@typescript-eslint/typescript-estree': 5.59.6_typescript@4.9.4 - debug: 4.3.4 - eslint: 8.31.0 - typescript: 4.9.4 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/parser/5.59.6_ko3fmmbeyij36muomfgt2u76xu: resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -14043,7 +14138,7 @@ packages: '@typescript-eslint/visitor-keys': 6.7.4 dev: true - /@typescript-eslint/type-utils/5.59.6_iukboom6ndih5an6iafl45j2fe: + /@typescript-eslint/type-utils/5.59.6_a3vgt4pcadxos4gox5ryckz5rq: resolution: {integrity: sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -14053,12 +14148,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.59.6_typescript@4.9.4 - '@typescript-eslint/utils': 5.59.6_iukboom6ndih5an6iafl45j2fe + '@typescript-eslint/typescript-estree': 5.59.6_typescript@5.2.2 + '@typescript-eslint/utils': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq debug: 4.3.4 eslint: 8.31.0 - tsutils: 3.21.0_typescript@4.9.4 - typescript: 4.9.4 + tsutils: 3.21.0_typescript@5.2.2 + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true @@ -14152,27 +14247,6 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.59.6_typescript@4.9.4: - resolution: {integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 5.59.6 - '@typescript-eslint/visitor-keys': 5.59.6 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.4 - tsutils: 3.21.0_typescript@4.9.4 - typescript: 4.9.4 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/typescript-estree/5.59.6_typescript@4.9.5: resolution: {integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -14277,6 +14351,26 @@ packages: - supports-color dev: true + /@typescript-eslint/utils/5.59.6_a3vgt4pcadxos4gox5ryckz5rq: + resolution: {integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0_eslint@8.31.0 + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.13 + '@typescript-eslint/scope-manager': 5.59.6 + '@typescript-eslint/types': 5.59.6 + '@typescript-eslint/typescript-estree': 5.59.6_typescript@5.2.2 + eslint: 8.31.0 + eslint-scope: 5.1.1 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/utils/5.59.6_eslint@8.45.0: resolution: {integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -14297,26 +14391,6 @@ packages: - typescript dev: true - /@typescript-eslint/utils/5.59.6_iukboom6ndih5an6iafl45j2fe: - resolution: {integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.0_eslint@8.31.0 - '@types/json-schema': 7.0.11 - '@types/semver': 7.3.13 - '@typescript-eslint/scope-manager': 5.59.6 - '@typescript-eslint/types': 5.59.6 - '@typescript-eslint/typescript-estree': 5.59.6_typescript@4.9.4 - eslint: 8.31.0 - eslint-scope: 5.1.1 - semver: 7.5.4 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /@typescript-eslint/utils/5.59.6_mhvizudgavq6jzvqzwcqcv4nnm: resolution: {integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -16030,7 +16104,6 @@ packages: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: semver: 7.5.4 - dev: false /bundle-require/3.1.2_esbuild@0.15.18: resolution: {integrity: sha512-Of6l6JBAxiyQ5axFxUM6dYeP/W7X2Sozeo/4EYB9sJhL+dqL7TKjg+shwxp6jlu/6ZSERfsYtIpSJ1/x3XkAEA==} @@ -16141,7 +16214,6 @@ packages: ssri: 10.0.5 tar: 6.1.13 unique-filename: 3.0.0 - dev: false /cache-base/1.0.1: resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} @@ -16407,7 +16479,7 @@ packages: static-extend: 0.1.2 dev: false - /class-variance-authority/0.5.2_typescript@4.9.4: + /class-variance-authority/0.5.2_typescript@5.2.2: resolution: {integrity: sha512-j7Qqw3NPbs4IpO80gvdACWmVvHiLLo5MECacUBLnJG17CrLpWaQ7/4OaWX6P0IO1j2nvZ7AuSfBS/ImtEUZJGA==} peerDependencies: typescript: '>= 4.5.5 < 6' @@ -16415,7 +16487,7 @@ packages: typescript: optional: true dependencies: - typescript: 4.9.4 + typescript: 5.2.2 dev: false /classnames/2.3.2: @@ -17854,7 +17926,6 @@ packages: /err-code/2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - dev: false /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -18152,6 +18223,18 @@ packages: resolve.exports: 2.0.2 dev: true + /esbuild-plugins-node-modules-polyfill/1.6.1_esbuild@0.17.6: + resolution: {integrity: sha512-6sAwI24PV8W0zxeO+i4BS5zoQypS3SzEGwIdxpzpy65riRuK8apMw8PN0aKVLCTnLr0FgNIxUMRd9BsreBrtog==} + engines: {node: '>=14.0.0'} + peerDependencies: + esbuild: ^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0 || ^0.19.0 + dependencies: + '@jspm/core': 2.0.1 + esbuild: 0.17.6 + local-pkg: 0.4.3 + resolve.exports: 2.0.2 + dev: true + /esbuild-register/3.4.2_esbuild@0.17.10: resolution: {integrity: sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==} peerDependencies: @@ -18696,7 +18779,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.59.6_iukboom6ndih5an6iafl45j2fe + '@typescript-eslint/parser': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq debug: 3.2.7 eslint: 8.31.0 eslint-import-resolver-node: 0.3.7 @@ -18807,7 +18890,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.59.6_iukboom6ndih5an6iafl45j2fe + '@typescript-eslint/parser': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 @@ -18919,6 +19002,27 @@ packages: requireindex: 1.2.0 dev: true + /eslint-plugin-jest/26.9.0_qzdjua55qjokohvdpltmadfz3i: + resolution: {integrity: sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 5.59.6_7rqy3acf7ittvjpliyki2mhtka + '@typescript-eslint/utils': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq + eslint: 8.31.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /eslint-plugin-jest/26.9.0_w57sqwgpppmutnwrfqbwy723iu: resolution: {integrity: sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -18940,27 +19044,6 @@ packages: - typescript dev: true - /eslint-plugin-jest/26.9.0_x6daaykndggqmizjmaubfxsrba: - resolution: {integrity: sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - '@typescript-eslint/eslint-plugin': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - jest: '*' - peerDependenciesMeta: - '@typescript-eslint/eslint-plugin': - optional: true - jest: - optional: true - dependencies: - '@typescript-eslint/eslint-plugin': 5.59.6_bhjpu5ld5qmewts22wsycix4mi - '@typescript-eslint/utils': 5.59.6_iukboom6ndih5an6iafl45j2fe - eslint: 8.31.0 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /eslint-plugin-jsx-a11y/6.7.1_eslint@8.31.0: resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} engines: {node: '>=4.0'} @@ -19244,13 +19327,13 @@ packages: - ts-node dev: true - /eslint-plugin-testing-library/5.11.0_iukboom6ndih5an6iafl45j2fe: + /eslint-plugin-testing-library/5.11.0_a3vgt4pcadxos4gox5ryckz5rq: resolution: {integrity: sha512-ELY7Gefo+61OfXKlQeXNIDVVLPcvKTeiQOoMZG9TeuWa7Ln4dUNRv8JdRWBQI9Mbb427XGlVB1aa1QPZxBJM8Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} peerDependencies: eslint: ^7.5.0 || ^8.0.0 dependencies: - '@typescript-eslint/utils': 5.59.6_iukboom6ndih5an6iafl45j2fe + '@typescript-eslint/utils': 5.59.6_a3vgt4pcadxos4gox5ryckz5rq eslint: 8.31.0 transitivePeerDependencies: - supports-color @@ -19576,6 +19659,14 @@ packages: resolution: {integrity: sha512-rxZj1GkQhY4x1j/CSnybK9cGuMFQYFPLq0iNyopqf14aOVLFtMv7Esika+ObJWPWiOHuMOAHz3YkWoLYYRnzWQ==} dev: true + /estree-util-to-js/1.2.0: + resolution: {integrity: sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==} + dependencies: + '@types/estree-jsx': 1.0.0 + astring: 1.8.4 + source-map: 0.7.4 + dev: true + /estree-util-value-to-estree/1.3.0: resolution: {integrity: sha512-Y+ughcF9jSUJvncXwqRageavjrNPAI+1M/L3BI3PyLp1nmgYTGUXU6t5z1Y7OWuThoDdhPME07bQU+d5LxdJqw==} engines: {node: '>=12.0.0'} @@ -20263,9 +20354,8 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: false - /fork-ts-checker-webpack-plugin/7.3.0_vf3ejk3u7gfag4p4x6gqje5yuq: + /fork-ts-checker-webpack-plugin/7.3.0_dtthwp2bsqb7yvb7hoeealjg4i: resolution: {integrity: sha512-IN+XTzusCjR5VgntYFgxbxVx3WraPRnKehBFrf00cMSrtUuW9MsG9dhL6MWpY6MkjC3wVwoujfCDgZZCQwbswA==} engines: {node: '>=12.13.0', yarn: '>=1.0.0'} peerDependencies: @@ -20288,7 +20378,7 @@ packages: schema-utils: 3.3.0 semver: 7.5.4 tapable: 2.2.1 - typescript: 4.9.4 + typescript: 5.2.2 webpack: 5.88.2_uhpfu7q6noim4yjdo6qt2aajgu dev: true @@ -20472,7 +20562,6 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: minipass: 7.0.3 - dev: false /fs-monkey/1.0.4: resolution: {integrity: sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==} @@ -20597,7 +20686,6 @@ packages: /get-port/5.1.1: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} - dev: true /get-stdin/8.0.0: resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} @@ -20745,7 +20833,6 @@ packages: minimatch: 9.0.3 minipass: 7.0.3 path-scurry: 1.10.1 - dev: false /glob/7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} @@ -21270,7 +21357,6 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: lru-cache: 7.18.3 - dev: false /hpagent/0.1.2: resolution: {integrity: sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==} @@ -21697,11 +21783,6 @@ packages: loose-envify: 1.4.0 dev: false - /ip-regex/4.3.0: - resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} - engines: {node: '>=8'} - dev: false - /ip/1.1.8: resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==} @@ -21943,13 +22024,6 @@ packages: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} - /is-ip/3.1.0: - resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} - engines: {node: '>=8'} - dependencies: - ip-regex: 4.3.0 - dev: false - /is-lambda/1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} dev: false @@ -22271,7 +22345,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: false /jaeger-client/3.19.0: resolution: {integrity: sha512-M0c7cKHmdyEUtjemnJyx/y9uX16XHocL46yQvyqDlPdvAcwPDbHrIbKjQdBqtiE4apQ/9dmr+ZLJYYPGnurgpw==} @@ -22913,7 +22986,6 @@ packages: /json-parse-even-better-errors/3.0.0: resolution: {integrity: sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: false /json-parse-helpfulerror/1.0.3: resolution: {integrity: sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==} @@ -23710,6 +23782,25 @@ packages: vfile-message: 3.1.3 dev: true + /mdast-util-mdx-jsx/2.1.4: + resolution: {integrity: sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==} + dependencies: + '@types/estree-jsx': 1.0.0 + '@types/hast': 2.3.4 + '@types/mdast': 3.0.10 + '@types/unist': 2.0.6 + ccount: 2.0.1 + mdast-util-from-markdown: 1.2.0 + mdast-util-to-markdown: 1.5.0 + parse-entities: 4.0.0 + stringify-entities: 4.0.3 + unist-util-remove-position: 4.0.1 + unist-util-stringify-position: 3.0.2 + vfile-message: 3.1.3 + transitivePeerDependencies: + - supports-color + dev: true + /mdast-util-mdx/1.1.0: resolution: {integrity: sha512-leKb9uG7laXdyFlTleYV4ZEaCpsxeU1LlkkR/xp35pgKrfV1Y0fNCuOw9vaRc2a9YDpH22wd145Wt7UY5yzeZw==} dependencies: @@ -23720,6 +23811,18 @@ packages: - supports-color dev: true + /mdast-util-mdx/2.0.1: + resolution: {integrity: sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==} + dependencies: + mdast-util-from-markdown: 1.2.0 + mdast-util-mdx-expression: 1.3.1 + mdast-util-mdx-jsx: 2.1.4 + mdast-util-mdxjs-esm: 1.3.0 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + dev: true + /mdast-util-mdxjs-esm/1.3.0: resolution: {integrity: sha512-7N5ihsOkAEGjFotIX9p/YPdl4TqUoMxL4ajNz7PbT89BqsdWJuBC9rvgt6wpbwTZqWWR0jKWqQbwsOWDBUZv4g==} dependencies: @@ -24968,7 +25071,6 @@ packages: is-core-module: 2.13.0 semver: 7.5.4 validate-npm-package-license: 3.0.4 - dev: false /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -25045,12 +25147,10 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: semver: 7.5.4 - dev: false /npm-normalize-package-bin/3.0.1: resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: false /npm-package-arg/10.1.0: resolution: {integrity: sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==} @@ -25060,7 +25160,6 @@ packages: proc-log: 3.0.0 semver: 7.5.4 validate-npm-package-name: 5.0.0 - dev: false /npm-packlist/7.0.4: resolution: {integrity: sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==} @@ -25077,7 +25176,6 @@ packages: npm-normalize-package-bin: 3.0.1 npm-package-arg: 10.1.0 semver: 7.5.4 - dev: false /npm-registry-fetch/14.0.5: resolution: {integrity: sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==} @@ -25875,7 +25973,7 @@ packages: /periscopic/3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} dependencies: - '@types/estree': 1.0.0 + '@types/estree': 1.0.2 estree-walker: 3.0.3 is-reference: 3.0.1 dev: true @@ -26200,7 +26298,7 @@ packages: dependencies: lilconfig: 2.1.0 postcss: 8.4.23 - ts-node: 10.9.1_fodzh64fuekdilycyvke2qmf2e + ts-node: 10.9.1_v2fyojhutw2zqp3xtxirvaa4jq yaml: 2.3.1 /postcss-load-config/4.0.1_ntcifew6f27c4l7dh5q6i7owpq: @@ -26217,7 +26315,7 @@ packages: dependencies: lilconfig: 2.1.0 postcss: 8.4.29 - ts-node: 10.9.1_fodzh64fuekdilycyvke2qmf2e + ts-node: 10.9.1_v2fyojhutw2zqp3xtxirvaa4jq yaml: 2.3.1 dev: true @@ -26664,7 +26762,6 @@ packages: /proc-log/3.0.0: resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: false /process-nextick-args/2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -26701,7 +26798,6 @@ packages: dependencies: err-code: 2.0.3 retry: 0.12.0 - dev: false /prompts-ncu/3.0.0: resolution: {integrity: sha512-qyz9UxZ5MlPKWVhWrCmSZ1ahm2GVYdjLb8og2sg0IPth1KRuhcggHGuijz0e41dkx35p1t1q3GRISGH7QGALFA==} @@ -26942,12 +27038,12 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: true - /react-docgen-typescript/2.2.2_typescript@4.9.4: + /react-docgen-typescript/2.2.2_typescript@5.2.2: resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} peerDependencies: typescript: '>= 4.3.x' dependencies: - typescript: 4.9.4 + typescript: 5.2.2 dev: true /react-docgen/5.4.3: @@ -27147,6 +27243,19 @@ packages: react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-router: 6.14.2_react@18.2.0 + dev: false + + /react-router-dom/6.17.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + '@remix-run/router': 1.10.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-router: 6.17.0_react@18.2.0 /react-router/6.14.2_react@18.2.0: resolution: {integrity: sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==} @@ -27156,6 +27265,16 @@ packages: dependencies: '@remix-run/router': 1.7.2 react: 18.2.0 + dev: false + + /react-router/6.17.0_react@18.2.0: + resolution: {integrity: sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + dependencies: + '@remix-run/router': 1.10.0 + react: 18.2.0 /react-smooth/2.0.4_v2m5e27vhdewzwhryxwfaorcca: resolution: {integrity: sha512-OkFsrrMBTvQUwEJthE1KXSOj79z57yvEWeFefeXPib+RmQEI9B1Ub1PgzlzzUyBOvl/TjXt5nF2hmD4NsgAh8A==} @@ -27583,6 +27702,15 @@ packages: toml: 3.0.0 dev: true + /remark-mdx/2.3.0: + resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} + dependencies: + mdast-util-mdx: 2.0.1 + micromark-extension-mdxjs: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: true + /remark-parse/10.0.2: resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} dependencies: @@ -27625,87 +27753,109 @@ packages: retext-smartypants: 5.2.0 unist-util-visit: 4.1.2 - /remix-auth-email-link/1.5.2_3kyxpfn573t2hovk377zjamxda: + /remix-auth-email-link/1.5.2_7ym4eehrzvo4xt5yocgqipdaji: resolution: {integrity: sha512-LCbaYkhsENbRK70O36Ul/jBtvNcNdBabjOIXYhrTNwx4qYA/fFkX8Sr3GMuDhoMzHnXQy7zHtsqbg/DTHocX6g==} peerDependencies: '@remix-run/server-runtime': ^1.1.1 remix-auth: ^3.2.1 dependencies: - '@remix-run/server-runtime': 1.19.2 + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 crypto-js: 4.1.1 - remix-auth: 3.4.0_ya7xgmpozdkweczbsj2asvumvm + remix-auth: 3.4.0_ybjp5xbtg4zthziheradfmei64 dev: false - /remix-auth-github/1.3.0_3kyxpfn573t2hovk377zjamxda: + /remix-auth-github/1.3.0_7ym4eehrzvo4xt5yocgqipdaji: resolution: {integrity: sha512-dqveIbXc1yXQXiB8UVjKSR3qgHFUZ0VJOu2/GTT5uMKZY0//FSnp1w8gK0btGlbc1HW97gig16uS84et6FBMiA==} peerDependencies: '@remix-run/server-runtime': ^1.0.0 remix-auth: ^3.4.0 dependencies: - '@remix-run/server-runtime': 1.19.2 - remix-auth: 3.4.0_ya7xgmpozdkweczbsj2asvumvm - remix-auth-oauth2: 1.5.0_3kyxpfn573t2hovk377zjamxda + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 + remix-auth: 3.4.0_ybjp5xbtg4zthziheradfmei64 + remix-auth-oauth2: 1.5.0_7ym4eehrzvo4xt5yocgqipdaji transitivePeerDependencies: - supports-color dev: false - /remix-auth-oauth2/1.5.0_3kyxpfn573t2hovk377zjamxda: + /remix-auth-oauth2/1.5.0_7ym4eehrzvo4xt5yocgqipdaji: resolution: {integrity: sha512-vv/CX2bCeVf8vzACfS1Fae+i75Cw0/As3RLkp00+9n9IBEvKi07LhUxTbODNA51jRh+wInnmCRIwsHafKQDD6g==} peerDependencies: '@remix-run/server-runtime': ^1.0.0 remix-auth: ^3.2.2 dependencies: - '@remix-run/server-runtime': 1.19.2 + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 debug: 4.3.4 - remix-auth: 3.4.0_ya7xgmpozdkweczbsj2asvumvm + remix-auth: 3.4.0_ybjp5xbtg4zthziheradfmei64 uuid: 8.3.2 transitivePeerDependencies: - supports-color dev: false - /remix-auth/3.4.0_ya7xgmpozdkweczbsj2asvumvm: + /remix-auth/3.4.0_ybjp5xbtg4zthziheradfmei64: resolution: {integrity: sha512-VRliJo9VRAS4sSYgMjYbi7rYRixPWU2Tb8PFZb06OIj0nONK/1KRzHf2+Y6VGZeIacepLVgan6g2IUJjayE2rQ==} peerDependencies: '@remix-run/react': ^1.0.0 '@remix-run/server-runtime': ^1.0.0 dependencies: - '@remix-run/react': 1.19.2_biqbaboplfbrettd7655fr4n2y - '@remix-run/server-runtime': 1.19.2 + '@remix-run/react': 2.1.0_vegmnuvoswdxuttpuhvewcem44 + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 uuid: 8.3.2 dev: false - /remix-typedjson/0.1.7_uyy3oyfmtzakxsapatosuzqil4: - resolution: {integrity: sha512-h7WoKbKBGsVdZZp3s79U5SDrMvQ3We7BjGaSIPxchnJOTS3GUDat0kBoy/gh9TVKRf5KYCee+K1UlFAYQmLQsA==} + /remix-typedjson/0.3.1_7t5z2pmrpxrx2dh2jpp55scw5q: + resolution: {integrity: sha512-XaLU3slWaUXTWPQe6u5rX4xJuqr665+wk1mRmepFu5Hn4DTP9V0zGOkCqCphCExxZ2Kqg5u+uZbas6AZRaxQzA==} peerDependencies: - '@remix-run/react': ^1.6.5 - '@remix-run/server-runtime': ^1.6.5 + '@remix-run/react': ^1.16.0 || ^2.0 + '@remix-run/server-runtime': ^1.16.0 || ^2.0 react: ^17.0.2 || ^18.0.0 dependencies: - '@remix-run/react': 1.19.2_biqbaboplfbrettd7655fr4n2y - '@remix-run/server-runtime': 1.19.2 + '@remix-run/react': 2.1.0_vegmnuvoswdxuttpuhvewcem44 + '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 react: 18.2.0 dev: false - /remix-utils/6.0.0_jtxmz5feu54a7fcxnhi2w5jyki: - resolution: {integrity: sha512-S7Xec0YHZxGFEDawWpIbU7HQAZC0j51FmAvcCyBRuxjo71aAIMdmez47dgF8T91yxpHV1xlIKPL7LhBzmYsOZw==} - engines: {node: '>=14'} + /remix-utils/7.1.0_nb5lmxjinjv74o775xrkc35gga: + resolution: {integrity: sha512-cceintceWvmNvgLLFeAUkWRcdWuOHGDLaWh0aeL0bLGWnMPBilIyT74Rira1az/ImS9owfh8tjLL4w/22AXJiA==} + engines: {node: '>=18.0.0'} peerDependencies: - '@remix-run/react': ^1.10.0 - '@remix-run/server-runtime': ^1.10.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - zod: ^3.19.1 + '@remix-run/cloudflare': ^2.0.0 + '@remix-run/deno': ^2.0.0 + '@remix-run/node': ^2.0.0 + '@remix-run/react': ^2.0.0 + '@remix-run/router': ^1.7.2 + crypto-js: ^4.1.1 + intl-parse-accept-language: ^1.0.0 + is-ip: ^5.0.1 + react: ^18.0.0 + zod: ^3.22.4 + peerDependenciesMeta: + '@remix-run/cloudflare': + optional: true + '@remix-run/deno': + optional: true + '@remix-run/node': + optional: true + '@remix-run/react': + optional: true + '@remix-run/router': + optional: true + crypto-js: + optional: true + intl-parse-accept-language: + optional: true + is-ip: + optional: true + react: + optional: true + zod: + optional: true dependencies: - '@remix-run/react': 1.19.2_biqbaboplfbrettd7655fr4n2y - '@remix-run/server-runtime': 1.19.2 + '@remix-run/node': 2.1.0_typescript@5.2.2 + '@remix-run/react': 2.1.0_vegmnuvoswdxuttpuhvewcem44 intl-parse-accept-language: 1.0.0 - is-ip: 3.1.0 react: 18.2.0 - schema-dts: 1.1.0_typescript@4.9.4 - type-fest: 2.19.0 - uuid: 8.3.2 + type-fest: 4.6.0 zod: 3.22.3 - transitivePeerDependencies: - - typescript dev: false /remote-git-tags/3.0.0: @@ -27965,7 +28115,6 @@ packages: /retry/0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} - dev: false /retry/0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} @@ -28152,14 +28301,6 @@ packages: dependencies: loose-envify: 1.4.0 - /schema-dts/1.1.0_typescript@4.9.4: - resolution: {integrity: sha512-vdmbs/5ycj4zyKpZIDqTcy+IZi4s7c38RVAYuDmRi7zgxUT8wRWPMLzg0jr7FjdVunYu9yZ00F3+XcZTTFcTOQ==} - peerDependencies: - typescript: '>=4.1.0' - dependencies: - typescript: 4.9.4 - dev: false - /schema-utils/3.1.2: resolution: {integrity: sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==} engines: {node: '>= 10.13.0'} @@ -28811,7 +28952,6 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: minipass: 7.0.3 - dev: false /ssri/8.0.1: resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} @@ -30008,37 +30148,6 @@ packages: typescript: 5.2.2 dev: true - /ts-node/10.9.1_fodzh64fuekdilycyvke2qmf2e: - resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@swc/core': 1.3.26 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.3 - '@types/node': 18.11.18 - acorn: 8.10.0 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 4.9.4 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - /ts-node/10.9.1_jch25vbq34sbabk3unb5q5rkqi: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true @@ -30101,6 +30210,37 @@ packages: yn: 3.1.1 dev: true + /ts-node/10.9.1_v2fyojhutw2zqp3xtxirvaa4jq: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@swc/core': 1.3.26 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 18.11.18 + acorn: 8.10.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.2.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + /ts-node/10.9.1_xj5cs2fmhcigm4w5bhhtewqeja: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true @@ -30468,16 +30608,6 @@ packages: tslib: 1.14.1 dev: true - /tsutils/3.21.0_typescript@4.9.4: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - typescript: 4.9.4 - dev: true - /tsutils/3.21.0_typescript@4.9.5: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -30687,6 +30817,11 @@ packages: engines: {node: '>=16'} dev: true + /type-fest/4.6.0: + resolution: {integrity: sha512-rLjWJzQFOq4xw7MgJrCZ6T1jIOvvYElXT12r+y0CC6u67hegDHaxcPqb2fZHOGlqxugGQPNB1EnTezjBetkwkw==} + engines: {node: '>=16'} + dev: false + /type-is/1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -30722,6 +30857,7 @@ packages: resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} engines: {node: '>=4.2.0'} hasBin: true + dev: true /typescript/4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} @@ -30742,7 +30878,6 @@ packages: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} hasBin: true - dev: true /ufo/1.3.0: resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} @@ -30851,7 +30986,6 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: unique-slug: 4.0.0 - dev: false /unique-slug/2.0.2: resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} @@ -30864,7 +30998,6 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: imurmurhash: 0.1.4 - dev: false /unique-string/2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} @@ -31258,7 +31391,6 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: builtins: 5.0.1 - dev: false /vary/1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} @@ -32011,7 +32143,6 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: false /why-is-node-running/2.2.2: resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} From 55946fcdb7033e01529511cb23ce68703018484a Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 26 Oct 2023 19:45:19 +0100 Subject: [PATCH 07/95] Update `@trigger.dev/nextjs` to support Next.js 14 (#690) --- .changeset/afraid-lies-listen.md | 5 + packages/nextjs/package.json | 10 +- pnpm-lock.yaml | 226 ++++++++++++++++++++++--------- 3 files changed, 172 insertions(+), 69 deletions(-) create mode 100644 .changeset/afraid-lies-listen.md diff --git a/.changeset/afraid-lies-listen.md b/.changeset/afraid-lies-listen.md new file mode 100644 index 000000000..21aade5b7 --- /dev/null +++ b/.changeset/afraid-lies-listen.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/nextjs": patch +--- + +Update to support Next.js 14 diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index fa60cd0de..ea1bf5a66 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -22,11 +22,13 @@ "@trigger.dev/tsconfig": "workspace:*", "@types/debug": "^4.1.7", "@types/ws": "^8.5.3", - "next": "^13.3.1", + "next": "^14.0.0", "rimraf": "^3.0.2", "tsup": "^6.5.0", "tsx": "^3.12.1", - "typescript": "^4.8.4" + "typescript": "^4.8.4", + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "scripts": { "clean": "rimraf dist", @@ -35,7 +37,7 @@ }, "peerDependencies": { "@trigger.dev/sdk": "workspace:^2.2.2", - "next": ">=12.0.0 <14.0.0" + "next": ">=12.0.0" }, "dependencies": { "debug": "^4.3.4" @@ -43,4 +45,4 @@ "engines": { "node": ">=18.0.0" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67dac69aa..7db846447 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -935,7 +935,9 @@ importers: '@types/debug': ^4.1.7 '@types/ws': ^8.5.3 debug: ^4.3.4 - next: ^13.3.1 + next: ^14.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 rimraf: ^3.0.2 tsup: ^6.5.0 tsx: ^3.12.1 @@ -946,7 +948,9 @@ importers: '@trigger.dev/tsconfig': link:../../config-packages/tsconfig '@types/debug': 4.1.7 '@types/ws': 8.5.4 - next: 13.3.1 + next: 14.0.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 rimraf: 3.0.2 tsup: 6.6.3_typescript@4.9.5 tsx: 3.12.2 @@ -7803,11 +7807,16 @@ packages: /@next/env/13.3.1: resolution: {integrity: sha512-EDtCoedIZC7JlUQ3uaQpSc4aVmyhbLHmQVALg7pFfQgOTjgSnn7mKtA0DiCMkYvvsx6aFb5octGMtWrOtGXW9A==} + dev: false /@next/env/13.4.12: resolution: {integrity: sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==} dev: false + /@next/env/14.0.0: + resolution: {integrity: sha512-cIKhxkfVELB6hFjYsbtEeTus2mwrTC+JissfZYM0n+8Fv+g8ucUfOlm3VEDtwtwydZ0Nuauv3bl0qF82nnCAqA==} + dev: true + /@next/eslint-plugin-next/13.4.12: resolution: {integrity: sha512-6rhK9CdxEgj/j1qvXIyLTWEaeFv7zOK8yJMulz3Owel0uek0U9MJCGzmKgYxM3aAUBo3gKeywCZKyQnJKto60A==} dependencies: @@ -7826,6 +7835,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: false optional: true /@next/swc-darwin-arm64/13.4.12: @@ -7837,12 +7847,22 @@ packages: dev: false optional: true + /@next/swc-darwin-arm64/14.0.0: + resolution: {integrity: sha512-HQKi159jCz4SRsPesVCiNN6tPSAFUkOuSkpJsqYTIlbHLKr1mD6be/J0TvWV6fwJekj81bZV9V/Tgx3C2HO9lA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@next/swc-darwin-x64/13.3.1: resolution: {integrity: sha512-lT36yYxosCfLtplFzJWgo0hrPu6/do8+msgM7oQkPeohDNdhjtjFUgOOwdSnPublLR6Mo2Ym4P/wl5OANuD2bw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] requiresBuild: true + dev: false optional: true /@next/swc-darwin-x64/13.4.12: @@ -7854,12 +7874,22 @@ packages: dev: false optional: true + /@next/swc-darwin-x64/14.0.0: + resolution: {integrity: sha512-4YyQLMSaCgX/kgC1jjF3s3xSoBnwHuDhnF6WA1DWNEYRsbOOPWjcYhv8TKhRe2ApdOam+VfQSffC4ZD+X4u1Cg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@next/swc-linux-arm64-gnu/13.3.1: resolution: {integrity: sha512-wRb76nLWJhonH8s3kxC/1tFguEkeOPayIwe9mkaz1G/yeS3OrjeyKMJsb4+Kdg0zbTo53bNCOl59NNtDM7yyyw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] requiresBuild: true + dev: false optional: true /@next/swc-linux-arm64-gnu/13.4.12: @@ -7871,12 +7901,22 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-gnu/14.0.0: + resolution: {integrity: sha512-io7fMkJ28Glj7SH8yvnlD6naIhRDnDxeE55CmpQkj3+uaA2Hko6WGY2pT5SzpQLTnGGnviK85cy8EJ2qsETj/g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@next/swc-linux-arm64-musl/13.3.1: resolution: {integrity: sha512-qz3BzjJRZ16Iq/jrp+pjiYOc0jTjHlfmxQmZk9x/+5uhRP6/eWQSTAPVJ33BMo6oK5O5N4644OgTAbzXzorecg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] requiresBuild: true + dev: false optional: true /@next/swc-linux-arm64-musl/13.4.12: @@ -7888,12 +7928,22 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-musl/14.0.0: + resolution: {integrity: sha512-nC2h0l1Jt8LEzyQeSs/BKpXAMe0mnHIMykYALWaeddTqCv5UEN8nGO3BG8JAqW/Y8iutqJsaMe2A9itS0d/r8w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@next/swc-linux-x64-gnu/13.3.1: resolution: {integrity: sha512-6mgkLmwlyWlomQmpl21I3hxgqE5INoW4owTlcLpNsd1V4wP+J46BlI/5zV5KWWbzjfncIqzXoeGs5Eg+1GHODA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] requiresBuild: true + dev: false optional: true /@next/swc-linux-x64-gnu/13.4.12: @@ -7905,12 +7955,22 @@ packages: dev: false optional: true + /@next/swc-linux-x64-gnu/14.0.0: + resolution: {integrity: sha512-Wf+WjXibJQ7hHXOdNOmSMW5bxeJHVf46Pwb3eLSD2L76NrytQlif9NH7JpHuFlYKCQGfKfgSYYre5rIfmnSwQw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@next/swc-linux-x64-musl/13.3.1: resolution: {integrity: sha512-uqm5sielhQmKJM+qayIhgZv1KlS5pqTdQ99b+Z7hMWryXS96qE0DftTmMZowBcUL6x7s2vSXyH5wPtO1ON7LBg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] requiresBuild: true + dev: false optional: true /@next/swc-linux-x64-musl/13.4.12: @@ -7922,12 +7982,22 @@ packages: dev: false optional: true + /@next/swc-linux-x64-musl/14.0.0: + resolution: {integrity: sha512-WTZb2G7B+CTsdigcJVkRxfcAIQj7Lf0ipPNRJ3vlSadU8f0CFGv/ST+sJwF5eSwIe6dxKoX0DG6OljDBaad+rg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@next/swc-win32-arm64-msvc/13.3.1: resolution: {integrity: sha512-WomIiTj/v3LevltlibNQKmvrOymNRYL+a0dp5R73IwPWN5FvXWwSELN/kiNALig/+T3luc4qHNTyvMCp9L6U5Q==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] requiresBuild: true + dev: false optional: true /@next/swc-win32-arm64-msvc/13.4.12: @@ -7939,12 +8009,22 @@ packages: dev: false optional: true + /@next/swc-win32-arm64-msvc/14.0.0: + resolution: {integrity: sha512-7R8/x6oQODmNpnWVW00rlWX90sIlwluJwcvMT6GXNIBOvEf01t3fBg0AGURNKdTJg2xNuP7TyLchCL7Lh2DTiw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@next/swc-win32-ia32-msvc/13.3.1: resolution: {integrity: sha512-M+PoH+0+q658wRUbs285RIaSTYnGBSTdweH/0CdzDgA6Q4rBM0sQs4DHmO3BPP0ltCO/vViIoyG7ks66XmCA5g==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] requiresBuild: true + dev: false optional: true /@next/swc-win32-ia32-msvc/13.4.12: @@ -7956,12 +8036,22 @@ packages: dev: false optional: true + /@next/swc-win32-ia32-msvc/14.0.0: + resolution: {integrity: sha512-RLK1nELvhCnxaWPF07jGU4x3tjbyx2319q43loZELqF0+iJtKutZ+Lk8SVmf/KiJkYBc7Cragadz7hb3uQvz4g==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@next/swc-win32-x64-msvc/13.3.1: resolution: {integrity: sha512-Sl1F4Vp5Z1rNXWZYqJwMuWRRol4bqOB6+/d7KqkgQ4AcafKPN1PZmpkCoxv4UFHtFNIB7EotnuIhtXu3zScicQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] requiresBuild: true + dev: false optional: true /@next/swc-win32-x64-msvc/13.4.12: @@ -7973,6 +8063,15 @@ packages: dev: false optional: true + /@next/swc-win32-x64-msvc/14.0.0: + resolution: {integrity: sha512-g6hLf1SUko+hnnaywQQZzzb3BRecQsoKkF3o/C+F+dOA4w/noVAJngUVkfwF0+2/8FzNznM7ofM6TGZO9svn7w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@nicolo-ribaudo/eslint-scope-5-internals/5.1.1-v1: resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} dependencies: @@ -12785,6 +12884,7 @@ packages: resolution: {integrity: sha512-SjY/p4MmECVVEWspzSRpQEM3sjR17sP8PbGxELWrT+YZMBfiUyt1MRUNjMV23zohwlG2HYtCQOsCwsTHguXkyg==} dependencies: tslib: 2.6.2 + dev: false /@swc/helpers/0.5.1: resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==} @@ -12792,6 +12892,12 @@ packages: tslib: 2.6.2 dev: false + /@swc/helpers/0.5.2: + resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} + dependencies: + tslib: 2.6.2 + dev: true + /@szmarczak/http-timer/1.1.2: resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} engines: {node: '>=6'} @@ -24738,48 +24844,6 @@ packages: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: false - /next/13.3.1: - resolution: {integrity: sha512-eByWRxPzKHs2oQz1yE41LX35umhz86ZSZ+mYyXBqn2IBi2hyUqxBA88avywdr4uyH+hCJczegGsDGWbzQA5Rqw==} - engines: {node: '>=14.18.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - fibers: '>= 3.1.0' - node-sass: ^6.0.0 || ^7.0.0 - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - fibers: - optional: true - node-sass: - optional: true - sass: - optional: true - dependencies: - '@next/env': 13.3.1 - '@swc/helpers': 0.5.0 - busboy: 1.6.0 - caniuse-lite: 1.0.30001532 - postcss: 8.4.14 - styled-jsx: 5.1.1 - optionalDependencies: - '@next/swc-darwin-arm64': 13.3.1 - '@next/swc-darwin-x64': 13.3.1 - '@next/swc-linux-arm64-gnu': 13.3.1 - '@next/swc-linux-arm64-musl': 13.3.1 - '@next/swc-linux-x64-gnu': 13.3.1 - '@next/swc-linux-x64-musl': 13.3.1 - '@next/swc-win32-arm64-msvc': 13.3.1 - '@next/swc-win32-ia32-msvc': 13.3.1 - '@next/swc-win32-x64-msvc': 13.3.1 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: true - /next/13.3.1_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-eByWRxPzKHs2oQz1yE41LX35umhz86ZSZ+mYyXBqn2IBi2hyUqxBA88avywdr4uyH+hCJczegGsDGWbzQA5Rqw==} engines: {node: '>=14.18.0'} @@ -24867,6 +24931,45 @@ packages: - babel-plugin-macros dev: false + /next/14.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-J0jHKBJpB9zd4+c153sair0sz44mbaCHxggs8ryVXSFBuBqJ8XdE9/ozoV85xGh2VnSjahwntBZZgsihL9QznA==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + sass: + optional: true + dependencies: + '@next/env': 14.0.0 + '@swc/helpers': 0.5.2 + busboy: 1.6.0 + caniuse-lite: 1.0.30001532 + postcss: 8.4.31 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + styled-jsx: 5.1.1_react@18.2.0 + watchpack: 2.4.0 + optionalDependencies: + '@next/swc-darwin-arm64': 14.0.0 + '@next/swc-darwin-x64': 14.0.0 + '@next/swc-linux-arm64-gnu': 14.0.0 + '@next/swc-linux-arm64-musl': 14.0.0 + '@next/swc-linux-x64-gnu': 14.0.0 + '@next/swc-linux-x64-musl': 14.0.0 + '@next/swc-win32-arm64-msvc': 14.0.0 + '@next/swc-win32-ia32-msvc': 14.0.0 + '@next/swc-win32-x64-msvc': 14.0.0 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: true + /ngrok/5.0.0-beta.2: resolution: {integrity: sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ==} engines: {node: '>=14.2'} @@ -26444,13 +26547,13 @@ packages: postcss: 8.4.29 dev: true - /postcss-scss/4.0.9_postcss@8.4.29: + /postcss-scss/4.0.9_postcss@8.4.31: resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.4.29 dependencies: - postcss: 8.4.29 + postcss: 8.4.31 dev: true /postcss-selector-parser/6.0.10: @@ -26482,6 +26585,7 @@ packages: nanoid: 3.3.6 picocolors: 1.0.0 source-map-js: 1.0.2 + dev: false /postcss/8.4.21: resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} @@ -26516,6 +26620,15 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postcss/8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + /postgres-array/2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -29306,22 +29419,6 @@ packages: inline-style-parser: 0.1.1 dev: true - /styled-jsx/5.1.1: - resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - dependencies: - client-only: 0.0.1 - dev: true - /styled-jsx/5.1.1_react@18.2.0: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -29337,7 +29434,6 @@ packages: dependencies: client-only: 0.0.1 react: 18.2.0 - dev: false /stylis/4.3.0: resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==} @@ -29474,8 +29570,8 @@ packages: eslint-scope: 7.2.0 eslint-visitor-keys: 3.4.2 espree: 9.6.0 - postcss: 8.4.29 - postcss-scss: 4.0.9_postcss@8.4.29 + postcss: 8.4.31 + postcss-scss: 4.0.9_postcss@8.4.31 svelte: 4.2.1 dev: true From 0ca8296b9acfa6fc55e141411a7b89f24f8b3d03 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 26 Oct 2023 20:03:37 +0100 Subject: [PATCH 08/95] chore: Update version for release (#686) Co-authored-by: github-actions[bot] --- .changeset/afraid-lies-listen.md | 5 ----- .changeset/new-llamas-call.md | 5 ----- .changeset/soft-ties-turn.md | 6 ------ .changeset/tricky-games-agree.md | 5 ----- integrations/airtable/CHANGELOG.md | 9 +++++++++ integrations/airtable/package.json | 6 +++--- integrations/github/CHANGELOG.md | 9 +++++++++ integrations/github/package.json | 6 +++--- integrations/linear/CHANGELOG.md | 9 +++++++++ integrations/linear/package.json | 6 +++--- integrations/openai/CHANGELOG.md | 9 +++++++++ integrations/openai/package.json | 6 +++--- integrations/plain/CHANGELOG.md | 9 +++++++++ integrations/plain/package.json | 6 +++--- integrations/replicate/CHANGELOG.md | 9 +++++++++ integrations/replicate/package.json | 6 +++--- integrations/resend/CHANGELOG.md | 9 +++++++++ integrations/resend/package.json | 6 +++--- integrations/sendgrid/CHANGELOG.md | 9 +++++++++ integrations/sendgrid/package.json | 6 +++--- integrations/slack/CHANGELOG.md | 8 ++++++++ integrations/slack/package.json | 4 ++-- integrations/stripe/CHANGELOG.md | 9 +++++++++ integrations/stripe/package.json | 6 +++--- integrations/supabase/CHANGELOG.md | 9 +++++++++ integrations/supabase/package.json | 6 +++--- integrations/typeform/CHANGELOG.md | 9 +++++++++ integrations/typeform/package.json | 6 +++--- packages/astro/CHANGELOG.md | 8 ++++++++ packages/astro/package.json | 4 ++-- packages/cli/CHANGELOG.md | 7 +++++++ packages/cli/package.json | 2 +- packages/core/CHANGELOG.md | 6 ++++++ packages/core/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 2 ++ packages/eslint-plugin/package.json | 2 +- packages/express/CHANGELOG.md | 9 +++++++++ packages/express/package.json | 6 +++--- packages/integration-kit/CHANGELOG.md | 2 ++ packages/integration-kit/package.json | 2 +- packages/nestjs/CHANGELOG.md | 8 ++++++++ packages/nestjs/package.json | 6 +++--- packages/nextjs/CHANGELOG.md | 9 +++++++++ packages/nextjs/package.json | 4 ++-- packages/react/CHANGELOG.md | 7 +++++++ packages/react/package.json | 4 ++-- packages/remix/CHANGELOG.md | 8 ++++++++ packages/remix/package.json | 4 ++-- packages/sveltekit/CHANGELOG.md | 8 ++++++++ packages/sveltekit/package.json | 4 ++-- packages/testing/CHANGELOG.md | 9 +++++++++ packages/testing/package.json | 2 +- packages/trigger-sdk/CHANGELOG.md | 9 +++++++++ packages/trigger-sdk/package.json | 4 ++-- 54 files changed, 257 insertions(+), 79 deletions(-) delete mode 100644 .changeset/afraid-lies-listen.md delete mode 100644 .changeset/new-llamas-call.md delete mode 100644 .changeset/soft-ties-turn.md delete mode 100644 .changeset/tricky-games-agree.md diff --git a/.changeset/afraid-lies-listen.md b/.changeset/afraid-lies-listen.md deleted file mode 100644 index 21aade5b7..000000000 --- a/.changeset/afraid-lies-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/nextjs": patch ---- - -Update to support Next.js 14 diff --git a/.changeset/new-llamas-call.md b/.changeset/new-llamas-call.md deleted file mode 100644 index 439125c5e..000000000 --- a/.changeset/new-llamas-call.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/express": patch ---- - -Remove web-fetch dependency and use native node Request/Response diff --git a/.changeset/soft-ties-turn.md b/.changeset/soft-ties-turn.md deleted file mode 100644 index 0f9393f1e..000000000 --- a/.changeset/soft-ties-turn.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -implement functionality to cancel job runs triggered by a given eventId. diff --git a/.changeset/tricky-games-agree.md b/.changeset/tricky-games-agree.md deleted file mode 100644 index 4d260ed31..000000000 --- a/.changeset/tricky-games-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -set error messages in runTask and executeJob diff --git a/integrations/airtable/CHANGELOG.md b/integrations/airtable/CHANGELOG.md index 2bf27b3d6..f7d106a91 100644 --- a/integrations/airtable/CHANGELOG.md +++ b/integrations/airtable/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/airtable +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/airtable/package.json b/integrations/airtable/package.json index 1679d58e9..715dd0c1f 100644 --- a/integrations/airtable/package.json +++ b/integrations/airtable/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/airtable", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev integration for airtable", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -26,8 +26,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.2", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/integration-kit": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.3", "airtable": "^0.12.1", "zod": "3.22.3" }, diff --git a/integrations/github/CHANGELOG.md b/integrations/github/CHANGELOG.md index 800d3f703..cb73ba2ab 100644 --- a/integrations/github/CHANGELOG.md +++ b/integrations/github/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/github +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/github/package.json b/integrations/github/package.json index 50a8adfbe..960f67461 100644 --- a/integrations/github/package.json +++ b/integrations/github/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/github", - "version": "2.2.2", + "version": "2.2.3", "description": "The official GitHub integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -29,8 +29,8 @@ "@octokit/request": "^6.2.5", "@octokit/request-error": "^4.0.1", "@octokit/webhooks": "^10.4.0", - "@trigger.dev/integration-kit": "workspace:^2.2.2", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/integration-kit": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.3", "octokit": "^2.0.14", "zod": "3.22.3" }, diff --git a/integrations/linear/CHANGELOG.md b/integrations/linear/CHANGELOG.md index 568fc8c4d..9cc3129a6 100644 --- a/integrations/linear/CHANGELOG.md +++ b/integrations/linear/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/linear +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/linear/package.json b/integrations/linear/package.json index 837204763..8c3bc1441 100644 --- a/integrations/linear/package.json +++ b/integrations/linear/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/linear", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev integration for @linear/sdk", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -27,8 +27,8 @@ }, "dependencies": { "@linear/sdk": "^8.0.0", - "@trigger.dev/integration-kit": "workspace:^2.2.2", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/integration-kit": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.3", "zod": "3.22.3" }, "engines": { diff --git a/integrations/openai/CHANGELOG.md b/integrations/openai/CHANGELOG.md index 3b2bea7d9..cea8638c9 100644 --- a/integrations/openai/CHANGELOG.md +++ b/integrations/openai/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/slack +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/openai/package.json b/integrations/openai/package.json index dbd31854b..2eb127fe0 100644 --- a/integrations/openai/package.json +++ b/integrations/openai/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/openai", - "version": "2.2.2", + "version": "2.2.3", "description": "The official OpenAI integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -27,8 +27,8 @@ }, "dependencies": { "openai": "^4.13.0", - "@trigger.dev/sdk": "workspace:^2.2.2", - "@trigger.dev/integration-kit": "workspace:^2.2.2" + "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.3" }, "engines": { "node": ">=18.0.0" diff --git a/integrations/plain/CHANGELOG.md b/integrations/plain/CHANGELOG.md index 6966b7e8e..f30d3d404 100644 --- a/integrations/plain/CHANGELOG.md +++ b/integrations/plain/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/plain +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/plain/package.json b/integrations/plain/package.json index 19a951305..b258721cf 100644 --- a/integrations/plain/package.json +++ b/integrations/plain/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/plain", - "version": "2.2.2", + "version": "2.2.3", "description": "The official Plain.com integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -24,8 +24,8 @@ "build:tsup": "tsup" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.2", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/integration-kit": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.3", "@team-plain/typescript-sdk": "^2.7.0" }, "engines": { diff --git a/integrations/replicate/CHANGELOG.md b/integrations/replicate/CHANGELOG.md index 7e6ca3e22..de4a70f88 100644 --- a/integrations/replicate/CHANGELOG.md +++ b/integrations/replicate/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/replicate +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/replicate/package.json b/integrations/replicate/package.json index 3f3a4e268..3c67931ec 100644 --- a/integrations/replicate/package.json +++ b/integrations/replicate/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/replicate", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev integration for replicate", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -26,8 +26,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.2", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/integration-kit": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.3", "replicate": "^0.18.1", "zod": "3.22.3" }, diff --git a/integrations/resend/CHANGELOG.md b/integrations/resend/CHANGELOG.md index a6be1cf55..4772bf573 100644 --- a/integrations/resend/CHANGELOG.md +++ b/integrations/resend/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/resend +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/resend/package.json b/integrations/resend/package.json index 003c1affb..83dcaffdb 100644 --- a/integrations/resend/package.json +++ b/integrations/resend/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/resend", - "version": "2.2.2", + "version": "2.2.3", "description": "The official Resend.com integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -24,8 +24,8 @@ "build:tsup": "tsup" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.2", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/integration-kit": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.3", "resend": "^1.0.0" }, "engines": { diff --git a/integrations/sendgrid/CHANGELOG.md b/integrations/sendgrid/CHANGELOG.md index 0cc1ca8f9..b249932f1 100644 --- a/integrations/sendgrid/CHANGELOG.md +++ b/integrations/sendgrid/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/sendgrid +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/sendgrid/package.json b/integrations/sendgrid/package.json index d7237407c..e2e525355 100644 --- a/integrations/sendgrid/package.json +++ b/integrations/sendgrid/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sendgrid", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev integration for @sendgrid/mail", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -27,8 +27,8 @@ }, "dependencies": { "@sendgrid/mail": "^7.7.0", - "@trigger.dev/sdk": "workspace:^2.2.2", - "@trigger.dev/integration-kit": "workspace:^2.2.2" + "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.3" }, "engines": { "node": ">=16.8.0" diff --git a/integrations/slack/CHANGELOG.md b/integrations/slack/CHANGELOG.md index 16f576a52..e6fd40018 100644 --- a/integrations/slack/CHANGELOG.md +++ b/integrations/slack/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/slack +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/slack/package.json b/integrations/slack/package.json index 79b4ef382..b8358b87b 100644 --- a/integrations/slack/package.json +++ b/integrations/slack/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/slack", - "version": "2.2.2", + "version": "2.2.3", "description": "The official Slack integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@slack/web-api": "^6.8.1", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/sdk": "workspace:^2.2.3", "zod": "3.22.3" }, "engines": { diff --git a/integrations/stripe/CHANGELOG.md b/integrations/stripe/CHANGELOG.md index b558f826b..63388a16b 100644 --- a/integrations/stripe/CHANGELOG.md +++ b/integrations/stripe/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/stripe +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/stripe/package.json b/integrations/stripe/package.json index b5998e8f0..ce12e029e 100644 --- a/integrations/stripe/package.json +++ b/integrations/stripe/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/stripe", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev integration for stripe", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -26,8 +26,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.2", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/integration-kit": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.3", "stripe": "^12.14.0", "zod": "3.22.3" }, diff --git a/integrations/supabase/CHANGELOG.md b/integrations/supabase/CHANGELOG.md index c77b3f10e..9dd755ac4 100644 --- a/integrations/supabase/CHANGELOG.md +++ b/integrations/supabase/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/supabase +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/supabase/package.json b/integrations/supabase/package.json index b7be7d7a0..b04bc7e3a 100644 --- a/integrations/supabase/package.json +++ b/integrations/supabase/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/supabase", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev integration for @supabase/supabase-js", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -27,8 +27,8 @@ }, "dependencies": { "@supabase/supabase-js": "^2.26.0", - "@trigger.dev/integration-kit": "workspace:^2.2.2", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/integration-kit": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.3", "supabase-management-js": "^0.1.4", "zod": "3.22.3" }, diff --git a/integrations/typeform/CHANGELOG.md b/integrations/typeform/CHANGELOG.md index adefb556d..8cb89e29b 100644 --- a/integrations/typeform/CHANGELOG.md +++ b/integrations/typeform/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/typeform +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/integration-kit@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/integrations/typeform/package.json b/integrations/typeform/package.json index 4bf24421c..43f849779 100644 --- a/integrations/typeform/package.json +++ b/integrations/typeform/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/typeform", - "version": "2.2.2", + "version": "2.2.3", "description": "The official Typeform integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -25,8 +25,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.2", - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/integration-kit": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.3", "@typeform/api-client": "^1.8.0", "zod": "3.22.3" }, diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index b77e8c9f0..b9acd3be8 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/astro +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/astro/package.json b/packages/astro/package.json index 5a8e85870..43f63d224 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,7 +1,7 @@ { "name": "@trigger.dev/astro", "description": "An Astro-native integration for Trigger.dev background jobs platform", - "version": "2.2.2", + "version": "2.2.3", "main": "./dist/index.js", "types": "./dist/index.d.ts", "files": [ @@ -20,7 +20,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.2" + "@trigger.dev/sdk": "workspace:^2.2.3" }, "devDependencies": { "astro": "^3.0.12", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 2e8b23262..65fc5f3d9 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,12 @@ # create-trigger +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] + - @trigger.dev/core@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index a3f8ba424..946e2658c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/cli", - "version": "2.2.2", + "version": "2.2.3", "description": "The Trigger.dev CLI", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 6848abf3d..01b149658 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # internal-platform +## 2.2.3 + +### Patch Changes + +- 6e1b8a11: implement functionality to cancel job runs triggered by a given eventId. + ## 2.2.2 ## 2.2.1 diff --git a/packages/core/package.json b/packages/core/package.json index 39ab3cfb3..7fe870fd6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/core", - "version": "2.2.2", + "version": "2.2.3", "description": "Core code used across the Trigger.dev SDK and platform", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index fe7537502..827a90c47 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,7 @@ # @trigger.dev/eslint-plugin +## 2.2.3 + ## 2.2.2 ## 2.2.1 diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index b372aad57..f0572b599 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/eslint-plugin", - "version": "2.2.2", + "version": "2.2.3", "description": "ESLint plugin with trigger.dev best practices", "keywords": [ "eslint", diff --git a/packages/express/CHANGELOG.md b/packages/express/CHANGELOG.md index 694496dee..29a37876c 100644 --- a/packages/express/CHANGELOG.md +++ b/packages/express/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/express +## 2.2.3 + +### Patch Changes + +- e80aa282: Remove web-fetch dependency and use native node Request/Response +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/express/package.json b/packages/express/package.json index 12931daed..94fa55400 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/express", - "version": "2.2.2", + "version": "2.2.3", "description": "Official Express adapter for Trigger.dev", "license": "MIT", "main": "./dist/index.js", @@ -19,7 +19,7 @@ "./package.json": "./package.json" }, "devDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/sdk": "workspace:^2.2.3", "@trigger.dev/tsconfig": "workspace:*", "@types/debug": "^4.1.7", "@types/express": "^4.17.13", @@ -33,7 +33,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.2" + "@trigger.dev/sdk": "workspace:^2.2.3" }, "dependencies": { "debug": "^4.3.4", diff --git a/packages/integration-kit/CHANGELOG.md b/packages/integration-kit/CHANGELOG.md index f198b3066..4db600a94 100644 --- a/packages/integration-kit/CHANGELOG.md +++ b/packages/integration-kit/CHANGELOG.md @@ -1,5 +1,7 @@ # @trigger.dev/integration-kit +## 2.2.3 + ## 2.2.2 ## 2.2.1 diff --git a/packages/integration-kit/package.json b/packages/integration-kit/package.json index 22fe6711a..5f7423b1e 100644 --- a/packages/integration-kit/package.json +++ b/packages/integration-kit/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/integration-kit", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev Integration Kit has helpers to make creating integrations easier", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/nestjs/CHANGELOG.md b/packages/nestjs/CHANGELOG.md index bf8120e10..f1046f4ec 100644 --- a/packages/nestjs/CHANGELOG.md +++ b/packages/nestjs/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/nestjs +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 0a4e611e6..24cb6caf3 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/nestjs", - "version": "2.2.2", + "version": "2.2.3", "description": "Official NestJS adapter for Trigger.dev", "license": "MIT", "main": "./dist/index.js", @@ -19,7 +19,7 @@ "./package.json": "./package.json" }, "devDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/sdk": "workspace:^2.2.3", "@trigger.dev/tsconfig": "workspace:*", "@types/debug": "^4.1.7", "@types/express": "^4.17.13", @@ -35,7 +35,7 @@ }, "peerDependencies": { "@nestjs/common": ">=10.0.0", - "@trigger.dev/sdk": "workspace:^2.2.2" + "@trigger.dev/sdk": "workspace:^2.2.3" }, "dependencies": { "@nestjs/common": "^10.2.4", diff --git a/packages/nextjs/CHANGELOG.md b/packages/nextjs/CHANGELOG.md index 176c27a41..d096104ed 100644 --- a/packages/nextjs/CHANGELOG.md +++ b/packages/nextjs/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/nextjs +## 2.2.3 + +### Patch Changes + +- 55946fcd: Update to support Next.js 14 +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index ea1bf5a66..d3cc21056 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/nextjs", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev Next.js integration", "license": "MIT", "main": "./dist/index.js", @@ -36,7 +36,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/sdk": "workspace:^2.2.3", "next": ">=12.0.0" }, "dependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 35487d2b5..a46d7e7c0 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/react +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] + - @trigger.dev/core@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index dcd47890c..34207d3a3 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/react", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev React SDK", "license": "MIT", "types": "dist/index.d.ts", @@ -27,7 +27,7 @@ }, "dependencies": { "@tanstack/react-query": "5.0.0-beta.2", - "@trigger.dev/core": "workspace:^2.2.2", + "@trigger.dev/core": "workspace:^2.2.3", "debug": "^4.3.4", "zod": "3.22.3" }, diff --git a/packages/remix/CHANGELOG.md b/packages/remix/CHANGELOG.md index c9fbe60bd..6e1b3b001 100644 --- a/packages/remix/CHANGELOG.md +++ b/packages/remix/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/remix +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/remix/package.json b/packages/remix/package.json index 660f08870..3c9dbfc37 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/remix", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev Remix integration", "license": "MIT", "main": "./dist/index.js", @@ -34,7 +34,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.2", + "@trigger.dev/sdk": "workspace:^2.2.3", "@remix-run/server-runtime": ">1.19.0" }, "dependencies": { diff --git a/packages/sveltekit/CHANGELOG.md b/packages/sveltekit/CHANGELOG.md index fb5b71745..34fd4d40d 100644 --- a/packages/sveltekit/CHANGELOG.md +++ b/packages/sveltekit/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/sveltekit +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index b3039431e..75842fa1f 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sveltekit", - "version": "2.2.2", + "version": "2.2.3", "description": "Trigger.dev svelteKit integration", "license": "MIT", "main": "./dist/index.js", @@ -33,7 +33,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.2" + "@trigger.dev/sdk": "workspace:^2.2.3" }, "dependencies": { "debug": "^4.3.4" diff --git a/packages/testing/CHANGELOG.md b/packages/testing/CHANGELOG.md index e195e1044..bb6450413 100644 --- a/packages/testing/CHANGELOG.md +++ b/packages/testing/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/testing +## 2.2.3 + +### Patch Changes + +- Updated dependencies [6e1b8a11] +- Updated dependencies [c4533c36] + - @trigger.dev/sdk@2.2.3 + - @trigger.dev/core@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/testing/package.json b/packages/testing/package.json index 550bc81a8..e57b6319a 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,7 +1,7 @@ { "name": "@trigger.dev/testing", "description": "A collection of useful tools to write tests for Trigger.dev.", - "version": "2.2.2", + "version": "2.2.3", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/trigger-sdk/CHANGELOG.md b/packages/trigger-sdk/CHANGELOG.md index 86ae5367b..0fadaaf08 100644 --- a/packages/trigger-sdk/CHANGELOG.md +++ b/packages/trigger-sdk/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/sdk +## 2.2.3 + +### Patch Changes + +- 6e1b8a11: implement functionality to cancel job runs triggered by a given eventId. +- c4533c36: set error messages in runTask and executeJob +- Updated dependencies [6e1b8a11] + - @trigger.dev/core@2.2.3 + ## 2.2.2 ### Patch Changes diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index fb226bb4c..e273cae7c 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sdk", - "version": "2.2.2", + "version": "2.2.3", "description": "trigger.dev Node.JS SDK", "license": "MIT", "main": "./dist/index.js", @@ -25,7 +25,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/core": "workspace:^2.2.2", + "@trigger.dev/core": "workspace:^2.2.3", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", From 6881de84ab77ef4149eaf23204b5f1e635b5ad93 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 26 Oct 2023 20:04:03 +0100 Subject: [PATCH 09/95] Fix pnpm lock file --- pnpm-lock.yaml | 54 +++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7db846447..3f9c5b43b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -378,8 +378,8 @@ importers: integrations/airtable: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x airtable: ^0.12.1 @@ -406,8 +406,8 @@ importers: '@octokit/types': ^9.2.3 '@octokit/webhooks': ^10.4.0 '@octokit/webhooks-types': ^6.10.0 - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' octokit: ^2.0.14 @@ -433,8 +433,8 @@ importers: integrations/linear: specifiers: '@linear/sdk': ^8.0.0 - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x rimraf: ^3.0.2 @@ -455,8 +455,8 @@ importers: integrations/openai: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' openai: ^4.13.0 @@ -477,8 +477,8 @@ importers: integrations/plain: specifiers: '@team-plain/typescript-sdk': ^2.7.0 - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' rimraf: ^3.0.2 @@ -495,8 +495,8 @@ importers: integrations/replicate: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x replicate: ^0.18.1 @@ -518,8 +518,8 @@ importers: integrations/resend: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' resend: ^1.0.0 @@ -538,8 +538,8 @@ importers: integrations/sendgrid: specifiers: '@sendgrid/mail': ^7.7.0 - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x rimraf: ^3.0.2 @@ -559,7 +559,7 @@ importers: integrations/slack: specifiers: '@slack/web-api': ^6.8.1 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' rimraf: ^3.0.2 @@ -577,8 +577,8 @@ importers: integrations/stripe: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@types/node': 16.x rimraf: ^3.0.2 stripe: ^12.14.0 @@ -601,8 +601,8 @@ importers: integrations/supabase: specifiers: '@supabase/supabase-js': ^2.26.0 - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/node': 18.x rimraf: ^3.0.2 @@ -625,8 +625,8 @@ importers: integrations/typeform: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.2 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/integration-kit': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.3 '@typeform/api-client': ^1.8.0 '@types/node': 16.x rimraf: ^3.0.2 @@ -860,7 +860,7 @@ importers: packages/express: specifiers: - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/express': ^4.17.13 @@ -906,7 +906,7 @@ importers: specifiers: '@nestjs/common': ^10.2.4 '@remix-run/web-fetch': ^4.3.5 - '@trigger.dev/sdk': workspace:^2.2.2 + '@trigger.dev/sdk': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/express': ^4.17.13 @@ -959,7 +959,7 @@ importers: packages/react: specifiers: '@tanstack/react-query': 5.0.0-beta.2 - '@trigger.dev/core': workspace:^2.2.2 + '@trigger.dev/core': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/react': 18.2.17 @@ -1054,7 +1054,7 @@ importers: packages/trigger-sdk: specifiers: - '@trigger.dev/core': workspace:^2.2.2 + '@trigger.dev/core': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/node': '18' From 401b7ee03d4c6d27cd44dfb26b6ba73e0462fce2 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Sat, 28 Oct 2023 13:09:01 +0000 Subject: [PATCH 10/95] Set NODE_ENV to production in supabase docs --- docs/documentation/guides/self-hosting/supabase.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/documentation/guides/self-hosting/supabase.mdx b/docs/documentation/guides/self-hosting/supabase.mdx index 0f40107f7..69f692ad6 100644 --- a/docs/documentation/guides/self-hosting/supabase.mdx +++ b/docs/documentation/guides/self-hosting/supabase.mdx @@ -29,6 +29,14 @@ ENCRYPTION_KEY=1189c93e399856a2a9a1454496171b2e ... ``` +3. Set `NODE_ENV` to production: + +```sh .env (excerpt) +... +NODE_ENV=production +... +``` + ## Create a Supabase DB Alternative: [Self-host Supabase](https://supabase.com/docs/guides/self-hosting/docker) @@ -84,7 +92,7 @@ ENCRYPTION_KEY=1189c93e399856a2a9a1454496171b2e DATABASE_URL=postgresql://postgres:@db..supabase.co:5432/postgres?schema=triggerdotdev DIRECT_URL=${DATABASE_URL} -NODE_ENV=development +NODE_ENV=production RUNTIME_PLATFORM=docker-compose ``` From 2635f4bd1009f72e8bd161209c8a93eb5f408a75 Mon Sep 17 00:00:00 2001 From: D-K-P Date: Mon, 30 Oct 2023 14:36:17 +0000 Subject: [PATCH 11/95] typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22e80d7ad..611d9505d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ All of October we're participating in Hacktoberfest and invite you to join us! W - Check out our [Hacktoberfest landing page](https://trigger.dev/hacktoberfest) for how to participate and win swag. - Contribute to either our [/trigger.dev](https://github.com/triggerdotdev/trigger.dev/labels/%F0%9F%8E%83%20hacktoberfest), [/api-reference](https://github.com/triggerdotdev/api-reference/issues?q=is%3Aopen+is%3Aissue+label%3A%F0%9F%8E%83hacktoberfest) or [/jobs-showcase](https://github.com/triggerdotdev/jobs-showcase/labels/%F0%9F%8E%83%20hacktoberfest) repositories and complete issues marked `🎃 Hacktoberfest` to be eligible for swag. -- Join our [Discord](https://discord.gg/JtBAxBr2m3) and get involved in with the community. +- Join our [Discord](https://discord.gg/JtBAxBr2m3) and get involved with the community. _New to Hacktober? Check out the [Hacktoberfest website](https://hacktoberfest.digitalocean.com/) for more information._ From 9c4be40adc136563c052734186aa481ea7174274 Mon Sep 17 00:00:00 2001 From: Hemachandar <132386067+hmacr@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:54:03 +0530 Subject: [PATCH 12/95] fix: append nanoid for dynamic trigger reg event (#628) * fix: append nanoid for dynamic trigger reg event * use idempotency-key as event-id * add changeset --- .changeset/strange-schools-boil.md | 5 +++++ ...pointSlug.triggers.$id.registrations.$key.ts | 14 +++++++++++++- packages/trigger-sdk/src/apiClient.ts | 17 ++++++++++++----- packages/trigger-sdk/src/triggerClient.ts | 9 +++++++-- packages/trigger-sdk/src/triggers/dynamic.ts | 3 ++- 5 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 .changeset/strange-schools-boil.md diff --git a/.changeset/strange-schools-boil.md b/.changeset/strange-schools-boil.md new file mode 100644 index 000000000..4941bdec3 --- /dev/null +++ b/.changeset/strange-schools-boil.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/sdk": patch +--- + +use idempotency-key as event-id for dynamic-trigger registrations diff --git a/apps/webapp/app/routes/api.v2.$endpointSlug.triggers.$id.registrations.$key.ts b/apps/webapp/app/routes/api.v2.$endpointSlug.triggers.$id.registrations.$key.ts index 2cb689e77..a59ae2fd5 100644 --- a/apps/webapp/app/routes/api.v2.$endpointSlug.triggers.$id.registrations.$key.ts +++ b/apps/webapp/app/routes/api.v2.$endpointSlug.triggers.$id.registrations.$key.ts @@ -10,6 +10,7 @@ import { authenticateApiRequest } from "~/services/apiAuth.server"; import { IngestSendEvent } from "~/services/events/ingestSendEvent.server"; import { logger } from "~/services/logger.server"; import { RegisterTriggerSourceServiceV2 } from "~/services/triggers/registerTriggerSourceV2.server"; +import { nanoid } from "nanoid"; const ParamsSchema = z.object({ endpointSlug: z.string(), @@ -17,6 +18,10 @@ const ParamsSchema = z.object({ key: z.string(), }); +const HeadersSchema = z.object({ + "idempotency-key": z.string().optional(), +}); + export async function action({ request, params }: ActionFunctionArgs) { logger.info("Registering trigger", { url: request.url }); @@ -77,11 +82,18 @@ export async function action({ request, params }: ActionFunctionArgs) { dynamicTriggerId: parsedParams.data.id, }; + const headers = HeadersSchema.safeParse(Object.fromEntries(request.headers)); + + const eventId = + headers.success && headers.data["idempotency-key"] + ? headers.data["idempotency-key"] + : `${registration.id}:${nanoid()}`; + const ingestEventService = new IngestSendEvent(); await ingestEventService.call( authenticatedEnv, { - id: registration.id, + id: eventId, name: REGISTER_SOURCE_EVENT_V2, source: "trigger.dev", payload, diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index f46f3384d..1b43842a2 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -289,7 +289,8 @@ export class ApiClient { client: string, id: string, key: string, - payload: RegisterTriggerBodyV2 + payload: RegisterTriggerBodyV2, + idempotencyKey?: string ): Promise { const apiKey = await this.#apiKey(); @@ -298,15 +299,21 @@ export class ApiClient { payload, }); + const headers: HeadersInit = { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }; + + if (idempotencyKey) { + headers["Idempotency-Key"] = idempotencyKey; + } + const response = await zodfetch( RegisterSourceEventSchemaV2, `${this.#apiUrl}/api/v2/${client}/triggers/${id}/registrations/${key}`, { method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${apiKey}`, - }, + headers: headers, body: JSON.stringify(payload), } ); diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index 9ed509d53..58023e8d8 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -623,8 +623,13 @@ export class TriggerClient { this.#registeredSchedules[key] = jobs; } - async registerTrigger(id: string, key: string, options: RegisterTriggerBodyV2) { - return this.#client.registerTrigger(this.id, id, key, options); + async registerTrigger( + id: string, + key: string, + options: RegisterTriggerBodyV2, + idempotencyKey?: string + ) { + return this.#client.registerTrigger(this.id, id, key, options, idempotencyKey); } async getAuth(id: string) { diff --git a/packages/trigger-sdk/src/triggers/dynamic.ts b/packages/trigger-sdk/src/triggers/dynamic.ts index 33c29bf75..36df964c7 100644 --- a/packages/trigger-sdk/src/triggers/dynamic.ts +++ b/packages/trigger-sdk/src/triggers/dynamic.ts @@ -140,7 +140,8 @@ export class DynamicTrigger< return this.#client.registerTrigger( this.id, key, - this.registeredTriggerForParams(params, options) + this.registeredTriggerForParams(params, options), + task.idempotencyKey ); }, { From 0876b875267311b0ee4522abed8788c7ed7e6f88 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Mon, 30 Oct 2023 16:24:46 +0000 Subject: [PATCH 13/95] No Tasks in a Run warning message (#688) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Card hover state is now grey * Added a callout warning if you don’t have any tasks * Conditionally show the no-task warning at the top of the run * New job to test the no-task warning message * Created a new example Job for the CLI that uses Tasks * Updated the Next.js cli joke job * Updated the Remix app cli job example * Updated the Astro app cli job example * Made sure the example job works in remix * Added changeset --------- Co-authored-by: Eric Allam --- .changeset/pretty-adults-admire.md | 5 ++ apps/webapp/app/components/run/RunCard.tsx | 2 +- .../webapp/app/components/run/RunOverview.tsx | 10 ++++ .../app/presenters/RunPresenter.server.ts | 1 + .../cli/src/templates/astro/exampleJob.js | 31 ++++++++--- .../cli/src/templates/nextjs/exampleJob.js | 33 +++++++++--- .../cli/src/templates/remix/exampleJob.js | 31 ++++++++--- references/job-catalog/package.json | 3 +- references/job-catalog/src/cli-example.ts | 54 +++++++++++++++++++ references/job-catalog/src/events.ts | 22 ++++++++ 10 files changed, 168 insertions(+), 24 deletions(-) create mode 100644 .changeset/pretty-adults-admire.md create mode 100644 references/job-catalog/src/cli-example.ts diff --git a/.changeset/pretty-adults-admire.md b/.changeset/pretty-adults-admire.md new file mode 100644 index 000000000..9283d53b2 --- /dev/null +++ b/.changeset/pretty-adults-admire.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/cli": patch +--- + +Added new example job which uses Tasks diff --git a/apps/webapp/app/components/run/RunCard.tsx b/apps/webapp/app/components/run/RunCard.tsx index 1731622c2..e3bc1e998 100644 --- a/apps/webapp/app/components/run/RunCard.tsx +++ b/apps/webapp/app/components/run/RunCard.tsx @@ -36,7 +36,7 @@ export function RunPanel({ ? "border-slate-850" : "border-slate-900", onClick && "cursor-pointer", - onClick && !selected && "hover:border-green-500/30", + onClick && !selected && "hover:border-slate-500/30", className )} onClick={() => onClick && onClick()} diff --git a/apps/webapp/app/components/run/RunOverview.tsx b/apps/webapp/app/components/run/RunOverview.tsx index 19da10d26..f33aa1668 100644 --- a/apps/webapp/app/components/run/RunOverview.tsx +++ b/apps/webapp/app/components/run/RunOverview.tsx @@ -159,6 +159,16 @@ export function RunOverview({ run, trigger, showRerun, paths }: RunOverviewProps
+ {(run.tasks.length === 0 || run.tasks.every((t) => t.noop)) && ( + + This Run completed but it did not use any Tasks – this can cause unpredictable + results. Read the docs to view the solution. + + )} Trigger { - // This logs a message to the console - await io.logger.info("🧪 Example Job: a joke with a delay"); - await io.logger.info("How do you comfort a JavaScript bug?"); - // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year + // Use a Task to generate a random number. Using a Tasks means it only runs once. + const result = await io.runTask("generate-random-number", async () => { + return { + num: Math.floor(Math.random() * 10000), + }; + }); + + // Use the random number in a joke and log it to the console. + await io.logger.info(`Why was the number ${result.num} afraid of the number 7?`); + + // Wait for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year. await io.wait("Wait 5 seconds for the punchline...", 5); - await io.logger.info("You console it! 🤦"); + + // Use a Task to display the answer. Tasks are important to use in all Jobs as they allow your Runs to resume again after e.g. a serverless function timeout. Learn more about Tasks in the docs: https://trigger.dev/docs/documentation/concepts/tasks + await io.runTask( + "task-example", + async () => { + return { + foo: "bar", + }; + }, + { name: `Answer: Because 7,8,9! And ${result.num} was next 🤦` } + ); await io.logger.info( "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" ); diff --git a/packages/cli/src/templates/nextjs/exampleJob.js b/packages/cli/src/templates/nextjs/exampleJob.js index ff0fbdc80..9cd551600 100644 --- a/packages/cli/src/templates/nextjs/exampleJob.js +++ b/packages/cli/src/templates/nextjs/exampleJob.js @@ -2,9 +2,9 @@ import { eventTrigger } from "@trigger.dev/sdk"; import { client } from "${jobsPathPrefix}trigger"; // Your first job -// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline +// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline. client.defineJob({ - // This is the unique identifier for your Job, it must be unique across all Jobs in your project + // This is the unique identifier for your Job, it must be unique across all Jobs in your project. id: "example-job", name: "Example Job: a joke with a delay", version: "0.0.1", @@ -13,15 +13,32 @@ client.defineJob({ name: "example.event", }), run: async (payload, io, ctx) => { - // This logs a message to the console - await io.logger.info("🧪 Example Job: a joke with a delay"); - await io.logger.info("How do you comfort a JavaScript bug?"); - // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year + // Use a Task to generate a random number. Using a Tasks means it only runs once. + const result = await io.runTask("generate-random-number", async () => { + return { + num: Math.floor(Math.random() * 10000), + }; + }); + + // Use the random number in a joke and log it to the console. + await io.logger.info(`Why was the number ${result.num} afraid of the number 7?`); + + // Wait for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year. await io.wait("Wait 5 seconds for the punchline...", 5); - await io.logger.info("You console it! 🤦"); + + // Use a Task to display the answer. Tasks are important to use in all Jobs as they allow your Runs to resume again after e.g. a serverless function timeout. Learn more about Tasks in the docs: https://trigger.dev/docs/documentation/concepts/tasks + await io.runTask( + "task-example", + async () => { + return { + foo: "bar", + }; + }, + { name: `Answer: Because 7,8,9! And ${result.num} was next 🤦` } + ); await io.logger.info( "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" ); // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job }, -}); +}); \ No newline at end of file diff --git a/packages/cli/src/templates/remix/exampleJob.js b/packages/cli/src/templates/remix/exampleJob.js index affba011f..4e4f95cd5 100644 --- a/packages/cli/src/templates/remix/exampleJob.js +++ b/packages/cli/src/templates/remix/exampleJob.js @@ -2,9 +2,9 @@ import { eventTrigger } from "@trigger.dev/sdk"; import { client } from "${jobsPathPrefix}trigger.server"; // Your first job -// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline +// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline. export const job = client.defineJob({ - // This is the unique identifier for your Job, it must be unique across all Jobs in your project + // This is the unique identifier for your Job, it must be unique across all Jobs in your project. id: "example-job", name: "Example Job: a joke with a delay", version: "0.0.1", @@ -13,12 +13,29 @@ export const job = client.defineJob({ name: "example.event", }), run: async (payload, io, ctx) => { - // This logs a message to the console - await io.logger.info("🧪 Example Job: a joke with a delay"); - await io.logger.info("How do you comfort a JavaScript bug?"); - // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year + // Use a Task to generate a random number. Using a Tasks means it only runs once. + const result = await io.runTask("generate-random-number", async () => { + return { + num: Math.floor(Math.random() * 10000), + }; + }); + + // Use the random number in a joke and log it to the console. + await io.logger.info(`Why was the number ${result.num} afraid of the number 7?`); + + // Wait for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year. await io.wait("Wait 5 seconds for the punchline...", 5); - await io.logger.info("You console it! 🤦"); + + // Use a Task to display the answer. Tasks are important to use in all Jobs as they allow your Runs to resume again after e.g. a serverless function timeout. Learn more about Tasks in the docs: https://trigger.dev/docs/documentation/concepts/tasks + await io.runTask( + "task-example", + async () => { + return { + foo: "bar", + }; + }, + { name: `Answer: Because 7,8,9! And ${result.num} was next 🤦` } + ); await io.logger.info( "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" ); diff --git a/references/job-catalog/package.json b/references/job-catalog/package.json index 0059ee0e2..6295d21fa 100644 --- a/references/job-catalog/package.json +++ b/references/job-catalog/package.json @@ -28,6 +28,7 @@ "replicate": "nodemon --watch src/replicate.ts -r tsconfig-paths/register -r dotenv/config src/replicate.ts", "misconfigured": "nodemon --watch src/misconfigured.ts -r tsconfig-paths/register -r dotenv/config src/misconfigured.ts", "auto-yield": "nodemon --watch src/auto-yield.ts -r tsconfig-paths/register -r dotenv/config src/auto-yield.ts", + "cli-example": "nodemon --watch src/cli-example.ts -r tsconfig-paths/register -r dotenv/config src/cli-example.ts", "dev:trigger": "trigger-cli dev --port 8080" }, "dependencies": { @@ -62,4 +63,4 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^3.14.1" } -} \ No newline at end of file +} diff --git a/references/job-catalog/src/cli-example.ts b/references/job-catalog/src/cli-example.ts new file mode 100644 index 000000000..b1eff1c2a --- /dev/null +++ b/references/job-catalog/src/cli-example.ts @@ -0,0 +1,54 @@ +import { createExpressServer } from "@trigger.dev/express"; +import { TriggerClient, eventTrigger } from "@trigger.dev/sdk"; + +export const client = new TriggerClient({ + id: "job-catalog", + apiKey: process.env["TRIGGER_API_KEY"], + apiUrl: process.env["TRIGGER_API_URL"], + verbose: false, + ioLogLocalEnabled: true, +}); + +// Your first job +// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline. +client.defineJob({ + // This is the unique identifier for your Job, it must be unique across all Jobs in your project. + id: "example-job", + name: "Example Job: a joke with a delay", + version: "0.0.1", + // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + // Use a Task to generate a random number. Using a Tasks means it only runs once. + const result = await io.runTask("generate-random-number", async () => { + return { + num: Math.floor(Math.random() * 10000), + }; + }); + + // Use the random number in a joke and log it to the console. + await io.logger.info(`Why was the number ${result.num} afraid of the number 7?`); + + // Wait for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year. + await io.wait("Wait 5 seconds for the punchline...", 5); + + // Use a Task to display the answer. Tasks are important to use in all Jobs as they allow your Runs to resume again after e.g. a serverless function timeout. Learn more about Tasks in the docs: https://trigger.dev/docs/documentation/concepts/tasks + await io.runTask( + "task-example", + async () => { + return { + foo: "bar", + }; + }, + { name: `Answer: Because 7,8,9! And ${result.num} was next 🤦` } + ); + await io.logger.info( + "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" + ); + // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job + }, +}); + +createExpressServer(client); diff --git a/references/job-catalog/src/events.ts b/references/job-catalog/src/events.ts index aad942691..ebc77da7a 100644 --- a/references/job-catalog/src/events.ts +++ b/references/job-catalog/src/events.ts @@ -77,6 +77,28 @@ client.defineJob({ }, }); +client.defineJob({ + id: "no-real-task", + name: "No real Task", + version: "0.0.1", + trigger: eventTrigger({ + name: "no.real.task", + schema: z.object({ + userId: z.string(), + }), + }), + run: async (payload, io, ctx) => { + await io.logger.info("Hello World", { ctx, payload }); + await io.wait("Wait 1 sec", 1); + //this is a real task + // await io.runTask("task-example-1", async () => { + // return { + // message: "Hello World", + // }; + // }); + } +}); + client.defineJob({ id: "cancel-runs-example", name: "Cancel Runs Example", From b859abdb6699315d663338671af96b178573f46e Mon Sep 17 00:00:00 2001 From: Hemachandar <132386067+hmacr@users.noreply.github.com> Date: Mon, 30 Oct 2023 22:34:29 +0530 Subject: [PATCH 14/95] fix: remove unstable createRemixStub in storybook (#707) --- apps/webapp/.storybook/preview.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/webapp/.storybook/preview.tsx b/apps/webapp/.storybook/preview.tsx index bdf5e3ab6..a995aea73 100644 --- a/apps/webapp/.storybook/preview.tsx +++ b/apps/webapp/.storybook/preview.tsx @@ -1,6 +1,6 @@ import type { Preview } from "@storybook/react"; import "../app/tailwind.css"; -import { unstable_createRemixStub } from "@remix-run/testing"; +import { createRemixStub } from "@remix-run/testing"; import React from "react"; import { LocaleContextProvider } from "../app/components/primitives/LocaleProvider"; import { OperatingSystemContextProvider } from "../app/components/primitives/OperatingSystemProvider"; @@ -26,10 +26,10 @@ const preview: Preview = { }, decorators: [ (Story) => { - const RemixStub = unstable_createRemixStub([ + const RemixStub = createRemixStub([ { path: "/*", - element: , + Component: Story, }, ]); From 2c91c492df90289072dbbcc188c39c1321c76f42 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:08:16 +0000 Subject: [PATCH 15/95] Feature: Cross-instance notification system (#689) * Add notification system * Graceful worker shutdown on migration notification * Add notification catalog * Rename pgListen call to on --- apps/webapp/app/platform/zodWorker.server.ts | 19 +++++- .../webapp/app/services/db/pgListen.server.ts | 66 +++++++++++++++++++ .../webapp/app/services/db/pgNotify.server.ts | 27 ++++++++ apps/webapp/app/services/db/types.ts | 11 ++++ apps/webapp/app/services/worker.server.ts | 5 ++ apps/webapp/package.json | 1 + pnpm-lock.yaml | 2 + 7 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 apps/webapp/app/services/db/pgListen.server.ts create mode 100644 apps/webapp/app/services/db/pgNotify.server.ts create mode 100644 apps/webapp/app/services/db/types.ts diff --git a/apps/webapp/app/platform/zodWorker.server.ts b/apps/webapp/app/platform/zodWorker.server.ts index d4480286d..496dae542 100644 --- a/apps/webapp/app/platform/zodWorker.server.ts +++ b/apps/webapp/app/platform/zodWorker.server.ts @@ -15,6 +15,8 @@ import omit from "lodash.omit"; import { z } from "zod"; import { PrismaClient, PrismaClientOrTransaction } from "~/db.server"; import { workerLogger as logger } from "~/services/logger.server"; +import { PgListenService } from "~/services/db/pgListen.server"; +import { safeJsonParse } from "~/utils/json"; export interface MessageCatalogSchema { [key: string]: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion; @@ -164,8 +166,23 @@ export class ZodWorker { this.#logDebug("pool:create", { attempts }); }); - this.#runner?.events.on("pool:listen:success", ({ workerPool, client }) => { + this.#runner?.events.on("pool:listen:success", async ({ workerPool, client }) => { this.#logDebug("pool:listen:success"); + + // hijack client instance to listen and react to incoming NOTIFY events + const pgListen = new PgListenService(client, this.#name, logger); + + await pgListen.call("trigger:graphile:migrate", async ({ latestMigration }) => { + this.#logDebug("Detected incoming migration", { latestMigration }); + + if (latestMigration > 10) { + // already migrated past v0.14 - nothing to do + return; + } + + // simulate SIGTERM to trigger graceful shutdown + this._handleSignal("SIGTERM"); + }); }); this.#runner?.events.on("pool:listen:error", ({ error }) => { diff --git a/apps/webapp/app/services/db/pgListen.server.ts b/apps/webapp/app/services/db/pgListen.server.ts new file mode 100644 index 000000000..94474f69f --- /dev/null +++ b/apps/webapp/app/services/db/pgListen.server.ts @@ -0,0 +1,66 @@ +import type { PoolClient } from "pg"; +import { z } from "zod"; +import { Logger } from "@trigger.dev/core"; +import { logger } from "~/services/logger.server"; +import { NotificationCatalog, NotificationChannel, notificationCatalog } from "./types"; +import { safeJsonParse } from "~/utils/json"; + +export class PgListenService { + #poolClient: PoolClient; + #logger: Logger; + #loggerNamespace: string; + + constructor(poolClient: PoolClient, loggerNamespace?: string, loggerInstance?: Logger) { + this.#poolClient = poolClient; + this.#logger = loggerInstance ?? logger; + this.#loggerNamespace = loggerNamespace ?? ""; + } + + public async on( + channelName: TChannel, + callback: (payload: z.infer) => Promise + ) { + this.#logDebug("Registering notification handler", { channelName }); + + const isValidChannel = channelName.match(/^[a-zA-Z0-9:-_]+$/); + + if (!isValidChannel) { + throw new Error(`Invalid channel name: ${channelName}`); + } + + this.#poolClient.query(`LISTEN "${channelName}"`).then(null, (error) => { + this.#logDebug("LISTEN error", error); + }); + + this.#poolClient.on("notification", async (notification) => { + if (notification.channel !== channelName) { + return; + } + + this.#logDebug("Notification received", { notification }); + + if (!notification.payload) { + return; + } + + const payload = safeJsonParse(notification.payload); + + const parsedPayload = notificationCatalog[channelName].safeParse(payload); + + if (!parsedPayload.success) { + throw new Error( + `Failed to parse notification payload: ${channelName} - ${JSON.stringify( + parsedPayload.error + )}` + ); + } + + await callback(parsedPayload.data); + }); + } + + #logDebug(message: string, args?: any) { + const namespace = this.#loggerNamespace ? `[${this.#loggerNamespace}]` : ""; + this.#logger.debug(`[pgListen]${namespace} ${message}`, args); + } +} diff --git a/apps/webapp/app/services/db/pgNotify.server.ts b/apps/webapp/app/services/db/pgNotify.server.ts new file mode 100644 index 000000000..2a80294eb --- /dev/null +++ b/apps/webapp/app/services/db/pgNotify.server.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; +import { PrismaClient, prisma } from "~/db.server"; +import { logger } from "~/services/logger.server"; +import { NotificationCatalog, NotificationChannel } from "./types"; + +export class PgNotifyService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call( + channelName: TChannel, + payload: z.infer + ) { + this.#logDebug("Sending notification", { channelName, notifyPayload: payload }); + + await this.#prismaClient.$executeRaw` + SELECT pg_notify(${channelName}, ${JSON.stringify(payload)}) + `; + } + + #logDebug(message: string, args?: any) { + logger.debug(`[pgNotify] ${message}`, args); + } +} diff --git a/apps/webapp/app/services/db/types.ts b/apps/webapp/app/services/db/types.ts new file mode 100644 index 000000000..2e5b6d222 --- /dev/null +++ b/apps/webapp/app/services/db/types.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const notificationCatalog = { + "trigger:graphile:migrate": z.object({ + latestMigration: z.number(), + }), +}; + +export type NotificationCatalog = typeof notificationCatalog; + +export type NotificationChannel = keyof NotificationCatalog; diff --git a/apps/webapp/app/services/worker.server.ts b/apps/webapp/app/services/worker.server.ts index 15f2534a2..dc77abc76 100644 --- a/apps/webapp/app/services/worker.server.ts +++ b/apps/webapp/app/services/worker.server.ts @@ -21,6 +21,7 @@ import { DeliverHttpSourceRequestService } from "./sources/deliverHttpSourceRequ import { PerformTaskOperationService } from "./tasks/performTaskOperation.server"; import { ProcessCallbackTimeoutService } from "./tasks/processCallbackTimeout"; import { ProbeEndpointService } from "./endpoints/probeEndpoint.server"; +import { PgNotifyService } from "./db/pgNotify.server"; const workerCatalog = { indexEndpoint: z.object({ @@ -121,6 +122,10 @@ if (env.NODE_ENV === "production") { } export async function init() { + // const pgNotify = new PgNotifyService(); + // await pgNotify.call("trigger:graphile:migrate", { latestMigration: 10 }); + // await new Promise((resolve) => setTimeout(resolve, 10000)) + if (env.WORKER_ENABLED === "true") { await workerQueue.initialize(); } diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 54a59d4cd..5cbb0ec2d 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -65,6 +65,7 @@ "@trigger.dev/core": "workspace:*", "@trigger.dev/database": "workspace:*", "@trigger.dev/sdk": "workspace:*", + "@types/pg": "8.6.6", "@uiw/react-codemirror": "^4.19.5", "class-variance-authority": "^0.5.2", "clsx": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f9c5b43b..db16395d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,7 @@ importers: '@types/morgan': ^1.9.3 '@types/node': ^18.11.15 '@types/node-fetch': ^2.6.2 + '@types/pg': 8.6.6 '@types/prismjs': ^1.26.0 '@types/qs': ^6.9.7 '@types/react': 18.2.17 @@ -236,6 +237,7 @@ importers: '@trigger.dev/core': link:../../packages/core '@trigger.dev/database': link:../../packages/database '@trigger.dev/sdk': link:../../packages/trigger-sdk + '@types/pg': 8.6.6 '@uiw/react-codemirror': 4.19.5_th22fcplkuhrqjnlojwclcaim4 class-variance-authority: 0.5.2_typescript@5.2.2 clsx: 1.2.1 From 8eedf994af34099e8e6d78a1dc949cd77dea421e Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 30 Oct 2023 21:58:04 +0000 Subject: [PATCH 16/95] Fixed call -> on --- apps/webapp/app/platform/zodWorker.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/app/platform/zodWorker.server.ts b/apps/webapp/app/platform/zodWorker.server.ts index 496dae542..78bb74ba4 100644 --- a/apps/webapp/app/platform/zodWorker.server.ts +++ b/apps/webapp/app/platform/zodWorker.server.ts @@ -172,7 +172,7 @@ export class ZodWorker { // hijack client instance to listen and react to incoming NOTIFY events const pgListen = new PgListenService(client, this.#name, logger); - await pgListen.call("trigger:graphile:migrate", async ({ latestMigration }) => { + await pgListen.on("trigger:graphile:migrate", async ({ latestMigration }) => { this.#logDebug("Detected incoming migration", { latestMigration }); if (latestMigration > 10) { From 9c5a7cf073bc91bbb0b59f7f4b0af40993ea0d13 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Tue, 31 Oct 2023 10:17:05 +0000 Subject: [PATCH 17/95] Fix: Allow errors and special values on Airtable field set (#697) * Allow errors and special values on field set * Add changeset --- .changeset/silent-snails-float.md | 5 +++ integrations/airtable/src/base.ts | 10 +++++ integrations/airtable/src/types.ts | 13 ++++++- references/job-catalog/src/airtable.ts | 51 +++++++++++++++++++++++++- 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 .changeset/silent-snails-float.md diff --git a/.changeset/silent-snails-float.md b/.changeset/silent-snails-float.md new file mode 100644 index 000000000..003bac869 --- /dev/null +++ b/.changeset/silent-snails-float.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/airtable": patch +--- + +Extend the Airtable field set to accept formula errors and special values diff --git a/integrations/airtable/src/base.ts b/integrations/airtable/src/base.ts index b354bc136..858bbf84e 100644 --- a/integrations/airtable/src/base.ts +++ b/integrations/airtable/src/base.ts @@ -38,6 +38,8 @@ export class Table { async (client) => { const result = await client .base(this.baseId) + // official types are wrong - we need to use our extended AirtableFieldSet here + // @ts-ignore .table(this.tableName) .select(params) .all(); @@ -55,6 +57,8 @@ export class Table { return this.runTask( key, async (client) => { + // official types are wrong - we need to use our extended AirtableFieldSet here + // @ts-ignore const result = await client.base(this.baseId).table(this.tableName).find(recordId); return toSerializableRecord(result); }, @@ -75,6 +79,8 @@ export class Table { async (client) => { const result = await client .base(this.baseId) + // official types are wrong - we need to use our extended AirtableFieldSet here + // @ts-ignore .table(this.tableName) .create(records); return result.map((record) => toSerializableRecord(record)); @@ -96,6 +102,8 @@ export class Table { async (client) => { const result = await client .base(this.baseId) + // official types are wrong - we need to use our extended AirtableFieldSet here + // @ts-ignore .table(this.tableName) .update(records); return result.map((record) => toSerializableRecord(record)); @@ -117,6 +125,8 @@ export class Table { async (client) => { const result = await client .base(this.baseId) + // official types are wrong - we need to use our extended AirtableFieldSet here + // @ts-ignore .table(this.tableName) .destroy(recordIds); return result.map((record) => toSerializableRecord(record)); diff --git a/integrations/airtable/src/types.ts b/integrations/airtable/src/types.ts index 8e917b72c..b108326c1 100644 --- a/integrations/airtable/src/types.ts +++ b/integrations/airtable/src/types.ts @@ -9,7 +9,8 @@ export type AirtableFieldSet = { | Collaborator | Collaborator[] | string[] - | Attachment[]; + | Attachment[] + | Formula; }; export type Collaborator = { @@ -31,6 +32,16 @@ export type Attachment = { }; }; +export type FormulaError = { + error: string; +}; + +export type FormulaSpecialValue = { + specialValue: "Infinity" | "NaN"; +}; + +export type Formula = string | number | FormulaError | FormulaSpecialValue; + export type Thumbnail = { url: string; width: number; diff --git a/references/job-catalog/src/airtable.ts b/references/job-catalog/src/airtable.ts index 703046bf3..6016a77c3 100644 --- a/references/job-catalog/src/airtable.ts +++ b/references/job-catalog/src/airtable.ts @@ -1,7 +1,7 @@ import { TriggerClient, eventTrigger } from "@trigger.dev/sdk"; import { createExpressServer } from "@trigger.dev/express"; import { z } from "zod"; -import { Airtable, Collaborator } from "@trigger.dev/airtable"; +import { Airtable, Collaborator, Formula } from "@trigger.dev/airtable"; export const client = new TriggerClient({ id: "job-catalog", @@ -77,6 +77,55 @@ client.defineJob({ }, }); +type Formulas = { + ValueOne?: number; + ValueTwo?: number; + Division: Formula; // ValueOne / ValueTwo +}; + +client.defineJob({ + id: "airtable-formulas", + name: "Airtable Formulas", + version: "0.1.0", + trigger: eventTrigger({ + name: "airtable.formulas", + schema: z.object({ + baseId: z.string(), + tableName: z.string(), + }), + }), + integrations: { + airtable, + }, + run: async (payload, io, ctx) => { + const table = io.airtable.base(payload.baseId).table(payload.tableName); + + const records = await table.getRecords("muliple records", { + fields: ["ValueOne", "ValueTwo", "Division"], + }); + + const divisionField = records[0].fields.Division; + + if (typeof divisionField !== "object") { + return records; + } + + if ("specialValue" in divisionField) { + await io.logger.log( + `Special value: ${divisionField.specialValue}${ + divisionField.specialValue === "Infinity" ? " - did you divide by zero?" : "" + }` + ); + } else if ("error" in divisionField) { + await io.logger.error(`Error: ${divisionField.error}`); + } else { + await io.logger.warn("Unknown formula value!"); + } + + return divisionField; + }, +}); + //todo webhooks require batch support // client.defineJob({ // id: "airtable-delete-webhooks", From c1710ae72d95df06bedca9e6fc07b0d8741fa665 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 31 Oct 2023 15:28:55 +0000 Subject: [PATCH 18/95] fix: Buffer import breaks @trigger.dev/react (#714) --- .changeset/dull-masks-smash.md | 7 + .../runs/performRunExecutionV2.server.ts | 24 ++- apps/webapp/package.json | 1 + apps/webapp/remix.config.js | 2 + apps/webapp/tsconfig.json | 2 + docs/_snippets/remix/troubleshooting.mdx | 46 ++++++ docs/documentation/guides/manual/remix.mdx | 2 + docs/documentation/guides/platforms/remix.mdx | 2 + docs/documentation/quickstarts/remix.mdx | 4 + packages/core-backend/CHANGELOG.md | 0 packages/core-backend/LICENSE | 21 +++ packages/core-backend/jest.config.js | 8 + packages/core-backend/package.json | 42 +++++ packages/{core => core-backend}/src/bloom.ts | 0 packages/core-backend/src/index.ts | 1 + .../core-backend/test/bloomFilter.test.ts | 28 ++++ packages/core-backend/tsconfig.build.json | 11 ++ packages/core-backend/tsconfig.json | 12 ++ packages/core-backend/tsup.config.ts | 20 +++ packages/core/src/index.ts | 1 - packages/trigger-sdk/package.json | 1 + packages/trigger-sdk/src/io.ts | 2 +- packages/trigger-sdk/tsconfig.json | 4 +- pnpm-lock.yaml | 149 +++++++++++++++++- 24 files changed, 373 insertions(+), 17 deletions(-) create mode 100644 .changeset/dull-masks-smash.md create mode 100644 docs/_snippets/remix/troubleshooting.mdx create mode 100644 packages/core-backend/CHANGELOG.md create mode 100644 packages/core-backend/LICENSE create mode 100644 packages/core-backend/jest.config.js create mode 100644 packages/core-backend/package.json rename packages/{core => core-backend}/src/bloom.ts (100%) create mode 100644 packages/core-backend/src/index.ts create mode 100644 packages/core-backend/test/bloomFilter.test.ts create mode 100644 packages/core-backend/tsconfig.build.json create mode 100644 packages/core-backend/tsconfig.json create mode 100644 packages/core-backend/tsup.config.ts diff --git a/.changeset/dull-masks-smash.md b/.changeset/dull-masks-smash.md new file mode 100644 index 000000000..15c378403 --- /dev/null +++ b/.changeset/dull-masks-smash.md @@ -0,0 +1,7 @@ +--- +"@trigger.dev/core-backend": patch +"@trigger.dev/sdk": patch +"@trigger.dev/core": patch +--- + +Creates a new package @trigger.dev/core-backend that includes code shared between @trigger.dev/sdk and the Trigger.dev server diff --git a/apps/webapp/app/services/runs/performRunExecutionV2.server.ts b/apps/webapp/app/services/runs/performRunExecutionV2.server.ts index ec9318181..743e3c640 100644 --- a/apps/webapp/app/services/runs/performRunExecutionV2.server.ts +++ b/apps/webapp/app/services/runs/performRunExecutionV2.server.ts @@ -1,9 +1,9 @@ import { - API_VERSIONS, - BloomFilter, + ApiEventLog, ConnectionAuth, EndpointHeadersSchema, RunJobAutoYieldWithCompletedTaskExecutionError, + RunJobBody, RunJobError, RunJobInvalidPayloadError, RunJobResumeWithTask, @@ -14,27 +14,25 @@ import { RunSourceContextSchema, supportsFeature, } from "@trigger.dev/core"; +import { BloomFilter } from "@trigger.dev/core-backend"; import { RuntimeEnvironmentType, type Task } from "@trigger.dev/database"; import { generateErrorMessage } from "zod-error"; import { eventRecordToApiJson } from "~/api.server"; +import { + MAX_RUN_CHUNK_EXECUTION_LIMIT, + MAX_RUN_YIELDED_EXECUTIONS, + RUN_CHUNK_EXECUTION_BUFFER, +} from "~/consts"; import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; +import { detectResponseIsTimeout } from "~/models/endpoint.server"; import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; import { resolveRunConnections } from "~/models/runConnection.server"; +import { prepareTasksForCaching, prepareTasksForCachingLegacy } from "~/models/task.server"; +import { CompleteRunTaskService } from "~/routes/api.v1.runs.$runId.tasks.$id.complete"; import { formatError } from "~/utils/formatErrors.server"; import { safeJsonZodParse } from "~/utils/json"; import { EndpointApi } from "../endpointApi.server"; import { logger } from "../logger.server"; -import { prepareTasksForCaching, prepareTasksForCachingLegacy } from "~/models/task.server"; -import { - MAX_RUN_CHUNK_EXECUTION_LIMIT, - MAX_RUN_YIELDED_EXECUTIONS, - RESPONSE_TIMEOUT_STATUS_CODES, - RUN_CHUNK_EXECUTION_BUFFER, -} from "~/consts"; -import { ApiEventLog } from "@trigger.dev/core"; -import { RunJobBody } from "@trigger.dev/core"; -import { CompleteRunTaskService } from "~/routes/api.v1.runs.$runId.tasks.$id.complete"; -import { detectResponseIsTimeout } from "~/models/endpoint.server"; import { forceYieldCoordinator } from "./forceYieldCoordinator.server"; type FoundRun = NonNullable>>; diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 5cbb0ec2d..665335d18 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -63,6 +63,7 @@ "@team-plain/typescript-sdk": "^2.2.0", "@trigger.dev/companyicons": "^1.5.14", "@trigger.dev/core": "workspace:*", + "@trigger.dev/core-backend": "workspace:*", "@trigger.dev/database": "workspace:*", "@trigger.dev/sdk": "workspace:*", "@types/pg": "8.6.6", diff --git a/apps/webapp/remix.config.js b/apps/webapp/remix.config.js index 4885a2485..ffccf5057 100644 --- a/apps/webapp/remix.config.js +++ b/apps/webapp/remix.config.js @@ -12,6 +12,7 @@ module.exports = { "marked", "axios", "@trigger.dev/core", + "@trigger.dev/core-backend", "@trigger.dev/sdk", "emails", "highlight.run", @@ -19,6 +20,7 @@ module.exports = { watchPaths: async () => { return [ "../../packages/core/src/**/*", + "../../packages/core-backend/src/**/*", "../../packages/trigger-sdk/src/**/*", "../../packages/emails/src/**/*", ]; diff --git a/apps/webapp/tsconfig.json b/apps/webapp/tsconfig.json index 9ca1b6269..bd08a2f27 100644 --- a/apps/webapp/tsconfig.json +++ b/apps/webapp/tsconfig.json @@ -23,6 +23,8 @@ "@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"], "@trigger.dev/core": ["../../packages/core/src/index"], "@trigger.dev/core/*": ["../../packages/core/src/*"], + "@trigger.dev/core-backend": ["../../packages/core-backend/src/index"], + "@trigger.dev/core-backend/*": ["../../packages/core-backend/src/*"], "@trigger.dev/database": ["../../packages/database/src/index"], "@trigger.dev/database/*": ["../../packages/database/src/*"], "emails": ["../../packages/emails/src/index"], diff --git a/docs/_snippets/remix/troubleshooting.mdx b/docs/_snippets/remix/troubleshooting.mdx new file mode 100644 index 000000000..407d3fb9c --- /dev/null +++ b/docs/_snippets/remix/troubleshooting.mdx @@ -0,0 +1,46 @@ +## Troubleshooting + +### `'TriggerProvider' not found` + +When running the Remix app, you may see an error like this: + +``` +import { TriggerProvider } from "@trigger.dev/react"; + ^^^^^^^^^^^^^^^ +SyntaxError: Named export 'TriggerProvider' not found. The requested module '@trigger.dev/react' is a CommonJS module, which may not support all module.exports as named exports. +CommonJS modules can always be imported via the default export, for example using: + +import pkg from '@trigger.dev/react'; +const { TriggerProvider } = pkg; +``` + +To fix this, edit your `remix.config.js` file and add the `@trigger.dev/react` package to your list of `serverDependenciesToBundle`: + +```js +export default { + // ... other config + serverDependenciesToBundle: ["@trigger.dev/react"], +}; +``` + +### `[ERROR] Node builtin "buffer"` + +When running the Remix app, you may see an error like this: + +``` +✘ [ERROR] Node builtin "buffer" (imported by "node_modules/@trigger.dev/core/dist/index.js") must be polyfilled for the browser. +You can enable this polyfill in your Remix config, e.g. `browserNodeBuiltinsPolyfill: { modules: { buffer: true } }` +[plugin browser-node-builtins-polyfill-plugin] +``` + +To fix this, edit your `remix.config.js` file and add the `browserNodeBuiltinsPolyfill` config: + +```js +export default { + browserNodeBuiltinsPolyfill: { + modules: { + buffer: "empty", + }, + }, +}; +``` diff --git a/docs/documentation/guides/manual/remix.mdx b/docs/documentation/guides/manual/remix.mdx index f2b27d6d6..4a9d62776 100644 --- a/docs/documentation/guides/manual/remix.mdx +++ b/docs/documentation/guides/manual/remix.mdx @@ -5,3 +5,5 @@ description: "How to manually setup Trigger.dev in your Remix project" --- + + diff --git a/docs/documentation/guides/platforms/remix.mdx b/docs/documentation/guides/platforms/remix.mdx index acbf6046d..76eb1c690 100644 --- a/docs/documentation/guides/platforms/remix.mdx +++ b/docs/documentation/guides/platforms/remix.mdx @@ -16,3 +16,5 @@ View our [guide for writing Jobs](/documentation/guides/create-a-job). ## Deployment View our [deployment guide](/documentation/guides/deployment) to learn how to deploy your Jobs. + + diff --git a/docs/documentation/quickstarts/remix.mdx b/docs/documentation/quickstarts/remix.mdx index b84679094..fd320b2a6 100644 --- a/docs/documentation/quickstarts/remix.mdx +++ b/docs/documentation/quickstarts/remix.mdx @@ -5,6 +5,8 @@ description: "Start creating Jobs in 5 minutes in your Remix project." icon: "r" --- +## Quickstart + This quick start guide will get you up and running with Trigger.dev. @@ -84,6 +86,8 @@ If you navigate to your Trigger.dev project you will see this Job in the "Jobs" + + diff --git a/packages/core-backend/CHANGELOG.md b/packages/core-backend/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core-backend/LICENSE b/packages/core-backend/LICENSE new file mode 100644 index 000000000..e51e7b10a --- /dev/null +++ b/packages/core-backend/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Trigger.dev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core-backend/jest.config.js b/packages/core-backend/jest.config.js new file mode 100644 index 000000000..e21cd117c --- /dev/null +++ b/packages/core-backend/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + moduleFileExtensions: ["ts", "tsx", "js"], + transform: { + "^.+\\.(ts|tsx)$": "ts-jest", + }, + testMatch: ["/test/**/*.ts?(x)", "/test/**/?(*.)+(spec|test).ts?(x)"], + testEnvironment: "node", +}; diff --git a/packages/core-backend/package.json b/packages/core-backend/package.json new file mode 100644 index 000000000..0731a001e --- /dev/null +++ b/packages/core-backend/package.json @@ -0,0 +1,42 @@ +{ + "name": "@trigger.dev/core-backend", + "version": "2.2.3", + "description": "Core code used across `@trigger.dev/sdk` and Trigger.dev server", + "license": "MIT", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "scripts": { + "clean": "rimraf dist", + "build": "npm run clean && npm run build:tsup", + "build:tsup": "tsup --dts-resolve", + "typecheck": "tsc --noEmit", + "test": "jest" + }, + "devDependencies": { + "@trigger.dev/tsconfig": "workspace:*", + "@types/jest": "^29.5.3", + "@types/node": "18", + "jest": "^29.6.2", + "rimraf": "^3.0.2", + "ts-jest": "^29.1.1", + "tsup": "^7.1.0", + "typescript": "^5.1.0" + }, + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/packages/core/src/bloom.ts b/packages/core-backend/src/bloom.ts similarity index 100% rename from packages/core/src/bloom.ts rename to packages/core-backend/src/bloom.ts diff --git a/packages/core-backend/src/index.ts b/packages/core-backend/src/index.ts new file mode 100644 index 000000000..01deb504c --- /dev/null +++ b/packages/core-backend/src/index.ts @@ -0,0 +1 @@ +export * from "./bloom"; diff --git a/packages/core-backend/test/bloomFilter.test.ts b/packages/core-backend/test/bloomFilter.test.ts new file mode 100644 index 000000000..b66a53ec8 --- /dev/null +++ b/packages/core-backend/test/bloomFilter.test.ts @@ -0,0 +1,28 @@ +import { BloomFilter } from "../src"; + +describe("BloomFilter", () => { + it("should be able to correct test for inclusion in the set", () => { + const filter = new BloomFilter(1000); + + filter.add("hello"); + filter.add("world"); + + expect(filter.test("hello")).toBe(true); + expect(filter.test("world")).toBe(true); + expect(filter.test("foo")).toBe(false); + }); + + it("should be able to serialize and deserialize", () => { + const filter = new BloomFilter(1000); + + filter.add("hello"); + filter.add("world"); + + const serialized = filter.serialize(); + const deserialized = BloomFilter.deserialize(serialized, 1000); + + expect(deserialized.test("hello")).toBe(true); + expect(deserialized.test("world")).toBe(true); + expect(deserialized.test("foo")).toBe(false); + }); +}); diff --git a/packages/core-backend/tsconfig.build.json b/packages/core-backend/tsconfig.build.json new file mode 100644 index 000000000..f444efc19 --- /dev/null +++ b/packages/core-backend/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "@trigger.dev/tsconfig/node18.json", + "include": ["src/globals.d.ts", "./src/**/*.ts", "tsup.config.ts"], + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "declaration": false, + "declarationMap": false + }, + "exclude": ["node_modules"] +} diff --git a/packages/core-backend/tsconfig.json b/packages/core-backend/tsconfig.json new file mode 100644 index 000000000..efef46673 --- /dev/null +++ b/packages/core-backend/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@trigger.dev/tsconfig/node18.json", + "include": ["src/globals.d.ts", "./src/**/*.ts", "tsup.config.ts", "./test/**/*.ts"], + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "declaration": false, + "declarationMap": false, + "types": ["jest"] + }, + "exclude": ["node_modules"] +} diff --git a/packages/core-backend/tsup.config.ts b/packages/core-backend/tsup.config.ts new file mode 100644 index 000000000..ec5c6d83d --- /dev/null +++ b/packages/core-backend/tsup.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "tsup"; + +export default defineConfig([ + { + name: "main", + config: "tsconfig.build.json", + entry: ["./src/index.ts"], + outDir: "./dist", + platform: "neutral", + format: ["cjs"], + legacyOutput: true, + sourcemap: true, + clean: true, + bundle: true, + splitting: false, + dts: true, + external: ["http", "https", "util", "events", "tty", "os", "timers"], + esbuildPlugins: [], + }, +]); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0810ae835..afaab0fec 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,7 +6,6 @@ export * from "./retry"; export * from "./replacements"; export * from "./searchParams"; export * from "./eventFilterMatches"; -export * from "./bloom"; export const API_VERSIONS = { LAZY_LOADED_CACHED_TASKS: "2023-09-29", diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index e273cae7c..5132539c7 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@trigger.dev/core": "workspace:^2.2.3", + "@trigger.dev/core-backend": "workspace:^2.2.3", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", diff --git a/packages/trigger-sdk/src/io.ts b/packages/trigger-sdk/src/io.ts index a4f8aeb8c..01333c3e1 100644 --- a/packages/trigger-sdk/src/io.ts +++ b/packages/trigger-sdk/src/io.ts @@ -1,6 +1,5 @@ import { API_VERSIONS, - BloomFilter, CachedTask, ConnectionAuth, CronOptions, @@ -19,6 +18,7 @@ import { UpdateTriggerSourceBodyV2, supportsFeature, } from "@trigger.dev/core"; +import { BloomFilter } from "@trigger.dev/core-backend"; import { AsyncLocalStorage } from "node:async_hooks"; import { webcrypto } from "node:crypto"; import { ApiClient } from "./apiClient"; diff --git a/packages/trigger-sdk/tsconfig.json b/packages/trigger-sdk/tsconfig.json index 7b13d85e2..8e3bd63ef 100644 --- a/packages/trigger-sdk/tsconfig.json +++ b/packages/trigger-sdk/tsconfig.json @@ -6,7 +6,9 @@ "emitDecoratorMetadata": true, "paths": { "@trigger.dev/core/*": ["../core/src/*"], - "@trigger.dev/core": ["../core/src/index"] + "@trigger.dev/core": ["../core/src/index"], + "@trigger.dev/core-backend/*": ["../core-backend/src/*"], + "@trigger.dev/core-backend": ["../core-backend/src/index"] }, "lib": ["DOM", "DOM.Iterable"], "declaration": false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db16395d5..991e628b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,6 +107,7 @@ importers: '@total-typescript/ts-reset': ^0.4.2 '@trigger.dev/companyicons': ^1.5.14 '@trigger.dev/core': workspace:* + '@trigger.dev/core-backend': workspace:* '@trigger.dev/database': workspace:* '@trigger.dev/sdk': workspace:* '@trigger.dev/tailwind-config': workspace:* @@ -235,6 +236,7 @@ importers: '@team-plain/typescript-sdk': 2.2.0 '@trigger.dev/companyicons': 1.5.14_biqbaboplfbrettd7655fr4n2y '@trigger.dev/core': link:../../packages/core + '@trigger.dev/core-backend': link:../../packages/core-backend '@trigger.dev/database': link:../../packages/database '@trigger.dev/sdk': link:../../packages/trigger-sdk '@types/pg': 8.6.6 @@ -783,6 +785,26 @@ importers: tsup: 7.1.0_typescript@4.9.5 typescript: 4.9.5 + packages/core-backend: + specifiers: + '@trigger.dev/tsconfig': workspace:* + '@types/jest': ^29.5.3 + '@types/node': '18' + jest: ^29.6.2 + rimraf: ^3.0.2 + ts-jest: ^29.1.1 + tsup: ^7.1.0 + typescript: ^5.1.0 + devDependencies: + '@trigger.dev/tsconfig': link:../../config-packages/tsconfig + '@types/jest': 29.5.3 + '@types/node': 18.17.1 + jest: 29.6.2_@types+node@18.17.1 + rimraf: 3.0.2 + ts-jest: 29.1.1_uvjn2wawyirmlmi655a42v3ske + tsup: 7.2.0_typescript@5.2.2 + typescript: 5.2.2 + packages/database: specifiers: '@prisma/client': 5.4.1 @@ -1057,6 +1079,7 @@ importers: packages/trigger-sdk: specifiers: '@trigger.dev/core': workspace:^2.2.3 + '@trigger.dev/core-backend': workspace:^2.2.3 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/node': '18' @@ -1083,6 +1106,7 @@ importers: zod: 3.22.3 dependencies: '@trigger.dev/core': link:../core + '@trigger.dev/core-backend': link:../core-backend chalk: 5.2.0 cronstrue: 2.21.0 debug: 4.3.4 @@ -22546,6 +22570,35 @@ packages: - ts-node dev: true + /jest-cli/29.6.2_@types+node@18.17.1: + resolution: {integrity: sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.6.2 + '@jest/test-result': 29.6.2 + '@jest/types': 29.6.1 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.10 + import-local: 3.1.0 + jest-config: 29.6.2_@types+node@18.17.1 + jest-util: 29.6.2 + jest-validate: 29.6.2 + prompts: 2.4.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jest-config/29.6.2_@types+node@16.18.11: resolution: {integrity: sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -22586,6 +22639,46 @@ packages: - supports-color dev: true + /jest-config/29.6.2_@types+node@18.17.1: + resolution: {integrity: sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.22.17 + '@jest/test-sequencer': 29.6.2 + '@jest/types': 29.6.1 + '@types/node': 18.17.1 + babel-jest: 29.6.2_@babel+core@7.22.17 + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.10 + jest-circus: 29.6.2 + jest-environment-node: 29.6.2 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.6.2 + jest-runner: 29.6.2 + jest-util: 29.6.2 + jest-validate: 29.6.2 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.6.2 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + /jest-config/29.6.2_@types+node@20.6.0: resolution: {integrity: sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -22966,6 +23059,27 @@ packages: - ts-node dev: true + /jest/29.6.2_@types+node@18.17.1: + resolution: {integrity: sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.6.2 + '@jest/types': 29.6.1 + import-local: 3.1.0 + jest-cli: 29.6.2_@types+node@18.17.1 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jiti/1.18.2: resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} hasBin: true @@ -30185,6 +30299,39 @@ packages: /ts-interface-checker/0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + /ts-jest/29.1.1_uvjn2wawyirmlmi655a42v3ske: + resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.6.2_@types+node@18.17.1 + jest-util: 29.6.2 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.4 + typescript: 5.2.2 + yargs-parser: 21.1.1 + dev: true + /ts-jest/29.1.1_xlkreayjyan5lfqjb5gzdqf3my: resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -30687,7 +30834,7 @@ packages: joycon: 3.1.1 postcss-load-config: 4.0.1 resolve-from: 5.0.0 - rollup: 3.10.0 + rollup: 3.29.1 source-map: 0.8.0-beta.0 sucrase: 3.32.0 tree-kill: 1.2.2 From ea25c37a106e1fff4f53ca20fe9ea468ea1ba1d0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 09:19:10 +0000 Subject: [PATCH 19/95] chore: Update version for release (#709) Co-authored-by: github-actions[bot] --- .changeset/dull-masks-smash.md | 7 ------- .changeset/pretty-adults-admire.md | 5 ----- .changeset/silent-snails-float.md | 5 ----- .changeset/strange-schools-boil.md | 5 ----- integrations/airtable/CHANGELOG.md | 10 ++++++++++ integrations/airtable/package.json | 6 +++--- integrations/github/CHANGELOG.md | 9 +++++++++ integrations/github/package.json | 6 +++--- integrations/linear/CHANGELOG.md | 9 +++++++++ integrations/linear/package.json | 6 +++--- integrations/openai/CHANGELOG.md | 9 +++++++++ integrations/openai/package.json | 6 +++--- integrations/plain/CHANGELOG.md | 9 +++++++++ integrations/plain/package.json | 6 +++--- integrations/replicate/CHANGELOG.md | 9 +++++++++ integrations/replicate/package.json | 6 +++--- integrations/resend/CHANGELOG.md | 9 +++++++++ integrations/resend/package.json | 6 +++--- integrations/sendgrid/CHANGELOG.md | 9 +++++++++ integrations/sendgrid/package.json | 6 +++--- integrations/slack/CHANGELOG.md | 8 ++++++++ integrations/slack/package.json | 4 ++-- integrations/stripe/CHANGELOG.md | 9 +++++++++ integrations/stripe/package.json | 6 +++--- integrations/supabase/CHANGELOG.md | 9 +++++++++ integrations/supabase/package.json | 6 +++--- integrations/typeform/CHANGELOG.md | 9 +++++++++ integrations/typeform/package.json | 6 +++--- packages/astro/CHANGELOG.md | 8 ++++++++ packages/astro/package.json | 4 ++-- packages/cli/CHANGELOG.md | 8 ++++++++ packages/cli/package.json | 2 +- packages/core-backend/CHANGELOG.md | 7 +++++++ packages/core-backend/package.json | 2 +- packages/core/CHANGELOG.md | 6 ++++++ packages/core/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 2 ++ packages/eslint-plugin/package.json | 2 +- packages/express/CHANGELOG.md | 8 ++++++++ packages/express/package.json | 6 +++--- packages/integration-kit/CHANGELOG.md | 2 ++ packages/integration-kit/package.json | 2 +- packages/nestjs/CHANGELOG.md | 8 ++++++++ packages/nestjs/package.json | 6 +++--- packages/nextjs/CHANGELOG.md | 8 ++++++++ packages/nextjs/package.json | 4 ++-- packages/react/CHANGELOG.md | 7 +++++++ packages/react/package.json | 4 ++-- packages/remix/CHANGELOG.md | 8 ++++++++ packages/remix/package.json | 4 ++-- packages/sveltekit/CHANGELOG.md | 8 ++++++++ packages/sveltekit/package.json | 4 ++-- packages/testing/CHANGELOG.md | 9 +++++++++ packages/testing/package.json | 2 +- packages/trigger-sdk/CHANGELOG.md | 10 ++++++++++ packages/trigger-sdk/package.json | 6 +++--- 56 files changed, 267 insertions(+), 82 deletions(-) delete mode 100644 .changeset/dull-masks-smash.md delete mode 100644 .changeset/pretty-adults-admire.md delete mode 100644 .changeset/silent-snails-float.md delete mode 100644 .changeset/strange-schools-boil.md diff --git a/.changeset/dull-masks-smash.md b/.changeset/dull-masks-smash.md deleted file mode 100644 index 15c378403..000000000 --- a/.changeset/dull-masks-smash.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@trigger.dev/core-backend": patch -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -Creates a new package @trigger.dev/core-backend that includes code shared between @trigger.dev/sdk and the Trigger.dev server diff --git a/.changeset/pretty-adults-admire.md b/.changeset/pretty-adults-admire.md deleted file mode 100644 index 9283d53b2..000000000 --- a/.changeset/pretty-adults-admire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/cli": patch ---- - -Added new example job which uses Tasks diff --git a/.changeset/silent-snails-float.md b/.changeset/silent-snails-float.md deleted file mode 100644 index 003bac869..000000000 --- a/.changeset/silent-snails-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/airtable": patch ---- - -Extend the Airtable field set to accept formula errors and special values diff --git a/.changeset/strange-schools-boil.md b/.changeset/strange-schools-boil.md deleted file mode 100644 index 4941bdec3..000000000 --- a/.changeset/strange-schools-boil.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -use idempotency-key as event-id for dynamic-trigger registrations diff --git a/integrations/airtable/CHANGELOG.md b/integrations/airtable/CHANGELOG.md index f7d106a91..5c117c984 100644 --- a/integrations/airtable/CHANGELOG.md +++ b/integrations/airtable/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/airtable +## 2.2.4 + +### Patch Changes + +- 9c5a7cf0: Extend the Airtable field set to accept formula errors and special values +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/airtable/package.json b/integrations/airtable/package.json index 715dd0c1f..1fbce6d52 100644 --- a/integrations/airtable/package.json +++ b/integrations/airtable/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/airtable", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev integration for airtable", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -26,8 +26,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.3", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.4", + "@trigger.dev/sdk": "workspace:^2.2.4", "airtable": "^0.12.1", "zod": "3.22.3" }, diff --git a/integrations/github/CHANGELOG.md b/integrations/github/CHANGELOG.md index cb73ba2ab..fc8432ebe 100644 --- a/integrations/github/CHANGELOG.md +++ b/integrations/github/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/github +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/github/package.json b/integrations/github/package.json index 960f67461..935336c3e 100644 --- a/integrations/github/package.json +++ b/integrations/github/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/github", - "version": "2.2.3", + "version": "2.2.4", "description": "The official GitHub integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -29,8 +29,8 @@ "@octokit/request": "^6.2.5", "@octokit/request-error": "^4.0.1", "@octokit/webhooks": "^10.4.0", - "@trigger.dev/integration-kit": "workspace:^2.2.3", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.4", + "@trigger.dev/sdk": "workspace:^2.2.4", "octokit": "^2.0.14", "zod": "3.22.3" }, diff --git a/integrations/linear/CHANGELOG.md b/integrations/linear/CHANGELOG.md index 9cc3129a6..4959453d2 100644 --- a/integrations/linear/CHANGELOG.md +++ b/integrations/linear/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/linear +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/linear/package.json b/integrations/linear/package.json index 8c3bc1441..e21164d8d 100644 --- a/integrations/linear/package.json +++ b/integrations/linear/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/linear", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev integration for @linear/sdk", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -27,8 +27,8 @@ }, "dependencies": { "@linear/sdk": "^8.0.0", - "@trigger.dev/integration-kit": "workspace:^2.2.3", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.4", + "@trigger.dev/sdk": "workspace:^2.2.4", "zod": "3.22.3" }, "engines": { diff --git a/integrations/openai/CHANGELOG.md b/integrations/openai/CHANGELOG.md index cea8638c9..f50857f6f 100644 --- a/integrations/openai/CHANGELOG.md +++ b/integrations/openai/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/slack +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/openai/package.json b/integrations/openai/package.json index 2eb127fe0..28a3fcbc1 100644 --- a/integrations/openai/package.json +++ b/integrations/openai/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/openai", - "version": "2.2.3", + "version": "2.2.4", "description": "The official OpenAI integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -27,8 +27,8 @@ }, "dependencies": { "openai": "^4.13.0", - "@trigger.dev/sdk": "workspace:^2.2.3", - "@trigger.dev/integration-kit": "workspace:^2.2.3" + "@trigger.dev/sdk": "workspace:^2.2.4", + "@trigger.dev/integration-kit": "workspace:^2.2.4" }, "engines": { "node": ">=18.0.0" diff --git a/integrations/plain/CHANGELOG.md b/integrations/plain/CHANGELOG.md index f30d3d404..918411ed1 100644 --- a/integrations/plain/CHANGELOG.md +++ b/integrations/plain/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/plain +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/plain/package.json b/integrations/plain/package.json index b258721cf..be64d659b 100644 --- a/integrations/plain/package.json +++ b/integrations/plain/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/plain", - "version": "2.2.3", + "version": "2.2.4", "description": "The official Plain.com integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -24,8 +24,8 @@ "build:tsup": "tsup" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.3", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.4", + "@trigger.dev/sdk": "workspace:^2.2.4", "@team-plain/typescript-sdk": "^2.7.0" }, "engines": { diff --git a/integrations/replicate/CHANGELOG.md b/integrations/replicate/CHANGELOG.md index de4a70f88..4e28767e8 100644 --- a/integrations/replicate/CHANGELOG.md +++ b/integrations/replicate/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/replicate +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/replicate/package.json b/integrations/replicate/package.json index 3c67931ec..a8c57f2a4 100644 --- a/integrations/replicate/package.json +++ b/integrations/replicate/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/replicate", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev integration for replicate", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -26,8 +26,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.3", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.4", + "@trigger.dev/sdk": "workspace:^2.2.4", "replicate": "^0.18.1", "zod": "3.22.3" }, diff --git a/integrations/resend/CHANGELOG.md b/integrations/resend/CHANGELOG.md index 4772bf573..9ad606b6f 100644 --- a/integrations/resend/CHANGELOG.md +++ b/integrations/resend/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/resend +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/resend/package.json b/integrations/resend/package.json index 83dcaffdb..b6723069c 100644 --- a/integrations/resend/package.json +++ b/integrations/resend/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/resend", - "version": "2.2.3", + "version": "2.2.4", "description": "The official Resend.com integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -24,8 +24,8 @@ "build:tsup": "tsup" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.3", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.4", + "@trigger.dev/sdk": "workspace:^2.2.4", "resend": "^1.0.0" }, "engines": { diff --git a/integrations/sendgrid/CHANGELOG.md b/integrations/sendgrid/CHANGELOG.md index b249932f1..aca9f59af 100644 --- a/integrations/sendgrid/CHANGELOG.md +++ b/integrations/sendgrid/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/sendgrid +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/sendgrid/package.json b/integrations/sendgrid/package.json index e2e525355..aba99c0e8 100644 --- a/integrations/sendgrid/package.json +++ b/integrations/sendgrid/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sendgrid", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev integration for @sendgrid/mail", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -27,8 +27,8 @@ }, "dependencies": { "@sendgrid/mail": "^7.7.0", - "@trigger.dev/sdk": "workspace:^2.2.3", - "@trigger.dev/integration-kit": "workspace:^2.2.3" + "@trigger.dev/sdk": "workspace:^2.2.4", + "@trigger.dev/integration-kit": "workspace:^2.2.4" }, "engines": { "node": ">=16.8.0" diff --git a/integrations/slack/CHANGELOG.md b/integrations/slack/CHANGELOG.md index e6fd40018..7e5c12054 100644 --- a/integrations/slack/CHANGELOG.md +++ b/integrations/slack/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/slack +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/slack/package.json b/integrations/slack/package.json index b8358b87b..2c7026fa2 100644 --- a/integrations/slack/package.json +++ b/integrations/slack/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/slack", - "version": "2.2.3", + "version": "2.2.4", "description": "The official Slack integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@slack/web-api": "^6.8.1", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.4", "zod": "3.22.3" }, "engines": { diff --git a/integrations/stripe/CHANGELOG.md b/integrations/stripe/CHANGELOG.md index 63388a16b..5fe83ec2e 100644 --- a/integrations/stripe/CHANGELOG.md +++ b/integrations/stripe/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/stripe +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/stripe/package.json b/integrations/stripe/package.json index ce12e029e..8bd1db61e 100644 --- a/integrations/stripe/package.json +++ b/integrations/stripe/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/stripe", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev integration for stripe", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -26,8 +26,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.3", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.4", + "@trigger.dev/sdk": "workspace:^2.2.4", "stripe": "^12.14.0", "zod": "3.22.3" }, diff --git a/integrations/supabase/CHANGELOG.md b/integrations/supabase/CHANGELOG.md index 9dd755ac4..84cc95b08 100644 --- a/integrations/supabase/CHANGELOG.md +++ b/integrations/supabase/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/supabase +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/supabase/package.json b/integrations/supabase/package.json index b04bc7e3a..2221126d9 100644 --- a/integrations/supabase/package.json +++ b/integrations/supabase/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/supabase", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev integration for @supabase/supabase-js", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -27,8 +27,8 @@ }, "dependencies": { "@supabase/supabase-js": "^2.26.0", - "@trigger.dev/integration-kit": "workspace:^2.2.3", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.4", + "@trigger.dev/sdk": "workspace:^2.2.4", "supabase-management-js": "^0.1.4", "zod": "3.22.3" }, diff --git a/integrations/typeform/CHANGELOG.md b/integrations/typeform/CHANGELOG.md index 8cb89e29b..6cc531268 100644 --- a/integrations/typeform/CHANGELOG.md +++ b/integrations/typeform/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/typeform +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/integration-kit@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/integrations/typeform/package.json b/integrations/typeform/package.json index 43f849779..9a01dcd6c 100644 --- a/integrations/typeform/package.json +++ b/integrations/typeform/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/typeform", - "version": "2.2.3", + "version": "2.2.4", "description": "The official Typeform integration for Trigger.dev", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -25,8 +25,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/integration-kit": "workspace:^2.2.3", - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/integration-kit": "workspace:^2.2.4", + "@trigger.dev/sdk": "workspace:^2.2.4", "@typeform/api-client": "^1.8.0", "zod": "3.22.3" }, diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index b9acd3be8..aa3d80430 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/astro +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/astro/package.json b/packages/astro/package.json index 43f63d224..258e544e1 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,7 +1,7 @@ { "name": "@trigger.dev/astro", "description": "An Astro-native integration for Trigger.dev background jobs platform", - "version": "2.2.3", + "version": "2.2.4", "main": "./dist/index.js", "types": "./dist/index.d.ts", "files": [ @@ -20,7 +20,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.3" + "@trigger.dev/sdk": "workspace:^2.2.4" }, "devDependencies": { "astro": "^3.0.12", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 65fc5f3d9..895c2da46 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # create-trigger +## 2.2.4 + +### Patch Changes + +- 0876b875: Added new example job which uses Tasks +- Updated dependencies [c1710ae7] + - @trigger.dev/core@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 946e2658c..6ac040af7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/cli", - "version": "2.2.3", + "version": "2.2.4", "description": "The Trigger.dev CLI", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/core-backend/CHANGELOG.md b/packages/core-backend/CHANGELOG.md index e69de29bb..271592d3e 100644 --- a/packages/core-backend/CHANGELOG.md +++ b/packages/core-backend/CHANGELOG.md @@ -0,0 +1,7 @@ +# @trigger.dev/core-backend + +## 2.2.4 + +### Patch Changes + +- c1710ae7: Creates a new package @trigger.dev/core-backend that includes code shared between @trigger.dev/sdk and the Trigger.dev server diff --git a/packages/core-backend/package.json b/packages/core-backend/package.json index 0731a001e..145f63a68 100644 --- a/packages/core-backend/package.json +++ b/packages/core-backend/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/core-backend", - "version": "2.2.3", + "version": "2.2.4", "description": "Core code used across `@trigger.dev/sdk` and Trigger.dev server", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 01b149658..9cb3629e4 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # internal-platform +## 2.2.4 + +### Patch Changes + +- c1710ae7: Creates a new package @trigger.dev/core-backend that includes code shared between @trigger.dev/sdk and the Trigger.dev server + ## 2.2.3 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 7fe870fd6..086242580 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/core", - "version": "2.2.3", + "version": "2.2.4", "description": "Core code used across the Trigger.dev SDK and platform", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 827a90c47..7041d124e 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,7 @@ # @trigger.dev/eslint-plugin +## 2.2.4 + ## 2.2.3 ## 2.2.2 diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index f0572b599..15d7d0491 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/eslint-plugin", - "version": "2.2.3", + "version": "2.2.4", "description": "ESLint plugin with trigger.dev best practices", "keywords": [ "eslint", diff --git a/packages/express/CHANGELOG.md b/packages/express/CHANGELOG.md index 29a37876c..4f75ca5bc 100644 --- a/packages/express/CHANGELOG.md +++ b/packages/express/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/express +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/express/package.json b/packages/express/package.json index 94fa55400..c66e156d6 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/express", - "version": "2.2.3", + "version": "2.2.4", "description": "Official Express adapter for Trigger.dev", "license": "MIT", "main": "./dist/index.js", @@ -19,7 +19,7 @@ "./package.json": "./package.json" }, "devDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.4", "@trigger.dev/tsconfig": "workspace:*", "@types/debug": "^4.1.7", "@types/express": "^4.17.13", @@ -33,7 +33,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.3" + "@trigger.dev/sdk": "workspace:^2.2.4" }, "dependencies": { "debug": "^4.3.4", diff --git a/packages/integration-kit/CHANGELOG.md b/packages/integration-kit/CHANGELOG.md index 4db600a94..0d775d13e 100644 --- a/packages/integration-kit/CHANGELOG.md +++ b/packages/integration-kit/CHANGELOG.md @@ -1,5 +1,7 @@ # @trigger.dev/integration-kit +## 2.2.4 + ## 2.2.3 ## 2.2.2 diff --git a/packages/integration-kit/package.json b/packages/integration-kit/package.json index 5f7423b1e..53ad9df1f 100644 --- a/packages/integration-kit/package.json +++ b/packages/integration-kit/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/integration-kit", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev Integration Kit has helpers to make creating integrations easier", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/nestjs/CHANGELOG.md b/packages/nestjs/CHANGELOG.md index f1046f4ec..34ba9e97a 100644 --- a/packages/nestjs/CHANGELOG.md +++ b/packages/nestjs/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/nestjs +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 24cb6caf3..57ef83933 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/nestjs", - "version": "2.2.3", + "version": "2.2.4", "description": "Official NestJS adapter for Trigger.dev", "license": "MIT", "main": "./dist/index.js", @@ -19,7 +19,7 @@ "./package.json": "./package.json" }, "devDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.4", "@trigger.dev/tsconfig": "workspace:*", "@types/debug": "^4.1.7", "@types/express": "^4.17.13", @@ -35,7 +35,7 @@ }, "peerDependencies": { "@nestjs/common": ">=10.0.0", - "@trigger.dev/sdk": "workspace:^2.2.3" + "@trigger.dev/sdk": "workspace:^2.2.4" }, "dependencies": { "@nestjs/common": "^10.2.4", diff --git a/packages/nextjs/CHANGELOG.md b/packages/nextjs/CHANGELOG.md index d096104ed..c31e80a49 100644 --- a/packages/nextjs/CHANGELOG.md +++ b/packages/nextjs/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/nextjs +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index d3cc21056..d3458985e 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/nextjs", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev Next.js integration", "license": "MIT", "main": "./dist/index.js", @@ -36,7 +36,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.4", "next": ">=12.0.0" }, "dependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index a46d7e7c0..14a4a3e91 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/react +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] + - @trigger.dev/core@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index 34207d3a3..8d588fc23 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/react", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev React SDK", "license": "MIT", "types": "dist/index.d.ts", @@ -27,7 +27,7 @@ }, "dependencies": { "@tanstack/react-query": "5.0.0-beta.2", - "@trigger.dev/core": "workspace:^2.2.3", + "@trigger.dev/core": "workspace:^2.2.4", "debug": "^4.3.4", "zod": "3.22.3" }, diff --git a/packages/remix/CHANGELOG.md b/packages/remix/CHANGELOG.md index 6e1b3b001..7c3de3861 100644 --- a/packages/remix/CHANGELOG.md +++ b/packages/remix/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/remix +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/remix/package.json b/packages/remix/package.json index 3c9dbfc37..35c6c0ce6 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/remix", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev Remix integration", "license": "MIT", "main": "./dist/index.js", @@ -34,7 +34,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.3", + "@trigger.dev/sdk": "workspace:^2.2.4", "@remix-run/server-runtime": ">1.19.0" }, "dependencies": { diff --git a/packages/sveltekit/CHANGELOG.md b/packages/sveltekit/CHANGELOG.md index 34fd4d40d..4540eb831 100644 --- a/packages/sveltekit/CHANGELOG.md +++ b/packages/sveltekit/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/sveltekit +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 75842fa1f..8df262554 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sveltekit", - "version": "2.2.3", + "version": "2.2.4", "description": "Trigger.dev svelteKit integration", "license": "MIT", "main": "./dist/index.js", @@ -33,7 +33,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^2.2.3" + "@trigger.dev/sdk": "workspace:^2.2.4" }, "dependencies": { "debug": "^4.3.4" diff --git a/packages/testing/CHANGELOG.md b/packages/testing/CHANGELOG.md index bb6450413..be5bda7e4 100644 --- a/packages/testing/CHANGELOG.md +++ b/packages/testing/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/testing +## 2.2.4 + +### Patch Changes + +- Updated dependencies [c1710ae7] +- Updated dependencies [9c4be40a] + - @trigger.dev/sdk@2.2.4 + - @trigger.dev/core@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/testing/package.json b/packages/testing/package.json index e57b6319a..da5fd542d 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,7 +1,7 @@ { "name": "@trigger.dev/testing", "description": "A collection of useful tools to write tests for Trigger.dev.", - "version": "2.2.3", + "version": "2.2.4", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/trigger-sdk/CHANGELOG.md b/packages/trigger-sdk/CHANGELOG.md index 0fadaaf08..5911b2e2c 100644 --- a/packages/trigger-sdk/CHANGELOG.md +++ b/packages/trigger-sdk/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/sdk +## 2.2.4 + +### Patch Changes + +- c1710ae7: Creates a new package @trigger.dev/core-backend that includes code shared between @trigger.dev/sdk and the Trigger.dev server +- 9c4be40a: use idempotency-key as event-id for dynamic-trigger registrations +- Updated dependencies [c1710ae7] + - @trigger.dev/core-backend@2.2.4 + - @trigger.dev/core@2.2.4 + ## 2.2.3 ### Patch Changes diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index 5132539c7..5397b9011 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sdk", - "version": "2.2.3", + "version": "2.2.4", "description": "trigger.dev Node.JS SDK", "license": "MIT", "main": "./dist/index.js", @@ -25,8 +25,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/core": "workspace:^2.2.3", - "@trigger.dev/core-backend": "workspace:^2.2.3", + "@trigger.dev/core": "workspace:^2.2.4", + "@trigger.dev/core-backend": "workspace:^2.2.4", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", From 67b5365d5072a5c2a0f5ee6690644e9a21bbf0b2 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 1 Nov 2023 10:16:52 +0000 Subject: [PATCH 20/95] Fix pnpm lock --- pnpm-lock.yaml | 56 +++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 991e628b5..18b297c3f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -382,8 +382,8 @@ importers: integrations/airtable: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x airtable: ^0.12.1 @@ -410,8 +410,8 @@ importers: '@octokit/types': ^9.2.3 '@octokit/webhooks': ^10.4.0 '@octokit/webhooks-types': ^6.10.0 - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' octokit: ^2.0.14 @@ -437,8 +437,8 @@ importers: integrations/linear: specifiers: '@linear/sdk': ^8.0.0 - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x rimraf: ^3.0.2 @@ -459,8 +459,8 @@ importers: integrations/openai: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' openai: ^4.13.0 @@ -481,8 +481,8 @@ importers: integrations/plain: specifiers: '@team-plain/typescript-sdk': ^2.7.0 - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' rimraf: ^3.0.2 @@ -499,8 +499,8 @@ importers: integrations/replicate: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x replicate: ^0.18.1 @@ -522,8 +522,8 @@ importers: integrations/resend: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' resend: ^1.0.0 @@ -542,8 +542,8 @@ importers: integrations/sendgrid: specifiers: '@sendgrid/mail': ^7.7.0 - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': 16.x rimraf: ^3.0.2 @@ -563,7 +563,7 @@ importers: integrations/slack: specifiers: '@slack/web-api': ^6.8.1 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': '18' rimraf: ^3.0.2 @@ -581,8 +581,8 @@ importers: integrations/stripe: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@types/node': 16.x rimraf: ^3.0.2 stripe: ^12.14.0 @@ -605,8 +605,8 @@ importers: integrations/supabase: specifiers: '@supabase/supabase-js': ^2.26.0 - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/node': 18.x rimraf: ^3.0.2 @@ -629,8 +629,8 @@ importers: integrations/typeform: specifiers: - '@trigger.dev/integration-kit': workspace:^2.2.3 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/integration-kit': workspace:^2.2.4 + '@trigger.dev/sdk': workspace:^2.2.4 '@typeform/api-client': ^1.8.0 '@types/node': 16.x rimraf: ^3.0.2 @@ -884,7 +884,7 @@ importers: packages/express: specifiers: - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/express': ^4.17.13 @@ -930,7 +930,7 @@ importers: specifiers: '@nestjs/common': ^10.2.4 '@remix-run/web-fetch': ^4.3.5 - '@trigger.dev/sdk': workspace:^2.2.3 + '@trigger.dev/sdk': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/express': ^4.17.13 @@ -983,7 +983,7 @@ importers: packages/react: specifiers: '@tanstack/react-query': 5.0.0-beta.2 - '@trigger.dev/core': workspace:^2.2.3 + '@trigger.dev/core': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/react': 18.2.17 @@ -1078,8 +1078,8 @@ importers: packages/trigger-sdk: specifiers: - '@trigger.dev/core': workspace:^2.2.3 - '@trigger.dev/core-backend': workspace:^2.2.3 + '@trigger.dev/core': workspace:^2.2.4 + '@trigger.dev/core-backend': workspace:^2.2.4 '@trigger.dev/tsconfig': workspace:* '@types/debug': ^4.1.7 '@types/node': '18' From c7434125e41ade5172aee0d54cae08a81f83df0e Mon Sep 17 00:00:00 2001 From: D-K-P <8297864+D-K-P@users.noreply.github.com> Date: Wed, 1 Nov 2023 15:12:01 +0000 Subject: [PATCH 21/95] Removed Hacktoberfest from the readme --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index 611d9505d..1919613c6 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,6 @@
-# ✨🎃 Get involved with Hacktoberfest 2023! 🎃✨ - -All of October we're participating in Hacktoberfest and invite you to join us! We have a bunch of issues labeled `🎃 Hacktoberfest` that are ready for you to work on which will count towards Hacktoberfest. We are also running our own game, earn 💎 points to win swag! - -- Check out our [Hacktoberfest landing page](https://trigger.dev/hacktoberfest) for how to participate and win swag. -- Contribute to either our [/trigger.dev](https://github.com/triggerdotdev/trigger.dev/labels/%F0%9F%8E%83%20hacktoberfest), [/api-reference](https://github.com/triggerdotdev/api-reference/issues?q=is%3Aopen+is%3Aissue+label%3A%F0%9F%8E%83hacktoberfest) or [/jobs-showcase](https://github.com/triggerdotdev/jobs-showcase/labels/%F0%9F%8E%83%20hacktoberfest) repositories and complete issues marked `🎃 Hacktoberfest` to be eligible for swag. -- Join our [Discord](https://discord.gg/JtBAxBr2m3) and get involved with the community. - -_New to Hacktober? Check out the [Hacktoberfest website](https://hacktoberfest.digitalocean.com/) for more information._ - -🎃 **Happy Hacking!** 🎃 - # About Trigger.dev Create long-running jobs directly in your codebase with features like API integrations, webhooks, scheduling and delays. From 620b83832bb59308a5b6c3faffa8e1a5e972090c Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 3 Nov 2023 11:05:00 +0000 Subject: [PATCH 22/95] Implement manually invokable jobs through the invokeTrigger (#700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement manually invokable jobs through the invokeTrigger Also implemented a job run notification system, that will POST details of a run on completion. This combines with the task callbackUrl system to implement the invokeAndWait * Document the invoke trigger * batch invoke and wait * background fetch timeouts * Use @whatwg-node/fetch instead of the polyfilled fetch * Fix some outdated dependencies in webapp * Improved subtask error propogation messages * Document the OpenAI changes and the batch invoke stuff * Fix dequeuing jobs * Don’t retry the OpenAI completion background task * Added OpenAI changesets * Use the new ResumeTaskService in ProcessCallbackTimeout as well --- .changeset/happy-dryers-guess.md | 6 + .changeset/lazy-schools-grin.md | 6 + .gitignore | 2 + .../components/primitives/LabelValueStack.tsx | 51 ++- apps/webapp/app/db.server.ts | 21 +- .../app/models/jobRunExecution.server.ts | 72 ++-- apps/webapp/app/models/task.server.ts | 1 + apps/webapp/app/platform/zodWorker.server.ts | 3 +- apps/webapp/app/routes/api.v1.endpoints.ts | 2 +- .../app/routes/api.v1.jobs.$jobSlug.invoke.ts | 88 ++++ ....runs.$runId.tasks.$id.callback.$secret.ts | 9 +- .../api.v1.runs.$runId.tasks.$id.complete.ts | 178 +++++--- .../app/routes/api.v1.runs.$runId.tasks.ts | 7 +- apps/webapp/app/services/email.server.ts | 17 +- .../webapp/app/services/endpointApi.server.ts | 7 +- .../app/services/jobs/invokeJob.server.ts | 143 +++++++ .../app/services/jobs/testJob.server.ts | 1 + apps/webapp/app/services/logger.server.ts | 29 +- .../app/services/runs/cancelRun.server.ts | 4 +- .../app/services/runs/continueRun.server.ts | 4 +- .../app/services/runs/createRun.server.ts | 44 +- .../runs/deliverRunSubscription.server.ts | 164 ++++++++ .../runs/deliverRunSubscriptions.server.ts | 44 ++ ...ver.ts => performRunExecutionV3.server.ts} | 281 +++++++++---- .../app/services/runs/startRun.server.ts | 4 +- .../tasks/performTaskOperation.server.ts | 135 +++++-- ...ut.ts => processCallbackTimeout.server.ts} | 9 +- .../app/services/tasks/resumeTask.server.ts | 107 +++++ apps/webapp/app/services/telemetry.server.ts | 25 +- apps/webapp/app/services/worker.server.ts | 63 ++- apps/webapp/app/utils/singleton.ts | 6 + apps/webapp/package.json | 8 +- apps/webapp/server.ts | 2 +- docs/_snippets/jobs/options.mdx | 1 + docs/documentation/concepts/limits.mdx | 1 + .../concepts/triggers/introduction.mdx | 17 +- .../concepts/triggers/invoke.mdx | 382 ++++++++++++++++++ docs/images/invoke-task.png | Bin 0 -> 45017 bytes docs/integrations/apis/openai-tasks.mdx | 132 ++++++ docs/mint.json | 5 +- docs/sdk/context.mdx | 22 +- docs/sdk/invoke-trigger.mdx | 86 ++++ docs/sdk/io/backgroundfetch.mdx | 86 +++- docs/sdk/job.mdx | 25 +- docs/sdk/run-notification.mdx | 118 ++++++ integrations/openai/src/chat.ts | 10 +- integrations/openai/src/completions.ts | 10 +- packages/core/src/index.ts | 1 + packages/core/src/logger.ts | 12 +- packages/core/src/schemas/api.ts | 89 +++- packages/core/src/schemas/fetch.ts | 8 + packages/core/src/schemas/runs.ts | 53 ++- packages/core/src/schemas/statuses.ts | 2 + packages/core/src/schemas/tasks.ts | 1 + packages/core/src/schemas/triggers.ts | 5 + .../migration.sql | 29 ++ .../migration.sql | 5 + packages/database/prisma/schema.prisma | 50 ++- packages/integration-kit/package.json | 5 +- packages/integration-kit/src/index.ts | 1 + packages/integration-kit/src/types.ts | 3 + packages/integration-kit/tsconfig.json | 5 +- packages/trigger-sdk/src/apiClient.ts | 82 +++- packages/trigger-sdk/src/errors.ts | 43 +- packages/trigger-sdk/src/index.ts | 1 + packages/trigger-sdk/src/io.ts | 129 +++++- packages/trigger-sdk/src/job.ts | 218 +++++++++- packages/trigger-sdk/src/triggerClient.ts | 206 ++++++---- .../trigger-sdk/src/triggers/invokeTrigger.ts | 83 ++++ packages/trigger-sdk/src/types.ts | 13 +- pnpm-lock.yaml | 119 +++--- references/job-catalog/package.json | 3 +- references/job-catalog/src/invoke.ts | 313 ++++++++++++++ 73 files changed, 3366 insertions(+), 551 deletions(-) create mode 100644 .changeset/happy-dryers-guess.md create mode 100644 .changeset/lazy-schools-grin.md create mode 100644 apps/webapp/app/routes/api.v1.jobs.$jobSlug.invoke.ts create mode 100644 apps/webapp/app/services/jobs/invokeJob.server.ts create mode 100644 apps/webapp/app/services/runs/deliverRunSubscription.server.ts create mode 100644 apps/webapp/app/services/runs/deliverRunSubscriptions.server.ts rename apps/webapp/app/services/runs/{performRunExecutionV2.server.ts => performRunExecutionV3.server.ts} (83%) rename apps/webapp/app/services/tasks/{processCallbackTimeout.ts => processCallbackTimeout.server.ts} (84%) create mode 100644 apps/webapp/app/services/tasks/resumeTask.server.ts create mode 100644 apps/webapp/app/utils/singleton.ts create mode 100644 docs/documentation/concepts/triggers/invoke.mdx create mode 100644 docs/images/invoke-task.png create mode 100644 docs/sdk/invoke-trigger.mdx create mode 100644 docs/sdk/run-notification.mdx create mode 100644 packages/database/prisma/migrations/20231028193441_prepare_for_invoke_trigger/migration.sql create mode 100644 packages/database/prisma/migrations/20231101105021_add_child_execution_mode_to_tasks/migration.sql create mode 100644 packages/integration-kit/src/types.ts create mode 100644 packages/trigger-sdk/src/triggers/invokeTrigger.ts create mode 100644 references/job-catalog/src/invoke.ts diff --git a/.changeset/happy-dryers-guess.md b/.changeset/happy-dryers-guess.md new file mode 100644 index 000000000..01fe5c1ad --- /dev/null +++ b/.changeset/happy-dryers-guess.md @@ -0,0 +1,6 @@ +--- +"@trigger.dev/sdk": patch +"@trigger.dev/core": patch +--- + +Added invokeTrigger(), which allows jobs to be manually invoked diff --git a/.changeset/lazy-schools-grin.md b/.changeset/lazy-schools-grin.md new file mode 100644 index 000000000..97d46701d --- /dev/null +++ b/.changeset/lazy-schools-grin.md @@ -0,0 +1,6 @@ +--- +"@trigger.dev/integration-kit": patch +"@trigger.dev/openai": patch +--- + +Allow customizing OpenAI background retries and timeouts diff --git a/.gitignore b/.gitignore index 276a4f633..c83ebf244 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ apps/**/public/build /test-results/ /playwright-report/ /playwright/.cache/ + +.cosine \ No newline at end of file diff --git a/apps/webapp/app/components/primitives/LabelValueStack.tsx b/apps/webapp/app/components/primitives/LabelValueStack.tsx index c271ea296..a8ff3e1c2 100644 --- a/apps/webapp/app/components/primitives/LabelValueStack.tsx +++ b/apps/webapp/app/components/primitives/LabelValueStack.tsx @@ -2,6 +2,7 @@ import { cn } from "~/utils/cn"; import { Paragraph } from "./Paragraph"; import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid"; import { SimpleTooltip } from "./Tooltip"; +import { Link } from "@remix-run/react"; const variations = { primary: { @@ -45,18 +46,7 @@ export function LabelValueStack({ {label} <> {href ? ( - - - {value} - - - - } - content={href} - /> + ) : ( {value} )} @@ -64,3 +54,40 @@ export function LabelValueStack({
); } + +type ValueButtonStackProps = { + value: React.ReactNode; + href: string; + variant?: keyof typeof variations; +}; + +function ValueButton({ value, href, variant = "secondary" }: ValueButtonStackProps) { + const variation = variations[variant]; + + const isExternalUrl = href.startsWith("http"); + + if (!isExternalUrl) { + return ( + + + {value} + + + ); + } + + return ( + + + {value} + + + + } + content={href} + /> + ); +} diff --git a/apps/webapp/app/db.server.ts b/apps/webapp/app/db.server.ts index 171a5b79d..ad108e918 100644 --- a/apps/webapp/app/db.server.ts +++ b/apps/webapp/app/db.server.ts @@ -3,6 +3,7 @@ import invariant from "tiny-invariant"; import { z } from "zod"; import { logger } from "./services/logger.server"; import { env } from "./env.server"; +import { singleton } from "./utils/singleton"; export type PrismaTransactionClient = Omit< PrismaClient, @@ -66,24 +67,7 @@ export async function $transaction( export { Prisma }; -let prisma: PrismaClient; - -declare global { - var __db__: PrismaClient; -} - -// this is needed because in development we don't want to restart -// the server with every change, but we want to make sure we don't -// create a new connection to the DB with every change either. -// in production we'll have a single connection to the DB. -if (process.env.NODE_ENV === "production") { - prisma = getClient(); -} else { - if (!global.__db__) { - global.__db__ = getClient(); - } - prisma = global.__db__; -} +export const prisma = singleton("prisma", getClient); function getClient() { const { DATABASE_URL } = process.env; @@ -143,7 +127,6 @@ function getClient() { return client; } -export { prisma }; export type { PrismaClient } from "@trigger.dev/database"; export const PrismaErrorSchema = z.object({ diff --git a/apps/webapp/app/models/jobRunExecution.server.ts b/apps/webapp/app/models/jobRunExecution.server.ts index 0a4422e07..c22cc5846 100644 --- a/apps/webapp/app/models/jobRunExecution.server.ts +++ b/apps/webapp/app/models/jobRunExecution.server.ts @@ -1,41 +1,47 @@ -import { JobRun, JobRunExecution } from "@trigger.dev/database"; +import { JobRun } from "@trigger.dev/database"; import { PrismaClientOrTransaction } from "~/db.server"; import { executionWorker } from "~/services/worker.server"; -export type EnqueueRunExecutionV2Options = { - runAt?: Date; - resumeTaskId?: string; - isRetry?: boolean; - skipRetrying?: boolean; - executionCount?: number; -}; - -export async function enqueueRunExecutionV2( - run: JobRun, - tx: PrismaClientOrTransaction, - options: EnqueueRunExecutionV2Options = {} -) { - const job = await executionWorker.enqueue( - "performRunExecutionV2", - { - id: run.id, - reason: run.status === "PREPROCESSING" ? "PREPROCESS" : "EXECUTE_JOB", - resumeTaskId: options.resumeTaskId, - isRetry: typeof options.isRetry === "boolean" ? options.isRetry : false, - }, - { - tx, - runAt: options.runAt, - jobKey: `job_run:${run.id}:${options.executionCount ?? 0}${ - options.resumeTaskId ? `:task:${options.resumeTaskId}` : "" - }`, - maxAttempts: options.skipRetrying ? 1 : undefined, - } - ); -} - export async function dequeueRunExecutionV2(run: JobRun, tx: PrismaClientOrTransaction) { return await executionWorker.dequeue(`job_run:${run.id}`, { tx, }); } + +export type EnqueueRunExecutionV3Options = { + runAt?: Date; + skipRetrying?: boolean; +}; + +export async function enqueueRunExecutionV3( + run: JobRun, + tx: PrismaClientOrTransaction, + options: EnqueueRunExecutionV3Options = {} +) { + const reason = run.status === "PREPROCESSING" ? "PREPROCESS" : "EXECUTE_JOB"; + + return await executionWorker.enqueue( + "performRunExecutionV3", + { + id: run.id, + reason: reason, + }, + { + tx, + runAt: options.runAt, + queueName: `job_run:${run.id}`, + jobKey: `job_run:${reason}:${run.id}`, + maxAttempts: options.skipRetrying ? 1 : undefined, + } + ); +} + +export async function dequeueRunExecutionV3(run: JobRun, tx: PrismaClientOrTransaction) { + await executionWorker.dequeue(`job_run:EXECUTE_JOB:${run.id}`, { + tx, + }); + + await executionWorker.dequeue(`job_run:PREPROCESS:${run.id}`, { + tx, + }); +} diff --git a/apps/webapp/app/models/task.server.ts b/apps/webapp/app/models/task.server.ts index f2df0a060..59eb6bc54 100644 --- a/apps/webapp/app/models/task.server.ts +++ b/apps/webapp/app/models/task.server.ts @@ -25,6 +25,7 @@ export function taskWithAttemptsToServerTask(task: TaskWithAttempts): ServerTask operation: task.operation, callbackUrl: task.callbackUrl, forceYield: task.run.forceYieldImmediately, + childExecutionMode: task.childExecutionMode, }; } diff --git a/apps/webapp/app/platform/zodWorker.server.ts b/apps/webapp/app/platform/zodWorker.server.ts index 78bb74ba4..bdf356b85 100644 --- a/apps/webapp/app/platform/zodWorker.server.ts +++ b/apps/webapp/app/platform/zodWorker.server.ts @@ -14,9 +14,8 @@ import { run as graphileRun, parseCronItems } from "graphile-worker"; import omit from "lodash.omit"; import { z } from "zod"; import { PrismaClient, PrismaClientOrTransaction } from "~/db.server"; -import { workerLogger as logger } from "~/services/logger.server"; import { PgListenService } from "~/services/db/pgListen.server"; -import { safeJsonParse } from "~/utils/json"; +import { workerLogger as logger } from "~/services/logger.server"; export interface MessageCatalogSchema { [key: string]: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion; diff --git a/apps/webapp/app/routes/api.v1.endpoints.ts b/apps/webapp/app/routes/api.v1.endpoints.ts index 3c3505b45..5abee0e26 100644 --- a/apps/webapp/app/routes/api.v1.endpoints.ts +++ b/apps/webapp/app/routes/api.v1.endpoints.ts @@ -60,7 +60,7 @@ export async function action({ request }: ActionFunctionArgs) { return json(endpoint); } catch (error) { if (error instanceof Error) { - logger.error("Error creating endpoint", { + logger.debug("Error creating endpoint", { url: request.url, error: error.message, }); diff --git a/apps/webapp/app/routes/api.v1.jobs.$jobSlug.invoke.ts b/apps/webapp/app/routes/api.v1.jobs.$jobSlug.invoke.ts new file mode 100644 index 000000000..8940c6ab4 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.jobs.$jobSlug.invoke.ts @@ -0,0 +1,88 @@ +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; +import { json } from "@remix-run/server-runtime"; +import { InvokeJobRequestBodySchema } from "@trigger.dev/core"; +import { z } from "zod"; +import { PrismaErrorSchema } from "~/db.server"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { InvokeJobService } from "~/services/jobs/invokeJob.server"; +import { logger } from "~/services/logger.server"; + +const ParamsSchema = z.object({ + jobSlug: z.string(), +}); + +const HeadersSchema = z.object({ + "idempotency-key": z.string().optional().nullable(), + "trigger-version": z.string().optional().nullable(), +}); + +export async function action({ request, params }: ActionFunctionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "POST") { + return { status: 405, body: "Method Not Allowed" }; + } + + // Authenticate the request + const authenticationResult = await authenticateApiRequest(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API Key" }, { status: 401 }); + } + + const parsed = ParamsSchema.safeParse(params); + + if (!parsed.success) { + return json({ error: "Invalid or Missing jobSlug" }, { status: 400 }); + } + + const { jobSlug } = parsed.data; + + const headers = HeadersSchema.safeParse(Object.fromEntries(request.headers)); + + if (!headers.success) { + return json({ error: "Invalid headers" }, { status: 400 }); + } + + const { "idempotency-key": idempotencyKey, "trigger-version": triggerVersion } = headers.data; + + // Now parse the request body + const anyBody = await request.json(); + + logger.debug("InvokeJobService.call() request body", { + body: anyBody, + jobSlug, + idempotencyKey, + triggerVersion, + }); + + const body = InvokeJobRequestBodySchema.safeParse(anyBody); + + if (!body.success) { + return json({ error: "Invalid request body" }, { status: 400 }); + } + + const service = new InvokeJobService(); + + try { + const run = await service.call( + authenticationResult.environment, + jobSlug, + body.data, + idempotencyKey ?? undefined + ); + + if (!run) { + return json({ error: "Job count not be invoked" }, { status: 500 }); + } + + return json({ id: run.id }); + } catch (error) { + const prismaError = PrismaErrorSchema.safeParse(error); + // Record not found in the database + if (prismaError.success && prismaError.data.code === "P2005") { + return json({ error: "Job not found" }, { status: 404 }); + } else { + return json({ error: "Internal Server Error" }, { status: 500 }); + } + } +} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts index 9acf5e3ea..a3dbc80d4 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts @@ -3,8 +3,9 @@ import { json } from "@remix-run/server-runtime"; import { RuntimeEnvironmentType } from "@trigger.dev/database"; import { z } from "zod"; import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; -import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; import { logger } from "~/services/logger.server"; +import { ResumeTaskService } from "~/services/tasks/resumeTask.server"; +import { workerQueue } from "~/services/worker.server"; const ParamsSchema = z.object({ runId: z.string(), @@ -96,14 +97,14 @@ export class CallbackRunTaskService { }, }); + await workerQueue.dequeue(`process-callback:${task.id}`, { tx }); + await this.#resumeRunExecution(task, tx); }); } async #resumeRunExecution(task: NonNullable, prisma: PrismaClientOrTransaction) { - await enqueueRunExecutionV2(task.run, prisma, { - skipRetrying: task.run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, - }); + await ResumeTaskService.enqueue(task.id, undefined, prisma); } } diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts index 0f26f46e4..74df41dc1 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts @@ -1,9 +1,13 @@ import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; import type { CompleteTaskBodyOutput, ServerTask } from "@trigger.dev/core"; -import { CompleteTaskBodyInputSchema } from "@trigger.dev/core"; +import { + API_VERSIONS, + CompleteTaskBodyInputSchema, + CompleteTaskBodyV2InputSchema, +} from "@trigger.dev/core"; import { z } from "zod"; -import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; +import { PrismaClientOrTransaction, prisma } from "~/db.server"; import { taskWithAttemptsToServerTask } from "~/models/task.server"; import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; @@ -14,6 +18,10 @@ const ParamsSchema = z.object({ id: z.string(), }); +const HeadersSchema = z.object({ + "trigger-version": z.string().optional().nullable(), +}); + export async function action({ request, params }: ActionFunctionArgs) { // Ensure this is a POST request if (request.method.toUpperCase() !== "POST") { @@ -31,6 +39,14 @@ export async function action({ request, params }: ActionFunctionArgs) { const { runId, id } = ParamsSchema.parse(params); + const headers = HeadersSchema.safeParse(Object.fromEntries(request.headers)); + + if (!headers.success) { + return json({ error: "Invalid headers" }, { status: 400 }); + } + + const { "trigger-version": triggerVersion } = headers.data; + // Now parse the request body const anyBody = await request.json(); @@ -40,16 +56,48 @@ export async function action({ request, params }: ActionFunctionArgs) { id, }); - const body = CompleteTaskBodyInputSchema.safeParse(anyBody); + if (triggerVersion === API_VERSIONS.SERIALIZED_TASK_OUTPUT) { + const body = CompleteTaskBodyV2InputSchema.safeParse(anyBody); - if (!body.success) { - return json({ error: "Invalid request body" }, { status: 400 }); + if (!body.success) { + return json({ error: "Invalid request body" }, { status: 400 }); + } + + // Make sure the length of the output is less than 3MB + if (body.data.output && body.data.output.length > 3 * 1024 * 1024) { + return json({ error: "Output must be less than 3MB" }, { status: 400 }); + } + + return await completeRunTask(authenticatedEnv, runId, id, { + ...body.data, + output: body.data.output ? (JSON.parse(body.data.output) as any) : undefined, + }); + } else { + const body = CompleteTaskBodyInputSchema.safeParse(anyBody); + + if (!body.success) { + return json({ error: "Invalid request body" }, { status: 400 }); + } + + // Make sure the length of the output is less than 3MB + if (JSON.stringify(body.data.output).length > 3 * 1024 * 1024) { + return json({ error: "Output must be less than 3MB" }, { status: 400 }); + } + + return await completeRunTask(authenticatedEnv, runId, id, body.data); } +} +async function completeRunTask( + environment: AuthenticatedEnvironment, + runId: string, + id: string, + taskBody: CompleteTaskBodyOutput +) { const service = new CompleteRunTaskService(); try { - const task = await service.call(authenticatedEnv, runId, id, body.data); + const task = await service.call(environment, runId, id, taskBody); logger.debug("CompleteRunTaskService.call() response body", { runId, @@ -84,79 +132,75 @@ export class CompleteRunTaskService { id: string, taskBody: CompleteTaskBodyOutput ): Promise { - // Using a transaction, we'll first check to see if the task already exists and return if if it does - // If it doesn't exist, we'll create it and return it - const task = await $transaction(this.#prismaClient, async (tx) => { - const existingTask = await tx.task.findUnique({ - where: { - id, - }, - include: { - run: true, - attempts: { - where: { - status: "PENDING", - }, - orderBy: { - number: "desc", - }, - take: 1, + const existingTask = await this.#prismaClient.task.findUnique({ + where: { + id, + }, + include: { + run: true, + attempts: { + where: { + status: "PENDING", }, + orderBy: { + number: "desc", + }, + take: 1, }, + }, + }); + + if (!existingTask) { + return; + } + + if (existingTask.runId !== runId) { + return; + } + + if (existingTask.run.environmentId !== environment.id) { + return; + } + + if ( + existingTask.status === "COMPLETED" || + existingTask.status === "ERRORED" || + existingTask.status === "CANCELED" + ) { + logger.debug("Task already completed", { + existingTask, }); - if (!existingTask) { - return; - } + return taskWithAttemptsToServerTask(existingTask); + } - if (existingTask.runId !== runId) { - return; - } - - if (existingTask.run.environmentId !== environment.id) { - return; - } - - if ( - existingTask.status === "COMPLETED" || - existingTask.status === "ERRORED" || - existingTask.status === "CANCELED" - ) { - logger.debug("Task already completed", { - existingTask, - }); - - return existingTask; - } - - if (existingTask.attempts.length === 1) { - await tx.taskAttempt.update({ - where: { - id: existingTask.attempts[0].id, - }, - data: { - status: "COMPLETED", - }, - }); - } - - return await tx.task.update({ + if (existingTask.attempts.length === 1) { + await this.#prismaClient.taskAttempt.update({ where: { - id, + id: existingTask.attempts[0].id, }, data: { status: "COMPLETED", - output: taskBody.output ?? undefined, - completedAt: new Date(), - outputProperties: taskBody.properties, - }, - include: { - attempts: true, - run: true, }, }); + } + + const updatedTask = await this.#prismaClient.task.update({ + where: { + id, + }, + data: { + status: "COMPLETED", + output: taskBody.output ?? undefined, + completedAt: new Date(), + outputProperties: taskBody.properties, + }, + include: { + attempts: true, + run: true, + }, }); - return task ? taskWithAttemptsToServerTask(task) : undefined; + return taskWithAttemptsToServerTask(updatedTask); } } diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts index 47d11db10..6c03e2810 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts @@ -286,6 +286,7 @@ export class RunTaskService { operation: taskBody.operation, callbackUrl, style: taskBody.style ?? { style: "normal" }, + childExecutionMode: taskBody.parallel ? "PARALLEL" : "SEQUENTIAL", attempts: { create: { number: 1, @@ -316,7 +317,11 @@ export class RunTaskService { { id: task.id, }, - { tx, runAt: new Date(Date.now() + taskBody.callback.timeoutInSeconds * 1000) } + { + tx, + runAt: new Date(Date.now() + taskBody.callback.timeoutInSeconds * 1000), + jobKey: `process-callback:${task.id}`, + } ); } } diff --git a/apps/webapp/app/services/email.server.ts b/apps/webapp/app/services/email.server.ts index 04672d8be..5e37a085f 100644 --- a/apps/webapp/app/services/email.server.ts +++ b/apps/webapp/app/services/email.server.ts @@ -7,13 +7,18 @@ import type { User } from "~/models/user.server"; import type { AuthUser } from "./authUser"; import { workerQueue } from "./worker.server"; import { logger } from "./logger.server"; +import { singleton } from "~/utils/singleton"; -const client = new EmailClient({ - apikey: env.RESEND_API_KEY, - imagesBaseUrl: env.APP_ORIGIN, - from: env.FROM_EMAIL ?? "team@email.trigger.dev", - replyTo: env.REPLY_TO_EMAIL ?? "help@email.trigger.dev", -}); +const client = singleton( + "email-client", + () => + new EmailClient({ + apikey: env.RESEND_API_KEY, + imagesBaseUrl: env.APP_ORIGIN, + from: env.FROM_EMAIL ?? "team@email.trigger.dev", + replyTo: env.REPLY_TO_EMAIL ?? "help@email.trigger.dev", + }) +); export async function sendMagicLinkEmail(options: SendEmailOptions): Promise { // Auto redirect when in development mode diff --git a/apps/webapp/app/services/endpointApi.server.ts b/apps/webapp/app/services/endpointApi.server.ts index af2c04d02..18952d4fd 100644 --- a/apps/webapp/app/services/endpointApi.server.ts +++ b/apps/webapp/app/services/endpointApi.server.ts @@ -1,8 +1,6 @@ import { API_VERSIONS, - ApiEventLog, - DeliverEventResponseSchema, - DeserializedJson, + ConnectionAuth, EndpointHeadersSchema, ErrorWithStackSchema, HttpSourceRequest, @@ -19,10 +17,9 @@ import { ValidateResponse, ValidateResponseSchema, } from "@trigger.dev/core"; +import { performance } from "node:perf_hooks"; import { safeBodyFromResponse, safeParseBodyFromResponse } from "~/utils/json"; import { logger } from "./logger.server"; -import { ConnectionAuth } from "@trigger.dev/core"; -import { performance } from "node:perf_hooks"; export class EndpointApiError extends Error { constructor(message: string, stack?: string) { diff --git a/apps/webapp/app/services/jobs/invokeJob.server.ts b/apps/webapp/app/services/jobs/invokeJob.server.ts new file mode 100644 index 000000000..db93a43d2 --- /dev/null +++ b/apps/webapp/app/services/jobs/invokeJob.server.ts @@ -0,0 +1,143 @@ +import { $transaction, PrismaClient, prisma } from "~/db.server"; +import { AuthenticatedEnvironment } from "../apiAuth.server"; +import { InvokeJobRequestBody } from "@trigger.dev/core"; +import { ulid } from "../ulid.server"; +import { CreateRunService } from "../runs/createRun.server"; + +export class InvokeJobService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call( + environment: AuthenticatedEnvironment, + jobSlug: string, + data: InvokeJobRequestBody, + idempotencyKey?: string + ) { + return await $transaction(this.#prismaClient, async (tx) => { + // Check if this is an idempotent request + if (idempotencyKey) { + const existingEvent = await tx.eventRecord.findUnique({ + where: { + eventId_environmentId: { + eventId: idempotencyKey, + environmentId: environment.id, + }, + }, + include: { + runs: true, + }, + }); + + if (existingEvent) { + return existingEvent.runs[0]; + } + } + + const job = await tx.job.findUniqueOrThrow({ + where: { + projectId_slug: { + projectId: environment.projectId, + slug: jobSlug, + }, + }, + include: { + aliases: { + where: { + environmentId: environment.id, + name: "latest", + }, + include: { + version: true, + }, + take: 1, + }, + }, + }); + + const alias = job.aliases[0]; + + if (!alias) { + throw new Error(`No version found for job ${jobSlug} in environment ${environment.slug}`); + } + + const version = alias.version; + + if (!version) { + throw new Error(`No version found for job ${jobSlug} in environment ${environment.slug}`); + } + + const options = data.options ?? {}; + + const externalAccount = options.accountId + ? await tx.externalAccount.upsert({ + where: { + environmentId_identifier: { + environmentId: environment.id, + identifier: options.accountId, + }, + }, + create: { + environmentId: environment.id, + organizationId: environment.organizationId, + identifier: options.accountId, + }, + update: {}, + }) + : undefined; + + const eventLog = await tx.eventRecord.create({ + data: { + organization: { + connect: { + id: environment.organizationId, + }, + }, + project: { + connect: { + id: environment.projectId, + }, + }, + environment: { + connect: { + id: environment.id, + }, + }, + externalAccount: externalAccount + ? { + connect: { + id: externalAccount.id, + }, + } + : undefined, + eventId: idempotencyKey ?? ulid(), + name: "invoke", + timestamp: new Date(), + payload: data.payload ?? {}, + context: data.context ?? {}, + source: "trigger.dev", + internal: true, + }, + }); + + const createRunService = new CreateRunService(tx); + + const run = await createRunService.call( + { + environment, + eventId: eventLog.id, + job: job, + version, + }, + { + callbackUrl: options.callbackUrl, + } + ); + + return run; + }); + } +} diff --git a/apps/webapp/app/services/jobs/testJob.server.ts b/apps/webapp/app/services/jobs/testJob.server.ts index ab95d1085..654d66a43 100644 --- a/apps/webapp/app/services/jobs/testJob.server.ts +++ b/apps/webapp/app/services/jobs/testJob.server.ts @@ -94,6 +94,7 @@ export class TestJobService { context: {}, source: event.source ?? "trigger.dev", isTest: true, + internal: true, }, }); diff --git a/apps/webapp/app/services/logger.server.ts b/apps/webapp/app/services/logger.server.ts index 75bb91ca1..f02eb6faa 100644 --- a/apps/webapp/app/services/logger.server.ts +++ b/apps/webapp/app/services/logger.server.ts @@ -1,17 +1,26 @@ import type { LogLevel } from "@trigger.dev/core"; import { Logger } from "@trigger.dev/core"; import { sensitiveDataReplacer } from "./sensitiveDataReplacer"; +import { singleton } from "~/utils/singleton"; -export const logger = new Logger( - "webapp", - (process.env.APP_LOG_LEVEL ?? "debug") as LogLevel, - ["examples", "output", "connectionString", "payload"], - sensitiveDataReplacer +export const logger = singleton( + "logger", + () => + new Logger( + "webapp", + (process.env.APP_LOG_LEVEL ?? "debug") as LogLevel, + ["examples", "output", "connectionString", "payload"], + sensitiveDataReplacer + ) ); -export const workerLogger = new Logger( - "worker", - (process.env.APP_LOG_LEVEL ?? "debug") as LogLevel, - ["examples", "output", "connectionString"], - sensitiveDataReplacer +export const workerLogger = singleton( + "worker-logger", + () => + new Logger( + "worker", + (process.env.APP_LOG_LEVEL ?? "debug") as LogLevel, + ["examples", "output", "connectionString"], + sensitiveDataReplacer + ) ); diff --git a/apps/webapp/app/services/runs/cancelRun.server.ts b/apps/webapp/app/services/runs/cancelRun.server.ts index 494bcc940..f33457a81 100644 --- a/apps/webapp/app/services/runs/cancelRun.server.ts +++ b/apps/webapp/app/services/runs/cancelRun.server.ts @@ -1,6 +1,6 @@ import { PrismaClient, prisma } from "~/db.server"; import { executionWorker } from "../worker.server"; -import { dequeueRunExecutionV2 } from "~/models/jobRunExecution.server"; +import { dequeueRunExecutionV3 } from "~/models/jobRunExecution.server"; export class CancelRunService { #prismaClient: PrismaClient; @@ -39,7 +39,7 @@ export class CancelRunService { }, }); - await dequeueRunExecutionV2(run, tx); + await dequeueRunExecutionV3(run, tx); }); } catch (error) { throw error; diff --git a/apps/webapp/app/services/runs/continueRun.server.ts b/apps/webapp/app/services/runs/continueRun.server.ts index 644cc2e0e..c001b8a4f 100644 --- a/apps/webapp/app/services/runs/continueRun.server.ts +++ b/apps/webapp/app/services/runs/continueRun.server.ts @@ -1,6 +1,6 @@ import { RuntimeEnvironmentType } from "@trigger.dev/database"; import { $transaction, Prisma, PrismaClient, prisma } from "~/db.server"; -import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; +import { enqueueRunExecutionV3 } from "~/models/jobRunExecution.server"; const RESUMABLE_STATUSES = ["FAILURE", "TIMED_OUT", "UNRESOLVED_AUTH", "ABORTED", "CANCELED"]; @@ -39,7 +39,7 @@ export class ContinueRunService { }, }); - await enqueueRunExecutionV2(run, tx, { + await enqueueRunExecutionV3(run, tx, { skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, }); }, diff --git a/apps/webapp/app/services/runs/createRun.server.ts b/apps/webapp/app/services/runs/createRun.server.ts index 7c1089772..131835875 100644 --- a/apps/webapp/app/services/runs/createRun.server.ts +++ b/apps/webapp/app/services/runs/createRun.server.ts @@ -11,17 +11,20 @@ export class CreateRunService { this.#prismaClient = prismaClient; } - public async call({ - environment, - eventId, - job, - version, - }: { - environment: AuthenticatedEnvironment; - eventId: string; - job: Job; - version: JobVersion; - }) { + public async call( + { + environment, + eventId, + job, + version, + }: { + environment: AuthenticatedEnvironment; + eventId: string; + job: Job; + version: JobVersion; + }, + options: { callbackUrl?: string } = {} + ) { const endpoint = await this.#prismaClient.endpoint.findUniqueOrThrow({ where: { id: version.endpointId, @@ -74,6 +77,25 @@ export class CreateRunService { }, }); + if (options.callbackUrl) { + await tx.jobRunSubscription.createMany({ + data: [ + { + runId: run.id, + recipientMethod: "WEBHOOK", + recipient: options.callbackUrl, + event: "SUCCESS", + }, + { + runId: run.id, + recipientMethod: "WEBHOOK", + recipient: options.callbackUrl, + event: "FAILURE", + }, + ], + }); + } + await workerQueue.enqueue( "startRun", { diff --git a/apps/webapp/app/services/runs/deliverRunSubscription.server.ts b/apps/webapp/app/services/runs/deliverRunSubscription.server.ts new file mode 100644 index 000000000..8f5a5636c --- /dev/null +++ b/apps/webapp/app/services/runs/deliverRunSubscription.server.ts @@ -0,0 +1,164 @@ +import { RunNotification } from "@trigger.dev/core"; +import { subtle } from "node:crypto"; +import { PrismaClient, prisma } from "~/db.server"; + +// Infer the type of the #findSubscription method +type FoundSubscription = NonNullable< + Awaited> +>; + +type FoundRun = FoundSubscription["run"]; + +export class DeliverRunSubscriptionService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call(id: string) { + const subscription = await this._findSubscription(id); + + if (!subscription) { + return; + } + + if (subscription.deliveredAt) { + return; + } + + if (subscription.status !== "ACTIVE") { + return; + } + + const { run } = subscription; + + const payload = this.#getPayload(run); + + const delivered = await this.#deliverPayload(subscription, payload); + + if (delivered) { + await this.#prismaClient.jobRunSubscription.update({ + where: { + id, + }, + data: { + deliveredAt: new Date(), + }, + }); + } else { + throw new Error(`Failed to deliver subscription ${id}`); + } + } + + async #deliverPayload( + subscription: FoundSubscription, + payload: RunNotification + ): Promise { + switch (subscription.recipientMethod) { + case "WEBHOOK": { + const url = subscription.recipient; + + const rawPayload = JSON.stringify(payload); + const hashPayload = Buffer.from(rawPayload, "utf-8"); + const hmacSecret = Buffer.from(subscription.run.environment.apiKey, "utf-8"); + const key = await subtle.importKey( + "raw", + hmacSecret, + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ); + const signature = await subtle.sign("HMAC", key, hashPayload); + const signatureHex = Buffer.from(signature).toString("hex"); + + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Trigger-Signature-256": signatureHex, + }, + body: rawPayload, + }); + + if (!response.ok) { + throw new Error( + `Failed to deliver webhook to ${url}: [${response.status}] ${response.statusText}` + ); + } + + return true; + } + } + } + + private async _findSubscription(id: string) { + return this.#prismaClient.jobRunSubscription.findUnique({ + where: { + id, + }, + include: { + run: { + include: { + job: true, + version: true, + statuses: true, + environment: true, + organization: true, + project: true, + event: true, + }, + }, + }, + }); + } + + #getPayload(run: FoundRun): RunNotification { + const { id, job, version, statuses, environment, organization, project, event } = run; + + const payload = { + id, + ok: run.status === "SUCCESS", + status: run.status, + startedAt: run.startedAt, + updatedAt: run.updatedAt, + completedAt: run.completedAt, + executionDurationInMs: run.executionDuration, + executionCount: run.executionCount, + job: { + id: job.id, + version: version.version, + }, + statuses: statuses.map((status) => ({ + key: status.key, + label: status.label, + state: status.state, + data: status.data, + history: status.history, + })), + environment: { + slug: environment.slug, + id: environment.id, + type: environment.type, + }, + organization: { + slug: organization.slug, + id: organization.id, + title: organization.title, + }, + project: { + slug: project.slug, + id: project.id, + name: project.name, + }, + invocation: { + id: event.id, + context: event.context, + timestamp: event.timestamp, + }, + ...(run.status === "SUCCESS" ? { output: run.output } : { error: run.output }), + }; + + return payload as RunNotification; + } +} diff --git a/apps/webapp/app/services/runs/deliverRunSubscriptions.server.ts b/apps/webapp/app/services/runs/deliverRunSubscriptions.server.ts new file mode 100644 index 000000000..752a1087d --- /dev/null +++ b/apps/webapp/app/services/runs/deliverRunSubscriptions.server.ts @@ -0,0 +1,44 @@ +import { JobRun } from "@trigger.dev/database"; +import { PrismaClient, prisma } from "~/db.server"; +import { workerQueue } from "../worker.server"; + +export class DeliverRunSubscriptionsService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call(id: string) { + const run = await this.#prismaClient.jobRun.findUnique({ + where: { + id, + }, + }); + + if (!run) { + return; + } + + const subscriptions = await this.#findSubscriptions(run); + + for (const subscription of subscriptions) { + await workerQueue.enqueue("deliverRunSubscription", { + id: subscription.id, + }); + } + } + + async #findSubscriptions(run: JobRun) { + const subscriptions = await this.#prismaClient.jobRunSubscription.findMany({ + where: { + runId: run.id, + deliveredAt: null, + status: "ACTIVE", + event: run.status === "SUCCESS" ? "SUCCESS" : "FAILURE", + }, + }); + + return subscriptions; + } +} diff --git a/apps/webapp/app/services/runs/performRunExecutionV2.server.ts b/apps/webapp/app/services/runs/performRunExecutionV3.server.ts similarity index 83% rename from apps/webapp/app/services/runs/performRunExecutionV2.server.ts rename to apps/webapp/app/services/runs/performRunExecutionV3.server.ts index 743e3c640..6beadd365 100644 --- a/apps/webapp/app/services/runs/performRunExecutionV2.server.ts +++ b/apps/webapp/app/services/runs/performRunExecutionV3.server.ts @@ -1,11 +1,13 @@ import { ApiEventLog, + AutoYieldMetadata, ConnectionAuth, EndpointHeadersSchema, RunJobAutoYieldWithCompletedTaskExecutionError, RunJobBody, RunJobError, RunJobInvalidPayloadError, + RunJobResumeWithParallelTask, RunJobResumeWithTask, RunJobRetryWithTask, RunJobSuccess, @@ -25,7 +27,7 @@ import { } from "~/consts"; import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; import { detectResponseIsTimeout } from "~/models/endpoint.server"; -import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; +import { enqueueRunExecutionV3 } from "~/models/jobRunExecution.server"; import { resolveRunConnections } from "~/models/runConnection.server"; import { prepareTasksForCaching, prepareTasksForCachingLegacy } from "~/models/task.server"; import { CompleteRunTaskService } from "~/routes/api.v1.runs.$runId.tasks.$id.complete"; @@ -34,6 +36,8 @@ import { safeJsonZodParse } from "~/utils/json"; import { EndpointApi } from "../endpointApi.server"; import { logger } from "../logger.server"; import { forceYieldCoordinator } from "./forceYieldCoordinator.server"; +import { workerQueue } from "../worker.server"; +import { ResumeTaskService } from "../tasks/resumeTask.server"; type FoundRun = NonNullable>>; type FoundTask = FoundRun["tasks"][number]; @@ -41,21 +45,29 @@ type FoundTask = FoundRun["tasks"][number]; // We need to limit the cached tasks to not be too large >3.5MB when serialized const TOTAL_CACHED_TASK_BYTE_LIMIT = 3500000; -export type PerformRunExecutionV2Input = { +export type PerformRunExecutionV3Input = { id: string; reason: "PREPROCESS" | "EXECUTE_JOB"; + + /** + * @deprecated This is no longer used + */ isRetry: boolean; + + /** + * @deprecated Resuming tasks now goes through ResumeTaskService, this is included here for backwards compatibility + */ resumeTaskId?: string; }; -export class PerformRunExecutionV2Service { +export class PerformRunExecutionV3Service { #prismaClient: PrismaClient; constructor(prismaClient: PrismaClient = prisma) { this.#prismaClient = prismaClient; } - public async call(input: PerformRunExecutionV2Input) { + public async call(input: PerformRunExecutionV3Input) { const run = await findRun(this.#prismaClient, input.id); if (!run) { @@ -159,13 +171,13 @@ export class PerformRunExecutionV2Service { }, }); - await enqueueRunExecutionV2(run, tx, { + await enqueueRunExecutionV3(run, tx, { skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, }); }); } } - async #executeJob(run: FoundRun, input: PerformRunExecutionV2Input) { + async #executeJob(run: FoundRun, input: PerformRunExecutionV3Input) { try { const { isRetry, resumeTaskId } = input; @@ -379,6 +391,11 @@ export class PerformRunExecutionV2Service { const status = safeBody.data.status; + logger.debug("Endpoint responded with status", { + status, + data: safeBody.data, + }); + switch (status) { case "SUCCESS": { await this.#completeRunWithSuccess(run, safeBody.data, durationInMs); @@ -386,7 +403,7 @@ export class PerformRunExecutionV2Service { break; } case "RESUME_WITH_TASK": { - await this.#resumeRunWithTask(run, safeBody.data, isRetry, durationInMs, executionCount); + await this.#resumeRunWithTask(run, safeBody.data, durationInMs); break; } @@ -396,7 +413,7 @@ export class PerformRunExecutionV2Service { break; } case "RETRY_WITH_TASK": { - await this.#retryRunWithTask(run, safeBody.data, isRetry, durationInMs, executionCount); + await this.#retryRunWithTask(run, safeBody.data, durationInMs); break; } @@ -415,33 +432,20 @@ export class PerformRunExecutionV2Service { break; } case "YIELD_EXECUTION": { - await this.#resumeYieldedRun( - run, - safeBody.data.key, - isRetry, - durationInMs, - executionCount - ); + await this.#resumeYieldedRun(run, safeBody.data.key, durationInMs); break; } case "AUTO_YIELD_EXECUTION": { - await this.#resumeAutoYieldedRun( - run, - safeBody.data, - isRetry, - durationInMs, - executionCount - ); + await this.#resumeAutoYieldedRun(run, safeBody.data, durationInMs); break; } case "AUTO_YIELD_EXECUTION_WITH_COMPLETED_TASK": { - await this.#resumeAutoYieldedRunWithCompletedTask( - run, - safeBody.data, - isRetry, - durationInMs, - executionCount - ); + await this.#resumeAutoYieldedRunWithCompletedTask(run, safeBody.data, durationInMs); + break; + } + case "RESUME_WITH_PARALLEL_TASK": { + await this.#resumeParallelRunWithTask(run, safeBody.data, durationInMs); + break; } default: { @@ -488,6 +492,11 @@ export class PerformRunExecutionV2Service { slug: run.organization.slug, title: run.organization.title, }, + project: { + id: run.project.id, + slug: run.project.slug, + name: run.project.name, + }, account: run.externalAccount ? { id: run.externalAccount.identifier, @@ -534,6 +543,11 @@ export class PerformRunExecutionV2Service { slug: run.organization.slug, title: run.organization.title, }, + project: { + id: run.project.id, + slug: run.project.slug, + name: run.project.name, + }, account: run.externalAccount ? { id: run.externalAccount.identifier, @@ -547,25 +561,34 @@ export class PerformRunExecutionV2Service { } async #completeRunWithSuccess(run: FoundRun, data: RunJobSuccess, durationInMs: number) { - await this.#prismaClient.jobRun.update({ - where: { id: run.id }, - data: { - completedAt: new Date(), - status: "SUCCESS", - output: data.output ?? undefined, - executionDuration: { - increment: durationInMs, + await $transaction(this.#prismaClient, async (tx) => { + await tx.jobRun.update({ + where: { id: run.id }, + data: { + completedAt: new Date(), + status: "SUCCESS", + output: data.output ?? undefined, + executionDuration: { + increment: durationInMs, + }, }, - }, + }); + + await workerQueue.enqueue( + "deliverRunSubscriptions", + { + id: run.id, + }, + { tx } + ); }); } async #resumeRunWithTask( run: FoundRun, data: RunJobResumeWithTask, - isRetry: boolean, durationInMs: number, - executionCount: number + executionCount: number = 1 ) { return await $transaction(this.#prismaClient, async (tx) => { await tx.jobRun.update({ @@ -574,25 +597,126 @@ export class PerformRunExecutionV2Service { executionDuration: { increment: durationInMs, }, + executionCount: { + increment: executionCount, + }, }, }); + if (data.task.outputProperties) { + await tx.task.update({ + where: { + id: data.task.id, + }, + data: { + outputProperties: data.task.outputProperties, + }, + }); + } + // If the task has an operation, then the next performRunExecution will occur // when that operation has finished // Tasks with callbacks enabled will also get processed separately, i.e. when // they time out, or on valid requests to their callbackUrl if (!data.task.operation && !data.task.callbackUrl) { - await enqueueRunExecutionV2(run, tx, { - runAt: data.task.delayUntil ?? undefined, - resumeTaskId: data.task.id, - isRetry, - skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, - executionCount, - }); + await ResumeTaskService.enqueue(data.task.id, data.task.delayUntil ?? undefined, tx); } }); } + async #resumeParallelRunWithTask( + run: FoundRun, + data: RunJobResumeWithParallelTask, + durationInMs: number + ) { + await this.#prismaClient.jobRun.update({ + where: { id: run.id }, + data: { + executionDuration: { + increment: durationInMs, + }, + executionCount: { + increment: 1, + }, + forceYieldImmediately: false, + }, + }); + + if (data.task.outputProperties) { + await this.#prismaClient.task.update({ + where: { + id: data.task.id, + }, + data: { + outputProperties: data.task.outputProperties, + }, + }); + } + + for (const childError of data.childErrors) { + switch (childError.status) { + case "AUTO_YIELD_EXECUTION": { + await this.#resumeAutoYieldedRun(run, childError, 0, 0); + + break; + } + case "AUTO_YIELD_EXECUTION_WITH_COMPLETED_TASK": { + await this.#resumeAutoYieldedRunWithCompletedTask(run, childError, 0, 0); + + break; + } + case "CANCELED": { + break; + } + case "ERROR": { + return await this.#failRunExecution( + this.#prismaClient, + "EXECUTE_JOB", + run, + childError.error ?? undefined, + "FAILURE", + durationInMs + ); + } + case "INVALID_PAYLOAD": { + return await this.#failRunExecution( + this.#prismaClient, + "EXECUTE_JOB", + run, + childError.errors, + "INVALID_PAYLOAD", + durationInMs + ); + } + case "RESUME_WITH_TASK": { + await this.#resumeRunWithTask(run, childError, 0, 0); + + break; + } + case "RETRY_WITH_TASK": { + await this.#retryRunWithTask(run, childError, 0, 0); + + break; + } + case "UNRESOLVED_AUTH_ERROR": { + return await this.#failRunExecution( + this.#prismaClient, + "EXECUTE_JOB", + run, + childError.issues, + "UNRESOLVED_AUTH", + durationInMs + ); + } + case "YIELD_EXECUTION": { + await this.#resumeYieldedRun(run, childError.key, 0, 0); + + break; + } + } + } + } + async #failRunWithError(execution: FoundRun, data: RunJobError, durationInMs: number) { return await $transaction(this.#prismaClient, async (tx) => { if (data.task) { @@ -656,9 +780,8 @@ export class PerformRunExecutionV2Service { async #resumeYieldedRun( run: FoundRun, key: string, - isRetry: boolean, durationInMs: number, - executionCount: number + executionCount: number = 1 ) { await $transaction(this.#prismaClient, async (tx) => { if (run.yieldedExecutions.length + 1 > MAX_RUN_YIELDED_EXECUTIONS) { @@ -683,7 +806,7 @@ export class PerformRunExecutionV2Service { increment: durationInMs, }, executionCount: { - increment: 1, + increment: executionCount, }, yieldedExecutions: { push: key, @@ -696,20 +819,17 @@ export class PerformRunExecutionV2Service { }, }); - await enqueueRunExecutionV2(run, tx, { - isRetry, + await enqueueRunExecutionV3(run, tx, { skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, - executionCount, }); }); } async #resumeAutoYieldedRun( run: FoundRun, - data: { location: string; timeRemaining: number; timeElapsed: number; limit?: number }, - isRetry: boolean, + data: AutoYieldMetadata, durationInMs: number, - executionCount: number + executionCount: number = 1 ) { await $transaction(this.#prismaClient, async (tx) => { await tx.jobRun.update({ @@ -721,7 +841,7 @@ export class PerformRunExecutionV2Service { increment: durationInMs, }, executionCount: { - increment: 1, + increment: executionCount, }, autoYieldExecution: { create: [ @@ -740,10 +860,8 @@ export class PerformRunExecutionV2Service { }, }); - await enqueueRunExecutionV2(run, tx, { - isRetry, + await enqueueRunExecutionV3(run, tx, { skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, - executionCount, }); }); } @@ -751,9 +869,8 @@ export class PerformRunExecutionV2Service { async #resumeAutoYieldedRunWithCompletedTask( run: FoundRun, data: RunJobAutoYieldWithCompletedTaskExecutionError, - isRetry: boolean, durationInMs: number, - executionCount: number + executionCount: number = 1 ) { await $transaction(this.#prismaClient, async (tx) => { await tx.jobRun.update({ @@ -765,7 +882,7 @@ export class PerformRunExecutionV2Service { increment: durationInMs, }, executionCount: { - increment: 1, + increment: executionCount, }, autoYieldExecution: { create: [ @@ -788,13 +905,11 @@ export class PerformRunExecutionV2Service { await service.call(run.environment, run.id, data.id, { properties: data.properties, - output: data.output, + output: data.output ? (JSON.parse(data.output) as any) : undefined, }); - await enqueueRunExecutionV2(run, tx, { - isRetry, + await enqueueRunExecutionV3(run, tx, { skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, - executionCount, }); }); } @@ -802,9 +917,8 @@ export class PerformRunExecutionV2Service { async #retryRunWithTask( run: FoundRun, data: RunJobRetryWithTask, - isRetry: boolean, durationInMs: number, - executionCount: number + executionCount: number = 1 ) { return await $transaction(this.#prismaClient, async (tx) => { // We need to check for an existing task attempt @@ -851,25 +965,22 @@ export class PerformRunExecutionV2Service { executionDuration: { increment: durationInMs, }, + executionCount: { + increment: executionCount, + }, }, }, }, }); - await enqueueRunExecutionV2(run, tx, { - runAt: data.retryAt, - resumeTaskId: data.task.id, - isRetry, - skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, - executionCount, - }); + await ResumeTaskService.enqueue(data.task.id, data.retryAt, tx); }); } async #resumeRunExecutionAfterTimeout( prisma: PrismaClientOrTransaction, run: FoundRun, - input: PerformRunExecutionV2Input, + input: PerformRunExecutionV3Input, durationInMs: number, executionCount: number ) { @@ -963,11 +1074,8 @@ export class PerformRunExecutionV2Service { }); // The run has timed out, so we need to enqueue a new execution - await enqueueRunExecutionV2(run, tx, { - resumeTaskId: input.resumeTaskId, - isRetry: input.isRetry, + await enqueueRunExecutionV3(run, tx, { skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, - executionCount, }); }); } @@ -1014,6 +1122,14 @@ export class PerformRunExecutionV2Service { }, }); + await workerQueue.enqueue( + "deliverRunSubscriptions", + { + id: run.id, + }, + { tx } + ); + break; } case "PREPROCESS": { @@ -1041,7 +1157,7 @@ export class PerformRunExecutionV2Service { }, }); - await enqueueRunExecutionV2(run, tx, { + await enqueueRunExecutionV3(run, tx, { skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, }); @@ -1080,6 +1196,7 @@ async function findRun(prisma: PrismaClientOrTransaction, id: string) { }, endpoint: true, organization: true, + project: true, externalAccount: true, runConnections: { include: { diff --git a/apps/webapp/app/services/runs/startRun.server.ts b/apps/webapp/app/services/runs/startRun.server.ts index 0773eda6c..4ad39970b 100644 --- a/apps/webapp/app/services/runs/startRun.server.ts +++ b/apps/webapp/app/services/runs/startRun.server.ts @@ -6,7 +6,7 @@ import { } from "@trigger.dev/database"; import type { PrismaClient, PrismaClientOrTransaction } from "~/db.server"; import { prisma } from "~/db.server"; -import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; +import { enqueueRunExecutionV3 } from "~/models/jobRunExecution.server"; import { workerQueue } from "../worker.server"; type FoundRun = NonNullable>>; @@ -88,7 +88,7 @@ export class StartRunService { const updatedRun = await updateRun(); - await enqueueRunExecutionV2(updatedRun, this.#prismaClient, { + await enqueueRunExecutionV3(updatedRun, this.#prismaClient, { skipRetrying: run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, }); } diff --git a/apps/webapp/app/services/tasks/performTaskOperation.server.ts b/apps/webapp/app/services/tasks/performTaskOperation.server.ts index eaf74b197..d15031c4d 100644 --- a/apps/webapp/app/services/tasks/performTaskOperation.server.ts +++ b/apps/webapp/app/services/tasks/performTaskOperation.server.ts @@ -4,15 +4,17 @@ import { FetchRetryOptions, FetchRetryStrategy, RedactString, + RetryOptions, calculateRetryAt, } from "@trigger.dev/core"; -import { RuntimeEnvironmentType, type Task } from "@trigger.dev/database"; +import { type Task } from "@trigger.dev/database"; import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; -import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; import { formatUnknownError } from "~/utils/formatErrors.server"; import { safeJsonFromResponse } from "~/utils/json"; import { logger } from "../logger.server"; import { workerQueue } from "../worker.server"; +import { ResumeTaskService } from "./resumeTask.server"; +import { fetch } from "@whatwg-node/fetch"; type FoundTask = Awaited>; @@ -38,8 +40,6 @@ export class PerformTaskOperationService { return await this.#resumeTask(task, null, 0); } - logger.debug("PerformTaskOperationService.call", { task }); - switch (task.operation) { case "fetch": { const fetchOperation = FetchOperationSchema.safeParse(task.params); @@ -51,52 +51,100 @@ export class PerformTaskOperationService { ); } - const { url, requestInit, retry } = fetchOperation.data; + const { url, requestInit, retry, timeout } = fetchOperation.data; const startTimeInMs = performance.now(); - const response = await fetch(url, { - method: requestInit?.method ?? "GET", - headers: normalizeHeaders(requestInit?.headers ?? {}), - body: requestInit?.body, - }); + const abortController = new AbortController(); - const durationInMs = Math.floor(performance.now() - startTimeInMs); + // calculate the actual timeout. If timeoutInMs is undefined, we use the default of 120s + // Also make sure the timeout is at least 1s, but not bigger than 120s + const actualTimeoutInMs = Math.min(Math.max(timeout?.durationInMs ?? 120000, 1000), 120000); - const jsonBody = await safeJsonFromResponse(response); + const timeoutId = setTimeout(() => { + abortController.abort(); + }, actualTimeoutInMs); - logger.debug("PerformTaskOperationService.call.fetch", { - url, - requestInit, - retry, - statusCode: response.status, - headers: Object.fromEntries(response.headers.entries()), - jsonBody, - durationInMs, - }); + try { + logger.debug("PerformTaskOperationService.call fetch request", { + task, + actualTimeoutInMs, + url, + retry, + }); - if (!response.ok) { - const retryAt = this.#calculateRetryForResponse(task, retry, response); + const response = await fetch(url, { + method: requestInit?.method ?? "GET", + headers: normalizeHeaders(requestInit?.headers ?? {}), + body: requestInit?.body, + signal: abortController.signal, + }); - if (retryAt) { - return await this.#retryTaskWithError( - task, - `Fetch failed with status ${response.status}`, - retryAt - ); + clearTimeout(timeoutId); + + const durationInMs = Math.floor(performance.now() - startTimeInMs); + + const jsonBody = await safeJsonFromResponse(response); + + logger.debug("PerformTaskOperationService.call fetch response", { + url, + requestInit, + retry, + statusCode: response.status, + headers: Object.fromEntries(response.headers.entries()), + jsonBody, + durationInMs, + }); + + if (!response.ok) { + const retryAt = this.#calculateRetryForResponse(task, retry, response); + + if (retryAt) { + return await this.#retryTaskWithError( + task, + `Fetch failed with status ${response.status}`, + retryAt + ); + } + + // See if there is a json body + if (jsonBody) { + return await this.#resumeTaskWithError(task, jsonBody); + } else { + return await this.#resumeTaskWithError(task, { + message: `Fetch failed with status ${response.status}`, + }); + } } - // See if there is a json body - if (jsonBody) { - return await this.#resumeTaskWithError(task, jsonBody); - } else { + return await this.#resumeTask(task, jsonBody, durationInMs); + } catch (error) { + if (error instanceof Error && error.name === "AbortError") { + const durationInMs = Math.floor(performance.now() - startTimeInMs); + + logger.debug("PerformTaskOperationService.call fetch timed out", { + url, + durationInMs, + error, + }); + + const retryAt = this.#calculateRetryForTimeout(task, timeout?.retry); + + if (retryAt) { + return await this.#retryTaskWithError( + task, + `Fetch timed out after ${actualTimeoutInMs.toFixed(0)}ms`, + retryAt + ); + } + return await this.#resumeTaskWithError(task, { - message: `Fetch failed with status ${response.status}`, + message: `Fetch timed out after ${actualTimeoutInMs.toFixed(0)}ms`, }); } - } - return await this.#resumeTask(task, jsonBody, durationInMs); + throw error; + } } default: { await this.#resumeTaskWithError(task, { @@ -142,6 +190,17 @@ export class PerformTaskOperationService { } } + #calculateRetryForTimeout( + task: NonNullable, + retry: RetryOptions | undefined + ): Date | undefined { + if (!retry) { + return; + } + + return calculateRetryAt(retry, task.attempts.length - 1); + } + #getRetryStrategyForStatusCode( statusCode: number, retry: FetchRetryOptions @@ -258,9 +317,7 @@ export class PerformTaskOperationService { } async #resumeRunExecution(task: NonNullable, prisma: PrismaClientOrTransaction) { - await enqueueRunExecutionV2(task.run, prisma, { - skipRetrying: task.run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, - }); + await ResumeTaskService.enqueue(task.id, undefined, prisma); } } diff --git a/apps/webapp/app/services/tasks/processCallbackTimeout.ts b/apps/webapp/app/services/tasks/processCallbackTimeout.server.ts similarity index 84% rename from apps/webapp/app/services/tasks/processCallbackTimeout.ts rename to apps/webapp/app/services/tasks/processCallbackTimeout.server.ts index 948691990..4ce4739c3 100644 --- a/apps/webapp/app/services/tasks/processCallbackTimeout.ts +++ b/apps/webapp/app/services/tasks/processCallbackTimeout.server.ts @@ -1,7 +1,6 @@ -import { RuntimeEnvironmentType } from "@trigger.dev/database"; import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; -import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; import { logger } from "../logger.server"; +import { ResumeTaskService } from "./resumeTask.server"; type FoundTask = Awaited>; @@ -37,7 +36,7 @@ export class ProcessCallbackTimeoutService { }, data: { status: "ERRORED", - error + error, }, }); @@ -55,9 +54,7 @@ export class ProcessCallbackTimeoutService { } async #resumeRunExecution(task: NonNullable, prisma: PrismaClientOrTransaction) { - await enqueueRunExecutionV2(task.run, prisma, { - skipRetrying: task.run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, - }); + await ResumeTaskService.enqueue(task.id, undefined, prisma); } } diff --git a/apps/webapp/app/services/tasks/resumeTask.server.ts b/apps/webapp/app/services/tasks/resumeTask.server.ts new file mode 100644 index 000000000..c47b04791 --- /dev/null +++ b/apps/webapp/app/services/tasks/resumeTask.server.ts @@ -0,0 +1,107 @@ +import { PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; +import { workerQueue } from "../worker.server"; +import { enqueueRunExecutionV3 } from "~/models/jobRunExecution.server"; +import { RuntimeEnvironmentType } from "@trigger.dev/database"; +import { logger } from "../logger.server"; + +type FoundTask = Awaited>; + +export class ResumeTaskService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call(id: string) { + const task = await findTask(this.#prismaClient, id); + + if (!task) { + return; + } + + if (task.status === "COMPLETED" || task.status === "ERRORED") { + return await this.#resumeRunExecution(task); + } + + const updatedTask = await this.#prismaClient.task.update({ + where: { + id: task.id, + }, + data: { + status: task.noop ? "COMPLETED" : "RUNNING", + completedAt: task.noop ? new Date() : undefined, + }, + include: { + attempts: true, + run: { + include: { + environment: true, + }, + }, + parent: true, + }, + }); + + // This will retry the task if it's not a noop, or just resume + // the run execution if it is a noop. + return await this.#resumeRunExecution(updatedTask); + } + + async #resumeRunExecution(task: NonNullable) { + logger.debug("ResumeTaskService.call resuming run execution", { + parent: task.parent, + taskId: task.id, + }); + + if (task.parent && task.parent.childExecutionMode === "PARALLEL") { + const children = await this.#prismaClient.task.findMany({ + where: { + parentId: task.parent.id, + }, + select: { + id: true, + status: true, + }, + }); + + const allChildrenCompleted = children.every( + (child) => + child.status === "COMPLETED" || child.status === "ERRORED" || child.status === "CANCELED" + ); + + logger.debug("ResumeTaskService.call parent executing children in parallel", { + parentId: task.parent.id, + allChildrenCompleted, + children, + }); + + if (!allChildrenCompleted) { + return; + } + } + + await enqueueRunExecutionV3(task.run, this.#prismaClient, { + skipRetrying: task.run.environment.type === RuntimeEnvironmentType.DEVELOPMENT, + }); + } + + public static async enqueue(id: string, runAt?: Date, tx?: PrismaClientOrTransaction) { + return await workerQueue.enqueue("resumeTask", { id }, { tx, jobKey: `resume:${id}`, runAt }); + } +} + +async function findTask(prisma: PrismaClient, id: string) { + return prisma.task.findUnique({ + where: { id }, + include: { + attempts: true, + run: { + include: { + environment: true, + }, + }, + parent: true, + }, + }); +} diff --git a/apps/webapp/app/services/telemetry.server.ts b/apps/webapp/app/services/telemetry.server.ts index 6f476feee..eec166e4b 100644 --- a/apps/webapp/app/services/telemetry.server.ts +++ b/apps/webapp/app/services/telemetry.server.ts @@ -5,6 +5,7 @@ import { env } from "~/env.server"; import type { Organization } from "~/models/organization.server"; import type { Project } from "~/models/project.server"; import type { User } from "~/models/user.server"; +import { singleton } from "~/utils/singleton"; type Options = { postHogApiKey?: string; @@ -236,13 +237,17 @@ type CaptureEvent = { userOnceProperties?: Record; }; -export const telemetry = new Telemetry({ - postHogApiKey: env.POSTHOG_PROJECT_KEY, - trigger: - env.TELEMETRY_TRIGGER_API_KEY && env.TELEMETRY_TRIGGER_API_URL - ? { - apiKey: env.TELEMETRY_TRIGGER_API_KEY, - apiUrl: env.TELEMETRY_TRIGGER_API_URL, - } - : undefined, -}); +export const telemetry = singleton( + "telemetry", + () => + new Telemetry({ + postHogApiKey: env.POSTHOG_PROJECT_KEY, + trigger: + env.TELEMETRY_TRIGGER_API_KEY && env.TELEMETRY_TRIGGER_API_URL + ? { + apiKey: env.TELEMETRY_TRIGGER_API_KEY, + apiUrl: env.TELEMETRY_TRIGGER_API_URL, + } + : undefined, + }) +); diff --git a/apps/webapp/app/services/worker.server.ts b/apps/webapp/app/services/worker.server.ts index dc77abc76..33b83811a 100644 --- a/apps/webapp/app/services/worker.server.ts +++ b/apps/webapp/app/services/worker.server.ts @@ -13,15 +13,17 @@ import { InvokeDispatcherService } from "./events/invokeDispatcher.server"; import { integrationAuthRepository } from "./externalApis/integrationAuthRepository.server"; import { IntegrationConnectionCreatedService } from "./externalApis/integrationConnectionCreated.server"; import { MissingConnectionCreatedService } from "./runs/missingConnectionCreated.server"; -import { PerformRunExecutionV2Service } from "./runs/performRunExecutionV2.server"; +import { PerformRunExecutionV3Service } from "./runs/performRunExecutionV3.server"; import { StartRunService } from "./runs/startRun.server"; import { DeliverScheduledEventService } from "./schedules/deliverScheduledEvent.server"; import { ActivateSourceService } from "./sources/activateSource.server"; import { DeliverHttpSourceRequestService } from "./sources/deliverHttpSourceRequest.server"; import { PerformTaskOperationService } from "./tasks/performTaskOperation.server"; -import { ProcessCallbackTimeoutService } from "./tasks/processCallbackTimeout"; +import { ProcessCallbackTimeoutService } from "./tasks/processCallbackTimeout.server"; import { ProbeEndpointService } from "./endpoints/probeEndpoint.server"; -import { PgNotifyService } from "./db/pgNotify.server"; +import { DeliverRunSubscriptionService } from "./runs/deliverRunSubscription.server"; +import { DeliverRunSubscriptionsService } from "./runs/deliverRunSubscriptions.server"; +import { ResumeTaskService } from "./tasks/resumeTask.server"; const workerCatalog = { indexEndpoint: z.object({ @@ -82,6 +84,15 @@ const workerCatalog = { simulate: z.object({ seconds: z.number(), }), + deliverRunSubscriptions: z.object({ + id: z.string(), + }), + deliverRunSubscription: z.object({ + id: z.string(), + }), + resumeTask: z.object({ + id: z.string(), + }), }; const executionWorkerCatalog = { @@ -91,6 +102,10 @@ const executionWorkerCatalog = { resumeTaskId: z.string().optional(), isRetry: z.boolean(), }), + performRunExecutionV3: z.object({ + id: z.string(), + reason: z.enum(["EXECUTE_JOB", "PREPROCESS"]), + }), }; let workerQueue: ZodWorker; @@ -336,6 +351,33 @@ function getWorkerQueue() { await new Promise((resolve) => setTimeout(resolve, payload.seconds * 1000)); }, }, + deliverRunSubscriptions: { + priority: 1, // smaller number = higher priority + maxAttempts: 5, + handler: async (payload, job) => { + const service = new DeliverRunSubscriptionsService(); + + await service.call(payload.id); + }, + }, + deliverRunSubscription: { + priority: 1, // smaller number = higher priority + maxAttempts: 13, + handler: async (payload, job) => { + const service = new DeliverRunSubscriptionService(); + + await service.call(payload.id); + }, + }, + resumeTask: { + priority: 0, + maxAttempts: 3, + handler: async (payload, job) => { + const service = new ResumeTaskService(); + + return await service.call(payload.id); + }, + }, }, }); } @@ -359,7 +401,7 @@ function getExecutionWorkerQueue() { priority: 0, // smaller number = higher priority maxAttempts: 12, handler: async (payload, job) => { - const service = new PerformRunExecutionV2Service(); + const service = new PerformRunExecutionV3Service(); await service.call({ id: payload.id, @@ -369,6 +411,19 @@ function getExecutionWorkerQueue() { }); }, }, + performRunExecutionV3: { + priority: 0, // smaller number = higher priority + maxAttempts: 12, + handler: async (payload, job) => { + const service = new PerformRunExecutionV3Service(); + + await service.call({ + id: payload.id, + reason: payload.reason, + isRetry: false, + }); + }, + }, }, }); } diff --git a/apps/webapp/app/utils/singleton.ts b/apps/webapp/app/utils/singleton.ts new file mode 100644 index 000000000..ccd75280a --- /dev/null +++ b/apps/webapp/app/utils/singleton.ts @@ -0,0 +1,6 @@ +export function singleton(name: string, getValue: () => T): T { + const thusly = globalThis as any; + thusly.__trigger_singletons ??= {}; + thusly.__trigger_singletons[name] ??= getValue(); + return thusly.__trigger_singletons[name]; +} diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 665335d18..72d5f2c35 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -68,6 +68,7 @@ "@trigger.dev/sdk": "workspace:*", "@types/pg": "8.6.6", "@uiw/react-codemirror": "^4.19.5", + "@whatwg-node/fetch": "^0.9.14", "class-variance-authority": "^0.5.2", "clsx": "^1.2.1", "compression": "^1.7.4", @@ -95,13 +96,12 @@ "prismjs": "^1.29.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hot-toast": "^2.4.0", "react-hotkeys-hook": "^4.4.1", "react-use": "^17.4.0", "recharts": "^2.8.0", - "remix-auth": "^3.2.2", - "remix-auth-email-link": "^1.4.2", - "remix-auth-github": "^1.1.1", + "remix-auth": "^3.6.0", + "remix-auth-email-link": "^2.1.0", + "remix-auth-github": "^1.6.0", "remix-typedjson": "0.3.1", "remix-utils": "^7.1.0", "semver": "^7.5.0", diff --git a/apps/webapp/server.ts b/apps/webapp/server.ts index ac31b4ee2..0ceaa685d 100644 --- a/apps/webapp/server.ts +++ b/apps/webapp/server.ts @@ -51,7 +51,7 @@ app.all( } ); -const port = process.env.REMIX_APP_PORT || 3000; +const port = process.env.REMIX_APP_PORT || process.env.PORT || 3000; if (process.env.HTTP_SERVER_DISABLED !== "true") { const server = app.listen(port, () => { diff --git a/docs/_snippets/jobs/options.mdx b/docs/_snippets/jobs/options.mdx index d9fb0cdcb..702cc32b4 100644 --- a/docs/_snippets/jobs/options.mdx +++ b/docs/_snippets/jobs/options.mdx @@ -11,6 +11,7 @@ The `trigger` property is used to define when the Job should run. There are currently the following Trigger types: + - [invokeTrigger](/sdk/invoke-trigger) - [cronTrigger](/sdk/crontrigger) - [intervalTrigger](/sdk/intervaltrigger) - [eventTrigger](/sdk/eventtrigger) diff --git a/docs/documentation/concepts/limits.mdx b/docs/documentation/concepts/limits.mdx index 1bb7b0d0f..cb06a5ccd 100644 --- a/docs/documentation/concepts/limits.mdx +++ b/docs/documentation/concepts/limits.mdx @@ -14,6 +14,7 @@ The following limits apply to the Trigger.dev Cloud service and users of the sel | Runs (per Month) | 5,000 | Up to 1m | Custom | | Run Log retention | 24 hours | 7 days | Custom | | Connected Integrations | Up to 50 | Up to 1000 | Custom | +| Task Output Size | 3MB | 3MB | 3MB | | [Tasks per Run](#tasks-per-runs) | Up to 250 | Up to 1000 | Custom | | [Concurrent Run Executions](#concurrent-run-executions) | Up to 10 | Up to 10 | Custom | | [Maximum Task Duration](#maximum-task-duration) | < 2m | < 2m | < Deployment Grace Period | diff --git a/docs/documentation/concepts/triggers/introduction.mdx b/docs/documentation/concepts/triggers/introduction.mdx index c4a56cee3..bd0f1a440 100644 --- a/docs/documentation/concepts/triggers/introduction.mdx +++ b/docs/documentation/concepts/triggers/introduction.mdx @@ -1,21 +1,24 @@ --- title: "Triggers: Introduction" sidebarTitle: "Introduction" -description: "A Trigger is what starts a Job Run. It can be a webhook, a schedule, or an event." --- -We currently support three types of Triggers: Webhooks, Scheduled, and Events. You can use any of these to start a Job Run. +We currently support four types of Triggers: Manual Invoke, Events, Scheduled, and Webhooks. You can use any of these to start a Job Run. - - Start your Jobs in realtime when events happen in APIs - - - Run a Job on a repeating schedule + + Run your Job manually by using `Job.invoke()` Run your Job when you send events with data + + Run a Job on a repeating schedule + + + Start your Jobs in realtime when events happen in APIs + + This feature is in beta and not yet available to use on the Trigger.dev Cloud. + +Sometimes it makes sense to be able to invoke a Job manually, without having to specify an event, especially for cases where you want to get notified when the invoked Job Run is complete. + +To specify that a job is manually invokable, you can use the `invokeTrigger()` function when defining a job: + +```ts exampleJob.ts +import { invokeTrigger } from "@trigger.dev/sdk"; +import { client } from "@/trigger"; + +export const exampleJob = client.defineJob({ + id: "example-job", + name: "Example job", + version: "1.0.1", + trigger: invokeTrigger(), + run: async (payload, io, ctx) => { + // do something with the payload + }, +}); +``` + +And then you can invoke the job using the `Job.invoke()` method: + +```ts example.ts +import { exampleJob } from "./exampleJob"; + +const jobRun = await exampleJob.invoke({ foo: "bar" }); +``` + +## Payload Schema + +You can specify the type of the expected payload by passing a [Zod schema](https://zod.dev) to `invokeTrigger()`: + +```ts exampleJob.ts +import { invokeTrigger } from "@trigger.dev/sdk"; +import { client } from "@/trigger"; + +export const exampleJob = client.defineJob({ + id: "example-job", + name: "Example job", + version: "1.0.1", + trigger: invokeTrigger({ + //the expected payload shape + schema: z.object({ + userId: z.string(), + tier: z.enum(["free", "pro"]), + }), + }), + run: async (payload, io, ctx) => { + // payload is typed as { userId: string, tier: "free" | "pro" } + }, +}); +``` + +Now when you invoke the job, you will get a type error if the payload does not match the schema: + +```ts example.ts +import { exampleJob } from "./exampleJob"; +// this will throw a type error because the payload does not match the schema +const jobRun = await exampleJob.invoke({ foo: "bar" }); +// this will work +const jobRun = await exampleJob.invoke({ userId: "123", tier: "free" }); +``` + +## Invoking a Job + +As you can see in the example above, invoking a job is as simple as calling the `Job.invoke()` method. This method returns a `JobRun` object that you can use to track the progress of the job run, especially in conjunction with our [React hooks](/documentation/guides/react-hooks), like [useRunDetails()](/sdk/react/userundetails) + +```ts +import { exampleJob } from "./exampleJob"; + +// Somewhere in your backend code +const jobRun = await exampleJob.invoke({ userId: "123", tier: "free" }); + +// Somewhere in your frontend code +const { data: jobRunDetails } = useRunDetails(jobRun.id); +``` + + + There are many variations for how you could get the job run information passed from your backend + to your frontend, depending on the framework you are using and which rendering model. We'll leave + that as an exercise for the reader + + +### Deduplicate Invocations + +You can pass an optional `idempotencyKey` to the `invoke()` method to deduplicate invocations. This is useful when you want to make sure that a job is only invoked once for a given payload. + +```ts +import { exampleJob } from "./exampleJob"; + +// Somewhere in your backend code +const jobRun = await exampleJob.invoke( + { userId: "123", tier: "free" }, + { idempotencyKey: "abc123" } +); + +// This will not invoke the job again, but return the existing job run +const jobRun2 = await exampleJob.invoke( + { userId: "123", tier: "free" }, + { idempotencyKey: "abc123" } +); +``` + +### Callback URL + +You can also pass an optional `callbackUrl` to the `invoke()` method to get notified when the job run is complete, either successfully or with an error. + +```ts +import { exampleJob } from "./exampleJob"; + +// Somewhere in your backend code +const jobRun = await exampleJob.invoke( + { userId: "123", tier: "free" }, + { callbackUrl: `${process.env.VERCEL_URL}/api/trigger/runs` } +); +``` + +When the run is complete, we will issue a `POST` request to the URL with the [RunNotification payload](/sdk/run-notification). + +#### Verifying the callback + +You should make sure to verify the payload in your callback route to make sure that the request is coming from Trigger. You can do this by checking the `X-Trigger-Signature-256` header, which contains a HMAC signature of the payload using your [secret API Key](/documentation/concepts/environments-endpoints). + +```ts +import { crypto } from "node:crypto" + +app.post("/api/trigger/runs", (req, res) => { + // Create digest with payload + hmac secret + const hashPayload = req.rawBody; + const hmac = crypto.createHmac("sha256", process.env.TRIGGER_API_KEY); / + const digest = Buffer.from( + signatureAlgorithm + "=" + hmac.update(hashPayload).digest("hex"), + "utf8" + ); + + // Get hash sent by the provider + const providerSig = Buffer.from(req.get("X-Trigger-Signature-256") || "", "utf8"); + + // Compare digest signature with signature sent by provider + if (providerSig.length !== digest.length || !crypto.timingSafeEqual(digest, providerSig)) { + res.status(401).send("Unauthorized"); + } else { + // Webhook Authenticated + // process and respond... + res.json({ message: "Success" }); + } +}); +``` + +### Additional context + +You can pass an optional `context` object to the `invoke()` method, which will be available in the job run context. This is useful for passing additional information to the job run that doesn't make sense in the payload. + +```ts +import { exampleJob } from "./exampleJob"; + +// Somewhere in your backend code +const jobRun = await exampleJob.invoke( + { userId: "123", tier: "free" }, + { + context: { + traceId: "trace_123", + correlationId: "def456", + }, + } +); +``` + +And then you can access the context in the job run: + +```ts +export const exampleJob = client.defineJob({ + id: "example-job", + name: "Example job", + version: "1.0.1", + trigger: invokeTrigger({ + //the expected payload shape + schema: z.object({ + userId: z.string(), + tier: z.enum(["free", "pro"]), + }), + }), + run: async (payload, io, ctx) => { + console.log(ctx.event.context); // { traceId: "trace_123", correlationId: "def456" } + }, +}); +``` + +## Invoking a job from a job + +You can also invoke a job from another job: + +```ts +import { exampleJob } from "./exampleJob"; + +client.defineJob({ + id: "example-job2", + name: "Example job 2", + version: "1.0.1", + trigger: intervalTrigger({ + seconds: 60, + }), + run: async (payload, io, ctx) => { + const jobRun = await exampleJob.invoke("⚡", { userId: "123", tier: "free" }); + }, +}); +``` + +Notice how the `invoke()` method takes a string as the first argument. This is because under the hood `invoke()` is automatically creating a [Task](/documentation/concepts/tasks) and the `"⚡"` string is the `cacheKey` for the created task. You can easily see the run created via the Run Dashboard: + +![Task](/images/invoke-task.png) + + + If `Job.invoke()` is called within another job, and you **don't** include the `cacheKey` we'll + throw an error. + + +### Wait for completion + +You can also invoke a job and wait for it to complete before continuing execution of the current job: + +```ts +import { exampleJob } from "./exampleJob"; + +client.defineJob({ + id: "example-job2", + name: "Example job 2", + version: "1.0.1", + trigger: intervalTrigger({ + seconds: 60, + }), + run: async (payload, io, ctx) => { + const jobRun = await exampleJob.invokeAndWaitForCompletion("⚡", { + userId: "123", + tier: "free", + }); + + if (jobRun.ok) { + // The job run finished successfully + console.log(jobRun.output); + } else { + // The job run finished with an error + console.log(`The job failed with status ${jobRun.status}`, jobRun.error); + } + }, +}); +``` + +By default, `invokeAndWaitForCompletion()` will wait for the job run to complete for up to 60 minutes. You can change this by passing a `timeoutInSeconds` option: + +```ts +import { exampleJob } from "./exampleJob"; + +client.defineJob({ + id: "example-job2", + name: "Example job 2", + version: "1.0.1", + trigger: intervalTrigger({ + seconds: 60, + }), + run: async (payload, io, ctx) => { + const jobRun = await exampleJob.invokeAndWaitForCompletion( + "⚡", + { + userId: "123", + tier: "free", + }, + 86_400 // 24 hours + ); + + if (jobRun.ok) { + // The job run finished successfully + console.log(jobRun.output); + } else { + // The job run finished with an error + console.log(`The job failed with status ${jobRun.status}`, jobRun.error); + } + }, +}); +``` + + + If the invoked job run doesn't complete in the given time, the underlying task will fail, along + with the run + + +The return value of `invokeAndWaitForCompletion` is a [RunNotification](/sdk/run-notification) object. + +### Batch invoke and wait for completion + +You can also batch invoke jobs and wait for them all to complete before continuing execution of the current job: + +```ts +import { exampleJob } from "./exampleJob"; + +client.defineJob({ + id: "example-job2", + name: "Example job 2", + version: "1.0.1", + trigger: intervalTrigger({ + seconds: 60, + }), + run: async (payload, io, ctx) => { + const runs = await exampleJob.batchInvokeAndWaitForCompletion("⚡", [ + { + payload: { + userId: "123", + tier: "free", + }, + timeoutInSeconds: 86_400, // 24 hours + }, + { + payload: { + userId: "abc", + tier: "paid", + }, + timeoutInSeconds: 86_400, // 24 hours + }, + ]); + + // runs is an array of RunNotification objects + }, +}); +``` + +You can batch up to 25 invocations at once, and we will run them in parallel and wait for all of them to complete before continuing execution of the current job. + +You can in an optional `options` object to each invocation, if you want to pass `context` and `accountId` to each invocation: + +```ts +import { exampleJob } from "./exampleJob"; + +client.defineJob({ + id: "example-job2", + name: "Example job 2", + version: "1.0.1", + trigger: intervalTrigger({ + seconds: 60, + }), + run: async (payload, io, ctx) => { + const runs = await exampleJob.batchInvokeAndWaitForCompletion("⚡", [ + { + payload: { + userId: "123", + tier: "free", + }, + timeoutInSeconds: 86_400, // 24 hours + options: { + context: { + traceId: "trace_123", + correlationId: "def456", + }, + accountId: "abc123", + }, + }, + { + payload: { + userId: "abc", + tier: "paid", + }, + timeoutInSeconds: 86_400, // 24 hours + options: { + context: { + traceId: "trace_abc", + correlationId: "defabc", + }, + accountId: "abc123", + }, + }, + ]); + + // runs is an array of RunNotification objects + }, +}); +``` diff --git a/docs/images/invoke-task.png b/docs/images/invoke-task.png new file mode 100644 index 0000000000000000000000000000000000000000..d0a4d1c3a058a3883140c46ce83e884ba9227016 GIT binary patch literal 45017 zcmeFZWmsI>(m#kpLXbdkw-6veaF+xK?hqV;d*j+@2ofN;1`F=3!IlVfc2x5zD$DQ(T6e;pWg<-GfoAP076&!)8Q)Fd ziLVQ!<1YPTB-O*>USYtKSK>&yflQ%#)FyAG zLy@&Z<~x!-W)NxZD`(~FZ|CleBG96ZI#Cfmh42SybqibjHGg{xh+WLDC=J{!!o_lHr1 z%QWi?Whi5w`x&FXlWx2W*iw2NZ$c1u^^qi271#F7_3O^~If+QM8U~t2&cMk4z0MB; z$bz^+$NY%KxMbqw^g|I(w0@DKexT<4?il=49W{90Gt7;~u#X)hQhnBrP&r)l_Zu?K zo|gq5#0H-ZUd#C|C2u@`MQ~YJdtnzWTA-Uwk%hv6YH~vR+Jydl39ityjUT~Gc0UL$ zYiUF6zQ<`>UvJ^|9tU^bI{TKrP$z#@TE#xdPW*+vK|rrFN@MjVcx;QhDH=du7dsdahet&)Mnch*Bj1x|M=a!Oabnv>MtWk!SmPj~| zpXy_vuqS)$XFP16JW4bT-S1Ej>@s~~2gJ^gbX5R=R&HCsJ?EB(~Wu8!n z;0A^9f<&P~z_qnsyXd9(;7*Z{t)r#PUmr)MJY_&{x1TS5dGX@u-7|BP?U2jY;reFR zDVU3RHqAe8L3|pa10d`QoP8&v?a)tGZ$>I&%$a^BSp?t4pRZW_>Yh-Ujiu;+;e|36 zZFh4bqp7DkV@zOO;Xf2DJRP~_QyT*GJ0H2Exe}fYxv~4i`W^edWflIcz&^$_#^|@u*StQEgF18@|EU#p52_?-lVB@iA*^ zFn1JwORY}Ky{YJa(Hx%}&xJ647;$}e_WbSnlDMm2yUWQfLT8JcqvNk%9&B|40lrYj zD?Wcn8Og7S>?fUb78R2E^bp6LV1yMs8^^*OM7sBAWotg^0c5|B{HSq1et3b4qt=N+ z?8~bUF?oeLgy8a;=>_^=r%e^59g<#`QWg3>`sY;)enh1p%vC%P8nkn20@*OY?-#bu zTilNyRi2XOgc8#chEe?d_zIVb9`}*7N$fLvivBkr!%$KKha?7K3HrRvFx7(gCBDSs z_X*@-HU$fdd&kc7F+P*fRAHUOt(TO}eUk7gB>QOFlN&Q5k}&sVjA2{1j&$iW?^niA z%x#`JGT%V#_Xz4gTwmc2QJTJg`SP)R4<`o)FD6SjbCpdQwr$|Z#pCJEXU0@cv`XDO zAQAm88c!r}59506ub$uDK8(u&jq6&!&^^U&KZ`RE{SdBLE8+klV# zRH>_NQo>W39?vc8oumhdNIgv8`_A{C?^xfxe>#~E9X)jlkfhy--IrO;dx00KA;Zj2 zPIvIUK8`KkTZSqjAu%#BJ>f%wTH;4J1~~$`mV_uKgLmE~r>SL14b;H6+&u)9vkTECP$e!gEs9_e%7x7HSPhL&plyGaJYc?<|yEWM4 z+XU^JPbJ%!@nG^4%%jws)IY9Awjr9job2CC+I{I3b@}p4?#$whY!{i7`!ffrD(MAj z1dqFQ`h;hJ?0DVelgSNh@XTU~m)glwtlZQwZEiMuN_(<{XXVlnMdtZE(*tA7GxNDq z#Z#5##RoYVMIxFlYF9<>jo1aI#og2W6FMceMv!K3;nNVh6uRGZgrAGZoV>mRmtvd! znthfym%Nv3o3l>0y#u|Qya&%n&!9W&mkpO3m+Gf{yIJFhWW_w=w)7rmbA2~-4rE`^ z8qliICa@YvU5TF%laip3g+G}g$>iSW8s|9X8sN}1cO5F4NwL^Befi4Je9`7Qy+YA) z{AAm9=it;2A2}Wu|5HC&nC@Y&l)&}UGGwhKWksLM=C|U$sxyI;ysB4Bmfrp#`>1uAfr=+I)>tfz70*z z9hNSPG8DZW+IEPli1LbBAU}*8BgYpkFjE*znNey`(l3-NECp*os3CJ;MR2A5zW#4E zG6O*KxIUde5vZZb&&|aHzh$cFtU0ZvutlqB<-HY04oL)wr3eP)EoH4pl&8&eU0 z#lwr_;|0F`__iz(L}vYzDbg+y zNp-&{Q5BX`p2H|j6-DzyvRC7W@Fxm<+aLzwI0O}Jk;bv{#K!k3h05x4jHtDvcM?{WPJxrvJCMp7}i>!wGS z_{Y!5Gm3a)xsJpIX+)TOwJwR2o)v!Kis=Fu4QxYdK(-)uPrp{pR>O8af7Cnc0*|fm zAxoWQ9hO?}THjihnk$r-UEW=#=`rbw0*5ZS&K?`sTMV015SGnC2p=X-aMQPQ*|+fp zO1pS-pX8*L;zbxMGZ)#h@g3O3AO9fYCa~$Ey1@Qazd_U=M4Oc}kb|R+S4f^x!lB7u zZ~En+PHG~P5S`$Tmm&+CvCP|T_G6I*$AZ%mkQzKRkTS))V0V5!C*tw}vY#%WnkEE; z;q}H5FgQRMhHXheFIB@Bc@p|-~C__GJg4q zxUAEuy4XruWyP9Gnj+Bqq!-a3I+p}G7;=bQv1!f2f-+>Pe(s|t(3?Ev{UCEp9fDy>9K@ zV_gY;X_x<+VQXelY%qsMu@zuGS+Oi#Nta9)`KS4*ABIl4kX9?&E zx3EC^<&BOo`3%A8m(N=gMzOp8$FyR#1GvAHwf){E5bz=FuHFF^sIFU@5IdLcfBi;1 zn;J(5D?q|ML{zbatO>fli_c)m55^clOv~-C*su)nSKqJ%Q~kVcLRHfw`sH%P9`(ux zKs=)>4Yw%5&9!AM6crJe;m;TdD2Sv8sPHF5_$7)+_V4Gnh%XS3|9FptfDmepfb!2g zO7Q#rCkB4qm-)vXIW7bN9sUasetCXH`d4mL@~_DMdPWR~ry;ynmynf(-_=c>&CLNW zRt~PDXAG0@7mplebX*V+2pR4#L|HZZU-0^8tTnVk`jHV0=*b`Ajn0rpp%?3|pe@Eoiz zUI15PPgZ~n-9IY%S3Q#EE~d`bj;_`Y0P6dCjZGZfTt#SU?;HB}=O6tv_q6_>mH;mQ z92R_l?Dr|`9Bi-H|6Mn{sPO$;K^1FHb2}YLYkN4&;BAO<32+Plk^etZ{-?!1mDK*9 zk{n#G`2Sq=Pf33-s_A0xEa6}eZ_`!uf9C6-h5wxR&w|42_e1{^DgHs{Kiulqavl8Iht+#K174 zaf}S0)uawUK*IQsuYjl2jvGM_CLtg_#iw?RBxWF>c{n|gF6*Ht;qSsy5CToVzbM9d zI30uh;>myX@pCYb-+?&XvCD(p^!KH9H%(o)5PUbM=C=y%pRV`nR(`lT(vKfnvx9{vT zsn^KdgQKTbMa3YWaDqG{p9uWH@K8ICXmF=GowiCV2U0CFNuC`is4K;m>C}4nVp9mx zU%?JxnG0lt$*l44pR+AW+q{A6k2WDaPakN_M4P%K&=dp{0W9(ttJRly3~a0BZUhq< z{>tn zp|O`|dxHWDa$zA!x+B%5y&J1tK{Ym0%w9EG>=&EKMsZW|^PQ@DK34~g`Qlw43@P*) z0f}S|pe`U3kk`lxJrkyVXo=xsA~_N8@A|}aOGn0FvbVmHYVla{)e`jBeRe8*(9iGp z4TVX)Kw)$ps)R)aJl&?L>^T3@GALZ|+;J$Ct8nOU6+9 zNw5`mD*x+h4{y0Z)ARkg&6_5de9xnw%RF7XpCt8KiQXeWwDtpual_I&;S`E;_8&|_ zG!whKu6=3Htg$tc#wZm*5Z_?GfOh)g8cyImg`IK4nL7I!CEZ2@`&%9uTHId zxmC(+yDzXmktNv!oW!b^7sz!w&$@;ro#F2^U8du_d;w`GOf#N8$e781)J?F(T16O8 zEj+}b80l7lphx6d75XHjEJqn0%du==;VAd9%wU%E6W5d)z1`_zx`hUNm#l$E60SVE zCgWk8MExp?`~i#&c!aDu|G}Yu{oa3R6@kY+FDv~ zFZY^BRIVWfg{+7K$0!ey1pg&2Utq4Fb>(Y=jeME4Zx(=__wL;xSn;KmO+SS4Bud<@ z>h+e>MD)NSFnw6D!WVAY%$AQ@Qu3kDtZxt@x;iKV#j^xSTJoMEe*|XXq_k z(Y(ui2*Pfmf{6_1gEJ_jQ85Vz}EiTq(6>9zGU_~FxPN{%AKMt=YBPZ6R->k#{$pyAMrEr1g^t#Y#^@C z@*yUY`^Swnpp?e@KFmBX?wQBU@JF^pXn~-W>+2xYDDVAc;9+9ueBGmYmhrZCRBl}< z@5*&iPdybBSBqvu;cJo8ZQ$9`6FtuHq$&az+z0tVfiNthQ&lU=u+gv0Z1@XLfaMi? zN8kL2X#7Zq>0ZX&bh#em!F+xERe5G`Hj;EB?E%kRo-96-QG2Tn{Z?MQk~5B1G&(kU z409q&#JTq*-b=^H_i7QoZP*j-aDFsjhpnWUGTp>yHsonA_C6$Var+bkK^Ww2u|x*% zdss=YMSiaaA+}*Jnfj&^6go8TJckwMJ>&Ogq+A)D*&VMhO?aV7yQSbY6 z)ma@_>+K#zF0g@RIYuf(ywu`Rsp0XiV!FqATvu%(scyRe;z629s#CKeN&AQK+fKjS zna`J3NyZVq9zE4?#X-X)9}&9qsa=f(5C6WsI&3LK6RvP3j2`Q;UgU)D3nZf*9bUUQ z=qe~NvVlQ6V_)JS{a{t{(RZoU*gN*GR*PJe*mRmT9zT1rs+sM!rymQT@TSJIKQau! zdO%x5L@^P+p8>b=9%JXNq14EI_(t{`*1~v++}lg(uMcfKQG!*$iUPk9Ho|f}I_G|0 zmR2fgZ$sso94Aj)i|Vj%8Xzu83GJ6#sLkg=Qs=(o~a^!Ai|Xo{%`QIxb-i%OODn1=vfPQTuh?4Sg$4KzVA zS@54LCBWs3$ei{4s(~)G43dC34g|K7-UsGV+K%M}3y+p)jyBgd=2ozY*lnw%a+-(h zCBjnRTRJBWe_h*NK4h#w(iTJGsQT zl?(x5H-c@&G~WEu0a~eW8Ky!?3Z%BZ#+8kh!}fT%EaWtBGe6DIwvm6kf;mI?fSN#G zAg+%V^~tN$bLL?>x5Jh3{S`Xhum^Oc0P#h*iu+@@JSY2q3;%QD`u@KJ`x~eH|CCA6 zV>&q;j#|}Pb)5uqhe+b?(y!yz6UgHjY?DB*bN|bNsp+iF?cEq%mN@R67BhoDgfy7v?W^i0*L(js--j0#9i04pUXSHM-p0m#0 zU~6XN-djHgq3QjEARzX@7@c&};e*ST8uxWz*<6hr7)c;s<$^13F^DMbOFd+LK6!c| zsY#Faw_Kp6x!kI!>O{mw9AR>b*7Vn-P}Bts668bVkq33DurC22(Uvo!gP4>Z>8JTV zSG$d>UTsC_QS-IcK554VM-f7-@giw3$<5CyOb=>+7$1pVsMG{Z`m~n_SP%c=QQg@g zp6XPE*p+d$Mv=T#oF%@saC!EiN;HJ)C?QQ@DAF-}9X4`bs#>jA&LWVQ;%%Gx?ex&E zpF9utS)3X*5YG+F(Ov3Bld6fVmG9_2k1fsWIJx}U&8Wmy_e80-X=pB`;p*GmgH)7M z6oSlirWT<_O#!u%-y{>p zUEsV-x@kEty!z}y^I#ziNkeLwbyGgL=u6FdEk#WoWIU`*d=jZQ1fNt6{6QdiZSrZ0 z*28!K95PunoTi1x!THM$omSbKSLHcFNh;p32+-^G4s(Vy(Mo zSIysBWLykJ?LIYTJH5w~>b~Qe=tO_RMo}KaM-N)GHP#Wx$gC|lEF^Eg>iKK6>sjeUc9X}ln?pFn=}lbc zr)zzMOm0KAwz~9tTdLV@kL_?c6Mt;s%_^~~67!$!v_Sr+{5u=*`Ra{^&T@etUhB1b zi~zjaSJaOiC&qRq36P{&9PcqBh3Ljy>F5Gav17;xI~BWunfE{wBR+pKo@_KqR3PP; zpF3?(e!{Yz4N~u&ZuCTg0_WcEPq+Hd*bVj1p-U`R=LZqL^`1LM@-q?qS#pV!W7Gi_ zWWh{M71lfFMt(^ISw3cdkPS{IW0Y=wM9H=48T95Uf5^lqXtnmOzS(I8KEH)V%-)~PaO!0guwdcG zI_u=RimnE#hQ2VOEUw&vMc9#5GNl?wM)kz?cLcK8!Za&1Tfvv!@f0;m$bCO%UTa4F z=y{p`QFZMv8EC-CH`J)+uf5&&x`S{4mZ>mPSLOEh%3-eFHo5iohaRWfuIr=e(wd!N zJ^`2TB|F31O=O$_S;h={#V_1RU$mxU@EE|cMi~tix@X0$=ksL8OCDrM^Et|{!gs%& zOzGjMS^Z1?PALi+}urBi&yu}WIQamSqj{b#b37lKBRox-f=X?zZ` z(PFof1&UcTEgpwQhSCru3ekP3lj~6Xg>P&&&RrLfL9P*Dd+-v-Dc2Ra|EQAEBiFe5 z(z*AbQ2-P0j0fg$NCh;XC>Z9n8jW~;SZ|p8<$dv+i(~NUG#EOV(!_OBif-iN#N<`y zt0w?Scaa1h!!1T>UV%ekPA%?m%_d8$?L^Jf^3H12T-!}Bp+`!A-C|w;lLf5l3V?x- zJSrR!VZ|h3i+jr6mcCy{mNAv!&|F;oj7jbLOIqjEPQmIfRl zu^BnyMmSh8$iC1x2y(*T2*MG4;{It4fwwTcc{o^dbFDjei`l;_e-2&Nfv6Ozf*7rczfXT3%-s8vln!%ZuQ6a z^6!3QC#4KwKHHs+4Mavy=_V!JW#85PfEwj>vXv-{-#dUxr2iiL==@&jZobrZF%S>P zhkT|yi_>sJ5@q#Rap~}?$jmHq0!e0#wY0Sk}#|@ZZfdEbfIqe!ci!ZqLham6}A%FUKIiu{?Wt^|h=) z(B1EKoMLw=#Y1g5GC(KpOULVt^`7UyMw?KwwvY0J@ANyqP8}@3lDVyZABw1_DQmHE z(Nr2bp7ESzP;bwCow3O{+hYu#A53CRPQs}>2dme@mVM=2iEWDTY+^H13IHOcj@Ag} zr6IOst94UBvaf4_U>D>|*&wuRWDhJ5mGza?m(bp37|&u3M;A17p+Y4(7?UXJ;=}?7 zZT7Ag15LcL{c-9qHqp4~-Qshxsb!eBd;D`X@cH(+`z0@Tg1x)^F{;%5;kE>h2yJ)z zs7{cd|Cr{i8|uc>?$EwOFx8U}iSJ$d=3%arMI36S?-ZMUeOnciKebeiPXloH_f_pR z?qQtGja8k&*YJ}R+HV!6E%>)0*D}D1tu%*&B__avC~O%fUX!J&`&uqb0BhdC&c|g` zY+yDp*Ds0ZbmmZ{inHgU8(cebuI%1-<0ZbD5*b4AXPq852&wH-^w3pdfK?2zz}$_- za7Q7Xws;vE<*d|yve6lMbF2aDte2~B7Mz=EIS(;ARH&_;f&=2P7VFrGc?XcrEY2!m?6TJP7Mf?F(AFx_fXcJSq7VEbIKdjk1Ef_NPCTn)v z^)1ucvth>=qf90>v`!WEEjZn|Dg)Yd<}L}nBNBtFd*vQJTam>%B2SibVBUAP9eLt` zNXs`fra@>}v06W(T$H(@vlY(1o>xtw3NKcv0+}0J-`KPWPc|nCpQ@~iW&oANEZxHh z_gw%p_;xK0S<4oI3H=NeY8t=Ge2YbtVSQ&nn$T71c7D33$o1NC!Jy(Uyb5LLAk2|{O zaky;%!B*({@_gZNBPW85J9&?aqz>f2@k%+}pVDsWZ4%qfcVE&`Tg`6WtE&Q$$TJ+$ z=AX@ab<3n$)7aYwWKfsvnH`wbx8GWEQ@LUe`(ln(82(acUyOkiEeDQN_E~UH(D(3^ z)HixQ_nm=VnzERI{q6S8={?c;z}&sc-c!p2(V68XcQ{D+MR{LNX72t;T*r@Z5NG8LW zjYA#lyP68fy&^eYH)p?mM*1w0l4l%~=DIEU8>*jfmfjoGC{oUH?e@v5d|wE7B~QJu zqQ-le6gD2PEKBOn3s{^KB7~kv1XGh1mFcEVs;dN%wXxs!Jg+mbohdv0R{N#CLUy`z z&{i_$vm;_M1^;fo%hph0#S$e9scgZ)flW|&2Fq(7S1Pn1nwVp<)uakR2WMMkqDmtI z?d|35;_UNzKC>_ox8(gOguk%oYdg>2fb zzv-+`9EB>O3A}bxS1ma+I~u6UFzBY{+TmE)N+FE|npf5W--@@MH-B5yfeHNFzzBIJ zDx=vm`@jp5!J>S=yLLk(?q8tqFA*Oj(Q?o{_x3NCxryTHq`GoEn=-xP7_a@fwoc>u z#8<`EoFAIw+n%CaUzKq#ZeRvmtrg&U)C@uR3I2<=tZh5N%ALU0i(~o1UT=X6C+B{T z6(0suX)H`#2VHkOeo7qj!30(Z?*IdDYk~E`j!_)4B%gF(hvikD+l+xNI913alM8)$ zqQ++q?_`mRe|J>cW4YT}31=XXoj!l`9BX_XN3bg|=_%b?v>{eYID^udmzVCMoa&F; z?tk5GIppP+U{2cssKYrFDsV~6=GY2>whXnp7pBJIgeao zl3%jC=2+=~&UB0W9Xs&D5<120T;YiNmRkehrmJocIHqCu5filEsTzu7>$$=1{;CME z#@9%z{Q5hl6`8>bzfM)QgsAPb!ef#p;|ARh3bQ>I|CFMuy4e}q%E4V8o79_2v!(Bw~ldszCZa5GGzvL3jNR=T$u7-Z3| zgazGKE1A5L1co;IzXFxG8{luVU739%>JuB?%{DuUH#E3nZW`Iq1dd4&39L*tyoMCj z0W+pcJ<6|7PezvI7gkgc&h!>^o({2HQfS}oIJ1-EOrM-Zaw#PTW5#VcTpu(VfMLQ+ zuG8N8z><;Vmp9tFLa*yO>g>F-Qjt|*rJvDqD`E{o?^+51w>Og+gNwR8Sd}YFwD1{b z?X;oaMD2Nq@bsJ?p+{ZzK4~Oc2i8Jlv>3zAlAkLA5|)*3$mDh>A3>??PW6QxCts;{ z>jFWonYn=%Il(7wG$)vZY;g_XI)rC`fe!A?9?`-);YqgQp#HvWWb?tT&#za$p%Nv= zUFnLLMD65oirFpMHDponFJ3QS_gvO@ zu65%-Z#5xy1KGh(oaNp&Tbw)zdCYM;pulQkQ%Al((RbiYofBzfVV-UtXaMd zN>D4O#<^+#OPK%L3j5hkVgIWGkGv~`{yMib z>u#eeF3**l+EuEY>4?h!+;NDn?2RbB+P-xX4`teO+&q1DV6JslqPsQTK5~kGvmU6o ze`rYx~HfFqJ+AfMrNS9f&#Ht|(wej|33DOWv!EZAnd(-_P>Ai^Z(%zXW z^2vBUJ4cJ5`z?9X~!fVl9ZZCdv7 zdDmBpe*KTg8$&S2M+6si`kQ@;T$TsBjU3;6KOAPk3)sLir4?_(LVchvS-J@@5CG9_7Bu^_KZxLYduXMO?OQ$FFwUw=`O?g&gaEQ@eXM zmGdf6d!V1;d14m7fhM8NZ($3f?yIwxUFdm=Y13Qdo8US`*vj3Z2IB4I{zzd5Q2_=2 z`SwKuK641=*4^B^rlIo6xOv=`|5*p&$X_)kZ9@KBm8Uziti`4idtA>4O9--1?4b-lS?LsBeC|CO^eyWdb-od(zH>*gDxL+!VG805Zz zxi=A2VtHQvU?JzNR7=tmHUp~ox!M)J2;PsF9&=9R+pQa6P#zH&yTmuDm2=GRQCQNVbD^X^$pj#Z_ z*qFc`nlRrexte^2gsOZ|F#S;1t2+>)7N~Q~!CI^EM8Sr-qzAt3i2_Am8_a2I@lTuJ z7@gthH2@2j9M=`$fXQe<(x!_PO~y`*Fkja#E9Hiwd>`~NkqKOigw>XjX;jowQ62h zF}(-K*+{*GS$=AyE1p3DnnYsbQZa7`fXnJPhTE`iR=A6u8Unzd{UA#nVDLBhhibMM zArH!(If%f-P;`26u0_X*#pv?|7ye!S&&|Z;dUDBa8l+3J8cIwmqf472faTM+NHUSd zm-Oog3J|owTz1DvO5^zj8LgA4ycvT!IJkj#3@*zfv&J!K0JdB}948&2<$Ojbgdcyc z&{5D9cV8_gI?hzwDCjPFMSJKL8BMq0M>$AxIZhg8oSV>`+m=SNZRemtX_=efKmnG) zhO8uy$ayvMf*R zolR2}(nZ3$29?&`180eK)P_yGVV|9WmAhm?=j&OlG%p1vXj%ZuDtEH;ENcFQpziUT z(xAdEA=2Vbj{L79nIoD+WX*@3m9MW{jM1acFK%HJP%jY8-R<6+i`gsz10G$X%SW2~ znJLYV@7gYByb9;xE?+BYaEw3?EC8`{DHkCK)FSz-vnU+%JoN>Hod9jzFLnpdIFAxn zGi0Exu-8o>R~^e|*|q#OkcQVQiENbWyK$Vy9cuYf5Fb~U497FH@)zHgkfe=PixEoqJyy%S{I~|VXFcmf1+vVbA&q9qf{5^xno{C8v|JAiW8kN zOlVx0UL*sT+BaQ(xkQWrYzDeu}6tlyg! zP;~==2=ADe;R|mZ_Jg9K;_TxWw%5_Be>oiu)FvElB5{j)doVOvXgL$G95D~bz$+UG zpxJU-z|k}Ny^6@xPH`YGSFOj+)nN*X;p_=7!^a>GLW#OXMXM+>92_DmYMbCr80|50 zO*FiBy=;VJXBJ|%Z+~OIHGy%4yRH^daT=(+l`oQVJWUZvpTQR$J1Tlb^c)4GTRjiep)2qAwiHqrv?e1G{GTi2x8z%7d)t&FuQG?GsF?a`c&D% z=J@;6e%K+gm+H`F_X-Xr4S##SCHwpaA8zdBKO+5RLtH9#m<2}PIaheX+hG)lRLH;f zlh&mjEnGJp%@f&Z5mq6t`|`{n}rEiYc>1b+?-jv3WzX{ z@vN84{VNV=Ans{umqT;v7(Tz(kgR9gb7$b&A-RY}naf?Mcke#dm8CK0u)!DWukNVg zFCS}^=qm!6H?b$`MrO(rnedUc(g54n0AN1XxN?NZ$hr+L&!uV|V)A$v4 z=qm{5j=SZ6jmfCGroWAHZ}!Jh22>iUmpU-l2An3eK=!x|eoUp7>#gR{3?IRpFDprt z3$i1aMEuRK!?6b^*ZjJ|mt4kIPFN?|_inux-*e5fkF#j`*lih1e$@z?5KGsrKQMv) zn7DCY{eA6nBZ;iadM+Gp(R<_%cMY@trtOg>!l(N-M8npX6vG-v{(uSOacIcp(*pMb z-pa4*&Bd-%P!%HFEz%uV%XT}{Vn|ZsJq9@-KW%0CmK*@7SLDrzleJG1V<=UA!0_E`K zmUX!NaCJD1uS2R?Y?(FHVK50N98CuB2#zQoGGPs$64_umxkImq+lGX9UoG6;WFCF^ zYK0Odh?4_WFWh9YJc`pxTQT5}AN!cRUgsX{@t$oOPUo_$ zaD~QQyW|^hlq3$e5)7Vk6=dguB1iaiwBzjT z9-cY+p|@Or4v`EZ!z#OGdA(N!r0}bhlAypE3kuFL%r9QV@DNke37%a8i9iZ(+OJv2 z7gh{>N3^CDbo2WBhL@Gk;+`ws9z6BVL6$z7&SJQ&f(2c;Y zoT>u?EWx&8)a}lRyA}&Kz1ZY#D~nzy@#PKWfmbZ1hVJYf>R6FfVq-us+FzUmtd=O&RxOzt-gXjx{x5(2t&TDd?NFSA6U=| zS24W%Dds`Yz1cYq=#=qinl}94UM+!nr|%>Y@+59viy@=3jofW>_JZnV%$r~^u7RQ! zu&t>LTnC2Fw1J>Ug3zJDu1#rWwn*!=-+5}f)8!7hH39;RSO*UU}FjZ^9H_)*wF4MF! zu3StK0PrqNEmVo#foO*s?%6TL*I6z;~Riro@-WuC$O ztX_32K0c9{y4j1OIR1{`*4k}e)`a0!$QG5$GGxo7TNlP<-U-aKUTBCO_z)a1#BpPJ zl!GG~nqfOvZCZHYvu87Eo*q;BZkTj=F>eb6l7$bqK80L$kqtmnp+s86x;hm_?}o4R zx1H|vLzsNo3KTM_FXtw5vU8OVs`@T;>V2;&)rOlOe2b%f*Xg6{g8rsU-dJv2nhg4@ zMOo$tvK5#&R%2O}_~}w%y!P{Ux5PQ>f*^HttClakwoqTgB_h~8Cbz)vaElH$QSbP4 z&fUJ9gV}a_=sErC5riSF~D#9>+A6t6JJrANn#RCR}jD(-bc@6FUUaT zj(Y1_z_QeI2wDIj?AeY0`BJjiO+Cq0G62?eQzYTT0~)1?*M$RMS+IkN1r+RNiV`b| zCg)AgP&&Bqd{<WEz(9Tevtr;^~c zB9=&7(C)2M=Zcaql+D?kN`A*Bd!^cA#Y-LxKaISqF{EJ$z{-e(kS<(o4H z9sC&@YGC;~Ez|68!3+-V*EM&QzR%N7qxlDml;5C7oj`^;I({+R%sCUp>Flz1dq)w9 z6pn9dB6}z7_+eo#1ruG1ke_*WeLyw8Jng@C=

~ajoAaLTj@MA>0jwdlZmz?~wbM zXBg_ar{swQo)URs(F59etk3k|Zqf%fbH}1yAVpjD(+OW)?pCk;Bg*mJz^gV?wfiNz z7x@n)rYH>$P$A;-hWXYBxxxzWw)f0KyA7~_>9u{LA|+9!L?PtqvNs2@pRMY8DgNvw zw=9$PxVLLX2P|F6@{}tZ9*ChexCC2tfFlwHJU2R)Y_bQcJM4luKjA=^`6_16O(^H- z;yv2*-7s4}6nU@eJ*&4^sevk&6|wyaI?zwPt-_?|F{jCIT!&lfr9!EW6VqPBm|Ufl zvw#zlnYheOr+cvbm2zk{I_BUKK?2XS(p81=J;FPY3=xR$SY|Dt^Dwh8dmA$dElq%M z$rqa25oM@+?hcQ2m{0Qxt6fz}WKN8~NgO;CHmDvSqS~RdIMl>7q05ImX|rq&VHLB0 zl3(h4^CHyd*6?pwad}O&IL>v@S-qG~V7_{ClV8DgxiM^z zGF*bdkJ#k7w4^y|H^J?1KeOaXe!{Ccy82wRc!4Tf-q9LbB&gUTpY(F=$qt*DH`4`C zjBGLOL3MH9$6L-yW9zC>mVCi+7k%I(8;94d(eB&yClLRXOq-w3FYurfnVs?E6yqD9 z`=L|Fw@R+=Qm*yS+iz&i<&u+rS`sFUo!=UZR^C!3rjA7o`Cw7#ma9{5^vCN>3q?rm zJVTGY#Z=Z^Q7)=dftZ)8wl!Zj9V+90E6krU2JvnSXTz-G<$-|AOBl1ol0K2SiTC&(psA+SR+@&<3zMgWZPzl2@fb;$XyO zc8qKa39%aehI%sW6Y0%Q$1)gNl)3a5bPoZEgBk8Sn9CPp@kvKS57Iel+^OrTX>MSU z`awJgRa#TfX1uoMf-97$px>|H1UUBOh414bSI<5JPR8AKX zdB*(r7&vMuJi2uqnsfsr6E;6aVbTZ1=nTUKE8CO0f}tuqdILOQhBn~^TjPgat%{4QlhZ_QE_(s=162!+Dk$mT=;qE*s|!=3$e7Oj^V z|DtdFD{LSY9zvV)sIL{}!PHN*@YEL`Utpj9Qs+PY5yhXwBLyswIN1XqOwEC(J|D=~ z^8K^x)W37aJc2uYrUnej|J}LqkDkT6fTw0|DccN6-gS|a4|z-C}e6ZV7d=-c;l zZYFvDFR1jta>~4bON@`rORYM8mX3KuWgp>xS|NY`UjY9OIL={#%Q^!i8|l9&7ys-H zbv9gB;P1MhSUlKFSOt7dxcU}T{Hxu6*9tEm_z{oVQK4vII8Exos+-}QHcyxL;fX{*O!K(jn4F2C$|L=hR#cKDY{ofh>D|7xoX6dKDhQF}3q`Qvy$MFE& z`@itmqSy!@X_AY0Yee0iL|@;)j0L%gMnUvRWwbFNJXZzc) zhXMd&?*EH|M0P-Earv`gq~QNy@6F$#Y~T3tN~s8yELq!7vZU;L3o2W(@7WoQeK#fv zNtP!e#AIje%M8XEG9iW-J43SXF~%4(^S!;7=ktD^$M-lsf57LLj$^_-_jO+9b)K*5 zTwbRRt6qP+ex#uqJbAV-Kb=4MgUIb>Dg~(eBceVpQtS6F%FModkQFwVEKGh3lww); z)>l7J=}eZg>~o$-2lDgROlw>uwK2`p4{n(h1C6o%&kRb8<}==}iUAY?&k_5H@4w3Y z#wJ^SE5-Tp%SHaBNbkR!G6Qf+w2@9Kq~3N5sp9atwgdJ)K4B4y3dDy(ptbb_WTVpH zQ+0*LUPNb{;u3D5(-!YoFp{%WVo$BHg!1X~zr*(~jmG1R;sSU3+v!89o06o}nMhQfV}%~9wHU@ARLulCpn&_P$wd%hoG;s(|o%Bd#H7S1;J^*XB#B74Y=l zERy$E4Y%B@=Wb&=jlsx$-Hj1*t!e)ZW_cc2@5lQ9TOB^hTU$C;`S?;FqCTbQcXl}a zuduqR$%lVSoBdl~bM6D1#$dEw2$*qtu4q_5l6+rY$yti;`ksCS6VGDtv5>)BsW{+^ z_un~Hg4dQ`+GJ)p#(nFZ3`)%?>{c=sta4~%abG5v@FrDZZe4}Sc#i^O@@!fte-9+94O+DTd%{o#Bt)^yG&L0yKFNZp~}i*hpZxC{*$qPFmY}F z>bbop>yz+F#w*n#LR1lF$;cyperG*L%6+iQd&E2R)hU%on>Y@|_8tG#vDsUPj1a*< zh49^giQD%r6Vi`bMj^mxqmi!LVhnr(0~eyks$R>S&ysRrAN%#H*2vxhER!v-^YL`S zL!_y~MDGNqkQp7NSY}M(xcAzGtxTpI3T}Bt?Cs{wHlLl(H4Ms;DS52K`d~WVVNHghJkLaM~Wv)u`*%s0{!L=hKMS z780#jo`x*mzuJnh}jS#kTR!+g?Pe;K;HB-{1iIdE!y}ZChb=PyMsLObx(kYZ`VUY z=xmWFrh*8Arh3<%(s8c~#4qP;i+gO4IgH0=WowDo!Q7zekOQ%~%I|;TmjChE-ZB7AFOy5qA=(icyO^p*CEnnf^!W|JjMq=RjxS$1h7aqguAI`|&?` z3PE>3E$j?D38j|k*4MG4bX@pHagLB`?uqJ@pkAhTJS+^1+bQ*B)Zpm?3Y#v&xozw# z(bX@c^{A2r|42nHW)=T;f8J65YwONjiUIbRHg=SeYopmEq55i8Mr)yzNE9a*zxnn` zgoDucY2!3IpmN~2Z+&gPtB#vGn;_#dZWzB(u%zfdNYDg8?7W--Rx=+QzF@1J_4DCRtg+0Ugma8Xf~7sWCBqW z7sNMc8=#&PK0s@LWG+ev+JTQ!n&*c~^Zv;_g*1Q+DOFcMZm>Mcr;@_kM(iBYmW*i; zOQ9S1r>u{kd;s{nXV+nbZlEJeGEIxsBw~A;SRGx>gg#hLm^Foe~OVXWukWltj;P-ScO#z3whlTCSrll`s}#64#7b`{FTC5LIFPgdC(R(S|c zgh*)irJG0n_R^uxS<3lnXclVY5gBtmPq^xWZgQ@_L4dXhL+C8O#PCQHo?0o&W{AMB z8FrIC8=pAhN_VB`PzvsBYSTtzZK`64K>wl+Uc%Qi8|N|rFIwwhld4iOH26NfT0wV| z3_SqQYX8}E5O{@%nD7!T`|l67(?&&OCUW&lWxhYAnvDrtreo@s!)KUcNJp(A_YtsN zVaz%oLTz+j$8em<(+PuDIAo#A)Tnd=!LrDTX{{%xVkL@Ht!Ay%ONOJM&cG+MuMix7QIhSX$?=PTE_+4&hT-b|*Jj zetXplg&m=C&rTf5cp6gXb)|7ML|-Kk0&qph#0xa@~>bvQ@F8WeX0Ot>3xGZs>*Z#dkN9V%<8ouMPLg9o z`ozhO$>2hKtpxnHqn_LM0KQG7e=5PVKmfMgla@-MjZGN@yZCpso{KH_H@F3;ya}Fe zXst!X7fdoSSSU~XE;%tU6f)V*(*$j5+yXNcvBt1e8E6Z`7mYF>@D)9;^AvZD(G1Sd z6E-m@^v@7CJt}^2Gagt#Xsst}uekg7oQ;m(7o`p0E<;75*W{{vT*o&zSH4RwIU+%* z(KyIzS+l`t>YUEET2B?dPO+!lhKAxKt4w{1gM3Y@|MhzN`T5;@fH=x*4gQ+q`IljN zYjJ2vbWfmhcb};@cF|&oOcolURZYT4M)-$XcgHi;^`gb-Scea|N_HZRKZuTvEjGg$ zvg8mwtT7b`uMLD;o?%VB#b{s1+tv;r?~RWfA7%b|1e941#n&Rz+u|{mf7?cFC(s+( zK4@2g=7Z!*r|R8ykuO;)8*1zqHE^}FGp*_DFvv`5{i=^F^;9W(R`FKfvT;n1vhboR zqUIy{cMf^0Rk@zJB;h;j<>Nwp`o80Rh?(eq;d0iqbPjT@X_YK)>NOCubY{tdzh%kliqRpwLt49@A~iD@8sTOIMerBIuG$->w+1|GdPsrr^v z`)(^!HAVVx1cEHR_LP(OdNjQZ%p*}@-@g3vZT%5KD+HJ;zfbD{M-0&07wA=F*Lp43 zuLudbPV)SbF6sZ{Y0zpIs{itJm+#Yd-GWCv!Qk*{y#SPGu#{u%Id<3inZ%rhx-Kjx z>a;PJCY+ft!%MYH_mOP!(;O$h$=m%pxHJTvxEr)wCEM?^@3NyhaYUdoSLe{0RXzU* z{5zSLs$U5Zkd@g=mM0hw-}9|LRTk_tJVC1O`N$iir8!~*vY_WW^Unhne~1neHBy&O z#(o{mLA^4^f^&T~o`1;*qPVRnRlJ29sN=;Q!i@j9sADH`pZx0u?oS&9ht6g)7>qYA z2qO)Z3T+|$Lr9`J+|E71=+qHz;B(HQIN}A|g@9aKTFmfh0W;ee25L|pG9ZN%WzAn- zxf?dnafBR?oiIKRY{jvZmWo9hbQjC*k(WtIrZIxft4YbrW68}1z0=>zk8~YR=z-mH zr#-_JEemWRiGq77=bn};iEkU*?b#SS+_%mnzE8RE@av|<&r7qqP8(&9$vQvtOC)aw z@tN*8h(D4za&6xaos|2sWC0>mzu2gMsMx{@=-lakL2u`lNXH^*l-s563UI3J5-M}; zHqN@YA0=4hLxp=;XM*Q2sDroGNFaTCmd_!d?HUvs6?$H(G^oVhHXM692Tg1gXt{6{ zQIoC8p*67&cjFu^o*NQkLU2LWR262Lsb&V?2Sev9IF3R-AA$f#aL)2XQ&M)SUYU6n zsL|D=q#WpX63jCNN!jmNTS@%$Y=7ngTo3C{Tf;J)+snh{mE*5c4a)|a@j2RRr{1w- z@ehJjODgU0AXCclT|XH0C=hl2@gdZH3J^X*G||B#7W{14_BJWyPj^=aoOp1M$S9 z$g1yNN3)oz1Mg-0%3kUy1wHfOG$731?nwc$NB-FM&@!m^t@&Pn>u-9{_YtMAk6MI<9V4QEaI6f|8JB3D$@VA$$vlX{}(6uXVn4BwWi9I zQdI&WW0QA#hAnd^0((plE*p^Ds-Btx2Yt9f#n8d~0|#2W&p>N>K3Z z0a#n=g!g`W)x?q3E1MV18E^KpjS_Ef#w_(n6C)f8O6=2uP~59Bnh-BcqT z3}{;qFmJdZiLA)(QU=$ppI>+TPjA$XQWUDJ=jhT1T@&s4KY4~Hz3(~Kj<4@7SVG54 z%P`Sth8esEZZJrRZ^@X)Nx;oi@itL=A-yHWRs7%#=rm?LnZBtU2n2O#_xjZ9hM5;z zGztrQ)NSH_n|QAqs4m>mAIf$#UQdDA8AwPWZXGG3h={_5UxkGXKmAX=l>B*S7T?|CS(sBd;?%120$bE zhf8!kYB-rzfDo0X=ADuoZv&xJlZ)_TqG9vP(!wXVad1sz<8D{CBxL#5NHHJ`qC@|} zQe&r*HsY0qp$Vl)=eoK32!mf$L+%o6&7LE+&P8GXqMA*doJvlX$4AgeMZ` zkw2o+2$Gu$*^Wsv^7?i<)_J%TL6{1;?fZ*!u)xYx4I$|Ks78=Zb^v=ay5t-8$;zAO zX&-MiZX-)Hb?$+uio9k)?ZoM)k^AT$xsu*}$^$`HDsIqoA-?k^TiW?ZPK`_jlO@t* zm;PwSPS{}c>g}hm46J>4!dy7Rev{augSZ4GE(u8Go8Xs`a|Wg=rD?V3W)Wj9i2dXx zHrf4cgG-^=zP}e=dw9Ttz-a1$+B7@Jo%b)+;${F~)UPR?UOJ(M4IT_mG@Ng+)m9Lb z0B?XK1BZxgCgF4}X2RUqp|jCUTvRjUcUq=ZwqDN_v%cBqx>Y|!=b>Pm^}5?&MW*vN zokH)pbM@u>nMo(Ej|98VCF@)vwtqk$w2ipJTPl_^d)79SO0rGbZm*)648JBZZ5;Sv zsH11owLI_V#R`k9G9?SU5g^mjPOWXb>*H=63S`&p`StvCpHk96%E98vAX3b*%{DpU zHXO(i*e){ABuJ(J0r3j2(|=tO%OPF}=6HT8)`|g+Q6!J>pxnHFC~WNF6iRH86DAwB zm|~*#vx1b6W~de&dLnUoU~50b5Ir=@cp~@y=W>9*cNm+U>LUnhEed(w_Fm9^P}Xpl zbCI=?P%5`tB+B->sE*{2;TO6RBrzFuZ}{n)?E~OJC)4XQ?CZrRdM<{2%{s?L1?X_b zUxYENqIsKY{%CXS<f>!+I?4G2(-ha3? zqh_tC!h4gyXr{miGX0yh(Oj3R91=8f?HR{OI!iQ+h&df}ZGSLze?^pYkeWJD2`H8XdT5$H@wOq-&@#hDTTzh@$>VqaCc_Dp3({Ia zs~1*$^euHC7wAl)72GXX3v=nYAPG(q-D^-h3-}mE^H0iYNo4j|wfgw4F;P<PUh*m>6#$Gi!t++mkt%lqa z_)t5P4jcl5ad?y9~W(H8?o*d zE-@u!tXrdiK!1g?)iitBM|ZXRy)@ZsDIm4y=WlABu$un3TniXVJG$i7_d>Nlj)}q% z1%1Es3OdRyHh}$d#ki?y=3%`}a_xIe@{~2_k~mL)Jw*gxqss8g6c&i8uwd`#yMipI zpaj>TUo#by%zFG(6hy8=_>#gVEQSblwh8*ibqChZZ_Koq0;N}Q`M*}gJT*8ps$|_$ zvD~sS+xm?yB@%GnT?XIGI1x~UkeK2%4{kH6x7FTH8a4BsI1UeI7fw1F--)_VZZ2Sm z*!dSrKX9%ipI3hDGG8`Lkvn7i1lSdUcaS@Qkh1TH1+32W$B{_u%IT*PETZ)&80X_( z<8DpJ`i)v)OpDGWV}U5zXJx3)9t*OVTrFoEYEbDe=$N?3DY>_AhHg6+1M z5VV^j;MMcgcgj=vlIBeyZ-<94`%ex#6e{YT<;uPPSA_jE?qAv!trt2wBW$V$iI>0_ zDtWBQeR8CZ2U)6f9eh0$Fq=PkY(EQJG8RB`o6oYQkK)rCjBq#vZ6yO!rk9k}&`s%xf+ZVmO>EMfR>C8P@-X$^WBsq||p&Y@=yz zIv9~fkHYbB*EVo93~KVh8?)@Z{$mQFLB7i`+1v|s%F|-%3nE?X^dUhrM2EGy%i02y zr;WJP(<9go`29CbrBoJVzUzUhYd_qjYNkr0 zoNf%kY{tLccGq*wE-K|-6lsunicl@eyYkHBm#Sv<%xk z+F~R&HAYZ*S-_l!10}ffo&t%HDo5#xbvC#LIzTH-S>f-{K zl#=WhD?kFWo{-Ix4>UeoPBOZnM(0s0UF{xBDWFcB_w+eHM~`>(byKuzp#ks5W%_$U zO=s&t_uh#%htl3FI^6i*1^3KEKHq_RO)k-IiYsz zuXAcb-?IUC;Y}`YnK^Iga_5INyT9kUv-{i;oLdIWtilKVs*KGtOP_`D$g!^8#%XKA z+mE~921z0vEY|z)i+ywE0fO3@k#kSpg!p6od<;+Ef5IZ!g0bkwa*tPq*Nz;m2IU6}~!Uz%9TO4sk@tBOP26 z-9c-mPP(jb2q6RP&Z{{rW5~RciU55*@lz>D1aYBe;zLn~-(DbaJeJXE7KV5UEK={P zyZ5<+zb5TJ)B;<1RqIQ|TAZKeU5HUZ-vb)NySfDtLuWB;9`*)l5xM4Cv`=NL`DUrZ zyawp*YoZIE=3${jQs@P!{FXMuz((Z~X-o z9Xr7X)GivGqyP}O9j;$y4`j?MludP?757r{OyS;hm}-W9kPG>@Be&^*8|S{Eqdl7<1xUv762q`3>y6v zXa1SLe-B_demkpcM}}+8*dCTWM{%tD&5Zu%qw;g$(K$jIN5*50g=!o|O?7Oz|9+$W z_bufyP|g1FLwMBx&zhnD)!+ve9~r+ocH#+;7D%YPb7VZ`_{qD6;mAiZK7YFo{paJA zLg3NQS^1BQ$GlYL2jF2!K?UQ{0=dBgRLOL;w&S0Cz8{wE(l&-z!%Iz>z%p1dKy>x3 z&Wkt&-x^>H#n|q+n*&tH97LF;#zQv0sC}(flWbHx!{t9sK|1zdGcBJk8W|`wtE)sv zH`F!m{pbev9@>%jzt@3Kyz$Ex4~SxY?jler0{wt3YfjY6knd5Q5EFD(^Ia7yDY0xW znxJ&VK?fry{Jk;>1u0%+7@j`YwoA1>b z+Mg6@UcCqq_}4+UzMhah&rZx$F7OOV#4&SAQkQe(f1p7+UPN`I(nEcN*YoOTjnqf=C-sznW!i(I`-2xLSh? zsnPMnImao=@~!FK{*D5;7At&RT5K+^>?{k%9T1 z5@$N8Dd~YhY<-Cw2VQ{LFeXR{Cy=jX4{+yJe~o4{?Y=%f20V5)scs+M4a{50+jZJG zs5f_iGSQF~($_$|>T9=Qfdo&E<)Gg$QFge-YTRbcIsmS{(v1PYO^kl%oSmE(T?z+&JCL(A>CW%MjNpOaIPwl(Tmz@@wmV)mu4$6^Ke; z9F%~0gT3k)lNb)p5p^AgxOFzmB!mKqo@4D9A_0}*X#nhE>#D=V*Y%>ZMlhfg9{=9M zXRd>h=*bQK#cTVCh8840enQu~md0$g)K-O4Mm4~!$(lf`$JN(8 zU>F^k_S~XPNv!-eNyuDRqHjxRWCssPjxOTTd16*e{)6zhLwT-fe4g*vdc9ONFgv9C zlnEsuq6W?o(ksIjkzh@ruUfxlE~jSYUNeHcxbZy|{|eu*)79R&?lGJo2Q;(gTM+VY zmUBsgAN=qc(9|HHp43=W1~3aKI?AbDP3$7;#Ei_;#CQ{M{^Z<`2n#h_2q`@+V_6A8 zyIj?mtV-g2e>>;s0g$&N8GxjU5!+kJ|^oqoydgaD|tdC^n0xal<@Q*T_ zkL23cCsIiI&sv4qozi~7{%C(t(}N#9&0F*1zPqDrH`39M{Or4h*hwtNC!EUc_8>tk_s|tk0?~p_sUlvst%a*!iHzr3wA^x_hwiJTtPB zpOcjDn0wXTWnkWMB}miFPXoRsrx2Ll$a3o4_<;kbGiT2>n)FNe-YURg9m6g6KLMrp zKthV&&kA&b{)67MQM-1VYb;Q;q34Bs0%X&WqZ6KzZRe>C&rhjYtyR`}St4R2$G-WF zyAA`_atTPF={QnNtw(KmG$bZ<9@Mo=Ff?!@Zx3u0E$!f?CfI`nIl)Hb89~0N!&|3B z1^CC#8Qqozb*mvfERBv66Ag-JrtGJMR3Vr0d|a15M3olSznTt9tA10|PiUd>8(*)N zc-K2B=nORzFpS2QnKe_Ac1Lm|%@+$?S*y=N;lnYw5J@+d#oi3Oo=`BJWPv+@wl!z{ z5Yggs&}QSZknZQLSul=>gQn}3nDBw-6-UEkfe~Kc8J&iHzpLaz;6M9f!uma6v7o^g| zwa^pPmZz1ro+<}YkdSg2tFGYY@d1^kzT9ZoE>+m;+cRSu>qG|FcC#WeO+lkTo^g## zQ1X`0eUH~pG@LH5al}!KbX{{afuLNI(PnPrX6vWg=pXG1A7N-&-bqxtO=33vVpN=b zQuVDgIjGKBLV7m83u>Aq30jwQ`Z4$cag`ii)vPRg6P`Y>8qbf!58>AULtlPNH=~?M z(bhf6dqJ(t4*y3@w+MsTx6k~a=#-YRZeDKr~5WMGQ*THpW9sWj7X z29t}L0^&$8?#uXu{181T#jZkuN1`fNczNoIzWdHRr&ku{3ybep7RYL@jF z;+X=9YIs`ixO-hwuI zQpwg6!Kcz4GXB^mmii!K%3t%0!H}6D)(MHbYvUun;}o<-u-N{)cT;kjjEdhN5mX!% z3luPtrg)h?o9C6?8UBmc#-2!FgZc1sBtw@(F3?rndc!C?GDW}@z^Fyw%U!tKs>TM zI}SY8bgG@JZd6ApVbC3Tvs?_2Df{YZT(%*!$Wn*s4P7{j-apIov$ER`Ckm~xz(thD(-s0`NU!a#D%96L3a2xwh8lMWAsK}{(>igZ9k$en` zr2pm}wmTpNJZG{HP`;I`%x_F2ZOjerO}DxHwwcX-Y3odQ*n2;QDcV#|X{=xg`BpK( zl*_|q;-I{67a${c*k~YSq``V0W=?ChR+IVj#^(|JFO<=b?q|I{(XN%Q?RCG&QjPqnH;s@- zKRo&13zTkCr2b&}#am-`X@CNFJmTEtk1O?-UuIJre7e0}u#p=pCVj_I9M!_H(jDP( zK!}iKI)jjYT2S31ox?^@9qW`|tJsyRMAYuLDw z*Kh#*GUZP&C<7GR3$>p$>_Lu4;4ZD01uy+j0;UmAeGz9Nk+Y&rdzI!~__<%*ueWT> zgwH6f4-yAQcpErg+-IW^M17W>{k}a3A?G*20xdjsRx3n8p!-V>hS^$dx`K4HaWcEO z(2K3~ykw8!S)G_~vOdd?X6|d@%=c{qfQ%OP-N?SP_iA^xa;C*9@#ZSMng>HoAth#T zRcoBGg;qg+WlgK2*W-=`>GSyPcmN^X=B3QrloK*yj@UEt1F(;m=s8(rm!xY(thYyY zlZ1#^MmLXYoL(q+;8ZN#88DBe^lmWArvnyj{HXT;ssi%;6EV0nl@Tca^&(JY2}K4a z)hL}FT}z%I7dXSaR*SCY6CBTGW^RiAa8^@U2-^C|r_<9RJP~k4lbHtnstFe>yZp`Z z(a%pNg3D4p!v@=G*~%(k-u7mK(tKh?x^8uz;(^!}yEc=N;Jn^HKwe9bpQPRa73hLU z{#QjzaQPoDeeB^ebe($D86g^HYI6Iw_L#N0^f?3C*VtO!JBoOPnZ( z1mL%uW5Gal*8%2ZqSsC8t5w#8g$hQql2<`Pg>Gu6A_dW~zOSr8dtH;m(Ea18Rwz+W zEZ2}VfhP3EQ1Q9b9Q|I;);ct^LY>9mBj4sZo&U6{#zqZ*^LJ3T&1P~G2P76_L}$@T zpoidwQKfhEu{(j<#db?`)Rf2Wm_%k_4%~5RCGVdM~4rDz)@23y zQfdK#y15_ZFfg+4(jrcc*Xz^L0pIPJm1*SodNpYsdS_W%9Dl`ShEM~Au58c~cna&I zwts|(Yr`&E6}{iQrw+RnSSDCJ$iyrNPxl|0QA4{-w2M=h?t!{~odt7Rswxfg_ys46 ztKXyl)0is*H}A_$jF;jy(q#M1xt5rwnPop+Dq<+0SC+W^+%Ye!A3)JsDw=8fZI9U4 zK_NlkD+FqnC?~X`>??6EEKPMX-=tP789*0dzGHP2^QhF_mXA86#&Ahb_k=bZiE7|T zn7{UwS$b!rdkeegG`!1RV@YO{^cdj%wg1o@qs`agNLN!B09yA0?saI&3#Kp~^Yb^o zcfML)5ulC5$SWM;7+p5`0)z0>yTrS;o62m*7CnB3S6T9xjc>l<jd<$~(~`XM0~#TDm`aEW~Vff|)Tc|8?#x^o=z;`ZbG>@C;C zrqAHi=mKKpnyN^O{k}WbV;^Yn%10pPL{Xdaa`?f7Y9qZB!X3hFd{F!*uC{`J+F*e+ ztwELVN!8q0fxCSht@2F$-2F&0N~Oobtx+Ps!;amXUnF_YNU39~UX9PAB}KzhDp0ub zE`fLg>iZh^$Bdr?oDc>T;m}aC`t&yB^B_qm*ln)n2|lqgXmkr*<1^mxJpYk?C^2@o>L*zL;d7i?pc40IFjuV01?RI_ych9d*Zxn1-jzlm zN)-tE;DrHLiR4zk512h0s<1H}P;oap9Q;VBHHgm&X&Ve4Wos_wA@I6~xf%sMtl#?y z2K!bj+4@{4qEC^^>c5E95D`N8534kE%_S3}@FaGNy&Jocx%MqySxs-fb#gfvPxIu= z=iRnRV-WRjGm)Q>WOmluPiz~zF>|VKAp<9{tfVdRhbb?1s$?}RrkzlNGk`z4cJ6#DFDUq)M!2(DwS0$OwzQDB z_Bt2o97EeX_sdWveFilpB_L;EXWtt<5gstZq&l%}LwBKsm$=VQ9hATq8pJpDgCqNf=#)XUF7&cGbZss&-G4fP!o< zq+>;{O{!#lctdcwrfiCdv>H@yDL-67tq?FPy|V&34fNgA!Q-FO)Hhs)z=&DAmv$Z| zyNRr%m6M2tU>2Swg;I_KxX(D;N6T>`S@in~|Ejha)*-StvNu|E#?Ll{MlJm|Ltys8 zm4vF^rWRL`b3`Dv9kIbpZpq?+DxTj#FW`~`KNTmzL9dJKYlp`Dnm_Tn2+{NVwoQjRgtjCq^n}1&l+Q$*nV%z-OKIe@r zSBVJ$Gfun_SC#e`6Wl!~AmsQ=%-izCV9No&mOv>_$R1Z++<%(_F69Dh8jzfMxcK|JH+pP%ektNFp$EY%)<*Kj{uKt&giGZzOp^cqE z{=nYEXFt7tW0S^h@5|Fc?feH6+plGY6QMTj4TZZf&;$6;XV9_b?Sn4`GRv3yr|-at z>!biK?}t#ipKsbGoGoVlupq%@a$lPBoil7($fGRit!SuBy6jQt$qR)iyd@^J3_29O z%Zd=J5%n~ucM(G+vF^u%Q7!$a)2*hBKKl3sTRG+~l!b8U-}a5BP1{nXfGS&?pUi~@QD-94w#@l_l@|69`gag$pUDD;w`Lh+Q{QCPP`m4vUjl=_UQjYqU0BFkPo5t&w5$C;of@IP4^sv+DNJdYr8t7E3W8JrJ$A_PJi~{( z;8lpoX5U?qNXgjc!hOZ()cXK@Lvao&Lq`nDEQFDzLxy#v43f*QZ$T4eJvK$K{SK@w zH=%d~RnBh$ROD!W7}UFEyv0TVUGpb1$Y}mLIOWMMy?o*k+cm*}%b?5nB|cdBQhJ_1 z(w#>dEYiKHcs(uQ9LH%|hUxg?*JS^2HcQHhrJ}OtDAl3$4=(!Kn!mjJ*@iAXLAlGM z_%D1joqSxfr1~EFZJEwnueGJ(&W2PonEg?@N1&lpVBKy)skTgyC?*#|lJv#v%gi)F zsdMPI)0{v71X%rQ^HhH4TdLTj>y3VEfk}nP+Eg|8jiQoYHJSY`_z)&c6^w0apO@w$ z>G@9gFa0);WLh~jM8$?zG->Ru;>3VsBV26lwEdPuJK5{G<{l%rmhTGE&CkWrD zKO9q*c)2OB6%*TT@~W`OFQJjwyPTpXHK~1YLG&E)2as2j5(4!W$IBQ$@!<*?eV8VJ z$b9JL&NPlt5*yNg>@Sp`;j7hE-s8u4$92qmh0UiXD=U>E`aNU}Yi6iRe3(}Ami{66 zP_t$cWViA{T`%hN3=T6xXxc40?k|2mPIOUuq*!dPct-!%z8cU^tPJnShL65vVQB1 z$*R|_1X#@-g~_vVM(8D=39pTEjXADcig~c$54sq=f9cUa$LFY({J`SP`&VB-2)`|| zk#gG|zvA@E6(^dIlvwrDsI8w#>yAx_m&ldPm53Uvwc2uCacJnym(w~|JiCl%1S?2Z zIIE~IwRToP2fyHLRR+4j-`^ykz#!7ko!O;X=gITVouEiX+bzUfarc(Y9#a7YR{jpc zAFM+oUETb~|+J^4i`)A{*2X;4BTP&Qwlamoeen1cO@Qwaj;OR ziqgV|MXBy6EUXSp8bG{OH@%+p6f@T^ZbnX%+|NOUfXq)wJxc%HBD0pGL(nSGvWPpp z6Z!~eC5nOc&@ytZf{F@}Fh7RjZ32t3($-#fX#g@WcR4ouriGLh>`fGm+v?+bTnTWs zc~uN>=habC^l$4Nw=`;vFS`KpD>+QiCq`3zqu%_c1mDJ!vG z$|-*80n2L(|xj&R9T2BO2L)W|*E*S=rhEI55$a$3O`v2072=eQ6&*f2Y zogTGN^*)M{^t>cl(0wNid!uM(sfNYERzhy)n~|;*H1rqEh2~)YmD6F5U1GUKbf?xy z+v&YGijM#?c|Lx5MsqxU$;+`~aX8!jv zixP=m8kES7rf!$5fgyOluzn7+tRMn-J-!X|N1wrc_=^rM zX?Dt9o=@-}H6iTJEkDcz1Z>C1RPdh0Vw|l(`vWAIi`jf z?q3g_e}^{5=`J%BJfC}7I^ZR7%h}hlsP?#7E59M2CvctwQPw9wIS_NC5JVJIvY$Y;;Ff)7h9UN0}>nh19#0|h9iQ# zr4`RV(f88t)(CNFaJq9$hrKMU$ zZu@-7T${eyHgTpQH3RljNqZoi5Ykf8KIYYgbccsrY%}SIlb0O@*IspaWXWxqx3Wwm z<)MHivf+;lGi{j2a(eoWOl9YvuTPsNIBU|Ut?k=gbOUrHTQLeN>f@-l) z!wQ1p>%D(!k(_yA+!lUZb6VKpGA;9$O4(D6#78te8^ zgy+zq>>aemP9EljdKJp*p(Yikr=HbsG!^xvY`-mJ%72xl$0lTs@~+NE{w1!fZP2aB z_RR)8wel(sqnPF<;490Abmo-?wa#kG+d)W-?|KHfV}2I`p%oxzge=^>_*LV2|9;cj zdwf4~mT_Dyvzw-3?9L&sB&4mjjluW-`0Tr8l2pp1)jQmLg3?%osWd1VpI;_uLzY~t zdWJl{1jApn#oa85|Fv1bi*EnJWuu;;HGU5SD!Ldl6%I?`_$tLUW7Hi%M%Z0Dc%?(&TLj#EdjGxK_vsY6b~Ch*^Dla6-|oh1OZuCcPQ1|?&2%VF5z2gAFq z_rDA#7l0J<1e@v*v4pKf^k-bw*r%F__uDr1aKP`Fb^>L^)QyoXX+of`Wwh*k`Hhuf zDXvWsmy0~5fVPur`;iiw(CsDi!~avJ`B)ghzGM7rT;}YWQ0wQ#l3ySMrA1J|;;Ux} zh+dc$O`*2{!*+xAQ5Y>#6)=F9s8XwW_54uf5b5CtakR?v?Y>G8DC{)e zPcsq$r}f@cqT3_*SZl)X#OruXn?~ZSFp6^BzEe(WgtoU2*c031p;R)VN-isPfKEhj z)_>2{Z{$~J+_-(3E672Ode{w5)b4(U$DJFCr*zskO8}Mf#P1(a82Iy7YBLwWoHW=^)t5zxYd^ z_)Qb*vzE93i9#(isV$Ga>rYSsmj})UxYAdL(v*6n4Fstuo!ft*geCphkONYzNm!f= z#YP+43kMfGXo}5V_}rChARL0cFk?VcWd`$#$Qgso{z6`s`(lDNq*XL-Ew zbVh(Z$mR3(p0k!pbeYJB$_&%1K&;{FDDf#F#EbJRg;QeLcia!wIB}_@hI+i`C&>6z zw5Qa46ex?iwc2jUC=l0T$r zrmgv!h1K@9`?)x^(9g8?XQM;TKi9mS=xr{wv*Zn+`K0tHvJox-z;j* zL9lr1`U@u4iJIvv21fN_$#>q@Hb9F@6)zR+eeTj8b_HA}$+~qPW#qL@_!5RSjjHZVI;c6F;lgu~&&;8CciU3dA%%88i-KLv zuVG6~XxWY1%MWri#~DxUo8fW!hs_FUk@>LHpD5u?bjqH2W-8Kk+~*yqlL-w&A##V7 zQ;$$Ihr;3nhE=b-7x~cqBbX__0B4=;O9Q{5KZ$rxxtT7r9kAln^KvA-Ds>cISMsz{ z@q6!oqMZ{5XPNoy6hH1LBgo#F(`i=qr?RL!r?>5rEnRDHl7pIbB>9zz!muU7ki_!5 z@U-*fg)}9g)?7u?R6w<-Qm-|Hy)F6{9zy5~%l1;(z$n4o^8df~t~?y-ZjVdJQ?9zn zk}a($Yp=!{BcWx=5<-?CL=2j&Z<>*JDk)2%+<3?6wIn-}Jw^+1ZG{4BX6CW{~J=*TuHR$ofWV2<}0 zU$I#2c+mO)w$&eXvC$uhB4bHU+n2 zUDD5f?E=+!+VAoLk3)_6i-XfM$a;nF zGJrQ?+!&)e*DYt3Of00Da| zVtfak>%u)r$~oUVUnh&ECsg%X8BKc@P3piZWx5U6FJ0Ys`0XaAY@9x8I^0yo;8_~>zlR%+!yJO)lyhg4&>`Z-MX$t3r zeMq&4SmUXN%ZEBPtv{}GVLFM^lCU*iGd>y*6w{#$3xD_69O#^nlNM#7-&W%!#JaEf zsOKF{)7dkk574&wgW@~x7v)PQq+{rN77DcpRs){bD#9FKcAgLYCpvL_i)4j}3SLog zujCt5bH^H^;P)Bj+aj6FBlxfA_HJZqwa=Vv-{pR{;w65d6l&?(lqO9FVzZDVXF(zL z45d+`Z2i&>`u&al*o&-j-ntYAZuZ_s$6-dwCC5>y#KZAsTSN?^zkQ8u*d`KvHF%$z z=P;Q!+U`n@d>g~9r(kdv(4I*nRRRusg@$ZpWTQ0iz8p)UZB=e#JwlxKBsoOv=B)Fa zq4l3N>GtM!gr4_w*V~M72(YohNH3x#nNjLqGfLy>3M#tMU7tC*NCQ;$P6ZUZ@F4^2 zM3o%v$_^`*`jd2Nl`+J`jlIran$tw4N6qOo+aOZ>rC23WM)#wh&>(&3?_fPAKEU(# z^Fv&{yS1H%oxJ6>^2BdenoDq<)biXamepdjoJ1tnw_X^Om9Jg~1BxTE8+T_M?wsP6 z?ow}*hLfMy?xN$>S7CO2fY?O2dS<1B;Y5X`egVrqDgP;+V`-NX2oa(B4q;#?5(k$N zz!pF`$|)37)pA}Sss^qG7*#mjB@dg*^?h36DxCti9#3)2Q7z%0(T#;|g^%O{4F|95 z)#^5F_z5!vRQxiq^OrUGb--i}{M9W~P}?QSvni6+^dxp!uPHSQRFFoYm`0ozK}B4G zR1jvw*M?V5cO5&ErAEaLAt7jT8k<=P1MLlij&uZMdR|gVs&HKmg|G~RD=G+aP+W7Z( z!-(L<#dC)vyNNUPTF@@Eyj+bBGv9htYeK8+DCSmOV+<3I_DsZu;nm(g?FEn!Uz~5m zlTYG5j&oJ_f^;u(#CF`^@@Y#4IjziZ&F+^B-2v{-7jr2Wyd9U%VX-|61LruYnGPZ_#&O<@6P7`KfZ?{sSfk3UaH$ zZ>1rhGhnDi*h7gjKP>YTdk@-(Ijauh?~e7B*uN<1|JnKf4|}Z+?w_XnANIDYU5r=# z0Y;Ho^JZ3PnoQszCCsNYU2xEQG)UWoM|>dXY1U`Z!mlJ7_iwJ8M%b!f@}L810H=i* z$huid86p&^R;<5xY$YOBf3t~<5!Wrg*coKh^pIai%RDmP_A>YjPD9dVIPx+d6UgKT z1_cj&d#v$08*cajQPiT={u0i_8P6yd(YRrD}dHTH==kQ>=z}qKaU00c6>m3!MdG8 z`qHO3IN7ID->Ke1Nw})nET``;O0A-iSTj}CGjfl!y@sKJR}k~2*aXU=%ii}DrJ0Kb zZ<4af>yImzE16odA-6j|CZT{a4?0z*jDXTM2)(+rDNSKanIA~wwLDYNdFCPe} z>JHThsr$KBhhrOzXBiis4ooKL2LM`rzKLRlG15`*UOZH26*zijO#}&hLFBM$N9!0= zbJj$Dd(x3Y0Oqazt(7B6tnOT@e-yZY6ECJ%62UE3yk7)~MS&ifUKB29s~KfWRn5_J zSxhdJ$_^oDv9jJNU5eX~i2nE=e%8bR8@w7c);d$L2F-qOisRD~J|RPy)W0j&;KLYGRnVC5YN qK(|u2MTf6tj(;EGf7#K){B=iK>svmLAxeb6=cI*|d5M|FZ~p>(qK5(i literal 0 HcmV?d00001 diff --git a/docs/integrations/apis/openai-tasks.mdx b/docs/integrations/apis/openai-tasks.mdx index d372bba8f..203137359 100644 --- a/docs/integrations/apis/openai-tasks.mdx +++ b/docs/integrations/apis/openai-tasks.mdx @@ -43,6 +43,57 @@ run: async (payload, io, ctx) => { }, ``` +You can also pass an optional third parameter to `backgroundCreateCompletion` to specify OpenAI request options: + +```ts requestOptions.ts +run: async (payload, io, ctx) => { + // This code showcases background text completion using the "gpt-3.5-turbo" model. + // It generates text based on the provided programming task and logs the result. + const programmingTask = `Create a function that checks if a string is a palindrome.`; + + const response = await io.openai.backgroundCreateCompletion("background-completion", { + model: "gpt-3.5-turbo", + prompt: `Coding task: ${programmingTask}\n\n`, + }, { + headers: { + "User-Agent": "my-user-agent" + } + }); + + await io.logger.info("codeSnippet", response.choices[0]?.text); +}, +``` + +This task is implemented using [io.backgroundFetch()](/sdk/io/backgroundfetch) and so you can also pass a 4th parameter customizing the retry and timeout options: + +```ts fetchOptions.ts +run: async (payload, io, ctx) => { + // This code showcases background text completion using the "gpt-3.5-turbo" model. + // It generates text based on the provided programming task and logs the result. + const programmingTask = `Create a function that checks if a string is a palindrome.`; + + const response = await io.openai.backgroundCreateCompletion("background-completion", { + model: "gpt-3.5-turbo", + prompt: `Coding task: ${programmingTask}\n\n`, + }, { + headers: { + "User-Agent": "my-user-agent" + } + }, { + timeout: { + durationInMs: 10000, + retry: { + limit: 3, + minTimeoutInMs: 1000, + factor: 2, + } + } + }); + + await io.logger.info("codeSnippet", response.choices[0]?.text); +}, +``` + ### `createChatCompletion` Generates text completions in a conversational context. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/chat/create) @@ -85,6 +136,87 @@ run: async (payload, io, ctx) => { }, ``` +You can also use the more "fluent" pattern used by the OpenAI SDK: + +```ts fluent.ts +run: async (payload, io, ctx) => { + // This code showcases background chat completion using the "gpt-3.5-turbo" model. + // It simulates a conversation with a user message and logs the response choices. + const response = await io.openai.chat.completions.backgroundCreate("background-chat-completion", { + model: "gpt-3.5-turbo", + messages: [ + { + role: "user", + content: "Create a good programming joke about background jobs", + }, + ], + }); + + await io.logger.info("choices", response.choices); +}, +``` + +Additionally, you can pass an optional third parameter to specify OpenAI request options: + +```ts requestOptions.ts +run: async (payload, io, ctx) => { + // This code showcases background text completion using the "gpt-3.5-turbo" model. + // It generates text based on the provided programming task and logs the result. + const programmingTask = `Create a function that checks if a string is a palindrome.`; + + const response = await io.openai.chat.completions.backgroundCreate("background-completion", { + model: "gpt-3.5-turbo", + messages: [ + { + role: "user", + content: "Create a good programming joke about background jobs", + }, + ], + }, { + headers: { + "User-Agent": "my-user-agent" + } + }); + + await io.logger.info("choices", response.choices); +}, +``` + +This task is implemented using [io.backgroundFetch()](/sdk/io/backgroundfetch) and so you can also pass a 4th parameter customizing the retry and timeout options: + +```ts fetchOptions.ts +run: async (payload, io, ctx) => { + // This code showcases background text completion using the "gpt-3.5-turbo" model. + // It generates text based on the provided programming task and logs the result. + const programmingTask = `Create a function that checks if a string is a palindrome.`; + + const response = await io.openai.chat.completions.backgroundCreate("background-completion", { + model: "gpt-3.5-turbo", + messages: [ + { + role: "user", + content: "Create a good programming joke about background jobs", + }, + ], + }, { + headers: { + "User-Agent": "my-user-agent" + } + }, { + timeout: { + durationInMs: 10000, + retry: { + limit: 3, + minTimeoutInMs: 1000, + factor: 2, + } + } + }); + + await io.logger.info("choices", response.choices); +}, +``` + ### `retrieveModel` Retrieves a specific model by ID. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/models/retrieve) diff --git a/docs/mint.json b/docs/mint.json index 98ba3365e..c07b1b755 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -117,9 +117,10 @@ "group": "Triggers", "pages": [ "documentation/concepts/triggers/introduction", - "documentation/concepts/triggers/webhooks", + "documentation/concepts/triggers/invoke", "documentation/concepts/triggers/events", "documentation/concepts/triggers/scheduled", + "documentation/concepts/triggers/webhooks", "documentation/concepts/triggers/dynamic" ] }, @@ -343,6 +344,8 @@ ] }, "sdk/context", + "sdk/run-notification", + "sdk/invoke-trigger", "sdk/eventtrigger", "sdk/crontrigger", "sdk/intervaltrigger", diff --git a/docs/sdk/context.mdx b/docs/sdk/context.mdx index dcdf5c9bd..98387c368 100644 --- a/docs/sdk/context.mdx +++ b/docs/sdk/context.mdx @@ -38,8 +38,7 @@ description: "The third parameter in a Job's `run()` function. An object that co The Environment's ID - The Environment's type. One of: "PRODUCTION", "STAGING", "DEVELOPMENT", - "PREVIEW". + The Environment's type. One of: "PRODUCTION", "STAGING", "DEVELOPMENT", "PREVIEW". @@ -74,6 +73,21 @@ description: "The third parameter in a Job's `run()` function. An object that co + + Metadata about the Project + + + The Project's slug + + + The Project's ID + + + The Project's title + + + + Metadata about the Event that triggered the Run. @@ -105,8 +119,8 @@ description: "The third parameter in a Job's `run()` function. An object that co - Metadata about the Account that triggered the Run. This is a Trigger.dev - Connect property which is coming soon. + Metadata about the Account that triggered the Run. See the [BYO Auth + docs](/documentation/guides/using-integrations-byo-auth) for more information. The Account's ID diff --git a/docs/sdk/invoke-trigger.mdx b/docs/sdk/invoke-trigger.mdx new file mode 100644 index 000000000..d3a6744fb --- /dev/null +++ b/docs/sdk/invoke-trigger.mdx @@ -0,0 +1,86 @@ +--- +title: "invokeTrigger()" +description: "Use `invokeTrigger()` to allow a Job to be manually triggered using `Job.invoke()`" +--- + +This feature is in beta and not yet available to use on the Trigger.dev Cloud. + +Setting `invokeTrigger()` on a job allows you to manually trigger the job using `Job.invoke()`, either from your backend or from inside another job. See our [Invoke Trigger guide](/documentation/guides/invoke) for more info. + +## Parameters + + + + + A [Zod](/documentation/guides/zod) schema that defines the shape of the + job payload. The default is `z.any()` which is `any`. This will be used to correctly type the `Job.invoke()` method. + + + Used to provide example payloads that are accepted by the job. + + This will be available in the dashboard and can be used to trigger test runs. + + + + The example's ID. + + + The name that's displayed in the dashboard. + + + The payload that's accepted by the job. + + + The icon to use for this example in the dashboard. + + + + + + + + + +```typescript invokeTrigger.ts +//this Job subscribes to an event called new.user +export const exampleJob = client.defineJob({ + id: "example-job", + name: "Example job", + version: "1.0.1", + trigger: invokeTrigger({ + //the expected payload shape + schema: z.object({ + userId: z.string(), + tier: z.union([z.literal("free"), z.literal("pro")]), + }), + //(optional) example payload object + examples: [ + { + id: "issue.opened", + name: "Issue opened", + payload: { + userId: "1234", + tier: "free", + }, + //optional + icon: "github", + }, + ], + }), + run: async (payload, io, ctx) => { + // do something with the payload + }, +}); +``` + +```typescript usage.ts +import { exampleJob } from "./invokeTrigger"; + +//invoke the job with the payload +await exampleJob.invoke({ + userId: "1234", + tier: "free", +}); +``` + + diff --git a/docs/sdk/io/backgroundfetch.mdx b/docs/sdk/io/backgroundfetch.mdx index 9dfb442bb..86f713ee1 100644 --- a/docs/sdk/io/backgroundfetch.mdx +++ b/docs/sdk/io/backgroundfetch.mdx @@ -98,6 +98,44 @@ An individual retrying strategy can be one of two types: + + Allows you to set timeouts for the request, as well as specific retry strategies for timeouts. + +{" "} + + + + The amount of time to wait before timing out the request. The minimum value is 1s and max is 2min + + + + + +{" "} + + + The maximum number of times to retry the request. + + + The minimum amount of time to wait before retrying the request. + + + The maximum amount of time to wait before retrying the request. + + + The exponential factor to use when calculating the next retry time. + + + Whether to randomize the retry time. + + + + + + + + + ## Returns @@ -106,7 +144,7 @@ A `Promise` that resolves after the specified amount of time. -```typescript backgroundFetch example +```typescript retrying client.defineJob({ id: "background-fetch-job", name: "Background fetch Job", @@ -128,8 +166,52 @@ client.defineJob({ }, { "429": { + strategy: "headers", + limitHeader: "x-ratelimit-limit", + remainingHeader: "x-ratelimit-remaining", + resetHeader: "x-ratelimit-reset", + }, + "5xx": { strategy: "backoff", - limit: 10, + limit: 5, + minTimeoutInMs: 1000, + maxTimeoutInMs: 30000, + factor: 1.8, + randomize: true, + }, + } + ); + + return response; + }, +}); +``` + +```typescript timeouts +client.defineJob({ + id: "background-fetch-job", + name: "Background fetch Job", + version: "0.0.1", + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + const response = io.backgroundFetch( + "fetch-some-data", + "https://example.com/api/endpoint", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: redactString`Bearer ${auth.apiKey}`, + }, + body: JSON.stringify({ foo: "bar" }), + }, + {}, + { + durationInMs: 10000, // 10 seconds + retry: { + limit: 6, minTimeoutInMs: 1000, maxTimeoutInMs: 60000, factor: 2, diff --git a/docs/sdk/job.mdx b/docs/sdk/job.mdx index 229bbf13c..db8e3601d 100644 --- a/docs/sdk/job.mdx +++ b/docs/sdk/job.mdx @@ -5,33 +5,10 @@ description: "Job is used to create and configure a Job" A [Job](/documentation/concepts/jobs) is used to define the [Trigger](/documentation/concepts/triggers), metadata, and what happens when it runs. -You can define a job in one of two ways, using the `new Job` constructor or by using the `TriggerClient.defineJob` instance method. +You can define a job by using the `TriggerClient.defineJob` instance method: -```ts constructor -new Job(client, { - id: "slack-kpi-summary", - name: "Slack kpi summary", - version: "0.1.1", - integrations: { - slack, - }, - trigger: cronTrigger({ - cron: "0 9 * * *", // 9am every day (UTC) - }), - run: async (payload, io, ctx) => { - const { revenue } = await db.getKpiSummary(payload.ts); - const response = await io.slack.postMessage("Slack 📝", { - text: `Yesterday's revenue was $${revenue}`, - channel: "C04GWUTDC3W", - }); - - return response; - }, -}); -``` - ```ts client.defineJob client.defineJob({ id: "github-integration-on-issue", diff --git a/docs/sdk/run-notification.mdx b/docs/sdk/run-notification.mdx new file mode 100644 index 000000000..58f0d6f97 --- /dev/null +++ b/docs/sdk/run-notification.mdx @@ -0,0 +1,118 @@ +--- +title: "RunNotification" +sidebarTitle: "RunNotification" +description: "The payload of a Run's completion webhook" +--- + +This document describes the payload of a Run's completion webhook. + +## Properties + + + The Run's ID + + + + Whether the Run completed successfully or failed. + + + + The Run's status. Will always be `"SUCCESS"` if `ok` is `true`. If `ok` is `false`, will be one of + `"FAILURE" | "TIMED_OUT" | "ABORTED" | "CANCELED" | "UNRESOLVED_AUTH" | "INVALID_PAYLOAD"` + + + + An array of the explicit run statuses that were created during the Run. See [Explicit Status + Hooks](/documentation/guides/react-hooks-statuses#useeventrunstatuses) for more. + + + + If the Run completed successfully, this will be the output of the Job. + + + + If the Run failed, this will be the error that caused the failure. + + + + When the Run started + + + When the Run started + + + The duration of the Run in milliseconds + + + The number of individual function executions performed to complete the Run + + + + Metadata about the Job + + + The Job's ID + + + The Job's version + + + + + + Metadata about the Environment + + + The Environment's slug + + + The Environment's ID + + + The Environment's type. One of: "PRODUCTION", "STAGING", "DEVELOPMENT", "PREVIEW". + + + + + + Metadata about the Organization + + + The Organization's slug + + + The Organization's ID + + + The Organization's title + + + + + + Metadata about the Project + + + The Project's slug + + + The Project's ID + + + The Project's title + + + + + + Metadata about the Account that triggered the Run. See the [BYO Auth + docs](/documentation/guides/using-integrations-byo-auth) for more information. + + + The Account's ID + + + The Account's metadata. This is additional data that been passed through + + + diff --git a/integrations/openai/src/chat.ts b/integrations/openai/src/chat.ts index 8c05ab8e7..970a33f04 100644 --- a/integrations/openai/src/chat.ts +++ b/integrations/openai/src/chat.ts @@ -8,6 +8,7 @@ import { createTaskUsageProperties, } from "./taskUtils"; import { OpenAIIntegrationOptions, OpenAIRequestOptions } from "./types"; +import { FetchRetryOptions, FetchTimeoutOptions } from "@trigger.dev/integration-kit"; export class Chat { constructor( @@ -47,7 +48,8 @@ export class Chat { backgroundCreate: ( key: IntegrationTaskKey, params: Prettify, - options: OpenAIRequestOptions = {} + options: OpenAIRequestOptions = {}, + fetchOptions: { retries?: FetchRetryOptions; timeout?: FetchTimeoutOptions } = {} ): Promise => { return this.runTask( key, @@ -72,7 +74,8 @@ export class Chat { ), body: JSON.stringify(params), }, - backgroundTaskRetries + fetchOptions.retries ?? backgroundTaskRetries, + fetchOptions.timeout ); task.outputProperties = createTaskUsageProperties(response.usage); @@ -88,6 +91,9 @@ export class Chat { text: params.model, }, ], + retry: { + limit: 0, + }, } ); }, diff --git a/integrations/openai/src/completions.ts b/integrations/openai/src/completions.ts index 0d4198b57..e9503c9c6 100644 --- a/integrations/openai/src/completions.ts +++ b/integrations/openai/src/completions.ts @@ -8,6 +8,7 @@ import { createTaskUsageProperties, } from "./taskUtils"; import { OpenAIIntegrationOptions, OpenAIRequestOptions } from "./types"; +import { FetchRetryOptions, FetchTimeoutOptions } from "@trigger.dev/integration-kit"; export class Completions { constructor( @@ -46,7 +47,8 @@ export class Completions { backgroundCreate( key: IntegrationTaskKey, params: Prettify, - options: OpenAIRequestOptions = {} + options: OpenAIRequestOptions = {}, + fetchOptions: { retries?: FetchRetryOptions; timeout?: FetchTimeoutOptions } = {} ): Promise { return this.runTask( key, @@ -71,7 +73,8 @@ export class Completions { ), body: JSON.stringify(params), }, - backgroundTaskRetries + fetchOptions.retries ?? backgroundTaskRetries, + fetchOptions.timeout ); task.outputProperties = createTaskUsageProperties(response.usage); @@ -87,6 +90,9 @@ export class Completions { text: params.model, }, ], + retry: { + limit: 0, + }, } ); } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index afaab0fec..2c54678a8 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -9,6 +9,7 @@ export * from "./eventFilterMatches"; export const API_VERSIONS = { LAZY_LOADED_CACHED_TASKS: "2023-09-29", + SERIALIZED_TASK_OUTPUT: "2023-11-01", } as const; export const PLATFORM_FEATURES = { diff --git a/packages/core/src/logger.ts b/packages/core/src/logger.ts index 51a6417c6..7a5343fc0 100644 --- a/packages/core/src/logger.ts +++ b/packages/core/src/logger.ts @@ -43,36 +43,37 @@ export class Logger { log(message: string, ...args: Array | undefined>) { if (this.#level < 0) return; - this.#structuredLog(console.log, message, ...args); + this.#structuredLog(console.log, message, "log", ...args); } error(message: string, ...args: Array | undefined>) { if (this.#level < 1) return; - this.#structuredLog(console.error, message, ...args); + this.#structuredLog(console.error, message, "error", ...args); } warn(message: string, ...args: Array | undefined>) { if (this.#level < 2) return; - this.#structuredLog(console.warn, message, ...args); + this.#structuredLog(console.warn, message, "warn", ...args); } info(message: string, ...args: Array | undefined>) { if (this.#level < 3) return; - this.#structuredLog(console.info, message, ...args); + this.#structuredLog(console.info, message, "info", ...args); } debug(message: string, ...args: Array | undefined>) { if (this.#level < 4) return; - this.#structuredLog(console.debug, message, ...args); + this.#structuredLog(console.debug, message, "debug", ...args); } #structuredLog( loggerFunction: (message: string, ...args: any[]) => void, message: string, + level: string, ...args: Array | undefined> ) { const structuredLog = { @@ -80,6 +81,7 @@ export class Logger { timestamp: new Date(), name: this.#name, message, + level, }; loggerFunction(JSON.stringify(structuredLog, createReplacer(this.#jsonReplacer))); diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index 57dd99843..9eb8ab389 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -485,6 +485,13 @@ export const RunJobBodySchema = z.object({ title: z.string(), slug: z.string(), }), + project: z + .object({ + id: z.string(), + name: z.string(), + slug: z.string(), + }) + .optional(), account: z .object({ id: z.string(), @@ -518,27 +525,29 @@ export const RunJobYieldExecutionErrorSchema = z.object({ export type RunJobYieldExecutionError = z.infer; -export const RunJobAutoYieldExecutionErrorSchema = z.object({ - status: z.literal("AUTO_YIELD_EXECUTION"), +export const AutoYieldMetadataSchema = z.object({ location: z.string(), timeRemaining: z.number(), timeElapsed: z.number(), limit: z.number().optional(), }); -export type RunJobAutoYieldExecutionError = z.infer; +export type AutoYieldMetadata = z.infer; + +export const RunJobAutoYieldExecutionErrorSchema = AutoYieldMetadataSchema.extend({ + status: z.literal("AUTO_YIELD_EXECUTION"), +}); + +export type RunJobAutoYieldExecutionError = Prettify< + z.infer +>; export const RunJobAutoYieldWithCompletedTaskExecutionErrorSchema = z.object({ status: z.literal("AUTO_YIELD_EXECUTION_WITH_COMPLETED_TASK"), id: z.string(), properties: z.array(DisplayPropertySchema).optional(), - output: z.any(), - data: z.object({ - location: z.string(), - timeRemaining: z.number(), - timeElapsed: z.number(), - limit: z.number().optional(), - }), + output: z.string().optional(), + data: AutoYieldMetadataSchema, }); export type RunJobAutoYieldWithCompletedTaskExecutionError = z.infer< @@ -589,6 +598,28 @@ export const RunJobSuccessSchema = z.object({ export type RunJobSuccess = z.infer; +export const RunJobErrorResponseSchema = z.union([ + RunJobAutoYieldExecutionErrorSchema, + RunJobAutoYieldWithCompletedTaskExecutionErrorSchema, + RunJobYieldExecutionErrorSchema, + RunJobErrorSchema, + RunJobUnresolvedAuthErrorSchema, + RunJobInvalidPayloadErrorSchema, + RunJobResumeWithTaskSchema, + RunJobRetryWithTaskSchema, + RunJobCanceledWithTaskSchema, +]); + +export type RunJobErrorResponse = z.infer; + +export const RunJobResumeWithParallelTaskSchema = z.object({ + status: z.literal("RESUME_WITH_PARALLEL_TASK"), + task: TaskSchema, + childErrors: z.array(RunJobErrorResponseSchema), +}); + +export type RunJobResumeWithParallelTask = z.infer; + export const RunJobResponseSchema = z.discriminatedUnion("status", [ RunJobAutoYieldExecutionErrorSchema, RunJobAutoYieldWithCompletedTaskExecutionErrorSchema, @@ -597,6 +628,7 @@ export const RunJobResponseSchema = z.discriminatedUnion("status", [ RunJobUnresolvedAuthErrorSchema, RunJobInvalidPayloadErrorSchema, RunJobResumeWithTaskSchema, + RunJobResumeWithParallelTaskSchema, RunJobRetryWithTaskSchema, RunJobCanceledWithTaskSchema, RunJobSuccessSchema, @@ -737,6 +769,7 @@ export const RunTaskOptionsSchema = z.object({ noop: z.boolean().default(false), redact: RedactSchema.optional(), trigger: TriggerMetadataSchema.optional(), + parallel: z.boolean().optional(), }); export type RunTaskOptions = z.input; @@ -793,6 +826,16 @@ export const CompleteTaskBodyInputSchema = RunTaskBodyInputSchema.pick({ export type CompleteTaskBodyInput = Prettify>; export type CompleteTaskBodyOutput = z.infer; +export const CompleteTaskBodyV2InputSchema = RunTaskBodyInputSchema.pick({ + properties: true, + description: true, + params: true, +}).extend({ + output: z.string().optional(), +}); + +export type CompleteTaskBodyV2Input = Prettify>; + export const FailTaskBodyInputSchema = z.object({ error: ErrorWithStackSchema, }); @@ -896,3 +939,29 @@ export const GetRunStatusesSchema = z.object({ statuses: z.array(JobRunStatusRecordSchema), }); export type GetRunStatuses = z.infer; + +export const InvokeJobResponseSchema = z.object({ + id: z.string(), +}); + +export const InvokeJobRequestBodySchema = z.object({ + payload: z.any(), + context: z.any().optional(), + options: z + .object({ + accountId: z.string().optional(), + callbackUrl: z.string().optional(), + }) + .optional(), +}); + +export type InvokeJobRequestBody = z.infer; + +export const InvokeOptionsSchema = z.object({ + accountId: z.string().optional(), + idempotencyKey: z.string().optional(), + context: z.any().optional(), + callbackUrl: z.string().optional(), +}); + +export type InvokeOptions = z.infer; diff --git a/packages/core/src/schemas/fetch.ts b/packages/core/src/schemas/fetch.ts index e6741c3c7..fdb8b8d0a 100644 --- a/packages/core/src/schemas/fetch.ts +++ b/packages/core/src/schemas/fetch.ts @@ -52,10 +52,18 @@ export const FetchRetryOptionsSchema = z.record(FetchRetryStrategySchema); */ export type FetchRetryOptions = z.infer; +export const FetchTimeoutOptionsSchema = z.object({ + durationInMs: z.number(), + retry: RetryOptionsSchema.optional(), +}); + +export type FetchTimeoutOptions = z.infer; + export const FetchOperationSchema = z.object({ url: z.string(), requestInit: FetchRequestInitSchema.optional(), retry: z.record(FetchRetryStrategySchema).optional(), + timeout: FetchTimeoutOptionsSchema.optional(), }); export type FetchOperation = z.infer; diff --git a/packages/core/src/schemas/runs.ts b/packages/core/src/schemas/runs.ts index cd239a392..80a25c90d 100644 --- a/packages/core/src/schemas/runs.ts +++ b/packages/core/src/schemas/runs.ts @@ -1,6 +1,8 @@ import { z } from "zod"; import { TaskStatusSchema } from "./tasks"; -import { JobRunStatusRecordSchema } from "./statuses"; +import { JobRunStatusRecord, JobRunStatusRecordSchema } from "./statuses"; +import { Prettify } from "../types"; +import { RuntimeEnvironmentType } from "./api"; export const RunStatusSchema = z.union([ z.literal("PENDING"), @@ -87,7 +89,7 @@ export const GetRunSchema = RunSchema.extend({ nextCursor: z.string().optional(), }); -export type GetRun = z.infer; +export type GetRun = Prettify>; const GetRunsOptionsSchema = z.object({ /** You can use this to get more tasks, if there are more than are returned in a single batch @default undefined */ @@ -104,3 +106,50 @@ export const GetRunsSchema = z.object({ /** If there are more runs, you can use this to get them */ nextCursor: z.string().optional(), }); + +type RunNotificationCommon = { + /** The Run id */ + id: string; + /** The Run status */ + statuses: JobRunStatusRecord[]; + /** When the run started */ + startedAt: Date; + /** When the run was last updated */ + updatedAt: Date; + /** When the run was completed */ + completedAt: Date; + + executionDurationInMs: number; + executionCount: number; + + /** Job metadata */ + job: { id: string; version: string }; + /** Environment metadata */ + environment: { slug: string; id: string; type: RuntimeEnvironmentType }; + /** Organization metadata */ + organization: { slug: string; id: string; title: string }; + /** Project metadata */ + project: { slug: string; id: string; name: string }; + /** Account metadata */ + account?: { id: string; metadata?: any }; + /** Invocation metadata */ + invocation: { id: string; context: any; timestamp: Date }; +}; + +export type SuccessfulRunNotification = RunNotificationCommon & { + ok: true; + /** The Run status */ + status: "SUCCESS"; + /** The output of the run */ + output: TOutput; +}; + +export type FailedRunNotification = RunNotificationCommon & { + ok: false; + /** The Run status */ + status: "FAILURE" | "TIMED_OUT" | "ABORTED" | "CANCELED" | "UNRESOLVED_AUTH" | "INVALID_PAYLOAD"; + /** The error of the run */ + error: any; +}; + +export type RunNotification = SuccessfulRunNotification | FailedRunNotification; diff --git a/packages/core/src/schemas/statuses.ts b/packages/core/src/schemas/statuses.ts index 2216161e1..ea74570bb 100644 --- a/packages/core/src/schemas/statuses.ts +++ b/packages/core/src/schemas/statuses.ts @@ -29,3 +29,5 @@ export const JobRunStatusRecordSchema = InitalStatusUpdateSchema.extend({ key: z.string(), history: StatusHistorySchema, }); + +export type JobRunStatusRecord = z.infer; diff --git a/packages/core/src/schemas/tasks.ts b/packages/core/src/schemas/tasks.ts index 15744bfc2..f03675031 100644 --- a/packages/core/src/schemas/tasks.ts +++ b/packages/core/src/schemas/tasks.ts @@ -32,6 +32,7 @@ export const TaskSchema = z.object({ style: StyleSchema.optional().nullable(), operation: z.string().optional().nullable(), callbackUrl: z.string().optional().nullable(), + childExecutionMode: z.enum(["SEQUENTIAL", "PARALLEL"]).optional().nullable(), }); export const ServerTaskSchema = TaskSchema.extend({ diff --git a/packages/core/src/schemas/triggers.ts b/packages/core/src/schemas/triggers.ts index edc5256bc..17787220c 100644 --- a/packages/core/src/schemas/triggers.ts +++ b/packages/core/src/schemas/triggers.ts @@ -35,6 +35,10 @@ export const StaticTriggerMetadataSchema = z.object({ rule: EventRuleSchema, }); +export const InvokeTriggerMetadataSchema = z.object({ + type: z.literal("invoke"), +}); + export const ScheduledTriggerMetadataSchema = z.object({ type: z.literal("scheduled"), schedule: ScheduleMetadataSchema, @@ -44,6 +48,7 @@ export const TriggerMetadataSchema = z.discriminatedUnion("type", [ DynamicTriggerMetadataSchema, StaticTriggerMetadataSchema, ScheduledTriggerMetadataSchema, + InvokeTriggerMetadataSchema, ]); export type TriggerMetadata = z.infer; diff --git a/packages/database/prisma/migrations/20231028193441_prepare_for_invoke_trigger/migration.sql b/packages/database/prisma/migrations/20231028193441_prepare_for_invoke_trigger/migration.sql new file mode 100644 index 000000000..5010b88e4 --- /dev/null +++ b/packages/database/prisma/migrations/20231028193441_prepare_for_invoke_trigger/migration.sql @@ -0,0 +1,29 @@ +-- CreateEnum +CREATE TYPE "JobRunSubscriptionRecipientMethod" AS ENUM ('WEBHOOK'); + +-- CreateEnum +CREATE TYPE "JobRunSubscriptionStatus" AS ENUM ('ACTIVE', 'INACTIVE'); + +-- CreateEnum +CREATE TYPE "JobRunSubscriptionEvents" AS ENUM ('SUCCESS', 'FAILURE'); + +-- AlterTable +ALTER TABLE "EventRecord" ADD COLUMN "internal" BOOLEAN NOT NULL DEFAULT false; + +-- CreateTable +CREATE TABLE "JobRunSubscription" ( + "id" TEXT NOT NULL, + "runId" TEXT NOT NULL, + "recipient" TEXT NOT NULL, + "recipientMethod" "JobRunSubscriptionRecipientMethod" NOT NULL DEFAULT 'WEBHOOK', + "event" "JobRunSubscriptionEvents" NOT NULL, + "status" "JobRunSubscriptionStatus" NOT NULL DEFAULT 'ACTIVE', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deliveredAt" TIMESTAMP(3), + + CONSTRAINT "JobRunSubscription_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "JobRunSubscription" ADD CONSTRAINT "JobRunSubscription_runId_fkey" FOREIGN KEY ("runId") REFERENCES "JobRun"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/database/prisma/migrations/20231101105021_add_child_execution_mode_to_tasks/migration.sql b/packages/database/prisma/migrations/20231101105021_add_child_execution_mode_to_tasks/migration.sql new file mode 100644 index 000000000..9bd543ef5 --- /dev/null +++ b/packages/database/prisma/migrations/20231101105021_add_child_execution_mode_to_tasks/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "TaskChildExecutionMode" AS ENUM ('SEQUENTIAL', 'PARALLEL'); + +-- AlterTable +ALTER TABLE "Task" ADD COLUMN "childExecutionMode" "TaskChildExecutionMode" NOT NULL DEFAULT 'SEQUENTIAL'; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 302be3bf8..98309140c 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -673,8 +673,9 @@ model EventRecord { updatedAt DateTime @updatedAt cancelledAt DateTime? - isTest Boolean @default(false) - runs JobRun[] + isTest Boolean @default(false) + internal Boolean @default(false) + runs JobRun[] @@unique([eventId, environmentId]) } @@ -741,6 +742,7 @@ model JobRun { executions JobRunExecution[] statuses JobRunStatusRecord[] autoYieldExecution JobRunAutoYieldExecution[] + subscriptions JobRunSubscription[] } enum JobRunStatus { @@ -772,6 +774,38 @@ model JobRunAutoYieldExecution { createdAt DateTime @default(now()) } +model JobRunSubscription { + id String @id @default(cuid()) + + run JobRun @relation(fields: [runId], references: [id], onDelete: Cascade, onUpdate: Cascade) + runId String + + recipient String + recipientMethod JobRunSubscriptionRecipientMethod @default(WEBHOOK) + + event JobRunSubscriptionEvents + status JobRunSubscriptionStatus @default(ACTIVE) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + deliveredAt DateTime? +} + +enum JobRunSubscriptionRecipientMethod { + WEBHOOK +} + +enum JobRunSubscriptionStatus { + ACTIVE + INACTIVE +} + +enum JobRunSubscriptionEvents { + SUCCESS + FAILURE +} + model JobRunExecution { id String @id @default(cuid()) @@ -849,9 +883,10 @@ model Task { runConnection RunConnection? @relation(fields: [runConnectionId], references: [id], onDelete: Cascade, onUpdate: Cascade) runConnectionId String? - children Task[] @relation("TaskParent") - executions JobRunExecution[] - attempts TaskAttempt[] + children Task[] @relation("TaskParent") + childExecutionMode TaskChildExecutionMode @default(SEQUENTIAL) + executions JobRunExecution[] + attempts TaskAttempt[] @@unique([runId, idempotencyKey]) } @@ -865,6 +900,11 @@ enum TaskStatus { CANCELED } +enum TaskChildExecutionMode { + SEQUENTIAL + PARALLEL +} + model TaskAttempt { id String @id @default(cuid()) diff --git a/packages/integration-kit/package.json b/packages/integration-kit/package.json index 53ad9df1f..850a6c2bf 100644 --- a/packages/integration-kit/package.json +++ b/packages/integration-kit/package.json @@ -34,9 +34,10 @@ "typecheck": "tsup --dts-resolve --no-dts" }, "dependencies": { - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "@trigger.dev/core": "workspace:*" }, "engines": { "node": ">=18.0.0" } -} +} \ No newline at end of file diff --git a/packages/integration-kit/src/index.ts b/packages/integration-kit/src/index.ts index 46d3c024f..bd5f3687a 100644 --- a/packages/integration-kit/src/index.ts +++ b/packages/integration-kit/src/index.ts @@ -4,3 +4,4 @@ export * from "./omit"; export * from "./properties"; export * from "./file"; export * from "./prettify"; +export * from "./types"; diff --git a/packages/integration-kit/src/types.ts b/packages/integration-kit/src/types.ts new file mode 100644 index 000000000..20d24ef32 --- /dev/null +++ b/packages/integration-kit/src/types.ts @@ -0,0 +1,3 @@ +import type { FetchRetryOptions, FetchTimeoutOptions } from "@trigger.dev/core"; + +export type { FetchRetryOptions, FetchTimeoutOptions }; diff --git a/packages/integration-kit/tsconfig.json b/packages/integration-kit/tsconfig.json index 765965d95..e5b64c11c 100644 --- a/packages/integration-kit/tsconfig.json +++ b/packages/integration-kit/tsconfig.json @@ -4,7 +4,10 @@ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, - "paths": {}, + "paths": { + "@trigger.dev/core/*": ["../core/src/*"], + "@trigger.dev/core": ["../core/src/index"] + }, "lib": ["DOM", "DOM.Iterable"], "declaration": false, "declarationMap": false diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index 1b43842a2..e0777c900 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -30,6 +30,10 @@ import { urlWithSearchParams, RunTaskResponseWithCachedTasksBodySchema, API_VERSIONS, + InvokeJobResponseSchema, + InvokeOptions, + InvokeJobRequestBody, + CompleteTaskBodyV2Input, } from "@trigger.dev/core"; import { z } from "zod"; @@ -139,7 +143,7 @@ export class ApiClient { ); } - async completeTask(runId: string, id: string, task: CompleteTaskBodyInput) { + async completeTask(runId: string, id: string, task: CompleteTaskBodyV2Input) { const apiKey = await this.#apiKey(); this.#logger.debug("Complete Task", { @@ -154,6 +158,7 @@ export class ApiClient { headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, + "Trigger-Version": API_VERSIONS.SERIALIZED_TASK_OUTPUT, }, body: JSON.stringify(task), } @@ -476,6 +481,33 @@ export class ApiClient { ); } + async invokeJob(jobId: string, payload: any, options: InvokeOptions = {}) { + const apiKey = await this.#apiKey(); + + this.#logger.debug("Invoking Job", { + jobId, + }); + + const body: InvokeJobRequestBody = { + payload, + context: options.context ?? {}, + options: { + accountId: options.accountId, + callbackUrl: options.callbackUrl, + }, + }; + + return await zodfetch(InvokeJobResponseSchema, `${this.#apiUrl}/api/v1/jobs/${jobId}/invoke`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + ...(options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : {}), + }, + body: JSON.stringify(body), + }); + } + async #apiKey() { const apiKey = getApiKey(this.#options.apiKey); @@ -573,7 +605,8 @@ async function zodfetchWithVersions< options?: { errorMessage?: string; optional?: TOptional; - } + }, + retryCount = 0 ): Promise< TOptional extends true ? VersionedResponseBody | undefined @@ -596,6 +629,22 @@ async function zodfetchWithVersions< throw new Error(body.error); } + if (response.status >= 500 && retryCount < 6) { + // retry with exponential backoff and jitter + const delay = exponentialBackoff(retryCount + 1, 2, 50, 1150, 50); + + await new Promise((resolve) => setTimeout(resolve, delay)); + + return zodfetchWithVersions( + versionedSchemaMap, + unversionedSchema, + url, + requestInit, + options, + retryCount + 1 + ); + } + if (response.status !== 200) { throw new Error( options?.errorMessage ?? `Failed to fetch ${url}, got status code ${response.status}` @@ -632,7 +681,8 @@ async function zodfetch | undefined : z.infer > { @@ -653,6 +703,15 @@ async function zodfetch= 500 && retryCount < 6) { + // retry with exponential backoff and jitter + const delay = exponentialBackoff(retryCount + 1, 2, 50, 1150, 50); + + await new Promise((resolve) => setTimeout(resolve, delay)); + + return zodfetch(schema, url, requestInit, options, retryCount + 1); + } + if (response.status !== 200) { throw new Error( options?.errorMessage ?? `Failed to fetch ${url}, got status code ${response.status}` @@ -663,3 +722,20 @@ async function zodfetch + ) {} +} + export class RetryWithTaskError { constructor( public cause: ErrorWithStack, @@ -33,8 +40,8 @@ export class AutoYieldWithCompletedTaskExecutionError { constructor( public id: string, public properties: DisplayProperty[] | undefined, - public output: any, - public data: { location: string; timeRemaining: number; timeElapsed: number } + public data: { location: string; timeRemaining: number; timeElapsed: number }, + public output?: string ) {} } @@ -42,27 +49,39 @@ export class ParsedPayloadSchemaError { constructor(public schemaErrors: SchemaError[]) {} } +export type TriggerInternalError = + | ResumeWithTaskError + | RetryWithTaskError + | CanceledWithTaskError + | YieldExecutionError + | AutoYieldExecutionError + | AutoYieldWithCompletedTaskExecutionError + | ResumeWithParallelTaskError; + /** Use this function if you're using a `try/catch` block to catch errors. * It checks if a thrown error is a special internal error that you should ignore. * If this returns `true` then you must rethrow the error: `throw err;` * @param err The error to check * @returns `true` if the error is a Trigger Error, `false` otherwise. */ -export function isTriggerError( - err: unknown -): err is - | ResumeWithTaskError - | RetryWithTaskError - | CanceledWithTaskError - | YieldExecutionError - | AutoYieldExecutionError - | AutoYieldWithCompletedTaskExecutionError { +export function isTriggerError(err: unknown): err is TriggerInternalError { return ( err instanceof ResumeWithTaskError || err instanceof RetryWithTaskError || err instanceof CanceledWithTaskError || err instanceof YieldExecutionError || err instanceof AutoYieldExecutionError || - err instanceof AutoYieldWithCompletedTaskExecutionError + err instanceof AutoYieldWithCompletedTaskExecutionError || + err instanceof ResumeWithParallelTaskError ); } + +// This error isn't an internal error but it can be used by the user to figure out which task caused the error +export class ErrorWithTask extends Error { + constructor( + public cause: ServerTask, + message: string + ) { + super(message); + } +} diff --git a/packages/trigger-sdk/src/index.ts b/packages/trigger-sdk/src/index.ts index ca5a81ba6..a019c59c2 100644 --- a/packages/trigger-sdk/src/index.ts +++ b/packages/trigger-sdk/src/index.ts @@ -6,6 +6,7 @@ export * from "./triggers/externalSource"; export * from "./triggers/dynamic"; export * from "./triggers/scheduled"; export * from "./triggers/notifications"; +export * from "./triggers/invokeTrigger"; export * from "./io"; export * from "./types"; diff --git a/packages/trigger-sdk/src/io.ts b/packages/trigger-sdk/src/io.ts index 01333c3e1..283c4e5f9 100644 --- a/packages/trigger-sdk/src/io.ts +++ b/packages/trigger-sdk/src/io.ts @@ -6,6 +6,7 @@ import { ErrorWithStackSchema, FetchRequestInit, FetchRetryOptions, + FetchTimeoutOptions, InitialStatusUpdate, IntervalOptions, LogLevel, @@ -13,7 +14,6 @@ import { RunTaskOptions, SendEvent, SendEventOptions, - SerializableJsonSchema, ServerTask, UpdateTriggerSourceBodyV2, supportsFeature, @@ -26,19 +26,22 @@ import { AutoYieldExecutionError, AutoYieldWithCompletedTaskExecutionError, CanceledWithTaskError, + ErrorWithTask, + ResumeWithParallelTaskError, ResumeWithTaskError, RetryWithTaskError, + TriggerInternalError, YieldExecutionError, isTriggerError, } from "./errors"; +import { IntegrationTaskKey } from "./integrations"; import { calculateRetryAt } from "./retry"; +import { TriggerStatus } from "./status"; import { TriggerClient } from "./triggerClient"; import { DynamicTrigger } from "./triggers/dynamic"; import { ExternalSource, ExternalSourceParams } from "./triggers/externalSource"; import { DynamicSchedule } from "./triggers/scheduled"; import { EventSpecification, TaskLogger, TriggerContext } from "./types"; -import { IntegrationTaskKey } from "./integrations"; -import { TriggerStatus } from "./status"; export type IOTask = ServerTask; @@ -85,6 +88,21 @@ export type IOStats = { noopCachedTaskMisses: number; }; +export interface OutputSerializer { + serialize(value: any): string; + deserialize(value: string): T; +} + +export class JSONOutputSerializer implements OutputSerializer { + serialize(value: any): string { + return JSON.stringify(value); + } + + deserialize(value?: string): any { + return value ? JSON.parse(value) : undefined; + } +} + export class IO { private _id: string; private _apiClient: ApiClient; @@ -102,6 +120,8 @@ export class IO { private _serverVersion: string; private _timeOrigin: number; private _executionTimeout?: number; + private _outputSerializer: OutputSerializer = new JSONOutputSerializer(); + private _visitedCacheKeys: Set = new Set(); get stats() { return this._stats; @@ -284,7 +304,8 @@ export class IO { cacheKey: string | any[], url: string, requestInit?: FetchRequestInit, - retry?: FetchRetryOptions + retry?: FetchRetryOptions, + timeout?: FetchTimeoutOptions ): Promise { const urlObject = new URL(url); @@ -295,7 +316,7 @@ export class IO { }, { name: `fetch ${urlObject.hostname}${urlObject.pathname}`, - params: { url, requestInit, retry }, + params: { url, requestInit, retry, timeout }, operation: "fetch", icon: "background", noop: false, @@ -313,7 +334,11 @@ export class IO { label: "background", text: "true", }, + ...(timeout ? [{ label: "timeout", text: `${timeout.durationInMs}ms` }] : []), ], + retry: { + limit: 0, + }, } )) as TResponseData; } @@ -583,6 +608,53 @@ export class IO { ); } + async parallel | void, TItem>( + cacheKey: string | any[], + items: Array, + callback: (item: TItem, index: number) => Promise, + options?: Pick + ): Promise> { + const results = await this.runTask( + cacheKey, + async (task) => { + const outcomes = await Promise.allSettled( + items.map((item, index) => spaceOut(() => callback(item, index), index, 15)) + ); + + // If all the outcomes are fulfilled, return the values + if (outcomes.every((outcome) => outcome.status === "fulfilled")) { + return outcomes.map( + (outcome) => (outcome as PromiseFulfilledResult).value + ) as Array<{}>; + } + + // If they any of the errors are non internal errors, throw the first one + const nonInternalErrors = outcomes + .filter((outcome) => outcome.status === "rejected" && !isTriggerError(outcome.reason)) + .map((outcome) => outcome as PromiseRejectedResult); + + if (nonInternalErrors.length > 0) { + throw nonInternalErrors[0].reason; + } + + // gather all the internal errors + const internalErrors = outcomes + .filter((outcome) => outcome.status === "rejected" && isTriggerError(outcome.reason)) + .map((outcome) => outcome as PromiseRejectedResult) + .map((outcome) => outcome.reason as TriggerInternalError); + + throw new ResumeWithParallelTaskError(task, internalErrors); + }, + { + name: "parallel", + parallel: true, + ...(options ?? {}), + } + ); + + return results as unknown as Array; + } + /** `io.runTask()` allows you to run a [Task](https://trigger.dev/docs/documentation/concepts/tasks) from inside a Job run. A Task is a resumable unit of a Run that can be retried, resumed and is logged. [Integrations](https://trigger.dev/docs/integrations) use Tasks internally to perform their actions. * * @param cacheKey Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information. @@ -613,6 +685,22 @@ export class IO { [this._id, parentId ?? "", cacheKey].flat() ); + if (this._visitedCacheKeys.has(idempotencyKey)) { + if (typeof cacheKey === "string") { + throw new Error( + `Task with cacheKey "${cacheKey}" has already been executed in this run. Each task must have a unique cacheKey.` + ); + } else { + throw new Error( + `Task with cacheKey "${cacheKey.join( + "-" + )}" has already been executed in this run. Each task must have a unique cacheKey.` + ); + } + } + + this._visitedCacheKeys.add(idempotencyKey); + const cachedTask = this._cachedTasks.get(idempotencyKey); if (cachedTask && cachedTask.status === "COMPLETED") { @@ -714,7 +802,10 @@ export class IO { task, }); - throw new Error(task.error ?? task?.output ? JSON.stringify(task.output) : "Task errored"); + throw new ErrorWithTask( + task, + task.error ?? task?.output ? JSON.stringify(task.output) : "Task errored" + ); } this.#detectAutoYield("before_execute_task", 1500); @@ -731,7 +822,7 @@ export class IO { return {} as T; } - const output = SerializableJsonSchema.parse(result) as T; + const output = this._outputSerializer.serialize(result); this._logger.debug("Completing using output", { idempotencyKey, @@ -741,7 +832,7 @@ export class IO { this.#detectAutoYield("before_complete_task", 500, task, output); const completedTask = await this._apiClient.completeTask(this._id, task.id, { - output: output ?? undefined, + output, properties: task.outputProperties ?? undefined, }); @@ -761,7 +852,7 @@ export class IO { this.#detectAutoYield("after_complete_task", 500); - return output; + return this._outputSerializer.deserialize(output); } catch (error) { if (isTriggerError(error)) { throw error; @@ -799,6 +890,13 @@ export class IO { } } + if (error instanceof ErrorWithTask) { + // This means a subtask errored, so we need to update the parent task and not retry it + await this._apiClient.failTask(this._id, task.id, { + error: error.cause.output as any, + }); + } + const parsedError = ErrorWithStackSchema.safeParse(error); if (options?.retry && !skipRetrying) { @@ -902,7 +1000,7 @@ export class IO { this._cachedTasks.set(task.idempotencyKey, task); } - #detectAutoYield(location: string, threshold: number = 1500, task?: ServerTask, output?: any) { + #detectAutoYield(location: string, threshold: number = 1500, task?: ServerTask, output?: string) { const timeRemaining = this.#getRemainingTimeInMillis(); if (timeRemaining && timeRemaining < threshold) { @@ -910,12 +1008,12 @@ export class IO { throw new AutoYieldWithCompletedTaskExecutionError( task.id, task.outputProperties ?? [], - output, { location, timeRemaining, timeElapsed: this.#getTimeElapsed(), - } + }, + output ); } else { throw new AutoYieldExecutionError(location, timeRemaining, this.#getTimeElapsed()); @@ -1019,3 +1117,10 @@ export class IOLogger implements TaskLogger { return this.callback("ERROR", message, properties); } } + +// Space out the execution of the callback by a delay of index * delay +async function spaceOut(callback: () => Promise, index: number, delay: number): Promise { + await new Promise((resolve) => setTimeout(resolve, index * delay)); + + return await callback(); +} diff --git a/packages/trigger-sdk/src/job.ts b/packages/trigger-sdk/src/job.ts index d3bc297b7..30aba21fa 100644 --- a/packages/trigger-sdk/src/job.ts +++ b/packages/trigger-sdk/src/job.ts @@ -1,12 +1,28 @@ -import { IntegrationConfig, JobMetadata, LogLevel, QueueOptions } from "@trigger.dev/core"; +import { + IntegrationConfig, + InvokeOptions, + JobMetadata, + LogLevel, + QueueOptions, + RunNotification, +} from "@trigger.dev/core"; import { IOWithIntegrations, TriggerIntegration } from "./integrations"; import { TriggerClient } from "./triggerClient"; -import type { EventSpecification, Trigger, TriggerContext, TriggerEventType } from "./types"; +import type { + EventSpecification, + Trigger, + TriggerContext, + TriggerEventType, + TriggerInvokeType, +} from "./types"; import { slugifyId } from "./utils"; +import { runLocalStorage } from "./runLocalStorage"; +import { Prettify } from "@trigger.dev/core"; export type JobOptions< TTrigger extends Trigger>, TIntegrations extends Record = {}, + TOutput extends any = any, > = { /** The `id` property is used to uniquely identify the Job. Only change this if you want to create a new Job. */ id: string; @@ -57,7 +73,7 @@ export type JobOptions< payload: TriggerEventType, io: IOWithIntegrations, context: TriggerContext - ) => Promise; + ) => Promise; // @internal __internal?: boolean; @@ -75,8 +91,9 @@ export type JobIO = TJob extends Job export class Job< TTrigger extends Trigger>, TIntegrations extends Record = {}, + TOutput extends any = any, > { - readonly options: JobOptions; + readonly options: JobOptions; client: TriggerClient; @@ -84,7 +101,7 @@ export class Job< /** An instance of [TriggerClient](/sdk/triggerclient) that is used to send events to the Trigger API. */ client: TriggerClient, - options: JobOptions + options: JobOptions ) { this.client = client; this.options = options; @@ -152,6 +169,197 @@ export class Job< }; } + async invoke( + cacheKey: string, + payload: TriggerInvokeType, + options?: InvokeOptions + ): Promise<{ id: string }>; + async invoke( + payload: TriggerInvokeType, + options?: InvokeOptions + ): Promise<{ id: string }>; + async invoke( + param1: string | TriggerInvokeType, + param2: TriggerInvokeType | InvokeOptions | undefined = undefined, + param3: InvokeOptions | undefined = undefined + ): Promise<{ id: string }> { + const runStore = runLocalStorage.getStore(); + + if (typeof param1 === "string") { + if (!runStore) { + throw new Error( + "Cannot invoke a job from outside of a run when passing a cacheKey. Make sure you are running the job from within a run or use the invoke method without the cacheKey." + ); + } + + const options = param3 ?? {}; + + return await runStore.io.runTask( + param1, + async (task) => { + const result = await this.client.invokeJob(this.id, param2, { + idempotencyKey: task.idempotencyKey, + ...options, + }); + + task.outputProperties = [ + { + label: "Run", + text: result.id, + url: `/orgs/${runStore.ctx.organization.slug}/projects/${runStore.ctx.project.slug}/jobs/${this.id}/runs/${result.id}/trigger`, + }, + ]; + + return result; + }, + { + name: `Manually Invoke '${this.name}'`, + params: param2, + properties: [ + { + label: "Job", + text: this.id, + url: `/orgs/${runStore.ctx.organization.slug}/projects/${runStore.ctx.project.slug}/jobs/${this.id}`, + }, + { + label: "Env", + text: runStore.ctx.environment.slug, + }, + ], + } + ); + } + + if (runStore) { + throw new Error("Cannot invoke a job from within a run without a cacheKey."); + } + + return await this.client.invokeJob(this.id, param1, param3); + } + + async invokeAndWaitForCompletion( + cacheKey: string | string[], + payload: TriggerInvokeType, + timeoutInSeconds: number = 60 * 60, // 1 hour + options: Prettify> = {} + ): Promise> { + const runStore = runLocalStorage.getStore(); + + if (!runStore) { + throw new Error( + "Cannot invoke a job from outside of a run using invokeAndWaitForCompletion. Make sure you are running the job from within a run or use the invoke method instead." + ); + } + + const { io, ctx } = runStore; + + return (await io.runTask( + cacheKey, + async (task) => { + const parsedPayload = this.trigger.event.parseInvokePayload + ? this.trigger.event.parseInvokePayload(payload) + ? payload + : undefined + : payload; + + const result = await this.client.invokeJob(this.id, parsedPayload, { + idempotencyKey: task.idempotencyKey, + callbackUrl: task.callbackUrl ?? undefined, + ...options, + }); + + task.outputProperties = [ + { + label: "Run", + text: result.id, + url: `/orgs/${ctx.organization.slug}/projects/${ctx.project.slug}/jobs/${this.id}/runs/${result.id}/trigger`, + }, + ]; + + return {}; // we don't want to return anything here, we just want to wait for the callback + }, + { + name: `Manually Invoke '${this.name}' and wait for completion`, + params: payload, + properties: [ + { + label: "Job", + text: this.id, + url: `/orgs/${ctx.organization.slug}/projects/${ctx.project.slug}/jobs/${this.id}`, + }, + { + label: "Env", + text: ctx.environment.slug, + }, + ], + callback: { + enabled: true, + timeoutInSeconds, + }, + } + )) as RunNotification; + } + + async batchInvokeAndWaitForCompletion( + cacheKey: string | string[], + batch: Array<{ + payload: TriggerInvokeType; + timeoutInSeconds?: number; + options?: Prettify>; + }> + ): Promise>> { + const runStore = runLocalStorage.getStore(); + + if (!runStore) { + throw new Error( + "Cannot invoke a job from outside of a run using batchInvokeAndWaitForCompletion." + ); + } + + // If there are no items in the batch, return an empty array + if (batch.length === 0) { + return []; + } + + // If there are too many items in the batch, throw an error + if (batch.length > 25) { + throw new Error( + `Cannot batch invoke more than 25 items. You tried to batch invoke ${batch.length} items.` + ); + } + + const { io, ctx } = runStore; + + const results = await io.parallel( + cacheKey, + batch, + async (item, index) => { + return (await this.invokeAndWaitForCompletion( + String(index), + item.payload, + item.timeoutInSeconds ?? 60 * 60, + item.options + )) as RunNotification<{}>; + }, + { + name: `Batch Invoke '${this.name}'`, + properties: [ + { + label: "Job", + text: this.id, + url: `/orgs/${ctx.organization.slug}/projects/${ctx.project.slug}/jobs/${this.id}`, + }, + { + label: "Env", + text: ctx.environment.slug, + }, + ], + } + ); + + return results as Array>; + } + // Make sure the id is valid (must only contain alphanumeric characters and dashes) // Make sure the version is valid (must be a valid semver version) #validate() { diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index 58023e8d8..3477549ec 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -11,6 +11,7 @@ import { IndexEndpointResponse, InitializeTriggerBodySchema, IntegrationConfig, + InvokeOptions, JobMetadata, LogLevel, Logger, @@ -24,6 +25,7 @@ import { RegisterTriggerBodyV2, RunJobBody, RunJobBodySchema, + RunJobErrorResponse, RunJobResponse, ScheduleMetadata, SendEvent, @@ -36,7 +38,9 @@ import { AutoYieldExecutionError, AutoYieldWithCompletedTaskExecutionError, CanceledWithTaskError, + ErrorWithTask, ParsedPayloadSchemaError, + ResumeWithParallelTaskError, ResumeWithTaskError, RetryWithTaskError, YieldExecutionError, @@ -321,6 +325,13 @@ export class TriggerClient { const results = await this.#executeJob(execution.data, job, timeOrigin, triggerVersion); + this.#internalLogger.debug("executed job", { + results, + job: job.id, + version: job.version, + triggerVersion, + }); + return { status: 200, body: results, @@ -467,8 +478,9 @@ export class TriggerClient { defineJob< TTrigger extends Trigger>, TIntegrations extends Record = {}, - >(options: JobOptions) { - return new Job(this, options); + TOutput extends any = any, + >(options: JobOptions) { + return new Job(this, options); } defineAuthResolver( @@ -685,6 +697,10 @@ export class TriggerClient { return this.#client.getRunStatuses(runId); } + async invokeJob(jobId: string, payload: any, options?: InvokeOptions) { + return this.#client.invokeJob(jobId, payload, options); + } + authorized( apiKey?: string | null ): "authorized" | "unauthorized" | "missing-client" | "missing-header" { @@ -790,91 +806,124 @@ export class TriggerClient { this.#logIOStats(io.stats); } - if (error instanceof AutoYieldExecutionError) { + if (error instanceof ResumeWithParallelTaskError) { return { - status: "AUTO_YIELD_EXECUTION", - location: error.location, - timeRemaining: error.timeRemaining, - timeElapsed: error.timeElapsed, - limit: body.runChunkExecutionLimit, - }; - } - - if (error instanceof AutoYieldWithCompletedTaskExecutionError) { - return { - status: "AUTO_YIELD_EXECUTION_WITH_COMPLETED_TASK", - id: error.id, - properties: error.properties, - output: error.output, - data: { - ...error.data, - limit: body.runChunkExecutionLimit, - }, - }; - } - - if (error instanceof YieldExecutionError) { - return { status: "YIELD_EXECUTION", key: error.key }; - } - - if (error instanceof ParsedPayloadSchemaError) { - return { status: "INVALID_PAYLOAD", errors: error.schemaErrors }; - } - - if (error instanceof ResumeWithTaskError) { - return { status: "RESUME_WITH_TASK", task: error.task }; - } - - if (error instanceof RetryWithTaskError) { - return { - status: "RETRY_WITH_TASK", + status: "RESUME_WITH_PARALLEL_TASK", task: error.task, - error: error.cause, - retryAt: error.retryAt, + childErrors: error.childErrors.map((childError) => { + return this.#convertErrorToExecutionResponse(childError, body); + }), }; } - if (error instanceof CanceledWithTaskError) { - return { - status: "CANCELED", - task: error.task, - }; - } - - if (error instanceof RetryWithTaskError) { - const errorWithStack = ErrorWithStackSchema.safeParse(error.cause); - - if (errorWithStack.success) { - return { - status: "ERROR", - error: errorWithStack.data, - task: error.task, - }; - } - - return { - status: "ERROR", - error: { message: "Unknown error" }, - task: error.task, - }; - } - - const errorWithStack = ErrorWithStackSchema.safeParse(error); - - if (errorWithStack.success) { - return { status: "ERROR", error: errorWithStack.data }; - } - - const message = typeof error === "string" ? error : JSON.stringify(error); - return { - status: "ERROR", - error: { name: "Unknown error", message }, - }; + return this.#convertErrorToExecutionResponse(error, body); } } + #convertErrorToExecutionResponse(error: any, body: RunJobBody): RunJobErrorResponse { + if (error instanceof AutoYieldExecutionError) { + return { + status: "AUTO_YIELD_EXECUTION", + location: error.location, + timeRemaining: error.timeRemaining, + timeElapsed: error.timeElapsed, + limit: body.runChunkExecutionLimit, + }; + } + + if (error instanceof AutoYieldWithCompletedTaskExecutionError) { + return { + status: "AUTO_YIELD_EXECUTION_WITH_COMPLETED_TASK", + id: error.id, + properties: error.properties, + output: error.output, + data: { + ...error.data, + limit: body.runChunkExecutionLimit, + }, + }; + } + + if (error instanceof YieldExecutionError) { + return { status: "YIELD_EXECUTION", key: error.key }; + } + + if (error instanceof ParsedPayloadSchemaError) { + return { status: "INVALID_PAYLOAD", errors: error.schemaErrors }; + } + + if (error instanceof ResumeWithTaskError) { + return { status: "RESUME_WITH_TASK", task: error.task }; + } + + if (error instanceof RetryWithTaskError) { + return { + status: "RETRY_WITH_TASK", + task: error.task, + error: error.cause, + retryAt: error.retryAt, + }; + } + + if (error instanceof CanceledWithTaskError) { + return { + status: "CANCELED", + task: error.task, + }; + } + + if (error instanceof ErrorWithTask) { + const errorWithStack = ErrorWithStackSchema.safeParse(error.cause.output); + + if (errorWithStack.success) { + return { + status: "ERROR", + error: errorWithStack.data, + task: error.cause, + }; + } + + return { + status: "ERROR", + error: { message: JSON.stringify(error.cause.output) }, + task: error.cause, + }; + } + + if (error instanceof RetryWithTaskError) { + const errorWithStack = ErrorWithStackSchema.safeParse(error.cause); + + if (errorWithStack.success) { + return { + status: "ERROR", + error: errorWithStack.data, + task: error.task, + }; + } + + return { + status: "ERROR", + error: { message: "Unknown error" }, + task: error.task, + }; + } + + const errorWithStack = ErrorWithStackSchema.safeParse(error); + + if (errorWithStack.success) { + return { status: "ERROR", error: errorWithStack.data }; + } + + const message = typeof error === "string" ? error : JSON.stringify(error); + + return { + status: "ERROR", + error: { name: "Unknown error", message }, + }; + } + #createRunContext(execution: RunJobBody): TriggerContext { - const { event, organization, environment, job, run, source } = execution; + const { event, organization, project, environment, job, run, source } = execution; return { event: { @@ -884,6 +933,7 @@ export class TriggerClient { timestamp: event.timestamp, }, organization, + project: project ?? { id: "unknown", name: "unknown", slug: "unknown" }, // backwards compat with old servers environment, job, run, diff --git a/packages/trigger-sdk/src/triggers/invokeTrigger.ts b/packages/trigger-sdk/src/triggers/invokeTrigger.ts new file mode 100644 index 000000000..bba593070 --- /dev/null +++ b/packages/trigger-sdk/src/triggers/invokeTrigger.ts @@ -0,0 +1,83 @@ +import { TriggerMetadata } from "@trigger.dev/core"; +import { ParsedPayloadSchemaError } from "../errors"; +import { Job } from "../job"; +import { TriggerClient } from "../triggerClient"; +import { EventSpecification, EventSpecificationExample, SchemaParser, Trigger } from "../types"; +import { formatSchemaErrors } from "../utils/formatSchemaErrors"; +import { TypeOf, ZodType, z } from "zod"; + +/** Configuration options for an InvokeTrigger */ +type InvokeTriggerOptions = { + /** A [Zod](https://trigger.dev/docs/documentation/guides/zod) schema that defines the shape of the event payload. + * The default is `z.any()` which is `any`. + * */ + schema?: TSchema; + examples?: EventSpecificationExample[]; +}; + +export class InvokeTrigger + implements Trigger, z.input>> +{ + #options: InvokeTriggerOptions; + + constructor(options: InvokeTriggerOptions) { + this.#options = options; + } + + toJSON(): TriggerMetadata { + return { + type: "invoke", + }; + } + + get event() { + return { + name: "invoke", + title: "Manual Invoke", + source: "trigger.dev", + examples: this.#options.examples ?? [], + icon: "trigger", + parsePayload: (rawPayload: unknown) => { + if (this.#options.schema) { + const results = this.#options.schema.safeParse(rawPayload); + + if (!results.success) { + throw new ParsedPayloadSchemaError(formatSchemaErrors(results.error.issues)); + } + + return results.data; + } + + return rawPayload as any; + }, + parseInvokePayload: (rawPayload: unknown) => { + if (this.#options.schema) { + const results = this.#options.schema.safeParse(rawPayload); + + if (!results.success) { + throw new ParsedPayloadSchemaError(formatSchemaErrors(results.error.issues)); + } + + return results.data; + } + + return rawPayload as any; + }, + }; + } + + attachToJob( + triggerClient: TriggerClient, + job: Job>>, any> + ): void {} + + get preprocessRuns() { + return false; + } +} + +export function invokeTrigger( + options?: InvokeTriggerOptions +): Trigger, z.input>> { + return new InvokeTrigger(options ?? {}); +} diff --git a/packages/trigger-sdk/src/types.ts b/packages/trigger-sdk/src/types.ts index e7938b236..2c0aba88e 100644 --- a/packages/trigger-sdk/src/types.ts +++ b/packages/trigger-sdk/src/types.ts @@ -32,6 +32,8 @@ export interface TriggerContext { environment: { slug: string; id: string; type: RuntimeEnvironmentType }; /** Organization metadata */ organization: { slug: string; id: string; title: string }; + /** Project metadata */ + project: { slug: string; id: string; name: string }; /** Run metadata */ run: { id: string; isTest: boolean; startedAt: Date; isRetry: boolean }; /** Event metadata */ @@ -69,6 +71,14 @@ export type TriggerEventType> = TTrigger extends T ? ReturnType : never; +export type TriggerInvokeType> = TTrigger extends Trigger< + infer TEventSpec +> + ? TEventSpec["parseInvokePayload"] extends (payload: unknown) => infer TInvoke + ? TInvoke + : any + : never; + export interface Trigger> { event: TEventSpec; toJSON(): TriggerMetadata; @@ -90,7 +100,7 @@ export type EventSpecificationExample = { payload: any; }; -export interface EventSpecification { +export interface EventSpecification { name: string | string[]; title: string; source: string; @@ -100,6 +110,7 @@ export interface EventSpecification { examples?: Array; filter?: EventFilter; parsePayload: (payload: unknown) => TEvent; + parseInvokePayload?: (payload: unknown) => TInvoke; runProperties?: (payload: TEvent) => DisplayProperty[]; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18b297c3f..45e523ae6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,6 +134,7 @@ importers: '@typescript-eslint/eslint-plugin': ^5.59.6 '@typescript-eslint/parser': ^5.59.6 '@uiw/react-codemirror': ^4.19.5 + '@whatwg-node/fetch': ^0.9.14 autoprefixer: ^10.4.13 class-variance-authority: ^0.5.2 clsx: ^1.2.1 @@ -170,13 +171,12 @@ importers: prop-types: ^15.8.1 react: ^18.2.0 react-dom: ^18.2.0 - react-hot-toast: ^2.4.0 react-hotkeys-hook: ^4.4.1 react-use: ^17.4.0 recharts: ^2.8.0 - remix-auth: ^3.2.2 - remix-auth-email-link: ^1.4.2 - remix-auth-github: ^1.1.1 + remix-auth: ^3.6.0 + remix-auth-email-link: ^2.1.0 + remix-auth-github: ^1.6.0 remix-typedjson: 0.3.1 remix-utils: ^7.1.0 rimraf: ^3.0.2 @@ -241,6 +241,7 @@ importers: '@trigger.dev/sdk': link:../../packages/trigger-sdk '@types/pg': 8.6.6 '@uiw/react-codemirror': 4.19.5_th22fcplkuhrqjnlojwclcaim4 + '@whatwg-node/fetch': 0.9.14 class-variance-authority: 0.5.2_typescript@5.2.2 clsx: 1.2.1 compression: 1.7.4 @@ -268,13 +269,12 @@ importers: prismjs: 1.29.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - react-hot-toast: 2.4.0_biqbaboplfbrettd7655fr4n2y react-hotkeys-hook: 4.4.1_biqbaboplfbrettd7655fr4n2y react-use: 17.4.0_biqbaboplfbrettd7655fr4n2y recharts: 2.8.0_v2m5e27vhdewzwhryxwfaorcca - remix-auth: 3.4.0_ybjp5xbtg4zthziheradfmei64 - remix-auth-email-link: 1.5.2_7ym4eehrzvo4xt5yocgqipdaji - remix-auth-github: 1.3.0_7ym4eehrzvo4xt5yocgqipdaji + remix-auth: 3.6.0_ybjp5xbtg4zthziheradfmei64 + remix-auth-email-link: 2.1.0_esw6v6dv7ru6ju3hn3i62b7n2m + remix-auth-github: 1.6.0_esw6v6dv7ru6ju3hn3i62b7n2m remix-typedjson: 0.3.1_7t5z2pmrpxrx2dh2jpp55scw5q remix-utils: 7.1.0_nb5lmxjinjv74o775xrkc35gga semver: 7.5.0 @@ -907,6 +907,7 @@ importers: packages/integration-kit: specifiers: + '@trigger.dev/core': workspace:* '@trigger.dev/tsconfig': workspace:* '@types/node': '18' '@types/uuid': ^9.0.0 @@ -916,6 +917,7 @@ importers: typescript: ^4.8.4 uuid: ^9.0.0 dependencies: + '@trigger.dev/core': link:../core uuid: 9.0.0 devDependencies: '@trigger.dev/tsconfig': link:../../config-packages/tsconfig @@ -14897,6 +14899,30 @@ packages: '@xtuc/long': 4.2.2 dev: true + /@whatwg-node/events/0.1.1: + resolution: {integrity: sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==} + engines: {node: '>=16.0.0'} + dev: false + + /@whatwg-node/fetch/0.9.14: + resolution: {integrity: sha512-wurZC82zzZwXRDSW0OS9l141DynaJQh7Yt0FD1xZ8niX7/Et/7RoiLiltbVU1fSF1RR9z6ndEaTUQBAmddTm1w==} + engines: {node: '>=16.0.0'} + dependencies: + '@whatwg-node/node-fetch': 0.5.0 + urlpattern-polyfill: 9.0.0 + dev: false + + /@whatwg-node/node-fetch/0.5.0: + resolution: {integrity: sha512-q76lDAafvHNGWedNAVHrz/EyYTS8qwRLcwne8SJQdRN5P3HydxU6XROFvJfTML6KZXQX2FDdGY4/SnaNyd7M0Q==} + engines: {node: '>=16.0.0'} + dependencies: + '@whatwg-node/events': 0.1.1 + busboy: 1.6.0 + fast-querystring: 1.1.2 + fast-url-parser: 1.1.3 + tslib: 2.6.2 + dev: false + /@xmldom/xmldom/0.8.6: resolution: {integrity: sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg==} engines: {node: '>=10.0.0'} @@ -20099,7 +20125,6 @@ packages: /fast-decode-uri-component/1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} - dev: true /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -20203,7 +20228,6 @@ packages: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} dependencies: fast-decode-uri-component: 1.0.1 - dev: true /fast-redact/3.3.0: resolution: {integrity: sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==} @@ -20221,6 +20245,12 @@ packages: resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} dev: true + /fast-url-parser/1.1.3: + resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} + dependencies: + punycode: 1.4.1 + dev: false + /fastest-stable-stringify/2.0.2: resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} dev: false @@ -21118,12 +21148,6 @@ packages: /globrex/0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - /goober/2.1.11: - resolution: {integrity: sha512-5SS2lmxbhqH0u9ABEWq7WPU69a4i2pYcHeCxqaNq6Cw3mnrF0ghWNM4tEGid4dKy8XNIAUbuThuozDHHKJVh3A==} - peerDependencies: - csstype: ^3.0.10 - dev: false - /gopd/1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: @@ -27340,20 +27364,6 @@ packages: - supports-color dev: false - /react-hot-toast/2.4.0_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==} - engines: {node: '>=10'} - peerDependencies: - react: '>=16' - react-dom: '>=16' - dependencies: - goober: 2.1.11 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - transitivePeerDependencies: - - csstype - dev: false - /react-hotkeys-hook/4.4.1_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-sClBMBioFEgFGYLTWWRKvhxcCx1DRznd+wkFHwQZspnRBkHTgruKIHptlK/U/2DPX8BhHoRGzpMVWUXMmdZlmw==} peerDependencies: @@ -27982,49 +27992,49 @@ packages: retext-smartypants: 5.2.0 unist-util-visit: 4.1.2 - /remix-auth-email-link/1.5.2_7ym4eehrzvo4xt5yocgqipdaji: - resolution: {integrity: sha512-LCbaYkhsENbRK70O36Ul/jBtvNcNdBabjOIXYhrTNwx4qYA/fFkX8Sr3GMuDhoMzHnXQy7zHtsqbg/DTHocX6g==} + /remix-auth-email-link/2.1.0_esw6v6dv7ru6ju3hn3i62b7n2m: + resolution: {integrity: sha512-1Hp4QDqN17tEEfiRd/6g1XDw6o6y+lyeeseDc0EGrR4vZcR7HLPR8ImfEtxplYpXlFSpOO7mzl6GgJprynjgcg==} peerDependencies: - '@remix-run/server-runtime': ^1.1.1 - remix-auth: ^3.2.1 + '@remix-run/server-runtime': ^2.0.1 + remix-auth: ^3.6.0 dependencies: '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 crypto-js: 4.1.1 - remix-auth: 3.4.0_ybjp5xbtg4zthziheradfmei64 + remix-auth: 3.6.0_ybjp5xbtg4zthziheradfmei64 + yarn: 1.22.19 dev: false - /remix-auth-github/1.3.0_7ym4eehrzvo4xt5yocgqipdaji: - resolution: {integrity: sha512-dqveIbXc1yXQXiB8UVjKSR3qgHFUZ0VJOu2/GTT5uMKZY0//FSnp1w8gK0btGlbc1HW97gig16uS84et6FBMiA==} + /remix-auth-github/1.6.0_esw6v6dv7ru6ju3hn3i62b7n2m: + resolution: {integrity: sha512-qdQmWVVEDHxnzMPn7XE0s7QX5GYba0uH3MDW+lVJiCoHDZCGymqmGCajLThL5vFJdwSx0C4GpQJ5EgEsIjQ3pA==} peerDependencies: '@remix-run/server-runtime': ^1.0.0 remix-auth: ^3.4.0 dependencies: '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 - remix-auth: 3.4.0_ybjp5xbtg4zthziheradfmei64 - remix-auth-oauth2: 1.5.0_7ym4eehrzvo4xt5yocgqipdaji + remix-auth: 3.6.0_ybjp5xbtg4zthziheradfmei64 + remix-auth-oauth2: 1.11.0_esw6v6dv7ru6ju3hn3i62b7n2m transitivePeerDependencies: - supports-color dev: false - /remix-auth-oauth2/1.5.0_7ym4eehrzvo4xt5yocgqipdaji: - resolution: {integrity: sha512-vv/CX2bCeVf8vzACfS1Fae+i75Cw0/As3RLkp00+9n9IBEvKi07LhUxTbODNA51jRh+wInnmCRIwsHafKQDD6g==} + /remix-auth-oauth2/1.11.0_esw6v6dv7ru6ju3hn3i62b7n2m: + resolution: {integrity: sha512-Yf1LF6NLYPFa7X2Rax/VEhXmYXFjZOi/q+7DmbMeoMHjAfkpqxbvzqqYSKIKDGR51z5TXR5na4to4380mir5bg==} peerDependencies: - '@remix-run/server-runtime': ^1.0.0 - remix-auth: ^3.2.2 + '@remix-run/server-runtime': ^1.0.0 || ^2.0.0 + remix-auth: ^3.6.0 dependencies: '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 debug: 4.3.4 - remix-auth: 3.4.0_ybjp5xbtg4zthziheradfmei64 - uuid: 8.3.2 + remix-auth: 3.6.0_ybjp5xbtg4zthziheradfmei64 transitivePeerDependencies: - supports-color dev: false - /remix-auth/3.4.0_ybjp5xbtg4zthziheradfmei64: - resolution: {integrity: sha512-VRliJo9VRAS4sSYgMjYbi7rYRixPWU2Tb8PFZb06OIj0nONK/1KRzHf2+Y6VGZeIacepLVgan6g2IUJjayE2rQ==} + /remix-auth/3.6.0_ybjp5xbtg4zthziheradfmei64: + resolution: {integrity: sha512-mxlzLYi+/GKQSaXIqIw15dxAT1wm+93REAeDIft2unrKDYnjaGhhpapyPhdbALln86wt9lNAk21znfRss3fG7Q==} peerDependencies: - '@remix-run/react': ^1.0.0 - '@remix-run/server-runtime': ^1.0.0 + '@remix-run/react': ^1.0.0 || ^2.0.0 + '@remix-run/server-runtime': ^1.0.0 || ^2.0.0 dependencies: '@remix-run/react': 2.1.0_vegmnuvoswdxuttpuhvewcem44 '@remix-run/server-runtime': 2.1.0_typescript@5.2.2 @@ -31468,6 +31478,10 @@ packages: qs: 6.11.0 dev: false + /urlpattern-polyfill/9.0.0: + resolution: {integrity: sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==} + dev: false + /use-callback-ref/1.3.0_e74vmjybjy5dsfplslbsgtbvvi: resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} engines: {node: '>=10'} @@ -32683,6 +32697,13 @@ packages: y18n: 5.0.8 yargs-parser: 21.1.1 + /yarn/1.22.19: + resolution: {integrity: sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ==} + engines: {node: '>=4.0.0'} + hasBin: true + requiresBuild: true + dev: false + /yauzl/2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} dependencies: diff --git a/references/job-catalog/package.json b/references/job-catalog/package.json index 6295d21fa..abd0533f2 100644 --- a/references/job-catalog/package.json +++ b/references/job-catalog/package.json @@ -29,6 +29,7 @@ "misconfigured": "nodemon --watch src/misconfigured.ts -r tsconfig-paths/register -r dotenv/config src/misconfigured.ts", "auto-yield": "nodemon --watch src/auto-yield.ts -r tsconfig-paths/register -r dotenv/config src/auto-yield.ts", "cli-example": "nodemon --watch src/cli-example.ts -r tsconfig-paths/register -r dotenv/config src/cli-example.ts", + "invoke": "nodemon --watch src/invoke.ts -r tsconfig-paths/register -r dotenv/config src/invoke.ts", "dev:trigger": "trigger-cli dev --port 8080" }, "dependencies": { @@ -63,4 +64,4 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^3.14.1" } -} +} \ No newline at end of file diff --git a/references/job-catalog/src/invoke.ts b/references/job-catalog/src/invoke.ts new file mode 100644 index 000000000..185a62b46 --- /dev/null +++ b/references/job-catalog/src/invoke.ts @@ -0,0 +1,313 @@ +import { createExpressServer } from "@trigger.dev/express"; +import { OpenAI } from "@trigger.dev/openai"; +import { TriggerClient, invokeTrigger } from "@trigger.dev/sdk"; +import { z } from "zod"; + +export const client = new TriggerClient({ + id: "job-catalog", + apiKey: process.env["TRIGGER_API_KEY"], + apiUrl: process.env["TRIGGER_API_URL"], + verbose: true, + ioLogLocalEnabled: true, +}); + +const invocableJob = client.defineJob({ + id: "invoke-example-1", + name: "Invoke Example 1", + version: "1.0.0", + enabled: true, + trigger: invokeTrigger({ + schema: z.object({ + message: z.string(), + forceError: z.boolean().default(false), + delay: z.number().default(5), + }), + }), + run: async (payload, io, ctx) => { + const generatingMemes = await io.createStatus("status-1", { + //the label is compulsory on this first call + label: "Generating memes", + //state is optional + state: "loading", + //data is an optional object. the values can be any type that is JSON serializable + data: { + progress: 0.1, + }, + }); + + await io.runTask("task-1", async () => { + await new Promise((resolve) => setTimeout(resolve, 1000)); + }); + + await io.wait("wait-1", payload.delay); + + await io.runTask("task-2", async () => { + await new Promise((resolve) => setTimeout(resolve, 1000)); + + return [{ hello: "there", ts: new Date() }]; + }); + + await generatingMemes.update("middle-generation", { + //label isn't specified so will remain the same + //state will be updated to "success" + state: "success", + //set data, this overrides the previous value + data: { + progress: 1, + urls: [ + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + ], + }, + }); + + if (payload.forceError) { + throw new Error("Forced error"); + } + + const response = await io.runTask("fetch-json", async () => + fetch("https://jsonhero.io/j/PjHo1o5MVeH4.json").then((r) => r.json()) + ); + + return response; + }, +}); + +client.defineJob({ + id: "invoke-example-2", + name: "Invoke Example 2", + version: "1.0.0", + enabled: true, + trigger: invokeTrigger(), + run: async (payload, io, ctx) => { + await invocableJob.invoke("invoke", { + message: "Hello World 1", + }); + + await invocableJob.invoke( + "invoke-with-url", + { + message: "Hello World 2", + }, + { + callbackUrl: process.env.REQUEST_BIN_URL, + } + ); + + const result = await invocableJob.invokeAndWaitForCompletion("invoke-and-wait", { + message: "Hello World 3", + forceError: true, + }); + + if (result.ok) { + await io.logger.info("Invoking job worked!", { result }); + } else { + await io.logger.error("Invoking job failed!", { result }); + } + }, +}); + +const simpleInvokableJob = client.defineJob({ + id: "simple-invoke-example-1", + name: "Simple Invoke Example 1", + version: "1.0.0", + enabled: true, + trigger: invokeTrigger(), + run: async (payload, io, ctx) => { + return payload; + }, +}); + +const openai = new OpenAI({ + id: "openai", + apiKey: process.env["OPENAI_API_KEY"]!, +}); + +const perplexity = new OpenAI({ + id: "perplexity", + apiKey: process.env["PERPLEXITY_API_KEY"]!, + baseURL: "https://api.perplexity.ai", + icon: "brand-open-source", +}); + +// This job performs a chat completion using either OpenAI or Perplexity, depending on the model passed in +const completionJob = client.defineJob({ + id: "openai-job", + name: "OpenAI Job", + version: "1.0.0", + enabled: true, + trigger: invokeTrigger({ + schema: z.object({ + model: z.string().default("gpt-3.5-turbo"), + prompt: z.string(), + }), + }), + integrations: { + openai, + perplexity, + }, + run: async (payload, io, ctx) => { + if (payload.model === "gpt-3.5-turbo") { + return await io.openai.chat.completions.backgroundCreate( + "background-chat-completion", + { + model: payload.model, + messages: [ + { + role: "user", + content: payload.prompt, + }, + ], + }, + {}, + { + // Set a timeout of 30 seconds, and retry it up to 3 times + timeout: { + durationInMs: 30000, + retry: { + limit: 3, + minTimeoutInMs: 1000, + factor: 2, + }, + }, + } + ); + } else { + return await io.perplexity.completions.backgroundCreate( + "background-completion", + { + model: payload.model, + prompt: payload.prompt, + }, + {}, + { + // Set a timeout of 30 seconds, and retry it up to 3 times + timeout: { + durationInMs: 30000, + retry: { + limit: 3, + minTimeoutInMs: 1000, + factor: 2, + }, + }, + } + ); + } + }, +}); + +const prompts = [ + "Advantages of quantum computing over classical computing", + "The ethical implications of advanced artificial intelligence", + "Design a thought experiment highlighting the paradoxes in quantum mechanics", + "Explain the Fermi Paradox, its potential solutions, and implications for humanity", + "Analyze Shakespeare's use of iambic pentameter in his plays", +]; + +client.defineJob({ + id: "batch-invoke-ai-example", + name: "Batch Invoke OpenAI Example", + version: "1.0.0", + trigger: invokeTrigger(), + run: async (payload, io, ctx) => { + // This will invoke the completionJob in parallel for each prompt, first trying OpenAI + const runs = await completionJob.batchInvokeAndWaitForCompletion( + "batch-invoke-and-wait", + prompts.map((prompt) => ({ + payload: { + model: "gpt-3.5-turbo", + prompt, + }, + })) + ); + + // Runs are returned in the same order as the prompts + const failedRuns = runs.map((run, i) => ({ run, prompt: prompts[i] })).filter((r) => !r.run.ok); + + // Run the failed runs with Perplexity by specifying the "mistral-7b-instruct" model + const retriedRuns = await completionJob.batchInvokeAndWaitForCompletion( + "batch-invoke-and-wait", + failedRuns.map((failedRun) => ({ + payload: { + model: "mistral-7b-instruct", + prompt: failedRun.prompt, + }, + })) + ); + + const failedPerplexityRuns = retriedRuns + .map((run, i) => ({ run, prompt: failedRuns[i].prompt })) + .filter((r) => !r.run.ok); + + // And so on and so forth + }, +}); + +client.defineJob({ + id: "batch-invoke-example", + name: "Batch Invoke Example", + version: "1.0.0", + trigger: invokeTrigger(), + run: async (payload, io, ctx) => { + await invocableJob.batchInvokeAndWaitForCompletion( + "batch-invoke-and-wait", + Array.from({ length: 2 }).map((_, i) => ({ + payload: { + message: `Hello World ${i}`, + delay: i % 2 === 0 ? 7 : 20, + }, + })) + ); + + await simpleInvokableJob.batchInvokeAndWaitForCompletion( + "batch-invoke-and-wait-simple", + Array.from({ length: 25 }).map((_, i) => ({ + payload: { + message: `Hello World ${i}`, + }, + options: { + context: { + i, + }, + accountId: "FB1C6C79-6C82-45B6-A8AA-207ADA9EE838", + }, + })) + ); + + await simpleInvokableJob.batchInvokeAndWaitForCompletion( + "batch-invoke-and-wait-2", + Array.from({ length: 2 }).map((_, i) => ({ + payload: { + message: `Hello World ${i}`, + }, + })) + ); + }, +}); + +export const exampleJob = client.defineJob({ + id: "example-job", + name: "Example job", + version: "1.0.1", + trigger: invokeTrigger({ + //the expected payload shape + schema: z.object({ + userId: z.string(), + tier: z.enum(["free", "pro"]), + }), + }), + run: async (payload, io, ctx) => { + // payload is typed as { userId: string, tier: "free" | "pro" } + }, +}); + +client.defineJob({ + id: "example-job2", + name: "Example job 2", + version: "1.0.1", + trigger: invokeTrigger(), + run: async (payload, io, ctx) => { + const jobRun = await exampleJob.invoke("⚡", { userId: "123", tier: "free" }); + }, +}); + +createExpressServer(client); From 2ae5178479be6011dd3ed02781f9acf5d8f0d9fa Mon Sep 17 00:00:00 2001 From: Hemachandar <132386067+hmacr@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:36:36 +0530 Subject: [PATCH 23/95] fix: show only the current user's dev env in run-test page (#711) --- .../webapp/app/presenters/TestJobPresenter.server.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/webapp/app/presenters/TestJobPresenter.server.ts b/apps/webapp/app/presenters/TestJobPresenter.server.ts index c87b035c8..cb6665994 100644 --- a/apps/webapp/app/presenters/TestJobPresenter.server.ts +++ b/apps/webapp/app/presenters/TestJobPresenter.server.ts @@ -66,6 +66,18 @@ export class TestJobPresenter { }, where: { name: "latest", + environment: { + OR: [ + { + orgMember: null, + }, + { + orgMember: { + userId, + }, + }, + ], + }, }, }, runs: { From cf8f994604541fa8a1cc4f8efe44e837b81e9508 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Fri, 3 Nov 2023 11:07:12 +0000 Subject: [PATCH 24/95] Feature: `io.random()` (#716) * Add io.random * Add changeset --------- Co-authored-by: Eric Allam --- .changeset/good-dolphins-jam.md | 5 ++ docs/_snippets/random-example.mdx | 21 +++++++ docs/mint.json | 1 + docs/sdk/io/overview.mdx | 4 ++ docs/sdk/io/random.mdx | 26 +++++++++ packages/trigger-sdk/src/io.ts | 86 +++++++++++++++++++++++++++- references/job-catalog/package.json | 1 + references/job-catalog/src/random.ts | 51 +++++++++++++++++ 8 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 .changeset/good-dolphins-jam.md create mode 100644 docs/_snippets/random-example.mdx create mode 100644 docs/sdk/io/random.mdx create mode 100644 references/job-catalog/src/random.ts diff --git a/.changeset/good-dolphins-jam.md b/.changeset/good-dolphins-jam.md new file mode 100644 index 000000000..f4cfbcfd5 --- /dev/null +++ b/.changeset/good-dolphins-jam.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/sdk": patch +--- + +Add `io.random()` which wraps `Math.random()` in a Task with helpful options. diff --git a/docs/_snippets/random-example.mdx b/docs/_snippets/random-example.mdx new file mode 100644 index 000000000..35003795e --- /dev/null +++ b/docs/_snippets/random-example.mdx @@ -0,0 +1,21 @@ +```typescript Random example +client.defineJob({ + id: "random-job", + name: "Random Job", + version: "0.0.1", + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + // generate random numbers + const small = await io.random("random-small"); + const large = await io.random("random-large", { + min: 10, + max: 200, + round: true + }); + + await io.logger.info(`${small} is smaller than ${large}`); + }, +}); +``` diff --git a/docs/mint.json b/docs/mint.json index c07b1b755..5dd98f571 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -335,6 +335,7 @@ "sdk/io/logger", "sdk/io/sendevent", "sdk/io/backgroundfetch", + "sdk/io/random", "sdk/io/try", "sdk/io/registerinterval", "sdk/io/unregisterinterval", diff --git a/docs/sdk/io/overview.mdx b/docs/sdk/io/overview.mdx index ae9058883..5e6e586e3 100644 --- a/docs/sdk/io/overview.mdx +++ b/docs/sdk/io/overview.mdx @@ -40,6 +40,10 @@ If you want to send an event from outside a run (e.g. just from your backend) yo `io.backgroundFetch()` allows you to fetch data from a URL that can take longer that the serverless timeout. The actual `fetch` request is performed on the Trigger.dev platform, and the response is sent back to you. An example use case is fetching data from a slow API, like some AI endpoints. +### [random()](/sdk/io/random) + +`io.random()` is identical to `Math.random()` when called without options but ensures your random numbers are not regenerated on resume or retry. It will return a pseudo-random floating-point number between optional `min` (default: 0, inclusive) and `max` (default: 1, exclusive). Can optionally `round` to the nearest integer. + ### [try()](/sdk/io/try) `io.try()` allows you to run Tasks and catch any errors that are thrown, it's similar to a normal `try/catch` block but works with [io.runTask()](/sdk/io/runtask). diff --git a/docs/sdk/io/random.mdx b/docs/sdk/io/random.mdx new file mode 100644 index 000000000..e6abf6c13 --- /dev/null +++ b/docs/sdk/io/random.mdx @@ -0,0 +1,26 @@ +--- +title: "io.random()" +sidebarTitle: "random()" +description: "`io.random()` is identical to `Math.random()` when called without options but ensures your random numbers are not regenerated on resume or retry. It will return a pseudo-random floating-point number between optional `min` (default: 0, inclusive) and `max` (default: 1, exclusive). Can optionally `round` to the nearest integer." +--- + +## Parameters + + + + Sets the lower bound (inclusive). Can't be higher than `max`. + + + Sets the upper bound (exclusive). Can't be lower than `min`. + + + Controls rounding to the nearest integer. Any `max` integer will become inclusive when enabled. Rounding with floating-point bounds may cause unexpected skew and boundary inclusivity. + + +## Returns + +A `Promise` that resolves with a pseudo-random number. Always resolves to an integer when rounding is enabled. + + + + diff --git a/packages/trigger-sdk/src/io.ts b/packages/trigger-sdk/src/io.ts index 283c4e5f9..184fcad55 100644 --- a/packages/trigger-sdk/src/io.ts +++ b/packages/trigger-sdk/src/io.ts @@ -232,6 +232,90 @@ export class IO { }); } + /** `io.random()` is identical to `Math.random()` when called without options but ensures your random numbers are not regenerated on resume or retry. It will return a pseudo-random floating-point number between optional `min` (default: 0, inclusive) and `max` (default: 1, exclusive). Can optionally `round` to the nearest integer. + * @param cacheKey Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information. + * @param min Sets the lower bound (inclusive). Can't be higher than `max`. + * @param max Sets the upper bound (exclusive). Can't be lower than `min`. + * @param round Controls rounding to the nearest integer. Any `max` integer will become inclusive when enabled. Rounding with floating-point bounds may cause unexpected skew and boundary inclusivity. + */ + async random( + cacheKey: string | any[], + { + min = 0, + max = 1, + round = false, + }: { + min?: number; + max?: number; + round?: boolean; + } = {} + ) { + return await this.runTask( + cacheKey, + async (task) => { + if (min > max) { + throw new Error( + `Lower bound can't be higher than upper bound - min: ${min}, max: ${max}` + ); + } + + if (min === max) { + await this.logger.warn( + `Lower and upper bounds are identical. The return value is not random and will always be: ${min}` + ); + } + + const withinBounds = (max - min) * Math.random() + min; + + if (!round) { + return withinBounds; + } + + if (!Number.isInteger(min) || !Number.isInteger(max)) { + await this.logger.warn( + "Rounding enabled with floating-point bounds. This may cause unexpected skew and boundary inclusivity." + ); + } + + const rounded = Math.round(withinBounds); + + return rounded; + }, + { + name: "random", + icon: "dice-5-filled", + params: { min, max, round }, + properties: [ + ...(min === 0 + ? [] + : [ + { + label: "min", + text: String(min), + }, + ]), + ...(max === 1 + ? [] + : [ + { + label: "max", + text: String(max), + }, + ]), + ...(round === false + ? [] + : [ + { + label: "round", + text: String(round), + }, + ]), + ], + style: { style: "minimal" }, + } + ); + } + /** `io.wait()` waits for the specified amount of time before continuing the Job. Delays work even if you're on a serverless platform with timeouts, or if your server goes down. They utilize [resumability](https://trigger.dev/docs/documentation/concepts/resumability) to ensure that the Run can be resumed after the delay. * @param cacheKey Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information. * @param seconds The number of seconds to wait. This can be very long, serverless timeouts are not an issue. @@ -975,7 +1059,7 @@ export class IO { */ brb = this.yield.bind(this); - /** `io.try()` allows you to run Tasks and catch any errors that are thrown, it's similar to a normal `try/catch` block but works with [io.runTask()](/sdk/io/runtask). + /** `io.try()` allows you to run Tasks and catch any errors that are thrown, it's similar to a normal `try/catch` block but works with [io.runTask()](https://trigger.dev/docs/sdk/io/runtask). * A regular `try/catch` block on its own won't work as expected with Tasks. Internally `runTask()` throws some special errors to control flow execution. This is necessary to deal with resumability, serverless timeouts, and retrying Tasks. * @param tryCallback The code you wish to run * @param catchCallback Thhis will be called if the Task fails. The callback receives the error diff --git a/references/job-catalog/package.json b/references/job-catalog/package.json index abd0533f2..a2da10af8 100644 --- a/references/job-catalog/package.json +++ b/references/job-catalog/package.json @@ -29,6 +29,7 @@ "misconfigured": "nodemon --watch src/misconfigured.ts -r tsconfig-paths/register -r dotenv/config src/misconfigured.ts", "auto-yield": "nodemon --watch src/auto-yield.ts -r tsconfig-paths/register -r dotenv/config src/auto-yield.ts", "cli-example": "nodemon --watch src/cli-example.ts -r tsconfig-paths/register -r dotenv/config src/cli-example.ts", + "random": "nodemon --watch src/random.ts -r tsconfig-paths/register -r dotenv/config src/random.ts", "invoke": "nodemon --watch src/invoke.ts -r tsconfig-paths/register -r dotenv/config src/invoke.ts", "dev:trigger": "trigger-cli dev --port 8080" }, diff --git a/references/job-catalog/src/random.ts b/references/job-catalog/src/random.ts new file mode 100644 index 000000000..6f637b827 --- /dev/null +++ b/references/job-catalog/src/random.ts @@ -0,0 +1,51 @@ +import { createExpressServer } from "@trigger.dev/express"; +import { TriggerClient, eventTrigger } from "@trigger.dev/sdk"; + +export const client = new TriggerClient({ + id: "job-catalog", + apiKey: process.env["TRIGGER_API_KEY"], + apiUrl: process.env["TRIGGER_API_URL"], + verbose: false, + ioLogLocalEnabled: true, +}); + +client.defineJob({ + id: "random-example", + name: "Random Example", + version: "1.0.0", + enabled: true, + trigger: eventTrigger({ + name: "random.example", + }), + run: async (payload, io, ctx) => { + // just like Math.random() but wrapped in a Task + await io.random("random-native"); + + // set lower and upper bounds - defaults to 0, 1 respectively + await io.random("random-min-max", { min: 10, max: 20 }); + + // set lower bound only (inclusive) + await io.random("random-min", { min: 0.5 }); + + // set upper bound only (exclusive) + await io.random("random-max", { max: 100 }); + + // round to the nearest integer + await io.random("random-round", { min: 100, max: 1000, round: true }); + + // rounding with floating-point bounds results in a warning + // this example will unexpectedly (but correctly!) output 1 or 2, skewing towards 2 + await io.random("random-round-float", { min: 0.9, max: 2.5, round: true }); + + // negative values work just fine + await io.random("random-negative", { min: -100, max: -50 }); + + // identical lower and upper bounds result in a warning + await io.random("random-warn-bounds", { min: 10, max: 10 }); + + // invalid ranges will fail + await io.random("random-error", { min: 10, max: 5 }); + }, +}); + +createExpressServer(client); From 7e57f1f3dddb2ff1d1cc90e040986e929c7ddf35 Mon Sep 17 00:00:00 2001 From: Alexandre Costa Date: Fri, 3 Nov 2023 08:10:26 -0300 Subject: [PATCH 25/95] [TRI-1449] Display warning message when duplicate job IDs are detected (#699) * add warning message when duplicate job IDs are detected * fix warning message for duplicated job IDs in defineJob * Create bright-buckets-bow.md --------- Co-authored-by: Eric Allam --- .changeset/bright-buckets-bow.md | 5 +++++ packages/trigger-sdk/package.json | 1 + packages/trigger-sdk/src/triggerClient.ts | 10 ++++++++++ 3 files changed, 16 insertions(+) create mode 100644 .changeset/bright-buckets-bow.md diff --git a/.changeset/bright-buckets-bow.md b/.changeset/bright-buckets-bow.md new file mode 100644 index 000000000..f9a33470b --- /dev/null +++ b/.changeset/bright-buckets-bow.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/sdk": patch +--- + +[TRI-1449] Display warning message when duplicate job IDs are detected diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index 5397b9011..a39403f43 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -28,6 +28,7 @@ "@trigger.dev/core": "workspace:^2.2.4", "@trigger.dev/core-backend": "workspace:^2.2.4", "chalk": "^5.2.0", + "colorette": "^2.0.20", "cronstrue": "^2.21.0", "debug": "^4.3.4", "evt": "^2.4.13", diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index 3477549ec..e015cbcde 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -33,6 +33,7 @@ import { SourceMetadataV2, StatusUpdate, } from "@trigger.dev/core"; +import { yellow } from "colorette"; import { ApiClient } from "./apiClient"; import { AutoYieldExecutionError, @@ -480,6 +481,15 @@ export class TriggerClient { TIntegrations extends Record = {}, TOutput extends any = any, >(options: JobOptions) { + + const existingRegisteredJob = this.#registeredJobs[options.id]; + + if (existingRegisteredJob) { + console.warn( + yellow(`[@trigger.dev/sdk] Warning: The Job "${existingRegisteredJob.id}" you're attempting to define has already been defined. Please assign a different ID to the job.`) + ); + } + return new Job(this, options); } From 14a2f4cc9472f7df41c14582788590f211588fd2 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 3 Nov 2023 11:26:16 +0000 Subject: [PATCH 26/95] Fix lock file --- pnpm-lock.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45e523ae6..7bd1f612a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1089,6 +1089,7 @@ importers: '@types/uuid': ^9.0.0 '@types/ws': ^8.5.3 chalk: ^5.2.0 + colorette: ^2.0.20 cronstrue: ^2.21.0 debug: ^4.3.4 encoding: ^0.1.13 @@ -1110,6 +1111,7 @@ importers: '@trigger.dev/core': link:../core '@trigger.dev/core-backend': link:../core-backend chalk: 5.2.0 + colorette: 2.0.20 cronstrue: 2.21.0 debug: 4.3.4 evt: 2.4.13 @@ -16847,9 +16849,8 @@ packages: color-string: 1.9.1 optional: true - /colorette/2.0.19: - resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} - dev: true + /colorette/2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} /combined-stream/1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} @@ -20905,7 +20906,7 @@ packages: resolution: {integrity: sha512-HsLoS07HiQ5oqvObOI+Qb2tyZH4Gj5nYGfF9qQcZNrPw+uEFhdXtgJr01aO2pWadGHucajYDLxxbtQkm97ON2A==} hasBin: true dependencies: - colorette: 2.0.19 + colorette: 2.0.20 defu: 6.1.2 https-proxy-agent: 5.0.1 mri: 1.2.0 @@ -32194,7 +32195,7 @@ packages: peerDependencies: webpack: ^4.0.0 || ^5.0.0 dependencies: - colorette: 2.0.19 + colorette: 2.0.20 memfs: 3.5.3 mime-types: 2.1.35 range-parser: 1.2.1 From 330ddb3311c44e44d2fb9105dded3908308687aa Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Fri, 3 Nov 2023 14:36:33 +0000 Subject: [PATCH 27/95] =?UTF-8?q?NoTask=20message=20only=20displays=20if?= =?UTF-8?q?=20the=20run=20has=20status=20=E2=80=9CSUCCESS=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webapp/app/components/run/RunOverview.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/webapp/app/components/run/RunOverview.tsx b/apps/webapp/app/components/run/RunOverview.tsx index f33aa1668..130ecfd0d 100644 --- a/apps/webapp/app/components/run/RunOverview.tsx +++ b/apps/webapp/app/components/run/RunOverview.tsx @@ -159,16 +159,17 @@ export function RunOverview({ run, trigger, showRerun, paths }: RunOverviewProps

- {(run.tasks.length === 0 || run.tasks.every((t) => t.noop)) && ( - - This Run completed but it did not use any Tasks – this can cause unpredictable - results. Read the docs to view the solution. - - )} + {run.status === "SUCCESS" && + (run.tasks.length === 0 || run.tasks.every((t) => t.noop)) && ( + + This Run completed but it did not use any Tasks – this can cause unpredictable + results. Read the docs to view the solution. + + )} Trigger Date: Fri, 3 Nov 2023 22:41:23 +0000 Subject: [PATCH 28/95] Fix a ReferenceError when using OpenAI.backgroundCreateChatCompletion --- .changeset/four-kangaroos-care.md | 5 +++++ integrations/openai/src/index.ts | 26 ++++++++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 .changeset/four-kangaroos-care.md diff --git a/.changeset/four-kangaroos-care.md b/.changeset/four-kangaroos-care.md new file mode 100644 index 000000000..ddd43914b --- /dev/null +++ b/.changeset/four-kangaroos-care.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/openai": patch +--- + +Fix a ReferenceError when using backgroundCreateChatCompletion diff --git a/integrations/openai/src/index.ts b/integrations/openai/src/index.ts index ce20ccacf..17116cca2 100644 --- a/integrations/openai/src/index.ts +++ b/integrations/openai/src/index.ts @@ -10,15 +10,15 @@ import { type TriggerIntegration, } from "@trigger.dev/sdk"; import OpenAIApi from "openai"; -import { Models } from "./models"; -import { OpenAIIntegrationOptions } from "./types"; -import { Completions } from "./completions"; import { Chat } from "./chat"; +import { Completions } from "./completions"; import { Edits } from "./edits"; -import { Images } from "./images"; import { Embeddings } from "./embeddings"; import { Files } from "./files"; import { FineTunes } from "./fineTunes"; +import { Images } from "./images"; +import { Models } from "./models"; +import { OpenAIIntegrationOptions } from "./types"; export type OpenAIRunTask = InstanceType["runTask"]; @@ -153,19 +153,29 @@ export class OpenAI implements TriggerIntegration { return new FineTunes(this.runTask.bind(this)); } - // this provides backwards compatibility for the old API retrieveModel = this.models.retrieve; listModels = this.models.list; deleteModel = this.models.delete; deleteFineTune = this.models.delete; createCompletion = this.completions.create; - backgroundCreateCompletion = this.completions.backgroundCreate; createChatCompletion = this.chat.completions.create; - backgroundCreateChatCompletion = this.chat.completions.backgroundCreate; /** - * @deprecated The Edits API is deprecated; please use Chat Completions instead. + * @deprecated Please use openai.completions.backgroundCreate instead */ + async backgroundCreateCompletion(...args: Parameters) { + return this.completions.backgroundCreate(...args); + } + + /** + * @deprecated Please use openai.chat.completions.backgroundCreate instead + */ + async backgroundCreateChatCompletion( + ...args: Parameters + ) { + return this.chat.completions.backgroundCreate(...args); + } + createEdit = this.edits.create; generateImage = this.images.generate; createImage = this.images.generate; From 10e935b864f03c3642e942ab46afad88d95b4b2f Mon Sep 17 00:00:00 2001 From: Abdou Belghalem Date: Mon, 6 Nov 2023 10:06:54 +0100 Subject: [PATCH 29/95] fix(TRI-1444): Allow only logins from WHITELISTED_EMAILS regex (fixes #685) (#721) --- .env.example | 2 ++ apps/webapp/app/env.server.ts | 2 ++ apps/webapp/app/models/user.server.ts | 5 +++++ apps/webapp/app/routes/magic.tsx | 2 +- apps/webapp/app/utils/regex.ts | 8 ++++++++ 5 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 apps/webapp/app/utils/regex.ts diff --git a/.env.example b/.env.example index 48170e8f3..35b863518 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,8 @@ APP_ORIGIN=http://localhost:3030 NODE_ENV=development # OPTIONAL VARIABLES +# This is used for validating emails that are allowed to log in. Every email that do not match this regex will be rejected. +# WHITELISTED_EMAILS="authorized@yahoo\.com|authorized@gmail\.com" # This is used for logging in via GitHub. You can leave these commented out if you don't want to use GitHub for authentication. # AUTH_GITHUB_CLIENT_ID= # AUTH_GITHUB_CLIENT_SECRET= diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 4e229834c..4eab34ad1 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { SecretStoreOptionsSchema } from "./services/secrets/secretStore.server"; +import { isValidRegex } from "./utils/regex"; const EnvironmentSchema = z.object({ NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]), @@ -10,6 +11,7 @@ const EnvironmentSchema = z.object({ SESSION_SECRET: z.string(), MAGIC_LINK_SECRET: z.string(), ENCRYPTION_KEY: z.string(), + WHITELISTED_EMAILS: z.string().refine(isValidRegex, "WHITELISTED_EMAILS must be a valid regex.").optional(), REMIX_APP_PORT: z.string().optional(), LOGIN_ORIGIN: z.string().default("http://localhost:3030"), APP_ORIGIN: z.string().default("http://localhost:3030"), diff --git a/apps/webapp/app/models/user.server.ts b/apps/webapp/app/models/user.server.ts index 31e8621ed..ae81cc482 100644 --- a/apps/webapp/app/models/user.server.ts +++ b/apps/webapp/app/models/user.server.ts @@ -1,6 +1,7 @@ import type { Prisma, User } from "@trigger.dev/database"; import type { GitHubProfile } from "remix-auth-github"; import { prisma } from "~/db.server"; +import { env } from "~/env.server"; export type { User } from "@trigger.dev/database"; type FindOrCreateMagicLink = { @@ -36,6 +37,10 @@ export async function findOrCreateUser(input: FindOrCreateUser): Promise { + if (env.WHITELISTED_EMAILS && !new RegExp(env.WHITELISTED_EMAILS).test(input.email)) { + throw new Error("This email is unauthorized"); + } + const existingUser = await prisma.user.findFirst({ where: { email: input.email, diff --git a/apps/webapp/app/routes/magic.tsx b/apps/webapp/app/routes/magic.tsx index d41575357..725640c6f 100644 --- a/apps/webapp/app/routes/magic.tsx +++ b/apps/webapp/app/routes/magic.tsx @@ -7,6 +7,6 @@ export async function loader({ request }: LoaderFunctionArgs) { await authenticator.authenticate("email-link", request, { successRedirect: redirectTo ?? "/", - failureRedirect: "/login", + failureRedirect: "/login/magic", }); } diff --git a/apps/webapp/app/utils/regex.ts b/apps/webapp/app/utils/regex.ts new file mode 100644 index 000000000..f4ba98e7e --- /dev/null +++ b/apps/webapp/app/utils/regex.ts @@ -0,0 +1,8 @@ +export function isValidRegex(regex: string) { + try { + new RegExp(regex); + return true; + } catch (err) { + return false; + } +} From 9c6838e5c88f4022a821e0f2733592670ec0337c Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 6 Nov 2023 15:32:40 +0000 Subject: [PATCH 30/95] HTTP endpoints, HTTP triggers and new sidebar (#720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Setup basic storybook page * storybook preview pages don’t have padding * Added a storybook story for the side menu * WIP on a new side menu component * New variant for the side menu button style * Side menu items can now recieve a badge ‘count’ * Storybook side menu update * Added a new font size * you can now set the spacing between an icon and the text * Moving the error and count badge to the right side of the button * Added a menu sub item button style * added sub items to the menu and all the rest of the items * Proper background class used * Added a side menu header and main org header * Removed the no-mobile blocker overlay so you can use the site on a phone * Better section name * Created a cusom popover trigger that can take anything as children. Also the popover titles can now take a variant * Added a popover for the user avatar menu item * Removed the tool tip * Arrow Popover can no also go full width * neater conditiona; * Removed unnessary div. And error icon can display an error message * menu item buttons can take a variant * Added placeholder items for the org dropdown * add a max height to the org menu popover and handle scrollbars * Added a border if the menu scrolls when you have lots of project items * warning badges display tooltips if you give them some error text * Removed the sticky header so the scrollbar looks better * Fixed a layout issue from removing the sticky header * Better spacing between lists * Popover can take any react component icon and an icon classname * Side menu headers now have a dropdown by clicking the ellipsis icon * Side menu items can take an icon colour * Updated styling for the dropdown Popover menus * Installed Tabler icons * Updated some icons to use Tabler * Started defining the HttpTrigger class * RequestFilterSchema is part of core * Use the RequestFilterSchema from core * EventSpecificationExample now derived from schema * Need to define the class like EventTrigger * Catalog reference that uses the httpTrigger * The logic for the httpTrigger is in the separate file, just call it from the client using defineHttpTrigger * Basic HttpTrigger * Import fixes * Transforming httpTrigger to the format we want * Initial httpTrigger types working * Added HTTP generic type * Added some notes * Rejigged some things to support verifying payloads * Use new security function for verifying * Added truncating text support if side menu names get long * Renamed some stuff * Removed test copy * HttpEndpoin that isn’t a trigger * Renamed hostname to source * Created HttpEndpoint as a top-level concept, it produces triggers using onRequest * Started working on registration of the httpEndpoints * Removed the new TriggerMetadata for now, http will use static * Added PayloadType to EventRecord. Default is JSON. * The payload from an HTTPEndpoint is now a Request * Initial TriggerHttpEndpoint migrations * Initial server side work * Migration to add shortcode to RuntimeEnvironment * Added TriggerHttpEndpointEnvironment table * Made the httptrigger catalog example runnable * Progress creating/updating httpEndpoints * HTTPEndpointEnvironment updating now works * Added a note about EventRecord HttpEndpoint relationship * When creating environments, create a shortcode * Fix for secret not being retrieved correctly * Request filtering * Add DOM types to Core * Early work dealing with a webhook arriving * Added some todo notes to discuss tomorrow * Added relations to the endpoint so we can call the user’s server * Basic handling of immediate responses * The immediate responses are now going back to the webhook provider * Handling is now generating events and responding where relevant. Not yet matching filters and doing verification * Added source to TriggerHttpEndpointEnvironment * The event filter is now optional * The source is passed through from the httpEndpointEnv * Added optional payloadType when ingesting an event * Added tabler * Updated @remix-run/testing usage for remix 2 * Fixed org menu header width and added org and project types * Added custom icons for Runs and Endpoints * Updated the menu icons * storybook side menu shows static project and org data * Updated side menu * Plumbed in paths * Updated eventFilter to deal with undefined/null payloads * Started work on filtering, but it’s not working * Strict null * async filtering it working * Added environment shortcodes to the seedCloud script * Don’t pass the secret back, added a verifyPayload function that gets called before runs start * Fix for skipping triggering runs * The verify callback is now passed through to the trigger so it can be called * If verifying fails, have a reason which we can show to the user * Don’t verify if it’s a test run * Fixed the log in the example code * Added loader and action to storybook preview * Fix for svg attribute name issues in EndpointIcon * Started work on the side menu * This logic has moved to a separate file, in a different branch * Don’t get the environments when getting orgs * Lots of fixes for the new SideMenu * Refactored some org/project things to fit the new structure better * Some progress restructuring the app for the new sidebar * More changes to data loading * Deleted some old pages. The redirecting is working if you’re on the root but logged in * Basic styling for the account page. We need a back button * Fixes for the integrations pages * Breadcrumb implemented * Add a border to the bottom of the breadcrumb * Feedback is working, not in Storybook * Removed expandSidebar from the Handle * Org level breadcrumbs * Getting the project in the org route now works properly * orgslug route before using the presenter * More UI improvements * Removed commented out line * Use presenters for org and root * Removed forceActive * Split the project selector and user menu out * The side menu items have correct active states * Fixed the scrolling * Put the org name in * Improved the admin features * Created the HTTP Endpoints list page * Work on the HTTP endpoint page * WIP on the HTTP Endpoint page * Fix for response filter not showing * Change page URLs to “http-endpoints” (from httpendpoints) * Slugify the HTTP endpoint id * Added todos for EventRecord associations * Removed the Job/Trigger tab for now * First draft of the help panels * Rejigged the HTTP endpoint page so it splits out the deployed and not deployed environments * Removed irrelevant columns * Added a link to the deployment guide * Removed the background image from the frameworks pages Also… - reordered the frameworks so supported ones come first in the list - added logos to 3 framework pages that were missing them - removed lots of un-used imports * Fixed an issue where the breadcrumb could be hidden on small screens so broken the pages The breadcrumb is always visible now * Renamed ‘Onboarding’ to ‘Framework setup’ * Added a new http-endpoint icon to the side menu * Side menu now has a nice looking scrollbar * Scrollbar on the usage and billing page updated to the nice style * Icon and colour updates to the side bar menu items * Small copy improvement * Fixed bug with the height of the org menu dropdown * Page breadcrumbs now don’t include duplicate path links * renamed uiBorder to ui-border * Remove the padding from the small menu item if it’s used as a button * Made the paragraph text a bit smaller * EventRecord has relationships with TriggerHttpEndpoint/TriggerHttpEndpointEnvironment * Improved the presenter used by the side menu. Also added the HTTP Endpoint badge count to the sidebar * The dropdown org menu has the same badge as the side menu * Improved the Projects page * Fixed button style miss-match in the jobs table dropdown * Made the popover dialogs support Headers and improved the regenerate API key popover * Made the active status of a job more subtle * Allow the Job to have a link on its Trigger * Simplified loader because we ditched the Jobs menu. Also showing no runs help panel now * Fixed type errors * Pages that are org level can scroll if the content gets too long * Fixed a redirect bug so you now redirect to the org page * Updated the shortcode migration, to add additional letters to the end * Removed the nav bar from the welcome onboarding page * Only show the countBadge if the number is > 0 * Jobs icon now has a default colour the same as the nav menu * Capital I for integration * Clone the request before calling verify * Separate the checks out so they can be easily logged * Make the failure reason optional * Made the verification error look better, if it’s provided * Working WhatsApp example * Can set the immediateResponseFilter to null in an update * Deal with a null immediateResponseFilter * Cal.com webhook working * Header height reduced slightly * Fixed button padding * Removed un-used imports * http endpoint table displays a message if you have no entries in the table * Removed some duplicate text and swapped primary buttons for secondary style * All ‘HTTP Endpoints’ now ‘HTTP endpoints’ * You have to use a RequestFilter when using HTTP Trigger * Synchronous eventDispatcher filtering again. Better content-type check * Stripe example * Side menu “Usage & Billing” is just “Usage” if self-hosting * Fix for filtering not working * Removed filter log * Fix for jobs being optional and null * Removed old nav bar from the invite page * Deleted old nav bar component * import cleanup * NoTask message only displays if the run has status “SUCCESS” * Added a global page loading indicator * Added documentation buttons to the most important pages * Added GitHub * Use “verifyRequestSignature” helpr * Removed todo * Fixed the layout of the Rerun Job popover * Jobs table has margin bottom so it sits off the bottom of the page * Small improvement to the search bar layout on the integration page * HTTP endpoint and Trigger concept docs * Reference docs and JSDocs for HTTP endpoint * SDK reference docs for HttpEndpoint * Added an HttpTrigger SDK reference, which mainly just point you elsewhere * Improved the copy in the help panels * Removed unused code * Improved HTTP endpoint comment about setting the secret * Delete unused asyncFilter * Fixed the delete modal not having a title * Always try get the arrayBuffer for an HTTP request, catch and use undefined as the default * Add extra method to invokeTrigger * Use a transaction for the secret store * verifyRequestSignature docs page and JSDocs * Added verifyRequestSignature docs page to nav * Warn if they use the same HttpEndpoint id twice * Added another job using the same HttpEndpoint * Renamed the api route to http-endpoints from httpendpoints * Debug bar for VSCode is now in the commandCenter --------- Co-authored-by: James Ritchie --- .vscode/settings.json | 3 +- apps/webapp/.storybook/preview.tsx | 3 + apps/webapp/app/assets/icons/EndpointIcon.tsx | 36 ++ .../app/assets/icons/IntegrationIcon.tsx | 5 + apps/webapp/app/assets/icons/RunsIcon.tsx | 18 + .../app/components/ImpersonationBanner.tsx | 22 +- .../environments/RegenerateApiKeyModal.tsx | 100 ++-- .../frameworks/FrameworkComingSoon.tsx | 73 ++- .../frameworks/FrameworkSelector.tsx | 87 ++-- .../helpContent/HelpContentText.tsx | 141 +++++- .../components/jobs/DeleteJobModalContent.tsx | 2 +- apps/webapp/app/components/jobs/JobsTable.tsx | 21 +- .../app/components/layout/AppLayout.tsx | 22 +- .../app/components/navigation/Breadcrumb.tsx | 32 +- .../app/components/navigation/JobsMenu.tsx | 65 --- .../app/components/navigation/NavBar.tsx | 47 -- .../navigation/PageNavigationIndicator.tsx | 9 + .../components/navigation/ProjectSideMenu.tsx | 206 --------- .../components/navigation/ProjectsMenu.tsx | 79 ---- .../app/components/navigation/SideMenu.tsx | 426 ++++++++++++++++++ .../app/components/primitives/Buttons.tsx | 64 ++- .../app/components/primitives/Dialog.tsx | 6 +- .../app/components/primitives/FormTitle.tsx | 2 +- .../webapp/app/components/primitives/Help.tsx | 7 +- .../app/components/primitives/NamedIcon.tsx | 19 +- .../app/components/primitives/PageHeader.tsx | 34 +- .../app/components/primitives/Paragraph.tsx | 5 + .../app/components/primitives/Popover.tsx | 78 +++- .../app/components/primitives/Sheet.tsx | 8 +- .../app/components/primitives/Spinner.tsx | 6 +- .../app/components/primitives/Table.tsx | 8 +- .../webapp/app/components/primitives/Tabs.tsx | 2 +- .../webapp/app/components/run/RunOverview.tsx | 55 ++- apps/webapp/app/components/runs/RunsTable.tsx | 2 +- apps/webapp/app/hooks/useFilterJobs.ts | 2 +- .../webapp/app/hooks/useIntegrationClient.tsx | 4 +- ...ojectChildPage.ts => useIsOrgChildPage.ts} | 4 +- apps/webapp/app/hooks/useJob.tsx | 5 +- apps/webapp/app/hooks/useJobs.tsx | 28 -- apps/webapp/app/hooks/useOrganizations.ts | 8 +- apps/webapp/app/models/organization.server.ts | 57 +-- .../EnvironmentsPresenter.server.ts | 18 +- .../HttpEndpointPresenter.server.ts | 154 +++++++ .../HttpEndpointsPresenter.server.ts | 64 +++ ...rationClientConnectionsPresenter.server.ts | 3 - .../IntegrationClientPresenter.server.ts | 6 +- ...IntegrationClientScopesPresenter.server.ts | 2 - .../IntegrationsPresenter.server.ts | 6 +- .../app/presenters/JobListPresenter.server.ts | 27 +- .../app/presenters/JobPresenter.server.ts | 234 ++++++++++ .../NewOrganizationPresenter.server.ts | 20 + .../OrganizationsPresenter.server.ts | 117 +++++ .../app/presenters/ProjectPresenter.server.ts | 10 +- .../SelectBestProjectPresenter.server.ts | 49 ++ apps/webapp/app/routes/_app._index/route.tsx | 19 + .../OrganizationGrid.tsx | 73 --- .../routes/_app._orgaccount._index/route.tsx | 62 --- .../app/routes/_app._orgaccount/route.tsx | 49 -- .../OrgAdminHeader.tsx | 55 --- .../route.tsx | 32 +- .../route.tsx | 29 +- .../route.tsx | 236 +++++----- .../route.tsx | 6 +- .../route.tsx | 6 +- .../route.tsx | 6 +- .../route.tsx | 17 +- .../route.tsx | 23 +- .../route.tsx | 25 +- .../FirstEndpointSheet.tsx | 9 +- .../route.tsx | 38 +- .../route.tsx | 236 ++++++++++ .../route.tsx | 136 ++++++ .../route.tsx | 11 + .../route.tsx | 25 +- .../route.tsx | 4 +- .../route.tsx | 23 +- .../route.tsx | 2 +- .../route.tsx | 73 ++- .../route.tsx | 5 - .../route.tsx | 142 +++--- .../route.tsx | 160 ++++--- .../route.tsx | 2 +- .../route.tsx | 185 ++++---- .../route.tsx | 337 +++++++------- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 144 +++--- .../route.tsx | 158 ++++--- .../route.tsx | 4 +- .../route.tsx | 11 +- .../route.tsx | 3 +- .../route.tsx | 4 +- .../route.tsx | 4 +- .../route.tsx | 2 +- .../route.tsx | 32 +- .../route.tsx | 16 +- .../_app.orgs.$organizationSlug/route.tsx | 106 +++-- .../webapp/app/routes/_app.orgs.new/route.tsx | 43 +- apps/webapp/app/routes/_app/route.tsx | 58 +-- .../route.tsx | 110 +++-- apps/webapp/app/routes/admin._index.tsx | 8 +- .../routes/api.v1.endpointindex.$indexId.ts | 7 +- ...$httpEndpointId.env.$envType.$shortcode.ts | 56 +++ .../app/routes/confirm-basic-details.tsx | 10 +- apps/webapp/app/routes/invites.tsx | 15 +- .../services/currentOrganization.server.ts | 44 -- .../app/services/currentProject.server.ts | 33 ++ .../webapp/app/services/endpointApi.server.ts | 31 ++ .../endpoints/performEndpointIndexService.ts | 21 +- .../app/services/environmentSort.server.ts | 20 + .../services/events/deliverEvent.server.ts | 44 +- .../services/events/ingestSendEvent.server.ts | 15 +- .../httpendpoint/HandleHttpEndpointService.ts | 241 ++++++++++ .../app/services/impersonation.server.ts | 2 +- .../app/services/jobs/registerJob.server.ts | 4 +- .../sources/handleHttpSource.server.ts | 15 +- apps/webapp/app/services/telemetry.server.ts | 5 +- .../triggers/registerHttpEndpoint.server.ts | 114 +++++ .../app/utils/createHttpSourceRequest.ts | 17 + apps/webapp/app/utils/handle.ts | 1 - apps/webapp/app/utils/pathBuilder.ts | 45 +- apps/webapp/package.json | 2 + apps/webapp/prisma/seedCloud.ts | 2 + apps/webapp/remix.config.js | 1 + apps/webapp/tailwind.config.js | 12 +- docs/_snippets/request-filter.mdx | 36 ++ .../documentation/concepts/http-endpoints.mdx | 86 ++++ docs/documentation/concepts/triggers/http.mdx | 107 +++++ .../concepts/triggers/introduction.mdx | 14 +- .../concepts/triggers/webhooks.mdx | 5 +- docs/images/http-endpoint.png | Bin 0 -> 283287 bytes docs/images/http-endpoints-list.png | Bin 0 -> 412925 bytes docs/mint.json | 44 +- docs/sdk/http-endpoint.mdx | 70 +++ docs/sdk/http-trigger.mdx | 54 +++ .../instancemethods/define-http-endpoint.mdx | 128 ++++++ docs/sdk/triggerclient/overview.mdx | 4 + docs/sdk/verify-request-signature.mdx | 67 +++ packages/core/src/eventFilterMatches.ts | 8 + packages/core/src/index.ts | 1 + packages/core/src/requestFilterMatches.ts | 47 ++ packages/core/src/schemas/api.ts | 28 ++ packages/core/src/schemas/eventFilter.ts | 22 +- packages/core/src/schemas/index.ts | 2 + packages/core/src/schemas/request.ts | 8 + packages/core/src/schemas/requestFilter.ts | 60 +++ packages/core/src/schemas/triggers.ts | 11 + packages/core/test/eventFilterMatches.test.ts | 14 + packages/core/tsconfig.json | 3 +- .../migration.sql | 5 + .../migration.sql | 29 ++ .../migration.sql | 19 + .../migration.sql | 46 ++ .../migration.sql | 34 ++ .../migration.sql | 2 + .../migration.sql | 15 + .../migration.sql | 2 + .../migration.sql | 8 + .../migration.sql | 8 + .../migration.sql | 9 + .../migration.sql | 9 + .../migration.sql | 3 + packages/database/prisma/schema.prisma | 131 ++++-- packages/trigger-sdk/src/httpEndpoint.ts | 215 +++++++++ packages/trigger-sdk/src/index.ts | 1 + packages/trigger-sdk/src/security.ts | 48 ++ packages/trigger-sdk/src/triggerClient.ts | 176 +++++++- packages/trigger-sdk/src/triggers/dynamic.ts | 4 + .../trigger-sdk/src/triggers/eventTrigger.ts | 4 + .../src/triggers/externalSource.ts | 4 + .../trigger-sdk/src/triggers/invokeTrigger.ts | 12 +- .../trigger-sdk/src/triggers/notifications.ts | 12 + .../trigger-sdk/src/triggers/scheduled.ts | 12 + packages/trigger-sdk/src/types.ts | 26 +- pnpm-lock.yaml | 53 ++- references/job-catalog/package.json | 2 + references/job-catalog/src/httpTrigger.ts | 171 +++++++ 177 files changed, 5623 insertions(+), 2257 deletions(-) create mode 100644 apps/webapp/app/assets/icons/EndpointIcon.tsx create mode 100644 apps/webapp/app/assets/icons/IntegrationIcon.tsx create mode 100644 apps/webapp/app/assets/icons/RunsIcon.tsx delete mode 100644 apps/webapp/app/components/navigation/JobsMenu.tsx delete mode 100644 apps/webapp/app/components/navigation/NavBar.tsx create mode 100644 apps/webapp/app/components/navigation/PageNavigationIndicator.tsx delete mode 100644 apps/webapp/app/components/navigation/ProjectSideMenu.tsx delete mode 100644 apps/webapp/app/components/navigation/ProjectsMenu.tsx create mode 100644 apps/webapp/app/components/navigation/SideMenu.tsx rename apps/webapp/app/hooks/{useIsProjectChildPage.ts => useIsOrgChildPage.ts} (69%) delete mode 100644 apps/webapp/app/hooks/useJobs.tsx create mode 100644 apps/webapp/app/presenters/HttpEndpointPresenter.server.ts create mode 100644 apps/webapp/app/presenters/HttpEndpointsPresenter.server.ts create mode 100644 apps/webapp/app/presenters/JobPresenter.server.ts create mode 100644 apps/webapp/app/presenters/NewOrganizationPresenter.server.ts create mode 100644 apps/webapp/app/presenters/OrganizationsPresenter.server.ts create mode 100644 apps/webapp/app/presenters/SelectBestProjectPresenter.server.ts create mode 100644 apps/webapp/app/routes/_app._index/route.tsx delete mode 100644 apps/webapp/app/routes/_app._orgaccount._index/OrganizationGrid.tsx delete mode 100644 apps/webapp/app/routes/_app._orgaccount._index/route.tsx delete mode 100644 apps/webapp/app/routes/_app._orgaccount/route.tsx delete mode 100644 apps/webapp/app/routes/_app.orgs.$organizationSlug._index/OrgAdminHeader.tsx rename apps/webapp/app/routes/{_app.orgs.$organizationSlug.projects.$projectParam.integrations => _app.orgs.$organizationSlug.integrations}/route.tsx (74%) rename apps/webapp/app/routes/{_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam._index => _app.orgs.$organizationSlug.integrations_.$clientParam._index}/route.tsx (94%) rename apps/webapp/app/routes/{_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.connections => _app.orgs.$organizationSlug.integrations_.$clientParam.connections}/route.tsx (93%) rename apps/webapp/app/routes/{_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.scopes => _app.orgs.$organizationSlug.integrations_.$clientParam.scopes}/route.tsx (87%) rename apps/webapp/app/routes/{_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam => _app.orgs.$organizationSlug.integrations_.$clientParam}/route.tsx (86%) create mode 100644 apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints.$httpEndpointParam/route.tsx create mode 100644 apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints._index/route.tsx create mode 100644 apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints/route.tsx rename apps/webapp/app/routes/{_app._orgaccount.account => account}/route.tsx (59%) create mode 100644 apps/webapp/app/routes/api.v1.http-endpoints.$httpEndpointId.env.$envType.$shortcode.ts delete mode 100644 apps/webapp/app/services/currentOrganization.server.ts create mode 100644 apps/webapp/app/services/currentProject.server.ts create mode 100644 apps/webapp/app/services/environmentSort.server.ts create mode 100644 apps/webapp/app/services/httpendpoint/HandleHttpEndpointService.ts create mode 100644 apps/webapp/app/services/triggers/registerHttpEndpoint.server.ts create mode 100644 apps/webapp/app/utils/createHttpSourceRequest.ts create mode 100644 docs/_snippets/request-filter.mdx create mode 100644 docs/documentation/concepts/http-endpoints.mdx create mode 100644 docs/documentation/concepts/triggers/http.mdx create mode 100644 docs/images/http-endpoint.png create mode 100644 docs/images/http-endpoints-list.png create mode 100644 docs/sdk/http-endpoint.mdx create mode 100644 docs/sdk/http-trigger.mdx create mode 100644 docs/sdk/triggerclient/instancemethods/define-http-endpoint.mdx create mode 100644 docs/sdk/verify-request-signature.mdx create mode 100644 packages/core/src/requestFilterMatches.ts create mode 100644 packages/core/src/schemas/request.ts create mode 100644 packages/core/src/schemas/requestFilter.ts create mode 100644 packages/database/prisma/migrations/20231023144342_added_event_record_payload_type_default_is_json/migration.sql create mode 100644 packages/database/prisma/migrations/20231023173456_added_trigger_http_endpoint_table/migration.sql create mode 100644 packages/database/prisma/migrations/20231024122540_http_endpoint_scoped_to_project_not_environment/migration.sql create mode 100644 packages/database/prisma/migrations/20231024122736_runtime_environment_shortcodes/migration.sql create mode 100644 packages/database/prisma/migrations/20231025091939_trigger_http_endpoint_environment_created/migration.sql create mode 100644 packages/database/prisma/migrations/20231026100653_added_skip_triggering_runs_to_trigger_http_endpoint_environment/migration.sql create mode 100644 packages/database/prisma/migrations/20231026103218_add_endpoint_relation_to_http_endpoint_environment/migration.sql create mode 100644 packages/database/prisma/migrations/20231026103614_trigger_http_endpoint_environment_removed_the_environment_constraint/migration.sql create mode 100644 packages/database/prisma/migrations/20231026104408_trigger_http_endpoint_environment_added_the_environment_constraint_back_in/migration.sql create mode 100644 packages/database/prisma/migrations/20231026165235_trigger_http_endpoint_environment_added_source/migration.sql create mode 100644 packages/database/prisma/migrations/20231101202056_trigger_http_endpoint_environment_added_created_at_updated_at/migration.sql create mode 100644 packages/database/prisma/migrations/20231102143530_added_relationships_between_event_record_and_trigger_http_endpoint_trigger_http_endpoint_environment/migration.sql create mode 100644 packages/database/prisma/migrations/20231102171207_job_version_added_trigger_link_and_trigger_help/migration.sql create mode 100644 packages/trigger-sdk/src/httpEndpoint.ts create mode 100644 packages/trigger-sdk/src/security.ts create mode 100644 references/job-catalog/src/httpTrigger.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 5f5239032..f7666be0e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "deno.enablePaths": ["references/deno-reference"] + "deno.enablePaths": ["references/deno-reference"], + "debug.toolBarLocation": "commandCenter" } diff --git a/apps/webapp/.storybook/preview.tsx b/apps/webapp/.storybook/preview.tsx index a995aea73..14932b462 100644 --- a/apps/webapp/.storybook/preview.tsx +++ b/apps/webapp/.storybook/preview.tsx @@ -23,12 +23,15 @@ const preview: Preview = { }, ], }, + layout: "fullscreen", }, decorators: [ (Story) => { const RemixStub = createRemixStub([ { path: "/*", + action: () => ({ redirect: "/" }), + loader: () => ({ redirect: "/" }), Component: Story, }, ]); diff --git a/apps/webapp/app/assets/icons/EndpointIcon.tsx b/apps/webapp/app/assets/icons/EndpointIcon.tsx new file mode 100644 index 000000000..d491e25a8 --- /dev/null +++ b/apps/webapp/app/assets/icons/EndpointIcon.tsx @@ -0,0 +1,36 @@ +export function EndpointIcon({ className }: { className?: string }) { + return ( + + + + + + + ); +} diff --git a/apps/webapp/app/assets/icons/IntegrationIcon.tsx b/apps/webapp/app/assets/icons/IntegrationIcon.tsx new file mode 100644 index 000000000..fd1839228 --- /dev/null +++ b/apps/webapp/app/assets/icons/IntegrationIcon.tsx @@ -0,0 +1,5 @@ +import { LogoIcon } from "~/components/LogoIcon"; + +export function IntegrationIcon() { + return ; +} diff --git a/apps/webapp/app/assets/icons/RunsIcon.tsx b/apps/webapp/app/assets/icons/RunsIcon.tsx new file mode 100644 index 000000000..8688058a6 --- /dev/null +++ b/apps/webapp/app/assets/icons/RunsIcon.tsx @@ -0,0 +1,18 @@ +export function RunsIcon({ className }: { className?: string }) { + return ( + + + + + + ); +} diff --git a/apps/webapp/app/components/ImpersonationBanner.tsx b/apps/webapp/app/components/ImpersonationBanner.tsx index d665d344b..d4486fc96 100644 --- a/apps/webapp/app/components/ImpersonationBanner.tsx +++ b/apps/webapp/app/components/ImpersonationBanner.tsx @@ -1,24 +1,18 @@ import { Form } from "@remix-run/react"; import { Paragraph } from "./primitives/Paragraph"; import { Button } from "./primitives/Buttons"; +import { UserMinusIcon } from "@heroicons/react/20/solid"; -export function ImpersonationBanner({ impersonationId }: { impersonationId: string }) { +export function ImpersonationBanner() { return ( -
- - - You are impersonating {impersonationId} - -
+
+ diff --git a/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx b/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx index f94e01cfc..86caba799 100644 --- a/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx +++ b/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx @@ -1,13 +1,14 @@ +import { ArrowPathIcon } from "@heroicons/react/20/solid"; +import { ExclamationTriangleIcon } from "@heroicons/react/24/solid"; +import { useFetcher } from "@remix-run/react"; +import { useState } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components/primitives/Dialog"; +import { generateTwoRandomWords } from "~/utils/randomWords"; import { Button } from "../primitives/Buttons"; -import { Dialog, DialogContent, DialogTrigger } from "~/components/primitives/Dialog"; -import { Header1, Header2 } from "../primitives/Headers"; +import { Header1 } from "../primitives/Headers"; import { Input } from "../primitives/Input"; -import { NamedIcon } from "../primitives/NamedIcon"; import { Paragraph } from "../primitives/Paragraph"; import { Spinner } from "../primitives/Spinner"; -import { useState } from "react"; -import { useFetcher } from "@remix-run/react"; -import { generateTwoRandomWords } from "~/utils/randomWords"; type ModalProps = { id: string; @@ -25,11 +26,16 @@ export function RegenerateApiKeyModal({ id, title }: ModalProps) { return ( - + {`Regenerate ${title} Environment Key`} { +const RegenerateApiKeyModalContent = ({ id, randomWord, title, closeModal }: ModalContentProps) => { const [confirmationText, setConfirmationText] = useState(""); const fetcher = useFetcher(); const isSubmitting = fetcher.state === "submitting"; @@ -52,55 +58,47 @@ const RegenerateApiKeyModalContent = ({ id, title, randomWord, closeModal }: Mod } return ( -
-
- {title} Environment +
+
+ + + Regenerating the keys for this environment will temporarily break any live Jobs in the + {title} Environment until the new API keys are set + in the relevant environment variables. +
- - Are you sure you want to regenerate the keys
- for this environment? -
- - <> - This will temporarily break any live jobs in the - {title} Environment until the new API keys - are set in the relevant environment variables. - - - - To confirm, please enter the text:{" "} - {randomWord} - - setConfirmationText(e.target.value)} - /> - +
+ Enter this text below to confirm: + + {randomWord} + +
+
+ setConfirmationText(e.target.value)} + className="rounded-r-none" + variant="large" + /> + +
); diff --git a/apps/webapp/app/components/frameworks/FrameworkComingSoon.tsx b/apps/webapp/app/components/frameworks/FrameworkComingSoon.tsx index c17848a11..da30882e4 100644 --- a/apps/webapp/app/components/frameworks/FrameworkComingSoon.tsx +++ b/apps/webapp/app/components/frameworks/FrameworkComingSoon.tsx @@ -3,11 +3,10 @@ import { GitHubDarkIcon } from "@trigger.dev/companyicons"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { projectSetupPath } from "~/utils/pathBuilder"; -import { PageGradient } from "../PageGradient"; import { LinkButton } from "../primitives/Buttons"; import { Header1 } from "../primitives/Headers"; -import { Paragraph } from "../primitives/Paragraph"; import { NamedIcon } from "../primitives/NamedIcon"; +import { Paragraph } from "../primitives/Paragraph"; export type FrameworkComingSoonProps = { frameworkName: string; githubIssueUrl: string; @@ -25,43 +24,41 @@ export function FrameworkComingSoon({ const project = useProject(); return ( - -
-
{children}
-
- {frameworkName} is coming soon! - - Choose a different framework - -
- - We're working hard to bring support for {frameworkName} in Trigger.dev. Follow along with - the GitHub issue or contribute and help us bring it to Trigger.dev faster. - - +
{children}
+
- + + We're working hard to bring support for {frameworkName} in Trigger.dev. Follow along with + the GitHub issue or contribute and help us bring it to Trigger.dev faster. + + + triggerdotdev/trigger.dev +

+ #{githubIssueNumber}Framework: + support for {frameworkName} +

+
+ + View on GitHub + +
+
+
); } diff --git a/apps/webapp/app/components/frameworks/FrameworkSelector.tsx b/apps/webapp/app/components/frameworks/FrameworkSelector.tsx index 3ce50b1e5..02715df96 100644 --- a/apps/webapp/app/components/frameworks/FrameworkSelector.tsx +++ b/apps/webapp/app/components/frameworks/FrameworkSelector.tsx @@ -24,7 +24,6 @@ import { projectSetupSvelteKitPath, } from "~/utils/pathBuilder"; import { Feedback } from "../Feedback"; -import { PageGradient } from "../PageGradient"; import { Button } from "../primitives/Buttons"; import { Header1 } from "../primitives/Headers"; @@ -33,51 +32,49 @@ export function FrameworkSelector() { const project = useProject(); return ( - -
-
- Choose a framework to get started… - - Request a framework - - } - defaultValue="feature" - /> -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+ Choose a framework to get started… + + Request a framework + + } + defaultValue="feature" + />
- + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
); } diff --git a/apps/webapp/app/components/helpContent/HelpContentText.tsx b/apps/webapp/app/components/helpContent/HelpContentText.tsx index ce057dae4..393e1a41f 100644 --- a/apps/webapp/app/components/helpContent/HelpContentText.tsx +++ b/apps/webapp/app/components/helpContent/HelpContentText.tsx @@ -4,8 +4,7 @@ import { StepNumber } from "~/components/primitives/StepNumber"; import { useJob } from "~/hooks/useJob"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; -import { IntegrationIcon } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route"; -import { jobTestPath } from "~/utils/pathBuilder"; +import { docsPath, jobTestPath } from "~/utils/pathBuilder"; import { CodeBlock } from "../code/CodeBlock"; import { InlineCode } from "../code/InlineCode"; import { EnvironmentLabel } from "../environments/EnvironmentLabel"; @@ -21,6 +20,7 @@ import selectEnvironment from "./select-environment.png"; import selectExample from "./select-example.png"; import { StepContentContainer } from "../StepContentContainer"; import { TriggerDevCommand } from "../SetupCommands"; +import { IntegrationIcon } from "~/assets/icons/IntegrationIcon"; export function HowToRunYourJob() { const organization = useOrganization(); @@ -294,3 +294,140 @@ export function HowToUseApiKeysAndEndpoints() { ); } + +export function WhatAreHttpEndpoints() { + return ( + <> + + HTTP endpoints allow you to trigger your Jobs from any webhooks. They require a bit more + work than using Integrations{" "} + but allow you to connect to any API. + + Getting started + + You need to define the HTTP endpoint in your code. To do this you use{" "} + client.defineHttpEndpoint(). This will create an HTTP endpoint. + + + Then you can create a Trigger from this by calling .onRequest() on + the created HTTP endpoint. + + + Read the HTTP endpoints guide to learn more. + + + An example: cal.com + + { + //this helper function makes verifying most webhooks easy + return await verifyRequestSignature({ + request, + headerName: "X-Cal-Signature-256", + secret: process.env.CALDOTCOM_SECRET!, + algorithm: "sha256", + }); + }, +}); + +client.defineJob({ + id: "http-caldotcom", + name: "HTTP Cal.com", + version: "1.0.0", + enabled: true, + //create a Trigger from the HTTP endpoint above. The filter is optional. + trigger: caldotcom.onRequest({ filter: { body: { triggerEvent: ["BOOKING_CANCELLED"] } } }), + run: async (request, io, ctx) => { + //note that when using HTTP endpoints, the first parameter is the request + //you need to get the body, usually it will be json so you do: + const body = await request.json(); + await io.logger.info("Body", body); + }, +});`} + /> + + ); +} + +export function HowToConnectHttpEndpoint() { + return ( + <> + Setting up your webhook + + To start receiving data you need to enter the Endpoint URL and secret into the API service + you want to receive webhooks from. + + + Go to the relevant API dashboard} /> + + + For example, if you want to receive webhooks from Cal.com then you should login to your + Cal.com account and go to their Settings/Developer/Webhooks page. + + + + Copy the Webhook URL and Secret} /> + + + A unique Webhook URL is created for each environment (Dev, Staging, and Prod). Jobs will + only be triggered from the relevant environment. + + + Copy the relevant Endpoint URL and secret from the table opposite and paste it into the + correct place in the API dashboard you located in the previous step. + + + + Add the Secret to your Environment variables} /> + + + You should also add the Secret to the Environment variables in your code and where you're + deploying. Usually in Node this means adding it to the .env file. + + + Use the secret in the verify() function of HTTP Endpoint. This + ensures that someone can't just send a request to your Endpoint and trigger a Job. + Different APIs do this verification in different ways – a common way is to have a header + that has a hash of the payload and secret. Refer to the API's documentation for more + information. + + + + Triggering runs + + + + In your code, you should use the .onRequest() function in a Job + Trigger. You can filter so only data that matches your criteria triggers the Job. + + + + + + If you're using the Staging or Prod environment, you need to make sure your code is + deployed. Deploy like you normally would –{" "} + + read our deployment guide + + . + + + + + + Now you need to actually perform an action on that third-party service that triggers the + webhook you've subscribed to. For example, add a new meeting using Cal.com. + + + + + Read the HTTP endpoints guide to learn more. + + + ); +} diff --git a/apps/webapp/app/components/jobs/DeleteJobModalContent.tsx b/apps/webapp/app/components/jobs/DeleteJobModalContent.tsx index f9fdf2775..eccd9ff31 100644 --- a/apps/webapp/app/components/jobs/DeleteJobModalContent.tsx +++ b/apps/webapp/app/components/jobs/DeleteJobModalContent.tsx @@ -40,7 +40,7 @@ export function DeleteJobDialogContent({ return (
-
+
{title} ID: {slug}
diff --git a/apps/webapp/app/components/jobs/JobsTable.tsx b/apps/webapp/app/components/jobs/JobsTable.tsx index 8f2e4fb84..5f2978f3c 100644 --- a/apps/webapp/app/components/jobs/JobsTable.tsx +++ b/apps/webapp/app/components/jobs/JobsTable.tsx @@ -1,7 +1,7 @@ -import { ProjectJob } from "~/hooks/useJobs"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { JobRunStatus } from "~/models/job.server"; +import { ProjectJob } from "~/presenters/JobListPresenter.server"; import { jobPath, jobTestPath } from "~/utils/pathBuilder"; import { Button } from "../primitives/Buttons"; import { DateTime } from "../primitives/DateTime"; @@ -30,7 +30,7 @@ export function JobsTable({ jobs, noResultsText }: { jobs: ProjectJob[]; noResul const project = useProject(); return ( - +
Job @@ -160,19 +160,18 @@ export function JobsTable({ jobs, noResultsText }: { jobs: ProjectJob[]; noResul - - - - + Delete Job + diff --git a/apps/webapp/app/components/layout/AppLayout.tsx b/apps/webapp/app/components/layout/AppLayout.tsx index d1099ead1..413c481da 100644 --- a/apps/webapp/app/components/layout/AppLayout.tsx +++ b/apps/webapp/app/components/layout/AppLayout.tsx @@ -11,7 +11,7 @@ export function AppContainer({ }) { return ( -
{children}
+
{children}
); } @@ -47,7 +47,11 @@ export function PageContainer({ children: React.ReactNode; className?: string; }) { - return
{children}
; + return ( +
+ {children} +
+ ); } export function PageBody({ @@ -58,7 +62,15 @@ export function PageBody({ scrollable?: boolean; }) { return ( -
{children}
+
+ {children} +
); } @@ -68,8 +80,8 @@ export function PageBodyPadding({ children }: { children: React.ReactNode }) { export function MainCenteredContainer({ children }: { children: React.ReactNode }) { return ( -
-
{children}
+
+
{children}
); } diff --git a/apps/webapp/app/components/navigation/Breadcrumb.tsx b/apps/webapp/app/components/navigation/Breadcrumb.tsx index b2d5fcd2d..6cad225f0 100644 --- a/apps/webapp/app/components/navigation/Breadcrumb.tsx +++ b/apps/webapp/app/components/navigation/Breadcrumb.tsx @@ -2,6 +2,7 @@ import { UIMatch, useMatches } from "@remix-run/react"; import { Fragment, ReactNode } from "react"; import { BreadcrumbIcon } from "../primitives/BreadcrumbIcon"; import { Handle } from "~/utils/handle"; +import { LinkButton } from "../primitives/Buttons"; export type BreadcrumbItem = (match: UIMatch, allMatches: UIMatch[]) => ReactNode; @@ -9,19 +10,26 @@ export function Breadcrumb() { const matches = useMatches() as UIMatch[]; return ( -
- {matches.map((match) => { - if (!match.handle || !match.handle.breadcrumb) return null; +
+ {matches + .filter((b) => b.handle && b.handle.breadcrumb) + .map((match, index) => { + const breadcrumb = match.handle.breadcrumb as BreadcrumbItem; - const breadcrumb = match.handle.breadcrumb as BreadcrumbItem; - - return ( - - - {breadcrumb(match, matches)} - - ); - })} + return ( + + {index !== 0 && } {breadcrumb(match, matches)} + + ); + })}
); } + +export function BreadcrumbLink({ title, to }: { title: string; to: string }) { + return ( + + {title} + + ); +} diff --git a/apps/webapp/app/components/navigation/JobsMenu.tsx b/apps/webapp/app/components/navigation/JobsMenu.tsx deleted file mode 100644 index 506910e88..000000000 --- a/apps/webapp/app/components/navigation/JobsMenu.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Link, UIMatch } from "@remix-run/react"; -import { useState } from "react"; -import { useJob } from "~/hooks/useJob"; -import { useOrganization } from "~/hooks/useOrganizations"; -import { useProject } from "~/hooks/useProject"; -import { useJobs } from "~/hooks/useJobs"; -import { cn } from "~/utils/cn"; -import { jobPath } from "~/utils/pathBuilder"; -import { LabelValueStack } from "../primitives/LabelValueStack"; -import { NamedIcon } from "../primitives/NamedIcon"; -import { - Popover, - PopoverArrowTrigger, - PopoverContent, - PopoverSectionHeader, -} from "../primitives/Popover"; - -export function JobsMenu({ matches }: { matches: UIMatch[] }) { - const [isOpen, setIsOpen] = useState(false); - const organization = useOrganization(matches); - const project = useProject(matches); - const projectJobs = useJobs(matches); - const currentJob = useJob(matches); - - return ( - <> - setIsOpen(open)}> - - {currentJob?.title ?? "Select a job"} - - - -
- {projectJobs.map((job) => { - const isSelected = job.id === currentJob?.id; - return ( - - - - {isSelected && } - - ); - })} -
-
-
- - ); -} diff --git a/apps/webapp/app/components/navigation/NavBar.tsx b/apps/webapp/app/components/navigation/NavBar.tsx deleted file mode 100644 index 9f09cd72b..000000000 --- a/apps/webapp/app/components/navigation/NavBar.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Popover, Transition } from "@headlessui/react"; -import { BookOpenIcon, ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid"; -import { Link } from "@remix-run/react"; -import { Fragment } from "react"; -import { cn } from "~/utils/cn"; -import { Feedback } from "../Feedback"; -import { LogoIcon } from "../LogoIcon"; -import { Button, LinkButton } from "../primitives/Buttons"; -import { Breadcrumb } from "./Breadcrumb"; -import { docsRoot } from "~/utils/pathBuilder"; - -export function NavBar() { - return ( -
-
- - - - -
-
- - Documentation - - - Help & feedback - - } - /> -
-
- ); -} - -export function BreadcrumbLink({ title, to }: { title: string; to: string }) { - return ( - - {title} - - ); -} diff --git a/apps/webapp/app/components/navigation/PageNavigationIndicator.tsx b/apps/webapp/app/components/navigation/PageNavigationIndicator.tsx new file mode 100644 index 000000000..3fb255ac5 --- /dev/null +++ b/apps/webapp/app/components/navigation/PageNavigationIndicator.tsx @@ -0,0 +1,9 @@ +import { useNavigation } from "@remix-run/react"; +import { Spinner } from "../primitives/Spinner"; + +export function PageNavigationIndicator() { + const navigation = useNavigation(); + if (navigation.state === "loading") { + return ; + } +} diff --git a/apps/webapp/app/components/navigation/ProjectSideMenu.tsx b/apps/webapp/app/components/navigation/ProjectSideMenu.tsx deleted file mode 100644 index 91a6cdf6d..000000000 --- a/apps/webapp/app/components/navigation/ProjectSideMenu.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { useMatches } from "@remix-run/react"; -import { motion } from "framer-motion"; -import { useOptionalJob } from "~/hooks/useJob"; -import { useOrganization } from "~/hooks/useOrganizations"; -import { useProject } from "~/hooks/useProject"; -import { cn } from "~/utils/cn"; -import { Handle } from "~/utils/handle"; -import { - accountPath, - organizationBillingPath, - organizationTeamPath, - projectEnvironmentsPath, - projectIntegrationsPath, - projectPath, - projectSetupPath, - projectTriggersPath, -} from "~/utils/pathBuilder"; -import { UserProfilePhoto } from "../UserProfilePhoto"; -import { NavLinkButton } from "../primitives/Buttons"; -import { NamedIcon, type IconNames } from "../primitives/NamedIcon"; -import { SimpleTooltip } from "../primitives/Tooltip"; - -export function SideMenuContainer({ children }: { children: React.ReactNode }) { - return
{children}
; -} - -const expandedWith = "14rem"; -const collapsedWith = "2.81rem"; - -const menuVariants = { - expanded: { - minWidth: expandedWith, - width: expandedWith, - }, - collapsed: { - minWidth: collapsedWith, - width: collapsedWith, - }, -}; - -export function ProjectSideMenu() { - const organization = useOrganization(); - const project = useProject(); - const matches = useMatches(); - - //the deepest route `handle` determines if the menu is expanded - const deepestMatch = matches.at(-1); - const handle = deepestMatch?.handle as Handle; - const isCollapsed = handle?.expandSidebar ? !handle.expandSidebar : true; - - const job = useOptionalJob(); - const jobsActive = - job !== undefined || - deepestMatch?.id === "routes/_app.orgs.$organizationSlug.projects.$projectParam._index"; - - return ( - -
- - - - -
-
- - - - - -
-
- ); -} - -const itemVariants = { - expanded: { - opacity: 1, - }, - collapsed: { - opacity: 0, - }, -}; - -function SideMenuItem({ - icon, - name, - to, - isCollapsed, - forceActive, - hasWarning = false, - target, -}: { - icon: IconNames | React.ComponentType; - name: string; - to: string; - isCollapsed: boolean; - hasWarning?: boolean; - forceActive?: boolean; - target?: string; -}) { - return ( - { - if (forceActive !== undefined) { - isActive = forceActive; - } - return cn( - "relative", - isActive || isPending - ? "bg-slate-800 text-bright group-hover:bg-slate-800" - : "text-dimmed group-hover:bg-slate-850 group-hover:text-bright" - ); - }} - > - - {name} - - {hasWarning && } - - } - content={name} - side="right" - hidden={!isCollapsed} - /> - ); -} diff --git a/apps/webapp/app/components/navigation/ProjectsMenu.tsx b/apps/webapp/app/components/navigation/ProjectsMenu.tsx deleted file mode 100644 index eae72eb49..000000000 --- a/apps/webapp/app/components/navigation/ProjectsMenu.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { UIMatch } from "@remix-run/react"; -import { Fragment, useState } from "react"; -import simplur from "simplur"; -import { Badge } from "~/components/primitives/Badge"; -import { useIsNewOrganizationPage, useOrganizations } from "~/hooks/useOrganizations"; -import { useOptionalProject } from "~/hooks/useProject"; -import { newOrganizationPath, newProjectPath, projectPath } from "~/utils/pathBuilder"; -import { - Popover, - PopoverArrowTrigger, - PopoverContent, - PopoverMenuItem, - PopoverSectionHeader, -} from "../primitives/Popover"; - -export function ProjectsMenu({ matches }: { matches: UIMatch[] }) { - const [isOpen, setIsOpen] = useState(false); - const organizations = useOrganizations(matches); - const isNewOrgPage = useIsNewOrganizationPage(matches); - const currentProject = useOptionalProject(matches); - - if (isNewOrgPage) { - return null; - } - - return ( - <> - setIsOpen(open)}> - - {currentProject?.name ?? "Select a project"} - - - {organizations.map((organization) => ( - - -
- {organization.projects.map((project) => { - const isSelected = project.id === currentProject?.id; - return ( - - {project.name} - {simplur`${project._count.jobs} job[|s]`} -
- } - isSelected={isSelected} - icon="folder" - /> - ); - })} - -
- - ))} -
- -
- - - - ); -} diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx new file mode 100644 index 000000000..435cbb078 --- /dev/null +++ b/apps/webapp/app/components/navigation/SideMenu.tsx @@ -0,0 +1,426 @@ +import { + AcademicCapIcon, + ArrowRightOnRectangleIcon, + ChartBarIcon, + EllipsisHorizontalIcon, +} from "@heroicons/react/20/solid"; +import { UserGroupIcon, UserPlusIcon } from "@heroicons/react/24/solid"; +import { useNavigation } from "@remix-run/react"; +import { IconExclamationCircle } from "@tabler/icons-react"; +import { AnchorHTMLAttributes, Fragment, useEffect, useRef, useState } from "react"; +import { MatchedOrganization } from "~/hooks/useOrganizations"; +import { usePathName } from "~/hooks/usePathName"; +import { MatchedProject } from "~/hooks/useProject"; +import { User } from "~/models/user.server"; +import { cn } from "~/utils/cn"; +import { + accountPath, + inviteTeamMemberPath, + logoutPath, + newOrganizationPath, + newProjectPath, + organizationBillingPath, + organizationIntegrationsPath, + organizationPath, + organizationTeamPath, + projectEnvironmentsPath, + projectHttpEndpointsPath, + projectPath, + projectSetupPath, + projectTriggersPath, +} from "~/utils/pathBuilder"; +import { Feedback } from "../Feedback"; +import { ImpersonationBanner } from "../ImpersonationBanner"; +import { LogoIcon } from "../LogoIcon"; +import { UserAvatar, UserProfilePhoto } from "../UserProfilePhoto"; +import { Button, LinkButton } from "../primitives/Buttons"; +import { Icon } from "../primitives/Icon"; +import { type IconNames } from "../primitives/NamedIcon"; +import { Paragraph } from "../primitives/Paragraph"; +import { + Popover, + PopoverArrowTrigger, + PopoverContent, + PopoverCustomTrigger, + PopoverMenuItem, + PopoverSectionHeader, +} from "../primitives/Popover"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip"; +import { useFeatures } from "~/hooks/useFeatures"; + +type SideMenuUser = Pick & { isImpersonating: boolean }; +type SideMenuProject = Pick< + MatchedProject, + "id" | "name" | "slug" | "hasInactiveExternalTriggers" | "jobCount" | "httpEndpointCount" +>; + +type SideMenuProps = { + user: SideMenuUser; + project: SideMenuProject; + organization: MatchedOrganization; + organizations: MatchedOrganization[]; +}; + +export function SideMenu({ user, project, organization, organizations }: SideMenuProps) { + const borderRef = useRef(null); + const [showHeaderDivider, setShowHeaderDivider] = useState(false); + const { isManagedCloud } = useFeatures(); + + useEffect(() => { + const handleScroll = () => { + if (borderRef.current) { + const shouldShowHeaderDivider = borderRef.current.scrollTop > 1; + if (showHeaderDivider !== shouldShowHeaderDivider) { + setShowHeaderDivider(shouldShowHeaderDivider); + } + } + }; + + borderRef.current?.addEventListener("scroll", handleScroll); + return () => borderRef.current?.removeEventListener("scroll", handleScroll); + }, [showHeaderDivider]); + + return ( +
+
+
+ + +
+
+
+ + + + + + + +
+
+ + + + + + + + +
+
+
+ + + + + Help & Feedback + + } + /> +
+
+
+ ); +} + +function ProjectSelector({ + project, + organization, + organizations, +}: { + project: SideMenuProject; + organization: MatchedOrganization; + organizations: MatchedOrganization[]; +}) { + const [isOrgMenuOpen, setOrgMenuOpen] = useState(false); + const navigation = useNavigation(); + + useEffect(() => { + setOrgMenuOpen(false); + }, [navigation.location?.pathname]); + + return ( + setOrgMenuOpen(open)} open={isOrgMenuOpen}> + + + {organization.title ?? "Select an organization"} + + + {organizations.map((organization) => ( + + +
+ {organization.projects.map((p) => { + const isSelected = p.id === project.id; + return ( + + {p.name} + +
+ } + isSelected={isSelected} + icon="folder" + /> + ); + })} + +
+ + ))} +
+ +
+ + + ); +} + +function UserMenu({ user }: { user: SideMenuUser }) { + const [isProfileMenuOpen, setProfileMenuOpen] = useState(false); + const navigation = useNavigation(); + + useEffect(() => { + setProfileMenuOpen(false); + }, [navigation.location?.pathname]); + + return ( + setProfileMenuOpen(open)}> + + + + + + +
+ {user.isImpersonating && } + {user.admin && ( + + )} + + +
+
+
+
+ ); +} + +function SideMenuHeader({ title, children }: { title: string; children: React.ReactNode }) { + const [isProjectMenuOpen, setProfileMenuOpen] = useState(false); + return ( +
+ + {title} + + setProfileMenuOpen(open)}> + + + + +
{children}
+
+
+
+ ); +} + +function SideMenuItem({ + icon, + iconColor, + name, + to, + hasWarning, + count, + target, + subItem = false, +}: { + icon?: IconNames | React.ComponentType; + iconColor?: string; + name: string; + to: string; + hasWarning?: string | boolean; + count?: number; + target?: AnchorHTMLAttributes["target"]; + subItem?: boolean; +}) { + const pathName = usePathName(); + const isActive = pathName === to; + + return ( + +
+ {name} +
+ {count !== undefined && count > 0 && } + {typeof hasWarning === "string" ? ( + + + + + + + {hasWarning} + + + + ) : ( + hasWarning && + )} +
+
+
+ ); +} + +function MenuCount({ count }: { count: number | string }) { + return
{count}
; +} diff --git a/apps/webapp/app/components/primitives/Buttons.tsx b/apps/webapp/app/components/primitives/Buttons.tsx index e469c2561..014b9096a 100644 --- a/apps/webapp/app/components/primitives/Buttons.tsx +++ b/apps/webapp/app/components/primitives/Buttons.tsx @@ -7,99 +7,129 @@ import { ShortcutKey } from "./ShortcutKey"; const variant = { "primary/small": { - textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80", + textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80 px-1", button: "h-6 px-[5px] text-xs bg-indigo-600 group-hover:bg-indigo-500/90 group-disabled:opacity-50 group-disabled:pointer-events-none", icon: "h-3.5", + iconSpacing: "gap-x-0.5", shortcutVariant: "small" as const, shortcut: "ml-1 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60 justify-self-center", }, "secondary/small": { - textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80", + textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80 px-1", button: "h-6 px-[5px] text-xs bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50 group-disabled:pointer-events-none", icon: "h-3.5", + iconSpacing: "gap-x-0.5", shortcutVariant: "small" as const, shortcut: "ml-1 -mr-0.5 border-dimmed/40 text-dimmed group-hover:text-bright/80 group-hover:border-dimmed/60", }, "tertiary/small": { - textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80", + textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80 px-1", button: "h-6 px-[5px] text-xs bg-transparent group-hover:bg-slate-850 disabled:opacity-50 group-disabled:pointer-events-none", icon: "h-3.5", + iconSpacing: "gap-x-0.5", shortcutVariant: "small" as const, shortcut: "ml-1 -mr-0.5 border-dimmed/40 text-dimmed group-hover:text-bright/80 group-hover:border-dimmed/60", }, "danger/small": { - textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80", + textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80 px-1", button: "h-6 px-[5px] text-xs bg-rose-600 group-hover:bg-rose-500 disabled:opacity-50 group-disabled:pointer-events-none", icon: "h-3.5", + iconSpacing: "gap-x-0.5", shortcutVariant: "small" as const, shortcut: "ml-1 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60", }, "primary/medium": { - textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80", + textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80 px-1", button: "h-8 px-2 text-sm bg-indigo-600 group-hover:bg-indigo-500/90 disabled:opacity-50", icon: "h-4", + iconSpacing: "gap-x-0.5", shortcutVariant: "medium" as const, shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60", }, "secondary/medium": { - textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80", + textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80 px-1", button: "h-8 px-2 text-sm bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50", icon: "h-4", + iconSpacing: "gap-x-0.5", shortcutVariant: "medium" as const, shortcut: "ml-1.5 -mr-0.5 border-dimmed/40 text-dimmed group-hover:border-dimmed group-hover:text-bright", }, "tertiary/medium": { - textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80", + textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80 px-1", button: "h-8 px-2 text-sm bg-transparent group-hover:bg-slate-850 disabled:opacity-50", icon: "h-4", + iconSpacing: "gap-x-0.5", shortcutVariant: "medium" as const, shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-dimmed group-hover:border-bright/60 group-hover:text-bright", }, "danger/medium": { - textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80", + textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80 px-1", button: "h-8 px-2 text-sm bg-rose-600 group-hover:bg-rose-500 disabled:opacity-50", icon: "h-4", + iconSpacing: "gap-x-0.5", shortcutVariant: "medium" as const, shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60", }, "primary/large": { - textColor: "text-bright group-hover:text-white transition group-disabled:text-dimmed/80", + textColor: "text-bright group-hover:text-white transition group-disabled:text-dimmed/80 px-1", button: - "h-10 px-2 text-sm font-medium bg-indigo-600 group-hover:bg-indigo-500/90 disabled:opacity-50", + "h-10 px-2 text-sm font-medium bg-indigo-600 group-hover:bg-indigo-500/90 group-disabled:opacity-50", icon: "h-5", + iconSpacing: "gap-x-0.5", shortcutVariant: undefined, shortcut: undefined, }, "secondary/large": { - textColor: "text-dimmed", + textColor: "text-dimmed px-1", button: "h-10 px-2 text-sm text-dimmed group-hover:text-bright transition font-medium bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50", icon: "h-5", + iconSpacing: "gap-x-0.5", shortcutVariant: undefined, shortcut: undefined, }, "danger/large": { - textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/50", + textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/50 px-1", button: "h-10 px-2 text-md bg-rose-600 group-hover:bg-rose-500 group-disabled:opacity-50 group-disabled:group-hover:bg-rose-600", icon: "h-5", + iconSpacing: "gap-x-0.5", shortcutVariant: "medium" as const, shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60", }, "menu-item": { - textColor: "text-bright", + textColor: "text-bright px-1", button: "h-9 px-[0.475rem] text-sm rounded-sm bg-transparent group-hover:bg-slate-800 transition", icon: "h-5", + iconSpacing: "gap-x-0.5", + shortcutVariant: undefined, + shortcut: undefined, + }, + "small-menu-item": { + textColor: "text-bright", + button: + "h-[1.8rem] px-[0.4rem] text-2sm rounded-sm text-dimmed bg-transparent group-hover:bg-slate-850 transition", + icon: "h-4", + iconSpacing: "gap-x-1.5", + shortcutVariant: undefined, + shortcut: undefined, + }, + "small-menu-sub-item": { + textColor: "text-dimmed", + button: + "h-[1.8rem] px-[0.5rem] ml-5 text-2sm rounded-sm text-dimmed bg-transparent group-hover:bg-slate-850 transition", + icon: undefined, + iconSpacing: undefined, shortcutVariant: undefined, shortcut: undefined, }, @@ -110,7 +140,7 @@ const allVariants = { variant: variant, }; -type ButtonContentPropsType = { +export type ButtonContentPropsType = { children?: React.ReactNode; LeadingIcon?: React.ComponentType | IconNamesOrString; TrailingIcon?: React.ComponentType | IconNamesOrString; @@ -140,6 +170,7 @@ export function ButtonContent(props: ButtonContentPropsType) { // Based on the size prop, we'll use the corresponding variant classnames const btnClassName = cn(allVariants.$all, variation.button); const iconClassName = variation.icon; + const iconSpacingClassName = variation.iconSpacing; const shortcutClassName = variation.shortcut; const textColorClassName = variation.textColor; @@ -148,7 +179,8 @@ export function ButtonContent(props: ButtonContentPropsType) {
{LeadingIcon && @@ -170,7 +202,7 @@ export function ButtonContent(props: ButtonContentPropsType) { {text && (typeof text === "string" ? ( - + {text} ) : ( diff --git a/apps/webapp/app/components/primitives/Dialog.tsx b/apps/webapp/app/components/primitives/Dialog.tsx index fd6cabc6c..e84c64863 100644 --- a/apps/webapp/app/components/primitives/Dialog.tsx +++ b/apps/webapp/app/components/primitives/Dialog.tsx @@ -43,12 +43,12 @@ const DialogContent = React.forwardRef< -
+
{children}
@@ -73,7 +73,7 @@ const DialogContent = React.forwardRef< DialogContent.displayName = DialogPrimitive.Content.displayName; const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
+
); DialogHeader.displayName = "DialogHeader"; diff --git a/apps/webapp/app/components/primitives/FormTitle.tsx b/apps/webapp/app/components/primitives/FormTitle.tsx index 02a33ae16..98b8c635b 100644 --- a/apps/webapp/app/components/primitives/FormTitle.tsx +++ b/apps/webapp/app/components/primitives/FormTitle.tsx @@ -21,7 +21,7 @@ export function FormTitle({
diff --git a/apps/webapp/app/components/primitives/Help.tsx b/apps/webapp/app/components/primitives/Help.tsx index da126acba..816b2e3f7 100644 --- a/apps/webapp/app/components/primitives/Help.tsx +++ b/apps/webapp/app/components/primitives/Help.tsx @@ -88,12 +88,7 @@ export function HelpContent({ )}
-
+
{children}
diff --git a/apps/webapp/app/components/primitives/NamedIcon.tsx b/apps/webapp/app/components/primitives/NamedIcon.tsx index 523fade79..d776a1d88 100644 --- a/apps/webapp/app/components/primitives/NamedIcon.tsx +++ b/apps/webapp/app/components/primitives/NamedIcon.tsx @@ -59,16 +59,18 @@ import { import { CompanyIcon, hasIcon } from "@trigger.dev/companyicons"; import { ActivityIcon, HourglassIcon } from "lucide-react"; import { DynamicTriggerIcon } from "~/assets/icons/DynamicTriggerIcon"; +import { EndpointIcon } from "~/assets/icons/EndpointIcon"; import { ErrorIcon } from "~/assets/icons/ErrorIcon"; +import { OneTreeIcon } from "~/assets/icons/OneTreeIcon"; +import { RunsIcon } from "~/assets/icons/RunsIcon"; +import { SaplingIcon } from "~/assets/icons/SaplingIcon"; import { ScheduleIcon } from "~/assets/icons/ScheduleIcon"; +import { TwoTreesIcon } from "~/assets/icons/TwoTreesIcon"; import { WebhookIcon } from "~/assets/icons/WebhookIcon"; import { cn } from "~/utils/cn"; +import { tablerIcons } from "~/utils/tablerIcons"; import { LogoIcon } from "../LogoIcon"; import { Spinner } from "./Spinner"; -import { SaplingIcon } from "~/assets/icons/SaplingIcon"; -import { TwoTreesIcon } from "~/assets/icons/TwoTreesIcon"; -import { OneTreeIcon } from "~/assets/icons/OneTreeIcon"; -import { tablerIcons } from "~/utils/tablerIcons"; import tablerSpritePath from "./tabler-sprite.svg"; const icons = { @@ -127,7 +129,9 @@ const icons = { "invite-member": (className: string) => ( ), - job: (className: string) => , + job: (className: string) => ( + + ), key: (className: string) => , lightbulb: (className: string) => , "clipboard-checked": (className: string) => ( @@ -183,6 +187,11 @@ const icons = { ), webhook: (className: string) => , + endpoint: (className: string) => , + "http-endpoint": (className: string) => ( + + ), + runs: (className: string) => , }; export type IconNames = keyof typeof icons; diff --git a/apps/webapp/app/components/primitives/PageHeader.tsx b/apps/webapp/app/components/primitives/PageHeader.tsx index e3fab7ed7..e1ff127b1 100644 --- a/apps/webapp/app/components/primitives/PageHeader.tsx +++ b/apps/webapp/app/components/primitives/PageHeader.tsx @@ -6,6 +6,9 @@ import { Paragraph } from "./Paragraph"; import { cn } from "~/utils/cn"; import { NamedIcon } from "./NamedIcon"; import { Tabs, TabsProps } from "./Tabs"; +import { Icon, RenderIcon } from "./Icon"; +import { Button, LinkButton } from "./Buttons"; +import { ArrowUpRightIcon } from "@heroicons/react/20/solid"; type WithChildren = { children: React.ReactNode; @@ -13,7 +16,7 @@ type WithChildren = { export function PageHeader({ children, hideBorder }: WithChildren & { hideBorder?: boolean }) { return ( -
+
{children}
); @@ -24,6 +27,7 @@ export function PageTitleRow({ children }: WithChildren) { } type PageTitleProps = { + icon?: RenderIcon; title: string; backButton?: { to: string; @@ -31,7 +35,7 @@ type PageTitleProps = { }; }; -export function PageTitle({ title, backButton }: PageTitleProps) { +export function PageTitle({ icon, title, backButton }: PageTitleProps) { return (
{backButton && ( @@ -48,7 +52,9 @@ export function PageTitle({ title, backButton }: PageTitleProps) {
)} - {title} + + {icon && } {title} +
); } @@ -89,6 +95,28 @@ export function PageInfoProperty({ icon, label, value, + to, +}: { + icon?: string | React.ReactNode; + label?: string; + value: React.ReactNode; + to?: string; +}) { + if (to === undefined) { + return ; + } + + return ( + + + + ); +} + +function PageInfoPropertyContent({ + icon, + label, + value, }: { icon?: string | React.ReactNode; label?: string; diff --git a/apps/webapp/app/components/primitives/Paragraph.tsx b/apps/webapp/app/components/primitives/Paragraph.tsx index 3abaaaded..8a3b6b386 100644 --- a/apps/webapp/app/components/primitives/Paragraph.tsx +++ b/apps/webapp/app/components/primitives/Paragraph.tsx @@ -51,6 +51,7 @@ const paragraphVariants = { text: "font-sans text-xxs font-normal text-bright", spacing: "mb-1", }, + "extra-extra-small/caps": { text: "font-sans text-xxs uppercase tracking-wider font-normal text-dimmed", spacing: "mb-1", @@ -59,6 +60,10 @@ const paragraphVariants = { text: "font-sans text-xxs uppercase tracking-wider font-normal text-bright", spacing: "mb-1", }, + "extra-extra-small/dimmed/caps": { + text: "font-sans text-xxs uppercase tracking-wider font-normal text-dimmed", + spacing: "mb-1", + }, }; export type ParagraphVariant = keyof typeof paragraphVariants; diff --git a/apps/webapp/app/components/primitives/Popover.tsx b/apps/webapp/app/components/primitives/Popover.tsx index 357b1c54e..cb374e75f 100644 --- a/apps/webapp/app/components/primitives/Popover.tsx +++ b/apps/webapp/app/components/primitives/Popover.tsx @@ -3,12 +3,11 @@ import * as React from "react"; import * as PopoverPrimitive from "@radix-ui/react-popover"; import { cn } from "~/utils/cn"; -import { Paragraph } from "./Paragraph"; +import { Paragraph, ParagraphVariant } from "./Paragraph"; import { ChevronDownIcon, EllipsisVerticalIcon } from "@heroicons/react/24/solid"; -import { LinkButton } from "./Buttons"; +import { ButtonContentPropsType, LinkButton } from "./Buttons"; const Popover = PopoverPrimitive.Root; - const PopoverTrigger = PopoverPrimitive.Trigger; const PopoverContent = React.forwardRef< @@ -22,19 +21,27 @@ const PopoverContent = React.forwardRef< sideOffset={sideOffset} avoidCollisions={true} className={cn( - "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + "z-50 min-w-max rounded-md border bg-midnight-850 p-4 text-popover-foreground shadow-md outline-none animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} - style={{ maxHeight: "var(--radix-popover-content-available-height)" }} + style={{ + maxHeight: "var(--radix-popover-content-available-height)", + }} {...props} /> )); PopoverContent.displayName = PopoverPrimitive.Content.displayName; -function PopoverSectionHeader({ title }: { title: string }) { +function PopoverSectionHeader({ + title, + variant = "extra-extra-small/dimmed/caps", +}: { + title: string; + variant?: ParagraphVariant; +}) { return ( - + {title} ); @@ -45,28 +52,33 @@ function PopoverMenuItem({ icon, title, isSelected, + variant = { variant: "small-menu-item" }, + leadingIconClassName, }: { to: string; - icon: string; + icon: string | React.ComponentType; title: React.ReactNode; isSelected?: boolean; + variant?: ButtonContentPropsType; + leadingIconClassName?: string; }) { return ( {title} ); } -function PopoverArrowTrigger({ +function PopoverCustomTrigger({ isOpen, children, className, @@ -76,15 +88,50 @@ function PopoverArrowTrigger({ - + {children} + + ); +} + +function PopoverArrowTrigger({ + isOpen, + children, + fullWidth = false, + overflowHidden = false, + className, + ...props +}: { + isOpen?: boolean; + fullWidth?: boolean; + overflowHidden?: boolean; +} & React.ComponentPropsWithoutRef) { + return ( + + {children} ); @@ -113,7 +160,8 @@ export { PopoverTrigger, PopoverContent, PopoverSectionHeader, + PopoverCustomTrigger, PopoverArrowTrigger, - PopoverMenuItem, PopoverVerticalEllipseTrigger, + PopoverMenuItem, }; diff --git a/apps/webapp/app/components/primitives/Sheet.tsx b/apps/webapp/app/components/primitives/Sheet.tsx index 5432f8007..844c29ca1 100644 --- a/apps/webapp/app/components/primitives/Sheet.tsx +++ b/apps/webapp/app/components/primitives/Sheet.tsx @@ -51,7 +51,7 @@ const SheetOverlay = React.forwardRef< SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - "fixed z-50 scale-100 gap-4 bg-midnight-900 shadow-lg shadow-white/10 opacity-100 border-l border-uiBorder", + "fixed z-50 scale-100 gap-4 bg-midnight-900 shadow-lg shadow-white/10 opacity-100 border-l border-ui-border", { variants: { position: { @@ -154,7 +154,7 @@ const SheetContent = React.forwardRef< {...props} >
-
+
Close @@ -181,7 +181,7 @@ export const SheetBody = ({ className, ...props }: React.HTMLAttributes) => (
) => (
-
{children}
+
{children}
); diff --git a/apps/webapp/app/components/primitives/Spinner.tsx b/apps/webapp/app/components/primitives/Spinner.tsx index fbe430848..b8593287c 100644 --- a/apps/webapp/app/components/primitives/Spinner.tsx +++ b/apps/webapp/app/components/primitives/Spinner.tsx @@ -5,7 +5,7 @@ export function Spinner({ color = "blue", }: { className?: string; - color?: "blue" | "white"; + color?: "blue" | "white" | "muted"; }) { const colors = { blue: { @@ -16,6 +16,10 @@ export function Spinner({ light: "rgba(255, 255, 255, 0.4)", dark: "rgba(255, 255, 255)", }, + muted: { + light: "#1C2433", + dark: "#3C4B62", + }, }; const currentColor = colors[color]; diff --git a/apps/webapp/app/components/primitives/Table.tsx b/apps/webapp/app/components/primitives/Table.tsx index c7240256a..9c02814d8 100644 --- a/apps/webapp/app/components/primitives/Table.tsx +++ b/apps/webapp/app/components/primitives/Table.tsx @@ -16,7 +16,7 @@ export const Table = forwardRef( return (
return (
{children} @@ -55,7 +55,7 @@ type TableBodyProps = { export const TableBody = forwardRef( ({ className, children }, ref) => { return ( - + {children} ); @@ -106,7 +106,7 @@ export const TableHeaderCell = forwardRef +
{tabs.map((tab, index) => ( {({ isActive, isPending }) => ( diff --git a/apps/webapp/app/components/run/RunOverview.tsx b/apps/webapp/app/components/run/RunOverview.tsx index 130ecfd0d..96f15f53a 100644 --- a/apps/webapp/app/components/run/RunOverview.tsx +++ b/apps/webapp/app/components/run/RunOverview.tsx @@ -1,6 +1,7 @@ import { conform, useForm } from "@conform-to/react"; import { parse } from "@conform-to/zod"; -import { BoltIcon, ForwardIcon } from "@heroicons/react/24/solid"; +import { PlayIcon } from "@heroicons/react/20/solid"; +import { BoltIcon } from "@heroicons/react/24/solid"; import { Form, Outlet, @@ -262,7 +263,9 @@ export function RunOverview({ run, trigger, showRerun, paths }: RunOverviewProps ) : ( run.output === null && ( - This Run returned nothing. + + This Run returned nothing. + ) )} @@ -273,7 +276,7 @@ export function RunOverview({ run, trigger, showRerun, paths }: RunOverviewProps {/* Detail view */}
- Detail + Details {selectedId ? : Select a task or trigger}
@@ -338,50 +341,52 @@ function RerunPopover({ Rerun Job - + {environmentType === "PRODUCTION" && ( - - This will rerun this Job in your Production environment. - +
+ + This will rerun this Job in your Production environment. + +
)} -
-
+
+
+ + Start a brand new Job run with the same Trigger data as this one. This will re-do + every Task. + - - - Start a brand new job run with the same Trigger data as this one. This will re-do - every task. -
{status === "FAILED" && ( -
+
+ + Continue running this Job run from where it left off. This will skip any Task that + has already been completed. + - - - Continue running this job run from where it left off. This will skip any task that - has already been completed. -
)}
diff --git a/apps/webapp/app/components/runs/RunsTable.tsx b/apps/webapp/app/components/runs/RunsTable.tsx index 7f185e3c3..77aabc6db 100644 --- a/apps/webapp/app/components/runs/RunsTable.tsx +++ b/apps/webapp/app/components/runs/RunsTable.tsx @@ -129,7 +129,7 @@ export function RunsTable({ function NoRuns({ title }: { title: string }) { return (
- + {title}
diff --git a/apps/webapp/app/hooks/useFilterJobs.ts b/apps/webapp/app/hooks/useFilterJobs.ts index 950e19a43..9d220952e 100644 --- a/apps/webapp/app/hooks/useFilterJobs.ts +++ b/apps/webapp/app/hooks/useFilterJobs.ts @@ -1,4 +1,4 @@ -import { ProjectJob } from "./useJobs"; +import { ProjectJob } from "~/presenters/JobListPresenter.server"; import { useTextFilter } from "./useTextFilter"; import { useToggleFilter } from "./useToggleFilter"; diff --git a/apps/webapp/app/hooks/useIntegrationClient.tsx b/apps/webapp/app/hooks/useIntegrationClient.tsx index e7c5d0b5f..bfe9fad24 100644 --- a/apps/webapp/app/hooks/useIntegrationClient.tsx +++ b/apps/webapp/app/hooks/useIntegrationClient.tsx @@ -1,14 +1,14 @@ import { UIMatch } from "@remix-run/react"; import { UseDataFunctionReturn } from "remix-typedjson"; import invariant from "tiny-invariant"; -import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route"; +import type { loader } from "~/routes/_app.orgs.$organizationSlug.integrations_.$clientParam/route"; import { useTypedMatchesData } from "./useTypedMatchData"; export type MatchedClient = UseDataFunctionReturn["client"]; export function useOptionalIntegrationClient(matches?: UIMatch[]) { const routeMatch = useTypedMatchesData({ - id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam", + id: "routes/_app.orgs.$organizationSlug.integrations_.$clientParam", matches, }); diff --git a/apps/webapp/app/hooks/useIsProjectChildPage.ts b/apps/webapp/app/hooks/useIsOrgChildPage.ts similarity index 69% rename from apps/webapp/app/hooks/useIsProjectChildPage.ts rename to apps/webapp/app/hooks/useIsOrgChildPage.ts index cd9cf2f8a..685630ccc 100644 --- a/apps/webapp/app/hooks/useIsProjectChildPage.ts +++ b/apps/webapp/app/hooks/useIsOrgChildPage.ts @@ -1,11 +1,11 @@ import { UIMatch, useMatches } from "@remix-run/react"; -export function useIsProjectChildPage(matches?: UIMatch[]) { +export function useIsOrgChildPage(matches?: UIMatch[]) { if (!matches) { matches = useMatches(); } return matches.some((matchData) => { - return matchData.id.startsWith("routes/_app.orgs.$organizationSlug.projects.$projectParam"); + return matchData.id.startsWith("routes/_app.orgs.$organizationSlug"); }); } diff --git a/apps/webapp/app/hooks/useJob.tsx b/apps/webapp/app/hooks/useJob.tsx index 32c6e682e..7064e8f30 100644 --- a/apps/webapp/app/hooks/useJob.tsx +++ b/apps/webapp/app/hooks/useJob.tsx @@ -9,17 +9,18 @@ export type MatchedJob = UseDataFunctionReturn["job"]; export const jobMatchId = "routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam"; + export function useOptionalJob(matches?: UIMatch[]) { const routeMatch = useTypedMatchesData({ id: jobMatchId, matches, }); - if (!routeMatch || !routeMatch.job) { + if (!routeMatch) { return undefined; } - return routeMatch.projectJobs.find((j) => j.id === routeMatch.job.id); + return routeMatch.job; } export function useJob(matches?: UIMatch[]) { diff --git a/apps/webapp/app/hooks/useJobs.tsx b/apps/webapp/app/hooks/useJobs.tsx deleted file mode 100644 index 8c9821cc4..000000000 --- a/apps/webapp/app/hooks/useJobs.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { UseDataFunctionReturn } from "remix-typedjson"; -import invariant from "tiny-invariant"; -import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route"; -import { UIMatch } from "@remix-run/react"; -import { useTypedMatchesData } from "./useTypedMatchData"; - -export type ProjectJob = UseDataFunctionReturn["projectJobs"][number]; - -export const jobsMatchId = - "routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam"; - -// This is only used in the JobsMenu component, which is the breadcrumb job list dropdown. -// This dropdown is only shown once you have selected a job, so we can assume that -// the route above has loaded and we can use the data from it. -export function useOptionalJobs(matches?: UIMatch[]) { - const routeMatch = useTypedMatchesData({ - id: jobsMatchId, - matches, - }); - - return routeMatch?.projectJobs; -} - -export function useJobs(matches?: UIMatch[]) { - const jobs = useOptionalJobs(matches); - invariant(jobs, "Jobs must be defined"); - return jobs; -} diff --git a/apps/webapp/app/hooks/useOrganizations.ts b/apps/webapp/app/hooks/useOrganizations.ts index 35a79d335..ace3e5da5 100644 --- a/apps/webapp/app/hooks/useOrganizations.ts +++ b/apps/webapp/app/hooks/useOrganizations.ts @@ -1,17 +1,17 @@ import { UseDataFunctionReturn, useTypedRouteLoaderData } from "remix-typedjson"; import invariant from "tiny-invariant"; import type { loader as orgLoader } from "~/routes/_app.orgs.$organizationSlug/route"; -import type { loader as appLoader } from "~/routes/_app/route"; import { hydrateObject, useMatchesData } from "~/utils"; import { useChanged } from "./useChanged"; import { UIMatch } from "@remix-run/react"; import { useTypedMatchesData } from "./useTypedMatchData"; -export type MatchedOrganization = UseDataFunctionReturn["organizations"][number]; +export type MatchedOrganization = UseDataFunctionReturn["organizations"][number]; +export const organizationMatchId = "routes/_app.orgs.$organizationSlug"; export function useOptionalOrganizations(matches?: UIMatch[]) { - const data = useTypedMatchesData({ - id: "routes/_app", + const data = useTypedMatchesData({ + id: "routes/_app.orgs.$organizationSlug", matches, }); return data?.organizations; diff --git a/apps/webapp/app/models/organization.server.ts b/apps/webapp/app/models/organization.server.ts index df0475fbe..07b46b1d2 100644 --- a/apps/webapp/app/models/organization.server.ts +++ b/apps/webapp/app/models/organization.server.ts @@ -9,61 +9,12 @@ import { customAlphabet } from "nanoid"; import slug from "slug"; import { prisma, PrismaClientOrTransaction } from "~/db.server"; import { createProject } from "./project.server"; +import { generate } from "random-words"; import { createApiKeyForEnv, createPkApiKeyForEnv, envSlug } from "./api-key.server"; export type { Organization }; const nanoid = customAlphabet("1234567890abcdef", 4); -const apiKeyId = customAlphabet( - "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - 12 -); - -export function getOrganizationFromSlug({ - userId, - slug, -}: Pick & { - userId: User["id"]; -}) { - return prisma.organization.findFirst({ - include: { - environments: true, - }, - where: { slug, members: { some: { userId } } }, - }); -} - -export function getOrganizations({ userId }: { userId: User["id"] }) { - return prisma.organization.findMany({ - where: { members: { some: { userId } } }, - orderBy: { createdAt: "desc" }, - include: { - environments: { - orderBy: { slug: "asc" }, - }, - projects: { - orderBy: { name: "asc" }, - include: { - _count: { - select: { - jobs: { - where: { - internal: false, - deletedAt: null, - }, - }, - }, - }, - }, - }, - _count: { - select: { - members: true, - }, - }, - }, - }); -} export async function createOrganization( { @@ -136,12 +87,14 @@ export async function createEnvironment( const slug = envSlug(type); const apiKey = createApiKeyForEnv(type); const pkApiKey = createPkApiKeyForEnv(type); + const shortcode = createShortcode().join("-"); return await prismaClient.runtimeEnvironment.create({ data: { slug, apiKey, pkApiKey, + shortcode, autoEnableInternalSources: type !== "DEVELOPMENT", organization: { connect: { @@ -158,3 +111,7 @@ export async function createEnvironment( }, }); } + +function createShortcode() { + return generate({ exactly: 2 }); +} diff --git a/apps/webapp/app/presenters/EnvironmentsPresenter.server.ts b/apps/webapp/app/presenters/EnvironmentsPresenter.server.ts index bd5f4ef87..23f803926 100644 --- a/apps/webapp/app/presenters/EnvironmentsPresenter.server.ts +++ b/apps/webapp/app/presenters/EnvironmentsPresenter.server.ts @@ -14,6 +14,7 @@ import { IndexEndpointStats, parseEndpointIndexStats, } from "@trigger.dev/core"; +import { sortEnvironments } from "~/services/environmentSort.server"; export type Client = { slug: string; @@ -197,31 +198,20 @@ export class EnvironmentsPresenter { } return { - environments: filtered - .map((environment) => ({ + environments: sortEnvironments( + filtered.map((environment) => ({ id: environment.id, apiKey: environment.apiKey, pkApiKey: environment.pkApiKey, type: environment.type, slug: environment.slug, })) - .sort((a, b) => { - const aIndex = environmentSortOrder.indexOf(a.type); - const bIndex = environmentSortOrder.indexOf(b.type); - return aIndex - bIndex; - }), + ), clients, }; } } -const environmentSortOrder: RuntimeEnvironmentType[] = [ - "DEVELOPMENT", - "PREVIEW", - "STAGING", - "PRODUCTION", -]; - function endpointClient( endpoint: Pick & { indexings: Pick[]; diff --git a/apps/webapp/app/presenters/HttpEndpointPresenter.server.ts b/apps/webapp/app/presenters/HttpEndpointPresenter.server.ts new file mode 100644 index 000000000..fe792483c --- /dev/null +++ b/apps/webapp/app/presenters/HttpEndpointPresenter.server.ts @@ -0,0 +1,154 @@ +import { TriggerHttpEndpoint } from "@trigger.dev/database"; +import { z } from "zod"; +import { PrismaClient, prisma } from "~/db.server"; +import { Project } from "~/models/project.server"; +import { User } from "~/models/user.server"; +import { sortEnvironments } from "~/services/environmentSort.server"; +import { httpEndpointUrl } from "~/services/httpendpoint/HandleHttpEndpointService"; +import { getSecretStore } from "~/services/secrets/secretStore.server"; + +export class HttpEndpointPresenter { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call({ + userId, + projectSlug, + httpEndpointKey, + }: { + userId: string; + projectSlug: string; + httpEndpointKey: string; + }) { + const httpEndpoint = await this.#prismaClient.triggerHttpEndpoint.findFirst({ + select: { + id: true, + key: true, + icon: true, + title: true, + updatedAt: true, + projectId: true, + secretReference: { + select: { + key: true, + provider: true, + }, + }, + httpEndpointEnvironments: { + select: { + id: true, + immediateResponseFilter: true, + skipTriggeringRuns: true, + source: true, + active: true, + updatedAt: true, + environment: { + select: { + type: true, + orgMember: { + select: { + userId: true, + }, + }, + }, + }, + }, + }, + }, + where: { + key: httpEndpointKey, + project: { + slug: projectSlug, + organization: { + members: { + some: { + userId, + }, + }, + }, + }, + }, + }); + + if (!httpEndpoint) { + throw new Error("Could not find http endpoint"); + } + + const environments = await this.#prismaClient.runtimeEnvironment.findMany({ + select: { + id: true, + type: true, + slug: true, + shortcode: true, + orgMember: { + select: { + userId: true, + }, + }, + }, + where: { + projectId: httpEndpoint.projectId, + }, + }); + + const relevantEnvironments = sortEnvironments( + environments + .filter( + (environment) => environment.orgMember === null || environment.orgMember.userId === userId + ) + .map((environment) => ({ + ...environment, + webhookUrl: httpEndpointUrl({ httpEndpointId: httpEndpoint.id, environment }), + })) + ); + + //get the secret + const secretStore = getSecretStore(httpEndpoint.secretReference.provider); + let secret: string | undefined; + try { + const secretData = await secretStore.getSecretOrThrow( + z.object({ secret: z.string() }), + httpEndpoint.secretReference.key + ); + secret = secretData.secret; + } catch (e) { + let error = e instanceof Error ? e.message : JSON.stringify(e); + throw new Error(`Could not retrieve secret: ${error}`); + } + if (!secret) { + throw new Error("Could not find secret"); + } + + const httpEndpointEnvironments = httpEndpoint.httpEndpointEnvironments + .filter( + (httpEndpointEnvironment) => + httpEndpointEnvironment.environment.orgMember === null || + httpEndpointEnvironment.environment.orgMember.userId === userId + ) + .map((endpointEnv) => ({ + ...endpointEnv, + immediateResponseFilter: endpointEnv.immediateResponseFilter != null, + environment: { + type: endpointEnv.environment.type, + }, + webhookUrl: relevantEnvironments.find((e) => e.type === endpointEnv.environment.type) + ?.webhookUrl, + })); + + return { + httpEndpoint: { + ...httpEndpoint, + + httpEndpointEnvironments, + }, + environments: relevantEnvironments, + unconfiguredEnvironments: relevantEnvironments.filter( + (e) => httpEndpointEnvironments.find((h) => h.environment.type === e.type) === undefined + ), + secret, + }; + } +} diff --git a/apps/webapp/app/presenters/HttpEndpointsPresenter.server.ts b/apps/webapp/app/presenters/HttpEndpointsPresenter.server.ts new file mode 100644 index 000000000..8f4d3134f --- /dev/null +++ b/apps/webapp/app/presenters/HttpEndpointsPresenter.server.ts @@ -0,0 +1,64 @@ +import { PrismaClient, prisma } from "~/db.server"; +import { Project } from "~/models/project.server"; +import { User } from "~/models/user.server"; + +export class HttpEndpointsPresenter { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call({ + userId, + slug, + }: Pick & { + userId: User["id"]; + }) { + const httpEndpoints = await this.#prismaClient.triggerHttpEndpoint.findMany({ + select: { + id: true, + key: true, + icon: true, + title: true, + updatedAt: true, + httpEndpointEnvironments: { + select: { + id: true, + environment: { + select: { + type: true, + orgMember: { + select: { + userId: true, + }, + }, + }, + }, + }, + }, + }, + where: { + project: { + slug, + organization: { + members: { + some: { + userId, + }, + }, + }, + }, + }, + }); + + return httpEndpoints.map((httpEndpoint) => ({ + ...httpEndpoint, + httpEndpointEnvironments: httpEndpoint.httpEndpointEnvironments.filter( + (httpEndpointEnvironment) => + httpEndpointEnvironment.environment.orgMember === null || + httpEndpointEnvironment.environment.orgMember.userId === userId + ), + })); + } +} diff --git a/apps/webapp/app/presenters/IntegrationClientConnectionsPresenter.server.ts b/apps/webapp/app/presenters/IntegrationClientConnectionsPresenter.server.ts index f1438ea3f..3e02167ec 100644 --- a/apps/webapp/app/presenters/IntegrationClientConnectionsPresenter.server.ts +++ b/apps/webapp/app/presenters/IntegrationClientConnectionsPresenter.server.ts @@ -1,7 +1,6 @@ import { User } from "@trigger.dev/database"; import { PrismaClient, prisma } from "~/db.server"; import { Organization } from "~/models/organization.server"; -import { Project } from "~/models/project.server"; import { ConnectionMetadataSchema } from "~/services/externalApis/types"; export class IntegrationClientConnectionsPresenter { @@ -14,12 +13,10 @@ export class IntegrationClientConnectionsPresenter { public async call({ userId, organizationSlug, - projectSlug, clientSlug, }: { userId: User["id"]; organizationSlug: Organization["slug"]; - projectSlug: Project["slug"]; clientSlug: string; }) { const connections = await this.#prismaClient.integrationConnection.findMany({ diff --git a/apps/webapp/app/presenters/IntegrationClientPresenter.server.ts b/apps/webapp/app/presenters/IntegrationClientPresenter.server.ts index 901d79b17..5d65ac668 100644 --- a/apps/webapp/app/presenters/IntegrationClientPresenter.server.ts +++ b/apps/webapp/app/presenters/IntegrationClientPresenter.server.ts @@ -17,12 +17,10 @@ export class IntegrationClientPresenter { public async call({ userId, organizationSlug, - projectSlug, clientSlug, }: { userId: User["id"]; organizationSlug: Organization["slug"]; - projectSlug: Project["slug"]; clientSlug: string; }) { const integration = await this.#prismaClient.integration.findFirst({ @@ -59,8 +57,8 @@ export class IntegrationClientPresenter { jobIntegrations: { where: { job: { - project: { - slug: projectSlug, + organization: { + slug: organizationSlug, }, internal: false, deletedAt: null, diff --git a/apps/webapp/app/presenters/IntegrationClientScopesPresenter.server.ts b/apps/webapp/app/presenters/IntegrationClientScopesPresenter.server.ts index 4687ee0f9..25bbc783a 100644 --- a/apps/webapp/app/presenters/IntegrationClientScopesPresenter.server.ts +++ b/apps/webapp/app/presenters/IntegrationClientScopesPresenter.server.ts @@ -15,12 +15,10 @@ export class IntegrationClientScopesPresenter { public async call({ userId, organizationSlug, - projectSlug, clientSlug, }: { userId: User["id"]; organizationSlug: Organization["slug"]; - projectSlug: Project["slug"]; clientSlug: string; }) { const integration = await this.#prismaClient.integration.findFirst({ diff --git a/apps/webapp/app/presenters/IntegrationsPresenter.server.ts b/apps/webapp/app/presenters/IntegrationsPresenter.server.ts index c38449a17..57e477ca6 100644 --- a/apps/webapp/app/presenters/IntegrationsPresenter.server.ts +++ b/apps/webapp/app/presenters/IntegrationsPresenter.server.ts @@ -25,11 +25,9 @@ export class IntegrationsPresenter { public async call({ userId, - projectSlug, organizationSlug, }: { userId: User["id"]; - projectSlug: Project["slug"]; organizationSlug: Organization["slug"]; }) { const clients = await this.#prismaClient.integration.findMany({ @@ -67,8 +65,8 @@ export class IntegrationsPresenter { jobIntegrations: { where: { job: { - project: { - slug: projectSlug, + organization: { + slug: organizationSlug, }, internal: false, deletedAt: null, diff --git a/apps/webapp/app/presenters/JobListPresenter.server.ts b/apps/webapp/app/presenters/JobListPresenter.server.ts index 3c997c646..587d60481 100644 --- a/apps/webapp/app/presenters/JobListPresenter.server.ts +++ b/apps/webapp/app/presenters/JobListPresenter.server.ts @@ -8,6 +8,9 @@ import { Organization } from "~/models/organization.server"; import { Project } from "~/models/project.server"; import { User } from "~/models/user.server"; import { z } from "zod"; +import { projectPath } from "~/utils/pathBuilder"; + +export type ProjectJob = Awaited>[0]; export class JobListPresenter { #prismaClient: PrismaClient; @@ -23,8 +26,8 @@ export class JobListPresenter { integrationSlug, }: { userId: User["id"]; - projectSlug: Project["slug"]; - organizationSlug?: Organization["slug"]; + projectSlug?: Project["slug"]; + organizationSlug: Organization["slug"]; integrationSlug?: string; }) { const orgWhere: Prisma.JobWhereInput["organization"] = organizationSlug @@ -68,6 +71,8 @@ export class JobListPresenter { }, }, }, + triggerLink: true, + triggerHelp: true, }, }, environment: { @@ -90,14 +95,21 @@ export class JobListPresenter { type: true, }, }, + project: { + select: { + slug: true, + }, + }, }, where: { internal: false, deletedAt: null, organization: orgWhere, - project: { - slug: projectSlug, - }, + project: projectSlug + ? { + slug: projectSlug, + } + : undefined, integrations: integrationsWhere, }, orderBy: [{ title: "asc" }], @@ -182,6 +194,11 @@ export class JobListPresenter { title: eventSpecification.title, icon: eventSpecification.icon, source: eventSpecification.source, + link: projectSlug + ? `${projectPath({ slug: organizationSlug }, { slug: projectSlug })}/${ + alias.version.triggerLink + }` + : undefined, }, integrations, hasIntegrationsRequiringAction: integrations.some( diff --git a/apps/webapp/app/presenters/JobPresenter.server.ts b/apps/webapp/app/presenters/JobPresenter.server.ts new file mode 100644 index 000000000..82c7c7654 --- /dev/null +++ b/apps/webapp/app/presenters/JobPresenter.server.ts @@ -0,0 +1,234 @@ +import { + DisplayProperty, + DisplayPropertySchema, + EventSpecificationSchema, + TriggerHelpSchema, +} from "@trigger.dev/core"; +import { PrismaClient, Prisma, prisma } from "~/db.server"; +import { Organization } from "~/models/organization.server"; +import { Project } from "~/models/project.server"; +import { User } from "~/models/user.server"; +import { z } from "zod"; +import { projectPath } from "~/utils/pathBuilder"; +import { Job } from "@trigger.dev/database"; + +export class JobPresenter { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call({ + userId, + jobSlug, + projectSlug, + organizationSlug, + }: { + userId: User["id"]; + jobSlug: Job["slug"]; + projectSlug: Project["slug"]; + organizationSlug: Organization["slug"]; + }) { + const job = await this.#prismaClient.job.findFirst({ + select: { + id: true, + slug: true, + title: true, + aliases: { + select: { + version: { + select: { + version: true, + eventSpecification: true, + properties: true, + status: true, + runs: { + select: { + createdAt: true, + status: true, + }, + take: 1, + orderBy: [{ createdAt: "desc" }], + }, + integrations: { + select: { + key: true, + integration: { + select: { + slug: true, + definition: true, + setupStatus: true, + }, + }, + }, + }, + triggerLink: true, + triggerHelp: true, + }, + }, + environment: { + select: { + type: true, + orgMember: { + select: { + userId: true, + }, + }, + }, + }, + }, + where: { + name: "latest", + }, + }, + dynamicTriggers: { + select: { + type: true, + }, + }, + project: { + select: { + slug: true, + }, + }, + _count: { + select: { + runs: { + where: { + isTest: false, + }, + }, + }, + }, + }, + where: { + slug: jobSlug, + deletedAt: null, + organization: { + members: { + some: { + userId, + }, + }, + }, + project: { + slug: projectSlug, + }, + }, + }); + + if (!job) { + return undefined; + } + + //the best alias to select: + // 1. Logged-in user dev + // 2. Prod + // 3. Any other user's dev + const sortedAliases = job.aliases.sort((a, b) => { + if (a.environment.type === "DEVELOPMENT" && a.environment.orgMember?.userId === userId) { + return -1; + } + + if (b.environment.type === "DEVELOPMENT" && b.environment.orgMember?.userId === userId) { + return 1; + } + + if (a.environment.type === "PRODUCTION") { + return -1; + } + + if (b.environment.type === "PRODUCTION") { + return 1; + } + + return 0; + }); + + const alias = sortedAliases.at(0); + + if (!alias) { + throw new Error(`No aliases found for job ${job.id}, this should never happen.`); + } + + const eventSpecification = EventSpecificationSchema.parse(alias.version.eventSpecification); + + const lastRuns = job.aliases + .map((alias) => alias.version.runs.at(0)) + .filter(Boolean) + .sort((a, b) => { + return b.createdAt.getTime() - a.createdAt.getTime(); + }); + + const lastRun = lastRuns.at(0); + + const integrations = alias.version.integrations.map((integration) => ({ + key: integration.key, + title: integration.integration.slug, + icon: integration.integration.definition.icon ?? integration.integration.definition.id, + setupStatus: integration.integration.setupStatus, + })); + + let properties: DisplayProperty[] = []; + + if (eventSpecification.properties) { + properties = [...properties, ...eventSpecification.properties]; + } + + if (alias.version.properties) { + const versionProperties = z.array(DisplayPropertySchema).parse(alias.version.properties); + properties = [...properties, ...versionProperties]; + } + + const environments = job.aliases.map((alias) => ({ + type: alias.environment.type, + enabled: alias.version.status === "ACTIVE", + lastRun: alias.version.runs.at(0)?.createdAt, + version: alias.version.version, + })); + + const projectRootPath = projectPath({ slug: organizationSlug }, { slug: projectSlug }); + + //we exclude test runs from this count + const hasRealRuns = job._count.runs > 0; + + return { + id: job.id, + slug: job.slug, + title: job.title, + version: alias.version.version, + status: alias.version.status, + dynamic: job.dynamicTriggers.length > 0, + event: { + title: eventSpecification.title, + icon: eventSpecification.icon, + source: eventSpecification.source, + link: `${projectRootPath}/${alias.version.triggerLink}`, + }, + noRunsHelp: hasRealRuns + ? undefined + : this.#getNoRunsHelp(alias.version.triggerHelp, projectRootPath), + integrations, + hasIntegrationsRequiringAction: integrations.some((i) => i.setupStatus === "MISSING_FIELDS"), + lastRun, + properties, + environments, + }; + } + + #getNoRunsHelp(data: Prisma.JsonValue, projectPath: string) { + const triggerHelp = TriggerHelpSchema.nullish().parse(data); + if (!triggerHelp) { + return undefined; + } + + if (triggerHelp.noRuns) { + triggerHelp.noRuns.link = triggerHelp.noRuns.link + ? `${projectPath}/${triggerHelp.noRuns.link}` + : undefined; + + return triggerHelp.noRuns; + } + } +} diff --git a/apps/webapp/app/presenters/NewOrganizationPresenter.server.ts b/apps/webapp/app/presenters/NewOrganizationPresenter.server.ts new file mode 100644 index 000000000..4d3fe0aae --- /dev/null +++ b/apps/webapp/app/presenters/NewOrganizationPresenter.server.ts @@ -0,0 +1,20 @@ +import { PrismaClient, User } from "@trigger.dev/database"; +import { prisma } from "~/db.server"; + +export class NewOrganizationPresenter { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call({ userId }: { userId: User["id"] }) { + const organizations = await this.#prismaClient.organization.findMany({ + where: { members: { some: { userId } } }, + }); + + return { + hasOrganizations: organizations.length > 0, + }; + } +} diff --git a/apps/webapp/app/presenters/OrganizationsPresenter.server.ts b/apps/webapp/app/presenters/OrganizationsPresenter.server.ts new file mode 100644 index 000000000..d34e93e5c --- /dev/null +++ b/apps/webapp/app/presenters/OrganizationsPresenter.server.ts @@ -0,0 +1,117 @@ +import { PrismaClient } from "@trigger.dev/database"; +import { prisma } from "~/db.server"; +import { getCurrentProjectId } from "~/services/currentProject.server"; +import { ProjectPresenter } from "./ProjectPresenter.server"; + +type Org = Awaited>[number]; + +export class OrganizationsPresenter { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call({ + userId, + organizationSlug, + request, + projectSlug, + }: { + userId: string; + organizationSlug: string; + request: Request; + projectSlug?: string; + }) { + const organizations = await this.getOrganizations(userId); + + const organization = organizations.find((o) => o.slug === organizationSlug); + if (!organization) { + throw new Response("Not Found", { status: 404 }); + } + + const project = await this.getProject(organization, projectSlug, request, userId); + + return { organizations, organization, project }; + } + + async getOrganizations(userId: string) { + const orgs = await this.#prismaClient.organization.findMany({ + where: { members: { some: { userId } } }, + orderBy: { createdAt: "desc" }, + include: { + projects: { + orderBy: { name: "asc" }, + include: { + _count: { + select: { + jobs: { + where: { + internal: false, + deletedAt: null, + }, + }, + sources: { + where: { + active: false, + }, + }, + httpEndpoints: true, + }, + }, + }, + }, + _count: { + select: { + members: true, + integrations: { + where: { + setupStatus: "MISSING_FIELDS", + }, + }, + }, + }, + }, + }); + + return orgs.map((org) => { + return { + id: org.id, + slug: org.slug, + title: org.title, + projects: org.projects.map((project) => ({ + id: project.id, + slug: project.slug, + name: project.name, + jobCount: project._count.jobs, + })), + hasUnconfiguredIntegrations: org._count.integrations > 0, + memberCount: org._count.members, + }; + }); + } + + async getProject( + organization: Org, + projectSlug: string | undefined, + request: Request, + userId: string + ) { + const projectPresenter = new ProjectPresenter(); + + if (!projectSlug) { + const projectId = await getCurrentProjectId(request); + const orgProject = organization.projects.find((p) => p.id === projectId); + if (!orgProject) { + throw new Response("Not Found", { status: 404 }); + } + projectSlug = orgProject.slug; + } + + const project = await projectPresenter.call({ userId, slug: projectSlug }); + if (!project) { + throw new Response("Not Found", { status: 404 }); + } + return project; + } +} diff --git a/apps/webapp/app/presenters/ProjectPresenter.server.ts b/apps/webapp/app/presenters/ProjectPresenter.server.ts index fe55f354a..0d18091c1 100644 --- a/apps/webapp/app/presenters/ProjectPresenter.server.ts +++ b/apps/webapp/app/presenters/ProjectPresenter.server.ts @@ -91,6 +91,13 @@ export class ProjectPresenter { active: false, }, }, + jobs: { + where: { + internal: false, + deletedAt: null, + }, + }, + httpEndpoints: true, }, }, organization: { @@ -135,7 +142,8 @@ export class ProjectPresenter { createdAt: project.createdAt, updatedAt: project.updatedAt, hasInactiveExternalTriggers: project._count.sources > 0, - hasUnconfiguredIntegrations: project.organization._count.integrations > 0, + jobCount: project._count.jobs, + httpEndpointCount: project._count.httpEndpoints, environments: project.environments.map((environment) => ({ id: environment.id, slug: environment.slug, diff --git a/apps/webapp/app/presenters/SelectBestProjectPresenter.server.ts b/apps/webapp/app/presenters/SelectBestProjectPresenter.server.ts new file mode 100644 index 000000000..55adbd6cc --- /dev/null +++ b/apps/webapp/app/presenters/SelectBestProjectPresenter.server.ts @@ -0,0 +1,49 @@ +import { PrismaClient } from "@trigger.dev/database"; +import { prisma } from "~/db.server"; +import { getCurrentProjectId } from "~/services/currentProject.server"; + +export class SelectBestProjectPresenter { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call({ userId, request }: { userId: string; request: Request }) { + //try get current project from cookie + const projectId = await getCurrentProjectId(request); + if (projectId) { + const project = await this.#prismaClient.project.findUnique({ + where: { id: projectId, organization: { members: { some: { userId } } } }, + include: { organization: true }, + }); + if (project) { + return { project, organization: project.organization }; + } + } + + //failing that, we pick the project with the most jobs + const projects = await this.#prismaClient.project.findMany({ + include: { + organization: true, + }, + where: { + organization: { + members: { some: { userId } }, + }, + }, + orderBy: { + jobs: { + _count: "desc", + }, + }, + take: 1, + }); + + if (projects.length === 0) { + throw new Response("Not Found", { status: 404 }); + } + + return { project: projects[0], organization: projects[0].organization }; + } +} diff --git a/apps/webapp/app/routes/_app._index/route.tsx b/apps/webapp/app/routes/_app._index/route.tsx new file mode 100644 index 000000000..466e074b2 --- /dev/null +++ b/apps/webapp/app/routes/_app._index/route.tsx @@ -0,0 +1,19 @@ +import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime"; +import { SelectBestProjectPresenter } from "~/presenters/SelectBestProjectPresenter.server"; +import { requireUserId } from "~/services/session.server"; +import { newOrganizationPath, projectPath } from "~/utils/pathBuilder"; + +//this loader chooses the best project to redirect you to, ideally based on the cookie +export const loader = async ({ request }: LoaderFunctionArgs) => { + const userId = await requireUserId(request); + + const presenter = new SelectBestProjectPresenter(); + try { + const { project, organization } = await presenter.call({ userId, request }); + + return redirect(projectPath(organization, project)); + } catch (e) { + //this should only happen if the user has no projects + return redirect(newOrganizationPath()); + } +}; diff --git a/apps/webapp/app/routes/_app._orgaccount._index/OrganizationGrid.tsx b/apps/webapp/app/routes/_app._orgaccount._index/OrganizationGrid.tsx deleted file mode 100644 index 5a7b004ac..000000000 --- a/apps/webapp/app/routes/_app._orgaccount._index/OrganizationGrid.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { ChevronRightIcon } from "@heroicons/react/24/solid"; -import { Link } from "@remix-run/react"; -import simplur from "simplur"; -import { Badge } from "~/components/primitives/Badge"; -import { LinkButton } from "~/components/primitives/Buttons"; -import { Header2 } from "~/components/primitives/Headers"; -import { NamedIcon } from "~/components/primitives/NamedIcon"; -import { Paragraph } from "~/components/primitives/Paragraph"; -import type { MatchedOrganization } from "~/hooks/useOrganizations"; -import { cn } from "~/utils/cn"; -import { newProjectPath, organizationPath, projectPath } from "~/utils/pathBuilder"; - -export function OrganizationGridItem({ organization }: { organization: MatchedOrganization }) { - return ( -
  • -
    - -
    -
    -
    -
  • - ); -} diff --git a/apps/webapp/app/routes/_app._orgaccount._index/route.tsx b/apps/webapp/app/routes/_app._orgaccount._index/route.tsx deleted file mode 100644 index 637b97851..000000000 --- a/apps/webapp/app/routes/_app._orgaccount._index/route.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { LoaderFunctionArgs, json } from "@remix-run/server-runtime"; -import { redirect } from "remix-typedjson"; -import { LinkButton } from "~/components/primitives/Buttons"; -import { useOptionalOrganizations } from "~/hooks/useOrganizations"; -import { getUsersInvites } from "~/models/member.server"; -import { getOrganizations } from "~/models/organization.server"; -import { requireUser } from "~/services/session.server"; -import { invitesPath, newOrganizationPath } from "~/utils/pathBuilder"; -import { OrganizationGridItem } from "./OrganizationGrid"; -import { Link } from "@remix-run/react"; -import { NamedIcon } from "~/components/primitives/NamedIcon"; -import { Paragraph } from "~/components/primitives/Paragraph"; - -export const loader = async ({ request }: LoaderFunctionArgs) => { - const user = await requireUser(request); - - //if there are invites then we should redirect to the invites page - const invites = await getUsersInvites({ email: user.email }); - if (invites.length > 0) { - return redirect(invitesPath()); - } - - //if there are no orgs, then redirect to create an org - const organizations = await getOrganizations({ userId: user.id }); - if (organizations.length === 0) { - return redirect(newOrganizationPath()); - } - - return json({}); -}; - -export default function Page() { - const organizations = useOptionalOrganizations(); - - return ( -
      - <> - {organizations && - organizations.map((organization) => ( - - ))} - -
    • - -
      -
      - -
    • -
    - ); -} diff --git a/apps/webapp/app/routes/_app._orgaccount/route.tsx b/apps/webapp/app/routes/_app._orgaccount/route.tsx deleted file mode 100644 index f23f336f5..000000000 --- a/apps/webapp/app/routes/_app._orgaccount/route.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { ArrowRightOnRectangleIcon } from "@heroicons/react/20/solid"; -import { Outlet } from "@remix-run/react"; -import { PageContainer, PageBody } from "~/components/layout/AppLayout"; -import { LinkButton } from "~/components/primitives/Buttons"; -import { - PageHeader, - PageTitleRow, - PageTitle, - PageButtons, - PageDescription, - PageTabs, -} from "~/components/primitives/PageHeader"; -import { - newOrganizationPath, - organizationsPath, - accountPath, - logoutPath, -} from "~/utils/pathBuilder"; - -export default function Page() { - return ( - - - - - - - Logout - - - - Create new Organizations and manage your account. - - - - - - - ); -} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/OrgAdminHeader.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/OrgAdminHeader.tsx deleted file mode 100644 index 073a0f276..000000000 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/OrgAdminHeader.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { LinkButton } from "~/components/primitives/Buttons"; -import { - PageHeader, - PageTitleRow, - PageTitle, - PageButtons, - PageDescription, - PageTabs, -} from "~/components/primitives/PageHeader"; -import { useOrganization } from "~/hooks/useOrganizations"; -import { - newProjectPath, - organizationBillingPath, - organizationPath, - organizationTeamPath, - organizationsPath, -} from "~/utils/pathBuilder"; - -export function OrgAdminHeader() { - const organization = useOrganization(); - - return ( - - - - - - Create a new project - - - - Manage your projects and team. - - - ); -} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/route.tsx index a23eca60f..ce1fdf340 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug._index/route.tsx @@ -1,20 +1,44 @@ import { Link } from "@remix-run/react"; import simplur from "simplur"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { LinkButton } from "~/components/primitives/Buttons"; import { Header3 } from "~/components/primitives/Headers"; import { NamedIcon } from "~/components/primitives/NamedIcon"; +import { + PageHeader, + PageTitleRow, + PageTitle, + PageButtons, +} from "~/components/primitives/PageHeader"; import { Paragraph } from "~/components/primitives/Paragraph"; import { useOrganization } from "~/hooks/useOrganizations"; +import { Handle } from "~/utils/handle"; import { newProjectPath, projectPath } from "~/utils/pathBuilder"; -import { OrgAdminHeader } from "./OrgAdminHeader"; + +export const handle: Handle = { + breadcrumb: (match) => , +}; export default function Page() { const organization = useOrganization(); return ( - + + + + + + Create a new project + + + +
      {organization.projects.length > 0 ? ( @@ -22,13 +46,13 @@ export default function Page() { return (
    • {project.name} - {simplur`${project._count.jobs} job[|s]`} + {simplur`${project.jobCount} Job[|s]`}
    • diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx index e0a38a6e6..763e99551 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx @@ -11,13 +11,26 @@ import { Header2 } from "~/components/primitives/Headers"; import { Paragraph } from "~/components/primitives/Paragraph"; import { TextLink } from "~/components/primitives/TextLink"; import { useOrganization } from "~/hooks/useOrganizations"; -import { OrganizationParamsSchema, jobPath, organizationTeamPath } from "~/utils/pathBuilder"; -import { OrgAdminHeader } from "../_app.orgs.$organizationSlug._index/OrgAdminHeader"; +import { + OrganizationParamsSchema, + jobPath, + newProjectPath, + organizationTeamPath, +} from "~/utils/pathBuilder"; import { Link } from "@remix-run/react/dist/components"; import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { OrgUsagePresenter } from "~/presenters/OrgUsagePresenter.server"; import { requireUserId } from "~/services/session.server"; +import { LinkButton } from "~/components/primitives/Buttons"; +import { + PageHeader, + PageTitleRow, + PageTitle, + PageButtons, +} from "~/components/primitives/PageHeader"; +import { Handle } from "~/utils/handle"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; export async function loader({ params, request }: LoaderFunctionArgs) { const userId = await requireUserId(request); @@ -47,13 +60,21 @@ const CustomTooltip = ({ active, payload, label }: TooltipProps) return null; }; +export const handle: Handle = { + breadcrumb: (match) => , +}; + export default function Page() { const organization = useOrganization(); const loaderData = useTypedLoaderData(); return ( - + + + + +
      @@ -151,7 +172,7 @@ export default function Page() {
      -
      +
      Jobs Runs diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations/route.tsx similarity index 74% rename from apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route.tsx rename to apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations/route.tsx index 8a9a1c476..2778bfd1d 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations/route.tsx @@ -1,15 +1,15 @@ import { ChevronRightIcon } from "@heroicons/react/24/solid"; import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { useCallback, useState } from "react"; +import { useState } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; +import { IntegrationIcon } from "~/assets/icons/IntegrationIcon"; import { Feedback } from "~/components/Feedback"; -import { LogoIcon } from "~/components/LogoIcon"; import { HowToConnectAnIntegration } from "~/components/helpContent/HelpContentText"; import { ConnectToIntegrationSheet } from "~/components/integrations/ConnectToIntegrationSheet"; import { IntegrationWithMissingFieldSheet } from "~/components/integrations/IntegrationWithMissingFieldSheet"; import { NoIntegrationSheet } from "~/components/integrations/NoIntegrationSheet"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { LinkButton } from "~/components/primitives/Buttons"; import { Callout } from "~/components/primitives/Callout"; import { DateTime } from "~/components/primitives/DateTime"; @@ -17,7 +17,7 @@ import { DetailCell } from "~/components/primitives/DetailCell"; import { Header2 } from "~/components/primitives/Headers"; import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help"; import { Input } from "~/components/primitives/Input"; -import { NamedIcon, NamedIconInBox } from "~/components/primitives/NamedIcon"; +import { NamedIcon } from "~/components/primitives/NamedIcon"; import { PageButtons, PageDescription, @@ -25,7 +25,6 @@ import { PageTitle, PageTitleRow, } from "~/components/primitives/PageHeader"; -import { Paragraph } from "~/components/primitives/Paragraph"; import { Switch } from "~/components/primitives/Switch"; import { Table, @@ -38,10 +37,9 @@ import { TableRow, } from "~/components/primitives/Table"; import { SimpleTooltip } from "~/components/primitives/Tooltip"; -import { useOrganization } from "~/hooks/useOrganizations"; +import { MatchedOrganization, useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { useTextFilter } from "~/hooks/useTextFilter"; -import { Organization } from "~/models/organization.server"; import { Project } from "~/models/project.server"; import { Client, @@ -51,20 +49,21 @@ import { import { requireUser } from "~/services/session.server"; import { Handle } from "~/utils/handle"; import { + OrganizationParamsSchema, ProjectParamSchema, docsCreateIntegration, + docsPath, integrationClientPath, } from "~/utils/pathBuilder"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); - const { organizationSlug, projectParam } = ProjectParamSchema.parse(params); + const { organizationSlug } = OrganizationParamsSchema.parse(params); const presenter = new IntegrationsPresenter(); const data = await presenter.call({ userId: user.id, organizationSlug, - projectSlug: projectParam, }); return typedjson(data); @@ -72,14 +71,12 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { export const handle: Handle = { breadcrumb: (match) => , - expandSidebar: true, }; export default function Integrations() { const { clients, clientMissingFields, options, callbackUrl } = useTypedLoaderData(); const organization = useOrganization(); - const project = useProject(); return ( @@ -87,8 +84,12 @@ export default function Integrations() { - - Create your own Integration + + Integrations documentation @@ -113,11 +114,7 @@ export default function Integrations() { options={options} /> )} - +
      @@ -144,94 +141,96 @@ function PossibleIntegrationsList({ }); return ( -
      -
      - Connect an API - - Trigger.dev Integrations - - } +
      +
      +
      + Connect an API + + Trigger.dev Integrations + + } + /> +
      + setFilterText(e.target.value)} /> -
      - setFilterText(e.target.value)} - /> -
      - {filteredItems.map((option) => { - switch (option.type) { - case "integration": - return ( - - } - /> - ); - case "api": - return ( - - } - /> - ); +
      + {filteredItems.map((option) => { + switch (option.type) { + case "integration": + return ( + + } + /> + ); + case "api": + return ( + + } + /> + ); + } + })} +
      + Missing an API? + + + } - })} -
      - Missing an API? - - - - } - defaultValue="integration" - /> + defaultValue="integration" + /> - Create an Integration - - - + Create an Integration + + + +
      ); } @@ -239,11 +238,9 @@ function PossibleIntegrationsList({ function ConnectedIntegrationsList({ clients, organization, - project, }: { clients: Client[]; - organization: Organization; - project: Project; + organization: MatchedOrganization; }) { const { filterText, setFilterText, filteredItems } = useTextFilter({ items: clients, @@ -278,19 +275,20 @@ function ConnectedIntegrationsList({
      Your connected Integrations -
      {clients.length > 0 && (
      - setFilterText(e.target.value)} - /> +
      + setFilterText(e.target.value)} + /> + +
    @@ -318,7 +316,7 @@ function ConnectedIntegrationsList({ ) : ( <> {filteredItems.map((client) => { - const path = integrationClientPath(organization, project, client); + const path = integrationClientPath(organization, client); return ( {client.title} @@ -496,7 +494,3 @@ function AddIntegrationConnection({ /> ); } - -export function IntegrationIcon() { - return ; -} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam._index/route.tsx similarity index 94% rename from apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam._index/route.tsx rename to apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam._index/route.tsx index 46bcdb351..f414b2434 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam._index/route.tsx @@ -3,7 +3,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { HowToUseThisIntegration } from "~/components/helpContent/HelpContentText"; import { JobSkeleton } from "~/components/jobs/JobSkeleton"; import { JobsTable } from "~/components/jobs/JobsTable"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Callout } from "~/components/primitives/Callout"; import { Header2 } from "~/components/primitives/Headers"; import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help"; @@ -23,14 +23,12 @@ import { export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); - const { organizationSlug, projectParam, clientParam } = - IntegrationClientParamSchema.parse(params); + const { organizationSlug, clientParam } = IntegrationClientParamSchema.parse(params); const jobsPresenter = new JobListPresenter(); const jobs = await jobsPresenter.call({ userId, - projectSlug: projectParam, organizationSlug, integrationSlug: clientParam, }); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.connections/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.connections/route.tsx similarity index 93% rename from apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.connections/route.tsx rename to apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.connections/route.tsx index b94e29e1d..393cf061c 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.connections/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.connections/route.tsx @@ -1,7 +1,7 @@ import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { connectionType } from "~/components/integrations/connectionType"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { DateTime } from "~/components/primitives/DateTime"; import { Paragraph } from "~/components/primitives/Paragraph"; import { @@ -20,14 +20,12 @@ import { IntegrationClientParamSchema, trimTrailingSlash } from "~/utils/pathBui export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); - const { organizationSlug, projectParam, clientParam } = - IntegrationClientParamSchema.parse(params); + const { organizationSlug, clientParam } = IntegrationClientParamSchema.parse(params); const presenter = new IntegrationClientConnectionsPresenter(); const { connections } = await presenter.call({ userId: userId, organizationSlug, - projectSlug: projectParam, clientSlug: clientParam, }); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.scopes/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.scopes/route.tsx similarity index 87% rename from apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.scopes/route.tsx rename to apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.scopes/route.tsx index 32a3ed0f3..032e7a8ae 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam.scopes/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam.scopes/route.tsx @@ -1,6 +1,6 @@ import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Paragraph } from "~/components/primitives/Paragraph"; import { IntegrationClientScopesPresenter } from "~/presenters/IntegrationClientScopesPresenter.server"; import { requireUserId } from "~/services/session.server"; @@ -9,14 +9,12 @@ import { IntegrationClientParamSchema, trimTrailingSlash } from "~/utils/pathBui export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); - const { organizationSlug, projectParam, clientParam } = - IntegrationClientParamSchema.parse(params); + const { organizationSlug, clientParam } = IntegrationClientParamSchema.parse(params); const presenter = new IntegrationClientScopesPresenter(); const { scopes } = await presenter.call({ userId: userId, organizationSlug, - projectSlug: projectParam, clientSlug: clientParam, }); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam/route.tsx similarity index 86% rename from apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route.tsx rename to apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam/route.tsx index f78cb2818..cf3d2a457 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.integrations_.$clientParam/route.tsx @@ -3,7 +3,7 @@ import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { connectionType } from "~/components/integrations/connectionType"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { ClipboardField } from "~/components/primitives/ClipboardField"; import { DateTime } from "~/components/primitives/DateTime"; import { @@ -26,19 +26,17 @@ import { integrationClientConnectionsPath, integrationClientPath, integrationClientScopesPath, - projectIntegrationsPath, + organizationIntegrationsPath, } from "~/utils/pathBuilder"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); - const { organizationSlug, clientParam, projectParam } = - IntegrationClientParamSchema.parse(params); + const { organizationSlug, clientParam } = IntegrationClientParamSchema.parse(params); const presenter = new IntegrationClientPresenter(); const client = await presenter.call({ userId: user.id, organizationSlug, - projectSlug: projectParam, clientSlug: clientParam, }); @@ -59,23 +57,22 @@ export const handle: Handle = { export default function Integrations() { const { client } = useTypedLoaderData(); const organization = useOrganization(); - const project = useProject(); let tabs = [ { label: "Jobs", - to: integrationClientPath(organization, project, client), + to: integrationClientPath(organization, client), }, ]; if (client.authMethod.type !== "local") { tabs.push({ label: "Connections", - to: integrationClientConnectionsPath(organization, project, client), + to: integrationClientConnectionsPath(organization, client), }); tabs.push({ label: "Scopes", - to: integrationClientScopesPath(organization, project, client), + to: integrationClientScopesPath(organization, client), }); } @@ -86,7 +83,7 @@ export default function Integrations() { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx index 8430ff60d..c08215b40 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx @@ -8,6 +8,7 @@ import simplur from "simplur"; import invariant from "tiny-invariant"; import { z } from "zod"; import { MainCenteredContainer } from "~/components/layout/AppLayout"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { Fieldset } from "~/components/primitives/Fieldset"; import { FormButtons } from "~/components/primitives/FormButtons"; @@ -17,12 +18,16 @@ import { Input } from "~/components/primitives/Input"; import { InputGroup } from "~/components/primitives/InputGroup"; import { Label } from "~/components/primitives/Label"; import { env } from "~/env.server"; -import { useOrganization } from "~/hooks/useOrganizations"; +import { organizationMatchId, useOrganization } from "~/hooks/useOrganizations"; +import { useTypedMatchData } from "~/hooks/useTypedMatchData"; import { inviteMembers } from "~/models/member.server"; import { redirectWithSuccessMessage } from "~/models/message.server"; import { scheduleEmail } from "~/services/email.server"; import { requireUserId } from "~/services/session.server"; +import { Handle } from "~/utils/handle"; import { acceptInvitePath, organizationTeamPath } from "~/utils/pathBuilder"; +import { loader } from "../_app.orgs.$organizationSlug/route"; +import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon"; const schema = z.object({ emails: z.preprocess((i) => { @@ -85,6 +90,20 @@ export const action: ActionFunction = async ({ request, params }) => { } }; +export const handle: Handle = { + breadcrumb: (match, matches) => { + const orgMatch = matches.find((m) => m.id === organizationMatchId); + const data = useTypedMatchData(orgMatch); + return ( + + {data && } + + + + ); + }, +}; + export default function Page() { const organization = useOrganization(); const lastSubmission = useActionData(); @@ -107,7 +126,7 @@ export default function Page() {
    diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx index d3503b8f2..c7b661d89 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx @@ -4,7 +4,6 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { FrameworkSelector } from "~/components/frameworks/FrameworkSelector"; import { JobsTable } from "~/components/jobs/JobsTable"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; import { Callout } from "~/components/primitives/Callout"; import { Header2 } from "~/components/primitives/Headers"; import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help"; @@ -27,20 +26,15 @@ import { useProject } from "~/hooks/useProject"; import { JobListPresenter } from "~/presenters/JobListPresenter.server"; import { requireUserId } from "~/services/session.server"; import { cn } from "~/utils/cn"; -import { Handle } from "~/utils/handle"; -import { - ProjectParamSchema, - projectIntegrationsPath, - trimTrailingSlash, -} from "~/utils/pathBuilder"; +import { ProjectParamSchema, organizationIntegrationsPath } from "~/utils/pathBuilder"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); - const { projectParam } = ProjectParamSchema.parse(params); + const { organizationSlug, projectParam } = ProjectParamSchema.parse(params); try { const presenter = new JobListPresenter(); - const jobs = await presenter.call({ userId, projectSlug: projectParam }); + const jobs = await presenter.call({ userId, organizationSlug, projectSlug: projectParam }); return typedjson({ jobs, @@ -54,11 +48,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { } }; -export const handle: Handle = { - breadcrumb: (match) => , - expandSidebar: true, -}; - export default function Page() { const organization = useOrganization(); const project = useProject(); @@ -78,8 +67,8 @@ export default function Page() { - - + + @@ -94,7 +83,7 @@ export default function Page() { {jobs.some((j) => j.hasIntegrationsRequiringAction) && ( Some of your Jobs have Integrations that have not been configured. @@ -200,7 +189,7 @@ function ExampleJobs() { {example.icon} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx index 5e3831689..6c59413d4 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/FirstEndpointSheet.tsx @@ -29,7 +29,6 @@ import { SelectValue, } from "~/components/primitives/Select"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; -import { useRef, useState } from "react"; type FirstEndpointSheetProps = { projectId: string; @@ -52,16 +51,16 @@ export function FirstEndpointSheet({ projectId, environments }: FirstEndpointShe return ( - Add your first endpoint +
    Add your first endpoint - We recommend you use{" "} - the CLI when working - in development. + We recommend you{" "} + use the CLI when + working in development.
    diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx index 8be34a8d7..77f41351c 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx @@ -10,14 +10,15 @@ import { import { EnvironmentLabel, environmentTitle } from "~/components/environments/EnvironmentLabel"; import { HowToUseApiKeysAndEndpoints } from "~/components/helpContent/HelpContentText"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Badge } from "~/components/primitives/Badge"; -import { ButtonContent } from "~/components/primitives/Buttons"; +import { ButtonContent, LinkButton } from "~/components/primitives/Buttons"; import { ClipboardField } from "~/components/primitives/ClipboardField"; import { DateTime } from "~/components/primitives/DateTime"; import { Header2, Header3 } from "~/components/primitives/Headers"; import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help"; import { + PageButtons, PageDescription, PageHeader, PageTitle, @@ -39,7 +40,11 @@ import { ClientEndpoint, EnvironmentsPresenter } from "~/presenters/Environments import { requireUserId } from "~/services/session.server"; import { cn } from "~/utils/cn"; import { Handle } from "~/utils/handle"; -import { ProjectParamSchema, projectEnvironmentsStreamingPath } from "~/utils/pathBuilder"; +import { + ProjectParamSchema, + docsPath, + projectEnvironmentsStreamingPath, +} from "~/utils/pathBuilder"; import { requestUrl } from "~/utils/requestUrl.server"; import { RuntimeEnvironmentType } from "../../../../../packages/database/src"; import { ConfigureEndpointSheet } from "./ConfigureEndpointSheet"; @@ -74,8 +79,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }; export const handle: Handle = { - breadcrumb: (match) => , - expandSidebar: true, + breadcrumb: (match) => , }; export default function Page() { @@ -128,6 +132,15 @@ export default function Page() { + + + Environments documentation + + API Keys and endpoints for your environments. @@ -164,13 +177,13 @@ export default function Page() { className="w-full max-w-none" secure value={environment.apiKey} - variant={"primary/medium"} + variant={"secondary/medium"} icon={Server} /> Public} /> @@ -234,12 +247,9 @@ export default function Page() { )) ) : ( - <> - Add your first endpoint - - - - + + + )} {selectedEndpoint && selectedEndpoint.endpoint && ( @@ -285,7 +295,7 @@ function EndpointRow({ The {environmentTitle({ type })} environment is not configured - Configure + Configure diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints.$httpEndpointParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints.$httpEndpointParam/route.tsx new file mode 100644 index 000000000..f1af7770f --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints.$httpEndpointParam/route.tsx @@ -0,0 +1,236 @@ +import { CheckIcon } from "@heroicons/react/20/solid"; +import { StopIcon } from "@heroicons/react/24/outline"; +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; +import { typedjson, useTypedLoaderData } from "remix-typedjson"; +import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; +import { HowToConnectHttpEndpoint } from "~/components/helpContent/HelpContentText"; +import { PageBody, PageContainer } from "~/components/layout/AppLayout"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; +import { LinkButton } from "~/components/primitives/Buttons"; +import { ClipboardField } from "~/components/primitives/ClipboardField"; +import { DateTime } from "~/components/primitives/DateTime"; +import { Header1 } from "~/components/primitives/Headers"; +import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help"; +import { + PageButtons, + PageHeader, + PageTitle, + PageTitleRow, +} from "~/components/primitives/PageHeader"; +import { Paragraph } from "~/components/primitives/Paragraph"; +import { + Table, + TableBody, + TableCell, + TableHeader, + TableHeaderCell, + TableRow, +} from "~/components/primitives/Table"; +import { TextLink } from "~/components/primitives/TextLink"; +import { useOrganization } from "~/hooks/useOrganizations"; +import { useProject } from "~/hooks/useProject"; +import { useTypedMatchData } from "~/hooks/useTypedMatchData"; +import { HttpEndpointPresenter } from "~/presenters/HttpEndpointPresenter.server"; +import { requireUserId } from "~/services/session.server"; +import { cn } from "~/utils/cn"; +import { Handle } from "~/utils/handle"; +import { HttpEndpointParamSchema, docsPath, projectHttpEndpointsPath } from "~/utils/pathBuilder"; + +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + const userId = await requireUserId(request); + const { projectParam, httpEndpointParam } = HttpEndpointParamSchema.parse(params); + + const presenter = new HttpEndpointPresenter(); + try { + const result = await presenter.call({ + userId, + projectSlug: projectParam, + httpEndpointKey: httpEndpointParam, + }); + + if (!result) { + throw new Response("Not Found", { status: 404 }); + } + + return typedjson(result); + } catch (e) { + console.log(e); + throw new Response(e instanceof Error ? e.message : JSON.stringify(e), { status: 404 }); + } +}; + +export const handle: Handle = { + breadcrumb: (match) => { + const data = useTypedMatchData(match); + return ( + + ); + }, +}; + +export default function Page() { + const { httpEndpoint, unconfiguredEnvironments, secret } = useTypedLoaderData(); + const organization = useOrganization(); + const project = useProject(); + + return ( + + + + + + + HTTP endpoints documentation + + + + + + + {(open) => ( +
    +
    +
    + + {httpEndpoint.httpEndpointEnvironments.length > 0 + ? "Ready to receive data" + : "Not deployed"} + + +
    + {httpEndpoint.httpEndpointEnvironments.length > 0 && ( +
    +
    + + + Environment + Endpoint URL + Secret + Source + Respond to request? + Updated + + + + {httpEndpoint.httpEndpointEnvironments.map((httpEnvironment) => { + return ( + + + + + + + + + + + {httpEnvironment.source} + + {httpEnvironment.immediateResponseFilter ? ( + + ) : ( + + )} + + + + + + ); + })} + +
    +
    + )} + + {unconfiguredEnvironments.length > 0 && ( + <> + Not deployed + + You need to deploy your code for the following environments to receive + webhooks –{" "} + + read our deployment guide + + . + +
    + + + + Environment + Endpoint URL + Secret + + + + {unconfiguredEnvironments.map((environment) => { + return ( + + + + + + + + + + + + ); + })} + +
    +
    + + )} +
    + + + +
    + )} + + + + ); +} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints._index/route.tsx new file mode 100644 index 000000000..52ed9ca8c --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints._index/route.tsx @@ -0,0 +1,136 @@ +import { LoaderFunctionArgs } from "@remix-run/server-runtime"; +import { typedjson, useTypedLoaderData } from "remix-typedjson"; +import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; +import { WhatAreHttpEndpoints } from "~/components/helpContent/HelpContentText"; +import { PageBody, PageContainer } from "~/components/layout/AppLayout"; +import { LinkButton } from "~/components/primitives/Buttons"; +import { DateTime } from "~/components/primitives/DateTime"; +import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help"; +import { Icon } from "~/components/primitives/Icon"; +import { + PageButtons, + PageDescription, + PageHeader, + PageTitle, + PageTitleRow, +} from "~/components/primitives/PageHeader"; +import { Paragraph } from "~/components/primitives/Paragraph"; +import { + Table, + TableBlankRow, + TableBody, + TableCell, + TableCellChevron, + TableHeader, + TableHeaderCell, + TableRow, +} from "~/components/primitives/Table"; +import { useOrganization } from "~/hooks/useOrganizations"; +import { useProject } from "~/hooks/useProject"; +import { HttpEndpointsPresenter } from "~/presenters/HttpEndpointsPresenter.server"; +import { requireUserId } from "~/services/session.server"; +import { cn } from "~/utils/cn"; +import { ProjectParamSchema, docsPath, projectHttpEndpointPath } from "~/utils/pathBuilder"; + +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + const userId = await requireUserId(request); + const { projectParam } = ProjectParamSchema.parse(params); + + const presenter = new HttpEndpointsPresenter(); + const httpEndpoints = await presenter.call({ userId, slug: projectParam }); + + return typedjson({ httpEndpoints }); +}; + +export default function Page() { + const { httpEndpoints } = useTypedLoaderData(); + const organization = useOrganization(); + const project = useProject(); + + return ( + + + + + + + HTTP endpoints documentation + + + + + HTTP endpoints allow you to receive webhooks from any API. + + + + + {(open) => ( +
    +
    +
    + +
    +
    + + + + ID + Title + Updated + Environments + Go to page + + + + {httpEndpoints.length > 0 ? ( + httpEndpoints.map((httpEndpoint) => { + const path = projectHttpEndpointPath(organization, project, httpEndpoint); + return ( + + +
    + + {httpEndpoint.key} +
    +
    + {httpEndpoint.title ?? "–"} + + + + +
    + {httpEndpoint.httpEndpointEnvironments.map((environment) => ( + + ))} +
    +
    + +
    + ); + }) + ) : ( + + No HTTP endpoints + + )} +
    +
    +
    +
    + + + +
    + )} +
    +
    +
    + ); +} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints/route.tsx new file mode 100644 index 000000000..bbaf2d369 --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.http-endpoints/route.tsx @@ -0,0 +1,11 @@ +import { Outlet } from "@remix-run/react"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; +import { Handle } from "~/utils/handle"; + +export const handle: Handle = { + breadcrumb: (match) => , +}; + +export default function Page() { + return ; +} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx index 17dcd7ab4..ceeb43196 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx @@ -1,27 +1,24 @@ import { useNavigation } from "@remix-run/react"; import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; +import simplur from "simplur"; import { z } from "zod"; import { HowToRunYourJob } from "~/components/helpContent/HelpContentText"; +import { Callout } from "~/components/primitives/Callout"; import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help"; import { RunsTable } from "~/components/runs/RunsTable"; +import { useJob } from "~/hooks/useJob"; +import { useOrganization } from "~/hooks/useOrganizations"; +import { useProject } from "~/hooks/useProject"; import { RunListPresenter } from "~/presenters/RunListPresenter.server"; import { requireUserId } from "~/services/session.server"; import { cn } from "~/utils/cn"; -import { Handle } from "~/utils/handle"; import { JobParamsSchema, - projectIntegrationsPath, jobRunsParentPath, - trimTrailingSlash, + organizationIntegrationsPath, } from "~/utils/pathBuilder"; import { ListPagination } from "./ListPagination"; -import { Callout } from "~/components/primitives/Callout"; -import { useOrganization } from "~/hooks/useOrganizations"; -import { useProject } from "~/hooks/useProject"; -import { useJob } from "~/hooks/useJob"; -import simplur from "simplur"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; export const DirectionSchema = z.union([z.literal("forward"), z.literal("backward")]); @@ -53,10 +50,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }); }; -export const handle: Handle = { - breadcrumb: (match) => , -}; - export default function Page() { const { list } = useTypedLoaderData(); const navigation = useNavigation(); @@ -68,11 +61,7 @@ export default function Page() { return ( <> {job.hasIntegrationsRequiringAction && ( - + {simplur`This Job has ${ job.integrations.filter((j) => j.setupStatus === "MISSING_FIELDS").length } Integration[|s] that [has|have] not been configured.`} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx index a2bdb1fd2..8cdbcec4f 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx @@ -3,7 +3,7 @@ import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { Fragment, useEffect } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { useEventSource } from "remix-utils/sse/react"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon"; import { RunOverview } from "~/components/run/RunOverview"; import { jobMatchId, useJob } from "~/hooks/useJob"; @@ -50,8 +50,6 @@ export const handle: Handle = { return ( - - {runData && runData.run && ( )} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.settings/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.settings/route.tsx index c52c1e8d2..fdfdbec59 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.settings/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.settings/route.tsx @@ -1,6 +1,7 @@ import { JobEnvironment, JobStatusTable } from "~/components/JobsStatusTable"; import { HowToDisableAJob } from "~/components/helpContent/HelpContentText"; import { DeleteJobDialogContent } from "~/components/jobs/DeleteJobModalContent"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Button } from "~/components/primitives/Buttons"; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components/primitives/Dialog"; import { Header2 } from "~/components/primitives/Headers"; @@ -10,8 +11,13 @@ import { useJob } from "~/hooks/useJob"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { cn } from "~/utils/cn"; +import { Handle } from "~/utils/handle"; import { projectJobsPath, projectPath } from "~/utils/pathBuilder"; +export const handle: Handle = { + breadcrumb: (match) => , +}; + export default function Page() { const job = useJob(); const organization = useOrganization(); @@ -45,15 +51,14 @@ export default function Page() { - - - + Delete Job +
    diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx index 73640b5a1..e8654c32f 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.test/route.tsx @@ -9,7 +9,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { JSONEditor } from "~/components/code/JSONEditor"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { Callout } from "~/components/primitives/Callout"; import { DateTime } from "~/components/primitives/DateTime"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx index 72af5ee25..dffd6cd79 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route.tsx @@ -4,10 +4,10 @@ import { Fragment } from "react"; import { typedjson } from "remix-typedjson"; import { JobStatusBadge } from "~/components/jobs/JobStatusBadge"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; -import { JobsMenu } from "~/components/navigation/JobsMenu"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon"; -import { LinkButton } from "~/components/primitives/Buttons"; +import { Button, LinkButton } from "~/components/primitives/Buttons"; +import { Callout } from "~/components/primitives/Callout"; import { NamedIcon } from "~/components/primitives/NamedIcon"; import { PageButtons, @@ -24,9 +24,12 @@ import { useJob } from "~/hooks/useJob"; import { useOrganization } from "~/hooks/useOrganizations"; import { projectMatchId, useProject } from "~/hooks/useProject"; import { useOptionalRun } from "~/hooks/useRun"; +import { useTypedMatchData } from "~/hooks/useTypedMatchData"; import { findJobByParams } from "~/models/job.server"; import { JobListPresenter } from "~/presenters/JobListPresenter.server"; +import { JobPresenter } from "~/presenters/JobPresenter.server"; import { requireUserId } from "~/services/session.server"; +import { titleCase } from "~/utils"; import { Handle } from "~/utils/handle"; import { JobParamsSchema, @@ -41,43 +44,34 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const { jobParam, projectParam, organizationSlug } = JobParamsSchema.parse(params); - const jobsPresenter = new JobListPresenter(); + const presenter = new JobPresenter(); + const job = await presenter.call({ + userId, + jobSlug: jobParam, + organizationSlug, + projectSlug: projectParam, + }); - const [job, projectJobs] = await Promise.all([ - findJobByParams({ - userId, - slug: jobParam, - projectSlug: projectParam, - organizationSlug, - }), - jobsPresenter.call({ userId, projectSlug: projectParam }), - ]); - - if (job === null) { + if (!job) { throw new Response("Not Found", { status: 404, statusText: `There is no Job ${jobParam} in this Project.`, }); } - //todo identify job - // analytics.job.identify({ job }); - return typedjson({ job, - projectJobs, }); }; export const handle: Handle = { - breadcrumb: (_match, matches) => { - const projectMatch = matches.find((m) => m.id === projectMatchId); + breadcrumb: (match) => { + const data = useTypedMatchData(match); return ( - - - - - + ); }, }; @@ -111,7 +105,12 @@ export default function Job() { - + {job.dynamic && } {job.properties && @@ -138,14 +137,7 @@ export default function Job() { - } + value={titleCase(job.status.toLowerCase())} /> @@ -154,14 +146,17 @@ export default function Job() { + + {job.noRunsHelp && ( + + {job.noRunsHelp.text} + + )} + , @@ -42,71 +32,69 @@ export default function SetUpAstro() { const appOrigin = useAppOrigin(); return ( - -
    -
    - -
    -
    - - Get setup in 5 minutes - -
    - - Choose a different framework - - - I'm stuck! - - } - defaultValue="help" - /> -
    -
    -
    - +
    + +
    +
    + + Get setup in 5 minutes + +
    + - Trigger.dev has full support for serverless. We will be adding support for long-running - servers soon. - -
    - - - - - - You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple - example Job in example.ts to help you - get started. - - - - - - - - - - - - - This page will automatically refresh. - -
    + Choose a different framework +
    + + I'm stuck! + + } + defaultValue="help" + />
    - +
    + + Trigger.dev has full support for serverless. We will be adding support for long-running + servers soon. + +
    + + + + + + You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple + example Job in example.ts to help you + get started. + + + + + + + + + + + + + This page will automatically refresh. + +
    +
    +
    ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx index 7823cf914..42179fbb3 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx @@ -2,11 +2,9 @@ import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/sol import invariant from "tiny-invariant"; import { ExpressLogo } from "~/assets/logos/ExpressLogo"; import { Feedback } from "~/components/Feedback"; -import { PageGradient } from "~/components/PageGradient"; -import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands"; +import { RunDevCommand, TriggerDevStep } from "~/components/SetupCommands"; import { StepContentContainer } from "~/components/StepContentContainer"; -import { InlineCode } from "~/components/code/InlineCode"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Badge } from "~/components/primitives/Badge"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { Callout } from "~/components/primitives/Callout"; @@ -35,86 +33,84 @@ export default function Page() { const appOrigin = useAppOrigin(); return ( - -
    -
    - -
    -
    - - Get setup in 5 minutes - -
    - - Choose a different framework - - - I'm stuck! - - } - defaultValue="help" - /> -
    -
    -
    - +
    + +
    +
    + + Get setup in 5 minutes + +
    + - Trigger.dev has full support for serverless. We will be adding support for long-running - servers soon. - -
    - - - Copy your server API Key to your clipboard: -
    - Server} - /> -
    - Now follow this guide: - - Manual installation guide - -
    - - - - - You may be using the `start` script instead, in which case substitute `dev` in the - above commands. - - - - - - - - - This page will automatically refresh. - -
    + Choose a different framework +
    + + I'm stuck! + + } + defaultValue="help" + />
    - +
    + + Trigger.dev has full support for serverless. We will be adding support for long-running + servers soon. + +
    + + + Copy your server API Key to your clipboard: +
    + Server} + /> +
    + Now follow this guide: + + Manual installation guide + +
    + + + + + You may be using the `start` script instead, in which case substitute `dev` in the + above commands. + + + + + + + + + This page will automatically refresh. + +
    +
    +
    ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.fastify/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.fastify/route.tsx index a9eaa7469..4346b1b99 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.fastify/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.fastify/route.tsx @@ -1,6 +1,6 @@ import { FastifyLogo } from "~/assets/logos/FastifyLogo"; import { FrameworkComingSoon } from "~/components/frameworks/FrameworkComingSoon"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Handle } from "~/utils/handle"; import { trimTrailingSlash } from "~/utils/pathBuilder"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nestjs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nestjs/route.tsx index d13b547a0..0f221f53c 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nestjs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nestjs/route.tsx @@ -1,11 +1,12 @@ import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid"; import invariant from "tiny-invariant"; +import { NestjsLogo } from "~/assets/logos/NestjsLogo"; import { Feedback } from "~/components/Feedback"; -import { PageGradient } from "~/components/PageGradient"; +import { TriggerDevStep } from "~/components/SetupCommands"; import { StepContentContainer } from "~/components/StepContentContainer"; import { InlineCode } from "~/components/code/InlineCode"; import { InstallPackages } from "~/components/code/InstallPackages"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { Header1 } from "~/components/primitives/Headers"; import { Paragraph } from "~/components/primitives/Paragraph"; @@ -18,7 +19,6 @@ import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete"; import { Handle } from "~/utils/handle"; import { projectSetupPath, trimTrailingSlash } from "~/utils/pathBuilder"; import { CodeBlock } from "../../components/code/CodeBlock"; -import { TriggerDevStep } from "~/components/SetupCommands"; export const handle: Handle = { breadcrumb: (match) => , @@ -126,95 +126,96 @@ export default function SetupNestJS() { invariant(devEnvironment, "devEnvironment is required"); return ( - -
    -
    - - Get setup in 2 minutes - -
    - - Choose a different framework - - - I'm stuck! - - } - defaultValue="help" - /> -
    -
    - <> - - - - - - - - Inside your .env file, create the following env variables: - - - - - - - Now, go to your app.module.ts and add the{" "} - TriggerDevModule: - - - - - - - Create a controller called{" "} - job.controller.ts and add the following code: - - - - - - - Now, add the new controller to your{" "} - app.module.ts: - - - - - - - Now, add this to the top-level of your package.json: - - - - - - - Finally, run your project with npm run start: - - - - - - - - - This page will automatically refresh. - - +
    +
    +
    - +
    + + Get setup in 5 minutes + +
    + + Choose a different framework + + + I'm stuck! + + } + defaultValue="help" + /> +
    +
    + <> + + + + + + + + Inside your .env file, create the following env variables: + + + + + + + Now, go to your app.module.ts and add the{" "} + TriggerDevModule: + + + + + + + Create a controller called{" "} + job.controller.ts and add the following code: + + + + + + + Now, add the new controller to your{" "} + app.module.ts: + + + + + + + Now, add this to the top-level of your package.json: + + + + + + + Finally, run your project with npm run start: + + + + + + + + + This page will automatically refresh. + + +
    ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nextjs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nextjs/route.tsx index 9cb4f3175..09fb9bab6 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nextjs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nextjs/route.tsx @@ -1,13 +1,14 @@ import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid"; import { useState } from "react"; import invariant from "tiny-invariant"; +import { NextjsLogo } from "~/assets/logos/NextjsLogo"; import { Feedback } from "~/components/Feedback"; -import { PageGradient } from "~/components/PageGradient"; import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands"; import { StepContentContainer } from "~/components/StepContentContainer"; import { InlineCode } from "~/components/code/InlineCode"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Button, LinkButton } from "~/components/primitives/Buttons"; +import { Callout } from "~/components/primitives/Callout"; import { ClientTabs, ClientTabsContent, @@ -21,14 +22,12 @@ import { Paragraph } from "~/components/primitives/Paragraph"; import { RadioGroup, RadioGroupItem } from "~/components/primitives/RadioButton"; import { StepNumber } from "~/components/primitives/StepNumber"; import { useAppOrigin } from "~/hooks/useAppOrigin"; -import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete"; import { useDevEnvironment } from "~/hooks/useEnvironments"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; +import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete"; import { Handle } from "~/utils/handle"; import { projectSetupPath, trimTrailingSlash } from "~/utils/pathBuilder"; -import { Callout } from "~/components/primitives/Callout"; -import { NextjsLogo } from "~/assets/logos/NextjsLogo"; type SelectionChoices = "use-existing-project" | "create-new-next-app"; @@ -47,171 +46,169 @@ export default function SetupNextjs() { invariant(devEnvironment, "devEnvironment is required"); return ( - -
    -
    - -
    -
    - - Get setup in {selectedValue === "create-new-next-app" ? "5" : "2"} minutes - -
    - - Choose a different framework - - - I'm stuck! - - } - defaultValue="help" - /> -
    -
    - setSelectedValue(value as SelectionChoices)} - > - } - /> - } - /> - - {selectedValue && ( - <> - - Trigger.dev has full support for serverless. We will be adding support for - long-running servers soon. - - {selectedValue === "create-new-next-app" ? ( - <> - - - - - npm - pnpm - yarn - - - - - - - - - - - - - - Trigger.dev works with either the Pages or App Router configuration. - - - - - - You have now created a new Next.js project. Let’s cd{" "} - into it using the project name you just provided: - - - - - - - - You’ll notice a new folder in your project called 'jobs'. We’ve added a very - simple example Job in examples.ts{" "} - to help you get started. - - - - - - - - - - - - - This page will automatically refresh. - - - ) : ( - <> - - - - - - You’ll notice a new folder in your project called 'jobs'. We’ve added a very - simple example Job in examples.ts{" "} - to help you get started. - - - - - - - - - - - - - This page will automatically refresh. - - - )} - - )} +
    +
    +
    - +
    + + Get setup in {selectedValue === "create-new-next-app" ? "5" : "2"} minutes + +
    + + Choose a different framework + + + I'm stuck! + + } + defaultValue="help" + /> +
    +
    + setSelectedValue(value as SelectionChoices)} + > + } + /> + } + /> + + {selectedValue && ( + <> + + Trigger.dev has full support for serverless. We will be adding support for long-running + servers soon. + + {selectedValue === "create-new-next-app" ? ( + <> + + + + + npm + pnpm + yarn + + + + + + + + + + + + + + Trigger.dev works with either the Pages or App Router configuration. + + + + + + You have now created a new Next.js project. Let’s cd into + it using the project name you just provided: + + + + + + + + You’ll notice a new folder in your project called 'jobs'. We’ve added a very + simple example Job in examples.ts{" "} + to help you get started. + + + + + + + + + + + + + This page will automatically refresh. + + + ) : ( + <> + + + + + + You’ll notice a new folder in your project called 'jobs'. We’ve added a very + simple example Job in examples.ts{" "} + to help you get started. + + + + + + + + + + + + + This page will automatically refresh. + + + )} + + )} +
    ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nuxt/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nuxt/route.tsx index 7ab0c20a9..f2560a2e7 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nuxt/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.nuxt/route.tsx @@ -1,6 +1,6 @@ import { NuxtLogo } from "~/assets/logos/NuxtLogo"; import { FrameworkComingSoon } from "~/components/frameworks/FrameworkComingSoon"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Handle } from "~/utils/handle"; import { trimTrailingSlash } from "~/utils/pathBuilder"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.redwood/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.redwood/route.tsx index 8fea7be77..a6d5286fd 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.redwood/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.redwood/route.tsx @@ -1,6 +1,6 @@ import { RedwoodLogo } from "~/assets/logos/RedwoodLogo"; import { FrameworkComingSoon } from "~/components/frameworks/FrameworkComingSoon"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Handle } from "~/utils/handle"; import { trimTrailingSlash } from "~/utils/pathBuilder"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.remix/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.remix/route.tsx index 12a9397ca..18a8e3c27 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.remix/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.remix/route.tsx @@ -1,33 +1,23 @@ import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid"; import invariant from "tiny-invariant"; +import { RemixLogo } from "~/assets/logos/RemixLogo"; import { Feedback } from "~/components/Feedback"; -import { PageGradient } from "~/components/PageGradient"; +import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands"; import { StepContentContainer } from "~/components/StepContentContainer"; import { InlineCode } from "~/components/code/InlineCode"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Button, LinkButton } from "~/components/primitives/Buttons"; -import { - ClientTabs, - ClientTabsContent, - ClientTabsList, - ClientTabsTrigger, -} from "~/components/primitives/ClientTabs"; -import { ClipboardField } from "~/components/primitives/ClipboardField"; +import { Callout } from "~/components/primitives/Callout"; import { Header1 } from "~/components/primitives/Headers"; -import { NamedIcon } from "~/components/primitives/NamedIcon"; import { Paragraph } from "~/components/primitives/Paragraph"; import { StepNumber } from "~/components/primitives/StepNumber"; import { useAppOrigin } from "~/hooks/useAppOrigin"; -import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete"; import { useDevEnvironment } from "~/hooks/useEnvironments"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; +import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete"; import { Handle } from "~/utils/handle"; import { projectSetupPath, trimTrailingSlash } from "~/utils/pathBuilder"; -import { Callout } from "~/components/primitives/Callout"; -import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands"; -import { Badge } from "~/components/primitives/Badge"; -import { RemixLogo } from "~/assets/logos/RemixLogo"; export const handle: Handle = { breadcrumb: (match) => , @@ -42,71 +32,69 @@ export default function SetUpRemix() { const appOrigin = useAppOrigin(); return ( - -
    -
    - -
    -
    - - Get setup in 5 minutes - -
    - - Choose a different framework - - - I'm stuck! - - } - defaultValue="help" - /> -
    -
    -
    - +
    + +
    +
    + + Get setup in 5 minutes + +
    + - Trigger.dev has full support for serverless. We will be adding support for long-running - servers soon. - -
    - - - - - - You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple - example Job in example.server.ts to - help you get started. - - - - - - - - - - - - - This page will automatically refresh. - -
    + Choose a different framework +
    + + I'm stuck! + + } + defaultValue="help" + />
    - +
    + + Trigger.dev has full support for serverless. We will be adding support for long-running + servers soon. + +
    + + + + + + You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple + example Job in example.server.ts to + help you get started. + + + + + + + + + + + + + This page will automatically refresh. + +
    +
    +
    ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.sveltekit/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.sveltekit/route.tsx index eb47e5fdf..3cdd1ab2b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.sveltekit/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.sveltekit/route.tsx @@ -1,26 +1,23 @@ import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid"; import invariant from "tiny-invariant"; +import { SvelteKitLogo } from "~/assets/logos/SveltekitLogo"; import { Feedback } from "~/components/Feedback"; -import { PageGradient } from "~/components/PageGradient"; import { RunDevCommand, TriggerDevStep } from "~/components/SetupCommands"; import { StepContentContainer } from "~/components/StepContentContainer"; -import { InlineCode } from "~/components/code/InlineCode"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; +import { Badge } from "~/components/primitives/Badge"; import { Button, LinkButton } from "~/components/primitives/Buttons"; +import { Callout } from "~/components/primitives/Callout"; import { ClipboardField } from "~/components/primitives/ClipboardField"; import { Header1 } from "~/components/primitives/Headers"; -import { NamedIcon } from "~/components/primitives/NamedIcon"; import { Paragraph } from "~/components/primitives/Paragraph"; import { StepNumber } from "~/components/primitives/StepNumber"; -import { useAppOrigin } from "~/hooks/useAppOrigin"; -import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete"; import { useDevEnvironment } from "~/hooks/useEnvironments"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; +import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete"; import { Handle } from "~/utils/handle"; import { projectSetupPath, trimTrailingSlash } from "~/utils/pathBuilder"; -import { Callout } from "~/components/primitives/Callout"; -import { Badge } from "~/components/primitives/Badge"; export const handle: Handle = { breadcrumb: (match) => ( @@ -34,80 +31,81 @@ export default function SetUpSveltekit() { const devEnvironment = useDevEnvironment(); invariant(devEnvironment, "Dev environment must be defined"); return ( - -
    -
    - - Get setup in 5 minutes - -
    - - Choose a different framework - - - I'm stuck! - - } - defaultValue="help" - /> -
    -
    -
    - +
    + +
    +
    + + Get setup in 5 minutes + +
    + - Trigger.dev has full support for serverless. We will be adding support for long-running - servers soon. - -
    - - - Copy your server API Key to your clipboard: -
    - Server} - /> -
    - Now follow this guide: - - Manual installation guide - -
    -
    - - - - - - - - - - - This page will automatically refresh. - -
    + Choose a different framework +
    + + I'm stuck! + + } + defaultValue="help" + />
    - +
    + + Trigger.dev has full support for serverless. We will be adding support for long-running + servers soon. + +
    + + + Copy your server API Key to your clipboard: +
    + Server} + /> +
    + Now follow this guide: + + Manual installation guide + +
    +
    + + + + + + + + + + + This page will automatically refresh. + +
    +
    +
    ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup/route.tsx index 5bee43084..ba8dbc1c3 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup/route.tsx @@ -1,11 +1,11 @@ import { Outlet } from "@remix-run/react"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { Handle } from "~/utils/handle"; import { trimTrailingSlash } from "~/utils/pathBuilder"; export const handle: Handle = { breadcrumb: (match) => ( - + ), }; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx index c1c27be1e..fa5363c31 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers._index/route.tsx @@ -2,7 +2,6 @@ import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid"; import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; import { LabelValueStack } from "~/components/primitives/LabelValueStack"; import { NamedIcon } from "~/components/primitives/NamedIcon"; import { Paragraph } from "~/components/primitives/Paragraph"; @@ -22,8 +21,7 @@ import { useProject } from "~/hooks/useProject"; import { TriggersPresenter } from "~/presenters/TriggersPresenter.server"; import { requireUser } from "~/services/session.server"; import { cn } from "~/utils/cn"; -import { Handle } from "~/utils/handle"; -import { ProjectParamSchema, externalTriggerPath, trimTrailingSlash } from "~/utils/pathBuilder"; +import { ProjectParamSchema, externalTriggerPath } from "~/utils/pathBuilder"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); @@ -39,13 +37,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { return typedjson(data); }; -export const handle: Handle = { - breadcrumb: (match) => ( - - ), - expandSidebar: true, -}; - export default function Integrations() { const { triggers } = useTypedLoaderData(); const organization = useOrganization(); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx index 02cfaa55d..e1468fdd9 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx @@ -3,7 +3,7 @@ import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid"; import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { DateTime } from "~/components/primitives/DateTime"; import { LabelValueStack } from "~/components/primitives/LabelValueStack"; import { NamedIcon } from "~/components/primitives/NamedIcon"; @@ -43,7 +43,6 @@ export const handle: Handle = { breadcrumb: (match) => ( ), - expandSidebar: true, }; export default function Integrations() { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers/route.tsx index c340d77c6..7830ff1e4 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers/route.tsx @@ -1,6 +1,6 @@ import { Outlet } from "@remix-run/react"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { LinkButton } from "~/components/primitives/Buttons"; import { PageButtons, @@ -39,7 +39,7 @@ export default function Page() { to={docsPath("documentation/concepts/triggers")} variant="secondary/small" > - Triggers Documentation + Triggers documentation diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx index fcfef7cb7..0be482778 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx @@ -4,7 +4,7 @@ import { Fragment } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon"; import { Callout, variantClasses } from "~/components/primitives/Callout"; import { Header2 } from "~/components/primitives/Headers"; @@ -27,7 +27,7 @@ import { requireUser, requireUserId } from "~/services/session.server"; import { Handle } from "~/utils/handle"; import { TriggerSourceParamSchema, - organizationsPath, + rootPath, projectTriggersPath, externalTriggerPath, externalTriggerRunsParentPath, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx index 0e14db661..becefab9f 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx @@ -3,7 +3,7 @@ import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { Fragment, useEffect } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { useEventSource } from "remix-utils/sse/react"; -import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon"; import { RunOverview } from "~/components/run/RunOverview"; import { prisma } from "~/db.server"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx index 45802ba25..c5e4fa41b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx @@ -3,13 +3,14 @@ import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson } from "remix-typedjson"; import invariant from "tiny-invariant"; import { RouteErrorDisplay } from "~/components/ErrorDisplay"; -import { ProjectSideMenu, SideMenuContainer } from "~/components/navigation/ProjectSideMenu"; -import { ProjectsMenu } from "~/components/navigation/ProjectsMenu"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; +import { useTypedMatchData } from "~/hooks/useTypedMatchData"; import { ProjectPresenter } from "~/presenters/ProjectPresenter.server"; -import { telemetry } from "~/services/telemetry.server"; +import { commitCurrentProjectSession, setCurrentProjectId } from "~/services/currentProject.server"; import { requireUserId } from "~/services/session.server"; +import { telemetry } from "~/services/telemetry.server"; import { Handle } from "~/utils/handle"; import { projectPath } from "~/utils/pathBuilder"; @@ -35,9 +36,16 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { telemetry.project.identify({ project }); - return typedjson({ - project, - }); + const session = await setCurrentProjectId(project.id, request); + + return typedjson( + { + project, + }, + { + headers: { "Set-Cookie": await commitCurrentProjectSession(session) }, + } + ); } catch (error) { if (error instanceof Response) { throw error; @@ -52,7 +60,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }; export const handle: Handle = { - breadcrumb: (_match, matches) => , + breadcrumb: (match) => { + const data = useTypedMatchData(match); + return ; + }, scripts: (match) => [ { src: "https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js", @@ -64,12 +75,7 @@ export const handle: Handle = { export default function Project() { return ( <> - - -
    - -
    -
    + ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx index 56d23cb92..0fd9c10a3 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.team/route.tsx @@ -30,8 +30,10 @@ import { getTeamMembersAndInvites, removeTeamMember } from "~/models/member.serv import { redirectWithSuccessMessage } from "~/models/message.server"; import { requireUserId } from "~/services/session.server"; import { inviteTeamMemberPath, organizationTeamPath, resendInvitePath } from "~/utils/pathBuilder"; -import { OrgAdminHeader } from "../_app.orgs.$organizationSlug._index/OrgAdminHeader"; import { DateTime } from "~/components/primitives/DateTime"; +import { PageHeader, PageTitleRow, PageTitle } from "~/components/primitives/PageHeader"; +import { BreadcrumbLink } from "~/components/navigation/Breadcrumb"; +import { Handle } from "~/utils/handle"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); @@ -93,6 +95,10 @@ export const action: ActionFunction = async ({ request, params }) => { type Member = UseDataFunctionReturn["members"][number]; type Invite = UseDataFunctionReturn["invites"][number]; +export const handle: Handle = { + breadcrumb: (match) => , +}; + export default function Page() { const user = useUser(); const { members, invites } = useTypedLoaderData(); @@ -100,10 +106,14 @@ export default function Page() { return ( - + + + + + Members -