From b1b9321ad2129867fa4288dcc7e2330e421c5103 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 18 Aug 2023 15:25:26 +0100 Subject: [PATCH] Job run performance improvements and adding "worker only" mode (#360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP job run performance improvements - Added a `perf` tool to better measure job run performance under heavy load - Removed `runFinished` job (not really needed) - startQueuedRuns now uses a jobKey with replace - Fixed an issue with ZodWorker when using jobKey * Publish improvement docker images * fixed the improvement docker publishing * Downgrade back to prisma 4.16.0 because 5.1.x broke docker builds * Changes to how queued runs work - Split the worker into two different workers, one dedicated to performRunExecution - Schedule performRunExecution in a single place, with a queue and using a round robin manually controlled concurrency - Remove startQueuedRuns - All runs are queued before they are started - Setting the worker maxPoolSize to the same as the worker concurrency - Starting to be able to split the docker image * Remove queue name from startRun graphile job * Make the prisma connection pool stuff configurable through env vars * Hardcode (for now) the max concurrent runs limit * Rewrite performRunExecution to be more performant PerformRunExecutionV2: - Does not create and manage jobRunExecution records - Does not reimplement retrying, uses graphile worker retrying instead I’ve kept around PerformRunExecutionV1 so this works when deploying. Definitely needs LOTS of testing * Fix issues with cached tasks - Limit the size of the cached tasks sent when executing a run, using the knapsack problem dynamic programming approach - Actually USE the cached tasks in IO by using the idempotencyKey instead of the task ID - Remove output from all logs - Added a stress test job catalog * Forgot to commit the logger updates * Never log connectionString * Login to docker hub to get around rate limits * Add additional logging to the graphile workers * Fix the *_ENABLED env vars * Allow adding and removing jobs to be done from the webapp * Don’t set the job to failed if it’s being retried * Deprecated queue options in the job and removed startPosition. Now using the job/env combo as the job queue name * Dequeung jobs doesn’t check if the runner is initialized * Fixed issues with retrying a run getting stuck on a cancelled task, and errors from parsing the results of dequeing a job * Remove queued round robin thing that isn’t used anymore * Added slack to job catalog * Better forwards compat * Added long delay * Fixed lock file --- .changeset/fuzzy-trees-lay.md | 5 + .changeset/happy-foxes-play.md | 6 + .github/workflows/publish.yml | 16 + apps/webapp/app/consts.ts | 3 +- apps/webapp/app/db.server.ts | 14 +- apps/webapp/app/env.server.ts | 9 + .../app/models/jobRunExecution.server.ts | 58 ++ apps/webapp/app/platform/zodWorker.server.ts | 124 +++- .../app/routes/api.v1.runs.$runId.tasks.ts | 26 + .../routes/resources.runs.$runId.cancel.ts | 17 +- .../webapp/app/services/endpointApi.server.ts | 4 + .../integrationConnectionCreated.server.ts | 12 +- .../app/services/jobs/registerJob.server.ts | 28 +- apps/webapp/app/services/logger.server.ts | 2 +- .../app/services/runs/cancelRun.server.ts | 21 +- .../app/services/runs/continueRun.server.ts | 89 +-- .../app/services/runs/createRun.server.ts | 2 +- ...ver.ts => performRunExecutionV1.server.ts} | 141 ++--- .../runs/performRunExecutionV2.server.ts | 593 ++++++++++++++++++ .../app/services/runs/runFinished.server.ts | 56 -- .../services/runs/startQueuedRuns.server.ts | 56 -- .../app/services/runs/startRun.server.ts | 96 +-- .../tasks/performTaskOperation.server.ts | 47 +- apps/webapp/app/services/worker.server.ts | 124 ++-- apps/webapp/server.ts | 18 +- docker/Dockerfile | 3 +- docs/sdk/job.mdx | 6 - examples/job-catalog/package.json | 7 +- examples/job-catalog/src/delays.ts | 36 ++ examples/job-catalog/src/events.ts | 32 + examples/job-catalog/src/openai.ts | 80 +++ examples/job-catalog/src/slack.ts | 33 + examples/job-catalog/src/stressTest.ts | 41 ++ package.json | 2 +- packages/core/src/logger.ts | 27 + packages/core/src/schemas/api.ts | 3 +- packages/database/package.json | 6 +- packages/trigger-sdk/src/io.ts | 2 +- packages/trigger-sdk/src/job.ts | 7 +- packages/trigger-sdk/src/triggerClient.ts | 5 - perf/package.json | 41 ++ perf/src/index.ts | 83 +++ perf/src/server.ts | 7 + perf/src/trigger.ts | 37 ++ perf/tsconfig.json | 37 ++ pnpm-lock.yaml | 520 ++++++--------- pnpm-workspace.yaml | 1 + 47 files changed, 1702 insertions(+), 881 deletions(-) create mode 100644 .changeset/fuzzy-trees-lay.md create mode 100644 .changeset/happy-foxes-play.md create mode 100644 apps/webapp/app/models/jobRunExecution.server.ts rename apps/webapp/app/services/runs/{performRunExecution.server.ts => performRunExecutionV1.server.ts} (88%) create mode 100644 apps/webapp/app/services/runs/performRunExecutionV2.server.ts delete mode 100644 apps/webapp/app/services/runs/runFinished.server.ts delete mode 100644 apps/webapp/app/services/runs/startQueuedRuns.server.ts create mode 100644 examples/job-catalog/src/delays.ts create mode 100644 examples/job-catalog/src/events.ts create mode 100644 examples/job-catalog/src/openai.ts create mode 100644 examples/job-catalog/src/slack.ts create mode 100644 examples/job-catalog/src/stressTest.ts create mode 100644 perf/package.json create mode 100644 perf/src/index.ts create mode 100644 perf/src/server.ts create mode 100644 perf/src/trigger.ts create mode 100644 perf/tsconfig.json diff --git a/.changeset/fuzzy-trees-lay.md b/.changeset/fuzzy-trees-lay.md new file mode 100644 index 000000000..97fc166a1 --- /dev/null +++ b/.changeset/fuzzy-trees-lay.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/sdk": patch +--- + +Fixed IO not setting the cached task key correctly, resulting in unnecessary API calls to trigger.dev diff --git a/.changeset/happy-foxes-play.md b/.changeset/happy-foxes-play.md new file mode 100644 index 000000000..bcfe4d1a6 --- /dev/null +++ b/.changeset/happy-foxes-play.md @@ -0,0 +1,6 @@ +--- +"@trigger.dev/sdk": patch +"@trigger.dev/core": patch +--- + +Deprecated queue options in the job and removed startPosition diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ca70af6bc..9af088b42 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - improvements/* tags: - "v.docker.*" paths: @@ -95,6 +96,12 @@ jobs: name: e2e Tests runs-on: buildjet-4vcpu-ubuntu-2204 steps: + - name: 🐳 Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: ⬇️ Checkout repo uses: actions/checkout@v3 with: @@ -154,6 +161,11 @@ jobs: version: ${{ steps.get_version.outputs.version }} short_sha: ${{ steps.get_commit.outputs.sha_short }} steps: + - name: 🐳 Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: ⬇️ Checkout repo uses: actions/checkout@v3 @@ -167,6 +179,10 @@ jobs: IMAGE_TAG="v${ORIGINAL_VERSION}" fi echo "IMAGE_TAG=${IMAGE_TAG}" + elif [[ $GITHUB_REF == refs/heads/improvements/* ]]; then + ORIGINAL_VERSION="${GITHUB_REF#refs/heads/improvements/}" + IMAGE_TAG="${ORIGINAL_VERSION}.rc" + echo "IMAGE_TAG=${IMAGE_TAG}" elif [[ $GITHUB_REF == refs/heads/* ]]; then IMAGE_TAG="${GITHUB_REF#refs/heads/}" echo "IMAGE_TAG=${IMAGE_TAG}" diff --git a/apps/webapp/app/consts.ts b/apps/webapp/app/consts.ts index ae82e6454..c6e9c85cd 100644 --- a/apps/webapp/app/consts.ts +++ b/apps/webapp/app/consts.ts @@ -1,6 +1,7 @@ export const LIVE_ENVIRONMENT = "live"; export const DEV_ENVIRONMENT = "development"; export const MAX_LIVE_PROJECTS = 1; -export const DEFAULT_MAX_CONCURRENT_RUNS = 10000; +export const DEFAULT_MAX_CONCURRENT_RUNS = 10; +export const MAX_CONCURRENT_RUNS_LIMIT = 20; export const PREPROCESS_RETRY_LIMIT = 2; export const EXECUTE_JOB_RETRY_LIMIT = 10; diff --git a/apps/webapp/app/db.server.ts b/apps/webapp/app/db.server.ts index 23ec17b9c..52c120544 100644 --- a/apps/webapp/app/db.server.ts +++ b/apps/webapp/app/db.server.ts @@ -2,6 +2,7 @@ import { PrismaClient, Prisma } from "@trigger.dev/database"; import invariant from "tiny-invariant"; import { z } from "zod"; import { logger } from "./services/logger.server"; +import { env } from "./env.server"; export type PrismaTransactionClient = Omit< PrismaClient, @@ -84,8 +85,16 @@ function getClient() { const { DATABASE_URL } = process.env; invariant(typeof DATABASE_URL === "string", "DATABASE_URL env var not set"); + const databaseUrl = new URL(DATABASE_URL); + + // We need to add the connection_limit and pool_timeout query params to the url, in a way that works if the DATABASE_URL already has query params + const query = databaseUrl.searchParams; + query.set("connection_limit", env.DATABASE_CONNECTION_LIMIT.toString()); + query.set("pool_timeout", env.DATABASE_POOL_TIMEOUT.toString()); + databaseUrl.search = query.toString(); + // Remove the username:password in the url and print that to the console - const urlWithoutCredentials = new URL(DATABASE_URL); + const urlWithoutCredentials = new URL(databaseUrl.href); urlWithoutCredentials.password = ""; console.log(`🔌 setting up prisma client to ${urlWithoutCredentials.toString()}`); @@ -93,8 +102,7 @@ function getClient() { const client = new PrismaClient({ datasources: { db: { - url: DATABASE_URL, - // We can't set directUrl here, and we don't have to + url: databaseUrl.href, }, }, log: [ diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 990fdb13e..46f3f9e81 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -4,6 +4,8 @@ import { SecretStoreOptionsSchema } from "./services/secrets/secretStore.server" const EnvironmentSchema = z.object({ NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]), DATABASE_URL: z.string(), + DATABASE_CONNECTION_LIMIT: z.coerce.number().int().default(10), + DATABASE_POOL_TIMEOUT: z.coerce.number().int().default(60), DIRECT_URL: z.string(), SESSION_SECRET: z.string(), MAGIC_LINK_SECRET: z.string(), @@ -31,6 +33,13 @@ const EnvironmentSchema = z.object({ RESEND_API_KEY: z.string().optional(), PLAIN_API_KEY: z.string().optional(), RUNTIME_PLATFORM: z.enum(["docker-compose", "ecs", "local"]).default("local"), + WORKER_SCHEMA: z.string().default("graphile_worker"), + WORKER_CONCURRENCY: z.coerce.number().int().default(10), + WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), + EXECUTION_WORKER_CONCURRENCY: z.coerce.number().int().default(10), + EXECUTION_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), + WORKER_ENABLED: z.string().default("true"), + EXECUTION_WORKER_ENABLED: z.string().default("true"), }); export type Environment = z.infer; diff --git a/apps/webapp/app/models/jobRunExecution.server.ts b/apps/webapp/app/models/jobRunExecution.server.ts new file mode 100644 index 000000000..41076ae64 --- /dev/null +++ b/apps/webapp/app/models/jobRunExecution.server.ts @@ -0,0 +1,58 @@ +import { JobRun, JobRunExecution } from "@trigger.dev/database"; +import { PrismaClientOrTransaction } from "~/db.server"; +import { executionWorker } from "~/services/worker.server"; + +export async function enqueueRunExecutionV1( + execution: JobRunExecution, + queueId: string, + concurrency: number, + tx: PrismaClientOrTransaction, + runAt?: Date +) { + const job = await executionWorker.enqueue( + "performRunExecution", + { + id: execution.id, + }, + { + queueName: `job:queue:${queueId}`, + tx, + runAt, + jobKey: `execution:${execution.runId}`, + } + ); +} + +export type EnqueueRunExecutionV2Options = { + runAt?: Date; + resumeTaskId?: string; + isRetry?: boolean; +}; + +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, + }, + { + queueName: `job:${run.jobId}:env:${run.environmentId}`, + tx, + runAt: options.runAt, + jobKey: `job_run:${run.id}`, + } + ); +} + +export async function dequeueRunExecutionV2(run: JobRun, tx: PrismaClientOrTransaction) { + return await executionWorker.dequeue(`job_run:${run.id}`, { + tx, + }); +} diff --git a/apps/webapp/app/platform/zodWorker.server.ts b/apps/webapp/app/platform/zodWorker.server.ts index b3b02da13..e698a6d06 100644 --- a/apps/webapp/app/platform/zodWorker.server.ts +++ b/apps/webapp/app/platform/zodWorker.server.ts @@ -51,6 +51,7 @@ const AddJobResultsSchema = z.array(GraphileJobSchema); export type ZodTasks = { [K in keyof TConsumerSchema]: { queueName?: string | ((payload: z.infer) => string); + jobKey?: string | ((payload: z.infer) => string | undefined); priority?: number; maxAttempts?: number; jobKeyMode?: "replace" | "preserve_run_at" | "unsafe_dedupe"; @@ -76,7 +77,12 @@ export type ZodWorkerEnqueueOptions = TaskSpec & { tx?: PrismaClientOrTransaction; }; +export type ZodWorkerDequeueOptions = { + tx?: PrismaClientOrTransaction; +}; + export type ZodWorkerOptions = { + name: string; runnerOptions: RunnerOptions; prisma: PrismaClient; schema: TMessageCatalog; @@ -85,6 +91,7 @@ export type ZodWorkerOptions = { }; export class ZodWorker { + #name: string; #schema: TMessageCatalog; #prisma: PrismaClient; #runnerOptions: RunnerOptions; @@ -93,6 +100,7 @@ export class ZodWorker { #runner?: GraphileRunner; constructor(options: ZodWorkerOptions) { + this.#name = options.name; this.#schema = options.schema; this.#prisma = options.prisma; this.#runnerOptions = options.runnerOptions; @@ -105,7 +113,7 @@ export class ZodWorker { return true; } - logger.debug("Initializing worker queue with options", { + this.#logDebug("Initializing worker queue with options", { runnerOptions: this.#runnerOptions, }); @@ -121,6 +129,54 @@ export class ZodWorker { throw new Error("Failed to initialize worker queue"); } + this.#runner?.events.on("pool:create", ({ workerPool }) => { + this.#logDebug("pool:create"); + }); + + this.#runner?.events.on("pool:listen:connecting", ({ workerPool, attempts }) => { + this.#logDebug("pool:create", { attempts }); + }); + + this.#runner?.events.on("pool:listen:success", ({ workerPool, client }) => { + this.#logDebug("pool:listen:success"); + }); + + this.#runner?.events.on("pool:listen:error", ({ error }) => { + this.#logDebug("pool:listen:error", { error }); + }); + + this.#runner?.events.on("pool:gracefulShutdown", ({ message }) => { + this.#logDebug("pool:gracefulShutdown", { workerMessage: message }); + }); + + this.#runner?.events.on("pool:gracefulShutdown:error", ({ error }) => { + this.#logDebug("pool:gracefulShutdown:error", { error }); + }); + + this.#runner?.events.on("worker:create", ({ worker }) => { + this.#logDebug("worker:create", { workerId: worker.workerId }); + }); + + this.#runner?.events.on("worker:release", ({ worker }) => { + this.#logDebug("worker:release", { workerId: worker.workerId }); + }); + + this.#runner?.events.on("worker:stop", ({ worker, error }) => { + this.#logDebug("worker:stop", { workerId: worker.workerId, error }); + }); + + this.#runner?.events.on("worker:fatalError", ({ worker, error, jobError }) => { + this.#logDebug("worker:fatalError", { workerId: worker.workerId, error, jobError }); + }); + + this.#runner?.events.on("gracefulShutdown", ({ signal }) => { + this.#logDebug("gracefulShutdown", { signal }); + }); + + this.#runner?.events.on("stop", () => { + this.#logDebug("stop"); + }); + return true; } @@ -133,23 +189,34 @@ export class ZodWorker { payload: z.infer, options?: ZodWorkerEnqueueOptions ): Promise { - if (!this.#runner) { - throw new Error("Worker not initialized"); - } - const task = this.#tasks[identifier]; const optionsWithoutTx = omit(options ?? {}, ["tx"]); + const taskWithoutJobKey = omit(task, ["jobKey"]); const spec = { ...optionsWithoutTx, - ...task, + ...taskWithoutJobKey, }; if (typeof task.queueName === "function") { spec.queueName = task.queueName(payload); } + if (typeof task.jobKey === "function") { + const jobKey = task.jobKey(payload); + + if (jobKey) { + spec.jobKey = jobKey; + } + } + + logger.debug("Enqueuing worker task", { + identifier, + payload, + spec, + }); + const job = await this.#addJob( identifier as string, payload, @@ -167,6 +234,17 @@ export class ZodWorker { return job; } + public async dequeue( + jobKey: string, + option?: ZodWorkerDequeueOptions + ): Promise { + const results = await this.#removeJob(jobKey, option?.tx ?? this.#prisma); + + logger.debug("dequeued worker task", { results, jobKey }); + + return results; + } + async #addJob( identifier: string, payload: unknown, @@ -192,8 +270,8 @@ export class ZodWorker { spec.maxAttempts || null, spec.jobKey || null, spec.priority || null, - spec.jobKeyMode || null, - spec.flags || null + spec.flags || null, + spec.jobKeyMode || null ); const rows = AddJobResultsSchema.safeParse(results); @@ -209,6 +287,32 @@ export class ZodWorker { return job as GraphileJob; } + async #removeJob(jobKey: string, tx: PrismaClientOrTransaction) { + try { + const result = await tx.$queryRawUnsafe( + `SELECT * FROM graphile_worker.remove_job( + job_key => $1::text + )`, + jobKey + ); + const job = AddJobResultsSchema.safeParse(result); + + if (!job.success) { + logger.debug("results returned from remove_job could not be parsed", { + error: job.error.flatten(), + result, + jobKey, + }); + + return; + } + + return job.data[0] as GraphileJob; + } catch (e) { + throw new Error(`Failed to remove job from queue, ${e}}`); + } + } + #createTaskListFromTasks() { const taskList: TaskList = {}; @@ -324,4 +428,8 @@ export class ZodWorker { throw error; } } + + #logDebug(message: string, args?: any) { + logger.debug(`[worker][${this.#name}] ${message}`, args); + } } 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 08fa9a6fa..f9e81c176 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts @@ -107,6 +107,32 @@ export class RunTaskService { }); if (existingTask) { + if (existingTask.status === "CANCELED") { + const existingTaskStatus = + (taskBody.delayUntil && taskBody.delayUntil.getTime() > Date.now()) || taskBody.trigger + ? "WAITING" + : taskBody.noop + ? "COMPLETED" + : "RUNNING"; + + const resumedExistingTask = await tx.task.update({ + where: { + id: existingTask.id, + }, + data: { + status: existingTaskStatus, + startedAt: new Date(), + completedAt: existingTaskStatus === "COMPLETED" ? new Date() : undefined, + }, + include: { + run: true, + attempts: true, + }, + }); + + return resumedExistingTask; + } + return existingTask; } diff --git a/apps/webapp/app/routes/resources.runs.$runId.cancel.ts b/apps/webapp/app/routes/resources.runs.$runId.cancel.ts index 37cab2d72..af475db06 100644 --- a/apps/webapp/app/routes/resources.runs.$runId.cancel.ts +++ b/apps/webapp/app/routes/resources.runs.$runId.cancel.ts @@ -2,6 +2,7 @@ import { parse } from "@conform-to/zod"; import { ActionFunction, json } from "@remix-run/node"; import { z } from "zod"; import { redirectWithSuccessMessage } from "~/models/message.server"; +import { logger } from "~/services/logger.server"; import { CancelRunService } from "~/services/runs/cancelRun.server"; export const cancelSchema = z.object({ @@ -31,7 +32,19 @@ export const action: ActionFunction = async ({ request, params }) => { request, `Canceled run. Any pending tasks will be canceled.` ); - } catch (error: any) { - return json({ errors: { body: error.message } }, { status: 400 }); + } catch (error) { + if (error instanceof Error) { + logger.error("Failed to cancel run", { + error: { + name: error.name, + message: error.message, + stack: error.stack, + }, + }); + return json({ errors: { body: error.message } }, { status: 400 }); + } else { + logger.error("Failed to cancel run", { error }); + return json({ errors: { body: "Unknown error" } }, { status: 400 }); + } } }; diff --git a/apps/webapp/app/services/endpointApi.server.ts b/apps/webapp/app/services/endpointApi.server.ts index d342ea874..e7cba31e3 100644 --- a/apps/webapp/app/services/endpointApi.server.ts +++ b/apps/webapp/app/services/endpointApi.server.ts @@ -162,6 +162,10 @@ export class EndpointApi { } async executeJobRequest(options: RunJobBody) { + logger.debug("executeJobRequest()", { + options, + }); + const response = await safeFetch(this.url, { method: "POST", headers: { diff --git a/apps/webapp/app/services/externalApis/integrationConnectionCreated.server.ts b/apps/webapp/app/services/externalApis/integrationConnectionCreated.server.ts index eb5984549..df03a86d3 100644 --- a/apps/webapp/app/services/externalApis/integrationConnectionCreated.server.ts +++ b/apps/webapp/app/services/externalApis/integrationConnectionCreated.server.ts @@ -109,15 +109,9 @@ export class IntegrationConnectionCreatedService { }); // We need to start the run again - await workerQueue.enqueue( - "startRun", - { - id: run.id, - }, - { - queueName: `job-queue:${run.queue.id}`, - } - ); + await workerQueue.enqueue("startRun", { + id: run.id, + }); } } } diff --git a/apps/webapp/app/services/jobs/registerJob.server.ts b/apps/webapp/app/services/jobs/registerJob.server.ts index 8c4b4b0cd..f671c9356 100644 --- a/apps/webapp/app/services/jobs/registerJob.server.ts +++ b/apps/webapp/app/services/jobs/registerJob.server.ts @@ -1,18 +1,18 @@ -import type { Endpoint, Integration, Job, JobIntegration, JobVersion } from "@trigger.dev/database"; import { IntegrationConfig, JobMetadata, SCHEDULED_EVENT, TriggerMetadata, } from "@trigger.dev/core"; +import type { Endpoint, Integration, Job, JobIntegration, JobVersion } from "@trigger.dev/database"; import { DEFAULT_MAX_CONCURRENT_RUNS } from "~/consts"; import type { PrismaClient } from "~/db.server"; import { prisma } from "~/db.server"; +import { ExtendedEndpoint, findEndpoint } from "~/models/endpoint.server"; +import type { RuntimeEnvironment } from "~/models/runtimeEnvironment.server"; import type { AuthenticatedEnvironment } from "../apiAuth.server"; import { logger } from "../logger.server"; -import type { RuntimeEnvironment } from "~/models/runtimeEnvironment.server"; import { RegisterScheduleSourceService } from "../schedules/registerScheduleSource.server"; -import { ExtendedEndpoint, findEndpoint } from "~/models/endpoint.server"; export class RegisterJobService { #prismaClient: PrismaClient; @@ -166,13 +166,9 @@ export class RegisterJobService { }); // Upsert the JobQueue - const queueName = - typeof metadata.queue === "string" - ? metadata.queue - : typeof metadata.queue === "object" - ? metadata.queue.name - : "default"; + const queueName = "default"; + // Job Queues are going to be deprecated or used for something else, we're just doing this for now const jobQueue = await this.#prismaClient.jobQueue.upsert({ where: { environmentId_name: { @@ -187,16 +183,10 @@ export class RegisterJobService { }, }, name: queueName, - maxJobs: - typeof metadata.queue === "object" - ? metadata.queue.maxConcurrent || DEFAULT_MAX_CONCURRENT_RUNS - : DEFAULT_MAX_CONCURRENT_RUNS, + maxJobs: DEFAULT_MAX_CONCURRENT_RUNS, }, update: { - maxJobs: - typeof metadata.queue === "object" - ? metadata.queue.maxConcurrent || DEFAULT_MAX_CONCURRENT_RUNS - : DEFAULT_MAX_CONCURRENT_RUNS, + maxJobs: DEFAULT_MAX_CONCURRENT_RUNS, }, }); @@ -245,10 +235,10 @@ export class RegisterJobService { version: metadata.version, eventSpecification, preprocessRuns: metadata.preprocessRuns, - startPosition: metadata.startPosition === "initial" ? "INITIAL" : "LATEST", + startPosition: "LATEST", }, update: { - startPosition: metadata.startPosition === "initial" ? "INITIAL" : "LATEST", + startPosition: "LATEST", eventSpecification, preprocessRuns: metadata.preprocessRuns, queue: { diff --git a/apps/webapp/app/services/logger.server.ts b/apps/webapp/app/services/logger.server.ts index 0d158c968..fdc886479 100644 --- a/apps/webapp/app/services/logger.server.ts +++ b/apps/webapp/app/services/logger.server.ts @@ -5,6 +5,6 @@ import { sensitiveDataReplacer } from "./sensitiveDataReplacer"; export const logger = new Logger( "webapp", (process.env.APP_LOG_LEVEL ?? "debug") as LogLevel, - ["examples"], + ["examples", "output", "connectionString", "payload"], sensitiveDataReplacer ); diff --git a/apps/webapp/app/services/runs/cancelRun.server.ts b/apps/webapp/app/services/runs/cancelRun.server.ts index c73fcdb61..494bcc940 100644 --- a/apps/webapp/app/services/runs/cancelRun.server.ts +++ b/apps/webapp/app/services/runs/cancelRun.server.ts @@ -1,5 +1,6 @@ import { PrismaClient, prisma } from "~/db.server"; -import { workerQueue } from "../worker.server"; +import { executionWorker } from "../worker.server"; +import { dequeueRunExecutionV2 } from "~/models/jobRunExecution.server"; export class CancelRunService { #prismaClient: PrismaClient; @@ -17,21 +18,11 @@ export class CancelRunService { }, }); - const shouldDecrementQueue = run.status === "STARTED" || run.status === "PREPROCESSING"; await tx.jobRun.update({ where: { id: runId }, data: { status: "CANCELED", completedAt: new Date(), - queue: shouldDecrementQueue - ? { - update: { - jobCount: { - decrement: 1, - }, - }, - } - : undefined, }, }); @@ -48,13 +39,7 @@ export class CancelRunService { }, }); - await workerQueue.enqueue( - "startQueuedRuns", - { - id: run.queueId, - }, - { tx } - ); + await dequeueRunExecutionV2(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 f6ee46623..97f0ae4f3 100644 --- a/apps/webapp/app/services/runs/continueRun.server.ts +++ b/apps/webapp/app/services/runs/continueRun.server.ts @@ -1,6 +1,5 @@ -import { EXECUTE_JOB_RETRY_LIMIT } from "~/consts"; import { $transaction, Prisma, PrismaClient, prisma } from "~/db.server"; -import { workerQueue } from "../worker.server"; +import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; const RESUMABLE_STATUSES = ["FAILURE", "TIMED_OUT", "ABORTED", "CANCELED"]; @@ -17,86 +16,26 @@ export class ContinueRunService { async (tx) => { const run = await tx.jobRun.findUniqueOrThrow({ where: { id: runId }, - include: { - queue: true, - }, }); if (!RESUMABLE_STATUSES.includes(run.status)) { throw new Error("Run is not resumable"); } - if (run.queue.jobCount >= run.queue.maxJobs) { - await tx.jobRun.update({ - where: { id: runId }, - data: { - status: "QUEUED", - queuedAt: new Date(), - startedAt: null, - completedAt: null, - output: Prisma.DbNull, - timedOutAt: null, - timedOutReason: null, - }, - }); - } else { - await tx.jobRun.update({ - where: { id: runId }, - data: { - status: "STARTED", - queuedAt: null, - startedAt: new Date(), - completedAt: null, - output: Prisma.DbNull, - timedOutAt: null, - timedOutReason: null, - queue: { - update: { - jobCount: { - increment: 1, - }, - }, - }, - }, - }); + await tx.jobRun.update({ + where: { id: runId }, + data: { + status: "QUEUED", + queuedAt: new Date(), + startedAt: null, + completedAt: null, + output: Prisma.DbNull, + timedOutAt: null, + timedOutReason: null, + }, + }); - const execution = await tx.jobRunExecution.create({ - data: { - run: { - connect: { - id: runId, - }, - }, - status: "PENDING", - reason: "EXECUTE_JOB", - retryLimit: EXECUTE_JOB_RETRY_LIMIT, - isRetry: true, - }, - }); - - const job = await workerQueue.enqueue( - "performRunExecution", - { - id: execution.id, - }, - { tx } - ); - - await tx.jobRunExecution.update({ - where: { id: execution.id }, - data: { - graphileJobId: job.id, - }, - }); - - await workerQueue.enqueue( - "startQueuedRuns", - { - id: run.queueId, - }, - { tx } - ); - } + await enqueueRunExecutionV2(run, tx); }, { timeout: 10000 } ); diff --git a/apps/webapp/app/services/runs/createRun.server.ts b/apps/webapp/app/services/runs/createRun.server.ts index 71bea2778..34d8cd4e7 100644 --- a/apps/webapp/app/services/runs/createRun.server.ts +++ b/apps/webapp/app/services/runs/createRun.server.ts @@ -75,7 +75,7 @@ export class CreateRunService { { id: run.id, }, - { tx, queueName: `job-queue:${jobQueue.id}` } + { tx } ); return run; diff --git a/apps/webapp/app/services/runs/performRunExecution.server.ts b/apps/webapp/app/services/runs/performRunExecutionV1.server.ts similarity index 88% rename from apps/webapp/app/services/runs/performRunExecution.server.ts rename to apps/webapp/app/services/runs/performRunExecutionV1.server.ts index 10f45458a..b56199a1b 100644 --- a/apps/webapp/app/services/runs/performRunExecution.server.ts +++ b/apps/webapp/app/services/runs/performRunExecutionV1.server.ts @@ -1,27 +1,26 @@ -import type { Task } from "@trigger.dev/database"; import { ApiEventLogSchema, CachedTaskSchema, - RunJobCanceledWithTask, RunJobError, RunJobResumeWithTask, RunJobRetryWithTask, RunJobSuccess, RunSourceContextSchema, } from "@trigger.dev/core"; +import type { Task } from "@trigger.dev/database"; import { generateErrorMessage } from "zod-error"; import { EXECUTE_JOB_RETRY_LIMIT } from "~/consts"; import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; +import { enqueueRunExecutionV1 } from "~/models/jobRunExecution.server"; import { resolveRunConnections } from "~/models/runConnection.server"; +import { formatError } from "~/utils/formatErrors.server"; import { safeJsonZodParse } from "~/utils/json"; import { EndpointApi } from "../endpointApi.server"; -import { workerQueue } from "../worker.server"; -import { formatError } from "~/utils/formatErrors.server"; import { logger } from "../logger.server"; type FoundRunExecution = NonNullable>>; -export class PerformRunExecutionService { +export class PerformRunExecutionV1Service { #prismaClient: PrismaClient; constructor(prismaClient: PrismaClient = prisma) { @@ -162,22 +161,7 @@ export class PerformRunExecutionService { }, }); - const job = await workerQueue.enqueue( - "performRunExecution", - { - id: runExecution.id, - }, - { tx } - ); - - await tx.jobRunExecution.update({ - where: { - id: runExecution.id, - }, - data: { - graphileJobId: job.id, - }, - }); + await enqueueRunExecutionV1(runExecution, run.queue.id, run.queue.maxJobs, tx); }); } } @@ -201,6 +185,12 @@ export class PerformRunExecutionService { data: { status: "STARTED", startedAt, + run: { + update: { + status: run.status === "QUEUED" ? "STARTED" : run.status, + startedAt: run.startedAt ?? new Date(), + }, + }, }, }); @@ -388,14 +378,6 @@ export class PerformRunExecutionService { completedAt: new Date(), }, }); - - await workerQueue.enqueue( - "runFinished", - { - id: run.id, - }, - { tx } - ); }); } @@ -426,22 +408,13 @@ export class PerformRunExecutionService { }, }); - const graphileJob = await workerQueue.enqueue( - "performRunExecution", - { - id: newJobExecution.id, - }, - { tx, runAt: data.task.delayUntil ?? undefined } + await enqueueRunExecutionV1( + newJobExecution, + run.queue.id, + run.queue.maxJobs, + tx, + data.task.delayUntil ?? undefined ); - - await tx.jobRunExecution.update({ - where: { - id: newJobExecution.id, - }, - data: { - graphileJobId: graphileJob.id, - }, - }); } }); } @@ -522,22 +495,13 @@ export class PerformRunExecutionService { }, }); - const graphileJob = await workerQueue.enqueue( - "performRunExecution", - { - id: newJobExecution.id, - }, - { tx, runAt: data.retryAt } + await enqueueRunExecutionV1( + newJobExecution, + run.queue.id, + run.queue.maxJobs, + tx, + data.retryAt ); - - await tx.jobRunExecution.update({ - where: { - id: newJobExecution.id, - }, - data: { - graphileJobId: graphileJob.id, - }, - }); }); } @@ -557,6 +521,13 @@ export class PerformRunExecutionService { // So when retryCount is 1, retryDelayInMs is 500ms // When retryCount is 2, retryDelayInMs is 750ms // When retryCount is 3, retryDelayInMs is 1125ms + // When retryCount is 4, retryDelayInMs is 1687ms + // When retryCount is 5, retryDelayInMs is 2531ms + // When retryCount is 6, retryDelayInMs is 3796ms + // When retryCount is 7, retryDelayInMs is 5694ms + // When retryCount is 8, retryDelayInMs is 8541ms + // When retryCount is 9, retryDelayInMs is 12812ms + // When retryCount is 10, retryDelayInMs is 19218ms const retryDelayInMs = Math.round(500 * Math.pow(1.5, retryCount - 1)); await tx.jobRunExecution.update({ @@ -572,20 +543,13 @@ export class PerformRunExecutionService { const runAt = new Date(Date.now() + retryDelayInMs); - const job = await workerQueue.enqueue( - "performRunExecution", - { id: execution.id }, - { runAt, tx } + await enqueueRunExecutionV1( + execution, + execution.run.queue.id, + execution.run.queue.maxJobs, + tx, + runAt ); - - await tx.jobRunExecution.update({ - where: { - id: execution.id, - }, - data: { - graphileJobId: job.id, - }, - }); }); } @@ -617,13 +581,6 @@ export class PerformRunExecutionService { }, }); - await workerQueue.enqueue( - "runFinished", - { - id: run.id, - }, - { tx } - ); break; } case "PREPROCESS": { @@ -645,14 +602,6 @@ export class PerformRunExecutionService { }, }); - await workerQueue.enqueue( - "runFinished", - { - id: run.id, - }, - { tx } - ); - break; } @@ -675,22 +624,7 @@ export class PerformRunExecutionService { }, }); - const job = await workerQueue.enqueue( - "performRunExecution", - { - id: runExecution.id, - }, - { tx } - ); - - await tx.jobRunExecution.update({ - where: { - id: runExecution.id, - }, - data: { - graphileJobId: job.id, - }, - }); + await enqueueRunExecutionV1(runExecution, run.queue.id, run.queue.maxJobs, tx); break; } @@ -733,6 +667,7 @@ async function findRunExecution(prisma: PrismaClientOrTransaction, id: string) { endpoint: true, organization: true, externalAccount: true, + queue: true, runConnections: { include: { integration: true, diff --git a/apps/webapp/app/services/runs/performRunExecutionV2.server.ts b/apps/webapp/app/services/runs/performRunExecutionV2.server.ts new file mode 100644 index 000000000..0b4c0b025 --- /dev/null +++ b/apps/webapp/app/services/runs/performRunExecutionV2.server.ts @@ -0,0 +1,593 @@ +import { + ApiEventLogSchema, + CachedTask, + RunJobError, + RunJobResumeWithTask, + RunJobRetryWithTask, + RunJobSuccess, + RunSourceContextSchema, +} from "@trigger.dev/core"; +import type { Task } from "@trigger.dev/database"; +import { generateErrorMessage } from "zod-error"; +import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; +import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; +import { resolveRunConnections } from "~/models/runConnection.server"; +import { formatError } from "~/utils/formatErrors.server"; +import { safeJsonZodParse } from "~/utils/json"; +import { EndpointApi } from "../endpointApi.server"; +import { logger } from "../logger.server"; + +type FoundRun = NonNullable>>; +type FoundTask = FoundRun["tasks"][number]; + +export class PerformRunExecutionV2Service { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call( + id: string, + reason: "PREPROCESS" | "EXECUTE_JOB", + isRetry: boolean = false, + resumeTaskId?: string + ) { + const run = await findRun(this.#prismaClient, id); + + if (!run) { + return; + } + + switch (reason) { + case "PREPROCESS": { + await this.#executePreprocessing(run); + break; + } + case "EXECUTE_JOB": { + await this.#executeJob(run, isRetry, resumeTaskId); + break; + } + } + } + + // Execute the preprocessing step of a run, which will send the payload to the endpoint and give the job + // an opportunity to generate run properties based on the payload. + // If the endpoint is not available, or the response is not ok, + // the run execution will be marked as failed and the run will start + async #executePreprocessing(run: FoundRun) { + const client = new EndpointApi(run.environment.apiKey, run.endpoint.url); + const event = ApiEventLogSchema.parse({ ...run.event, id: run.eventId }); + + const { response, parser } = await client.preprocessRunRequest({ + event, + job: { + id: run.version.job.slug, + version: run.version.version, + }, + run: { + id: run.id, + isTest: run.isTest, + }, + environment: { + id: run.environment.id, + slug: run.environment.slug, + type: run.environment.type, + }, + organization: { + id: run.organization.id, + slug: run.organization.slug, + title: run.organization.title, + }, + account: run.externalAccount + ? { + id: run.externalAccount.identifier, + metadata: run.externalAccount.metadata, + } + : undefined, + }); + + if (!response) { + return await this.#failRunExecution(this.#prismaClient, "PREPROCESS", run, { + message: "Could not connect to the endpoint", + }); + } + + if (!response.ok) { + return await this.#failRunExecution(this.#prismaClient, "PREPROCESS", run, { + message: `Endpoint responded with ${response.status} status code`, + }); + } + + const rawBody = await response.text(); + const safeBody = safeJsonZodParse(parser, rawBody); + + if (!safeBody) { + return await this.#failRunExecution(this.#prismaClient, "PREPROCESS", run, { + message: "Endpoint responded with invalid JSON", + }); + } + + if (!safeBody.success) { + return await this.#failRunExecution(this.#prismaClient, "PREPROCESS", run, { + message: generateErrorMessage(safeBody.error.issues), + }); + } + + if (safeBody.data.abort) { + return this.#failRunExecution( + this.#prismaClient, + "PREPROCESS", + run, + { message: "Endpoint aborted the run" }, + "ABORTED" + ); + } else { + await $transaction(this.#prismaClient, async (tx) => { + await tx.jobRun.update({ + where: { + id: run.id, + }, + data: { + status: "STARTED", + startedAt: new Date(), + properties: safeBody.data.properties, + }, + }); + + await enqueueRunExecutionV2(run, tx); + }); + } + } + async #executeJob(run: FoundRun, isRetry: boolean, resumeTaskId?: string) { + if (run.status === "CANCELED") { + await this.#cancelExecution(run); + return; + } + + const client = new EndpointApi(run.environment.apiKey, run.endpoint.url); + const event = ApiEventLogSchema.parse({ ...run.event, id: run.eventId }); + + const startedAt = new Date(); + + await this.#prismaClient.jobRun.update({ + where: { + id: run.id, + }, + data: { + status: run.status === "QUEUED" ? "STARTED" : run.status, + startedAt: run.startedAt ?? new Date(), + }, + }); + + const connections = await resolveRunConnections(run.runConnections); + + if (!connections.success) { + return this.#failRunExecutionWithRetry({ + message: `Could not resolve all connections for run ${run.id}, attempting to retry`, + }); + } + + let resumedTask: Task | undefined; + + if (resumeTaskId) { + resumedTask = + (await this.#prismaClient.task.findUnique({ + where: { + id: resumeTaskId, + }, + })) ?? undefined; + + if (resumedTask) { + resumedTask = await this.#prismaClient.task.update({ + where: { + id: resumeTaskId, + }, + data: { + status: resumedTask.noop ? "COMPLETED" : "RUNNING", + completedAt: resumedTask.noop ? new Date() : undefined, + }, + }); + } + } + + const sourceContext = RunSourceContextSchema.safeParse(run.event.sourceContext); + + const { response, parser, errorParser } = await client.executeJobRequest({ + event, + job: { + id: run.version.job.slug, + version: run.version.version, + }, + run: { + id: run.id, + isTest: run.isTest, + startedAt, + isRetry, + }, + environment: { + id: run.environment.id, + slug: run.environment.slug, + type: run.environment.type, + }, + organization: { + id: run.organization.id, + slug: run.organization.slug, + title: run.organization.title, + }, + account: run.externalAccount + ? { + id: run.externalAccount.identifier, + metadata: run.externalAccount.metadata, + } + : undefined, + connections: connections.auth, + source: sourceContext.success ? sourceContext.data : undefined, + tasks: prepareTasksForRun([run.tasks, resumedTask].flat().filter(Boolean)), + }); + + if (!response) { + return await this.#failRunExecutionWithRetry({ + message: `Connection could not be established to the endpoint (${run.endpoint.url})`, + }); + } + + const rawBody = await response.text(); + + if (!response.ok) { + logger.debug("Endpoint responded with non-200 status code", { + status: response.status, + runId: run.id, + endpoint: run.endpoint.url, + }); + + const errorBody = safeJsonZodParse(errorParser, rawBody); + + if (errorBody && errorBody.success) { + // Only retry if the error isn't a 4xx + if (response.status >= 400 && response.status <= 499) { + return await this.#failRunExecution( + this.#prismaClient, + "EXECUTE_JOB", + run, + errorBody.data + ); + } else { + return await this.#failRunExecutionWithRetry(errorBody.data); + } + } + + // Only retry if the error isn't a 4xx + if (response.status >= 400 && response.status <= 499 && response.status !== 408) { + return await this.#failRunExecution(this.#prismaClient, "EXECUTE_JOB", run, { + message: `Endpoint responded with ${response.status} status code`, + }); + } else { + return await this.#failRunExecutionWithRetry({ + message: `Endpoint responded with ${response.status} status code`, + }); + } + } + + const safeBody = safeJsonZodParse(parser, rawBody); + + if (!safeBody) { + return await this.#failRunExecution(this.#prismaClient, "EXECUTE_JOB", run, { + message: "Endpoint responded with invalid JSON", + }); + } + + if (!safeBody.success) { + return await this.#failRunExecution(this.#prismaClient, "EXECUTE_JOB", run, { + message: generateErrorMessage(safeBody.error.issues), + }); + } + + const status = safeBody.data.status; + + switch (status) { + case "SUCCESS": { + await this.#completeRunWithSuccess(run, safeBody.data); + + break; + } + case "RESUME_WITH_TASK": { + await this.#resumeRunWithTask(run, safeBody.data, isRetry); + + break; + } + case "ERROR": { + await this.#failRunWithError(run, safeBody.data); + + break; + } + case "RETRY_WITH_TASK": { + await this.#retryRunWithTask(run, safeBody.data, isRetry); + + break; + } + case "CANCELED": { + await this.#cancelExecution(run); + break; + } + default: { + const _exhaustiveCheck: never = status; + throw new Error(`Non-exhaustive match for value: ${status}`); + } + } + } + + async #completeRunWithSuccess(run: FoundRun, data: RunJobSuccess) { + await this.#prismaClient.jobRun.update({ + where: { id: run.id }, + data: { + completedAt: new Date(), + status: "SUCCESS", + output: data.output ?? undefined, + }, + }); + } + + async #resumeRunWithTask(run: FoundRun, data: RunJobResumeWithTask, isRetry: boolean) { + return await $transaction(this.#prismaClient, async (tx) => { + // If the task has an operation, then the next performRunExecution will occur + // when that operation has finished + if (!data.task.operation) { + await enqueueRunExecutionV2(run, tx, { + runAt: data.task.delayUntil ?? undefined, + resumeTaskId: data.task.id, + isRetry, + }); + } + }); + } + + async #failRunWithError(execution: FoundRun, data: RunJobError) { + return await $transaction(this.#prismaClient, async (tx) => { + if (data.task) { + await tx.task.update({ + where: { + id: data.task.id, + }, + data: { + status: "ERRORED", + completedAt: new Date(), + output: data.error ?? undefined, + }, + }); + } + + await this.#failRunExecution(tx, "EXECUTE_JOB", execution, data.error ?? undefined); + }); + } + + async #retryRunWithTask(run: FoundRun, data: RunJobRetryWithTask, isRetry: boolean) { + return await $transaction(this.#prismaClient, async (tx) => { + // We need to check for an existing task attempt + const existingAttempt = await tx.taskAttempt.findFirst({ + where: { + taskId: data.task.id, + status: "PENDING", + }, + orderBy: { + number: "desc", + }, + }); + + if (existingAttempt) { + await tx.taskAttempt.update({ + where: { + id: existingAttempt.id, + }, + data: { + status: "ERRORED", + error: formatError(data.error), + }, + }); + } + + // We need to create a new task attempt + await tx.taskAttempt.create({ + data: { + taskId: data.task.id, + number: existingAttempt ? existingAttempt.number + 1 : 1, + status: "PENDING", + runAt: data.retryAt, + }, + }); + + await tx.task.update({ + where: { + id: data.task.id, + }, + data: { + status: "WAITING", + }, + }); + + await enqueueRunExecutionV2(run, tx, { + runAt: data.retryAt, + resumeTaskId: data.task.id, + isRetry, + }); + }); + } + + async #failRunExecutionWithRetry(output: Record): Promise { + throw new Error(JSON.stringify(output)); + } + + async #failRunExecution( + prisma: PrismaClientOrTransaction, + reason: "EXECUTE_JOB" | "PREPROCESS", + run: FoundRun, + output: Record, + status: "FAILURE" | "ABORTED" = "FAILURE" + ): Promise { + await $transaction(prisma, async (tx) => { + switch (reason) { + case "EXECUTE_JOB": { + // If the execution is an EXECUTE_JOB reason, we need to fail the run + await tx.jobRun.update({ + where: { id: run.id }, + data: { + completedAt: new Date(), + status, + output, + }, + }); + + break; + } + case "PREPROCESS": { + // If the status is ABORTED, we need to fail the run + if (status === "ABORTED") { + await tx.jobRun.update({ + where: { id: run.id }, + data: { + completedAt: new Date(), + status, + output, + }, + }); + + break; + } + + await tx.jobRun.update({ + where: { + id: run.id, + }, + data: { + status: "STARTED", + startedAt: new Date(), + }, + }); + + await enqueueRunExecutionV2(run, tx); + + break; + } + } + }); + } + + async #cancelExecution(run: FoundRun) { + return; + } +} + +function prepareTasksForRun(tasks: FoundTask[]): CachedTask[] { + // We need to limit the cached tasks to not be too large >3.5MB when serialized + const TOTAL_CACHED_TASK_BYTE_LIMIT = 3500000; + + const cachedTasks = new Map(); // Cache for prepared tasks + const cachedTaskSizes = new Map(); // Cache for calculated task sizes + + // Helper function to get the cached prepared task, or prepare and cache if not already cached + function getCachedTask(task: FoundTask): CachedTask { + const taskId = task.id; + if (!cachedTasks.has(taskId)) { + cachedTasks.set(taskId, prepareTaskForRun(task)); + } + return cachedTasks.get(taskId)!; + } + + // Helper function to get the cached task size, or calculate and cache if not already cached + function getCachedTaskSize(task: CachedTask): number { + const taskId = task.id; + if (!cachedTaskSizes.has(taskId)) { + cachedTaskSizes.set(taskId, calculateCachedTaskSize(task)); + } + return cachedTaskSizes.get(taskId)!; + } + + // Create a dynamic programming array to store intermediate results + const dp: number[][] = []; + for (let i = 0; i <= tasks.length; i++) { + dp[i] = []; + for (let j = 0; j <= TOTAL_CACHED_TASK_BYTE_LIMIT; j++) { + dp[i][j] = 0; + } + } + + // Fill the dynamic programming array + for (let i = 1; i <= tasks.length; i++) { + const task = tasks[i - 1]; + const cachedTask = getCachedTask(task); + const taskSize = getCachedTaskSize(cachedTask); + for (let j = 0; j <= TOTAL_CACHED_TASK_BYTE_LIMIT; j++) { + if (taskSize <= j) { + dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - taskSize] + taskSize); + } else { + dp[i][j] = dp[i - 1][j]; + } + } + } + + // Traverse the dynamic programming array to find the included tasks + const tasksToRun: CachedTask[] = []; + let j = TOTAL_CACHED_TASK_BYTE_LIMIT; + for (let i = tasks.length; i > 0 && j > 0; i--) { + if (dp[i][j] !== dp[i - 1][j]) { + const task = tasks[i - 1]; + const cachedTask = getCachedTask(task); + tasksToRun.unshift(cachedTask); + j -= getCachedTaskSize(cachedTask); + } + } + + return tasksToRun; +} + +function prepareTaskForRun(task: FoundTask): CachedTask { + return { + id: task.id, + status: task.status, + idempotencyKey: task.idempotencyKey, + noop: task.noop, + output: task.output as any, + parentId: task.parentId, + }; +} + +function calculateCachedTaskSize(task: CachedTask): number { + return JSON.stringify(task).length; +} + +async function findRun(prisma: PrismaClientOrTransaction, id: string) { + return await prisma.jobRun.findUnique({ + where: { id }, + include: { + environment: true, + endpoint: true, + organization: true, + externalAccount: true, + queue: true, + runConnections: { + include: { + integration: true, + connection: { + include: { + dataReference: true, + }, + }, + }, + }, + tasks: { + where: { + status: { + in: ["COMPLETED"], + }, + }, + }, + event: true, + version: { + include: { + job: true, + organization: true, + }, + }, + }, + }); +} diff --git a/apps/webapp/app/services/runs/runFinished.server.ts b/apps/webapp/app/services/runs/runFinished.server.ts deleted file mode 100644 index 6a09d3e51..000000000 --- a/apps/webapp/app/services/runs/runFinished.server.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { PrismaClient } from "~/db.server"; -import { prisma } from "~/db.server"; -import { workerQueue } from "../worker.server"; -import { z } from "zod"; -import { RawEventSchema, SendEventOptionsSchema } from "@trigger.dev/core"; -import { IngestSendEvent } from "../events/ingestSendEvent.server"; - -const SendEventOutputSchema = z.object({ - events: z.array(RawEventSchema), - options: SendEventOptionsSchema.optional(), -}); - -export class RunFinishedService { - #prismaClient: PrismaClient; - #ingestEventService = new IngestSendEvent(); - - constructor(prismaClient: PrismaClient = prisma) { - this.#prismaClient = prismaClient; - } - - public async call(id: string) { - const run = await this.#prismaClient.jobRun.findUniqueOrThrow({ - where: { id }, - include: { - environment: { - include: { - project: true, - organization: true, - }, - }, - }, - }); - - // Make sure to start any queued runs once this run is finished - await workerQueue.enqueue("startQueuedRuns", { - id: run.queueId, - }); - - if ( - run.status === "SUCCESS" && - run.output && - typeof run.output === "object" && - "events" in run.output - ) { - // If the run successfully completes, we will parse the output and - // if it's in the form of { events: Array } then we will send the events - const parsedOutput = SendEventOutputSchema.safeParse(run.output); - - if (parsedOutput.success) { - for (const newEvent of parsedOutput.data.events) { - await this.#ingestEventService.call(run.environment, newEvent, parsedOutput.data.options); - } - } - } - } -} diff --git a/apps/webapp/app/services/runs/startQueuedRuns.server.ts b/apps/webapp/app/services/runs/startQueuedRuns.server.ts deleted file mode 100644 index 739c8e224..000000000 --- a/apps/webapp/app/services/runs/startQueuedRuns.server.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { PrismaClient } from "~/db.server"; -import { prisma } from "~/db.server"; -import { workerQueue } from "../worker.server"; - -export class StartQueuedRunsService { - #prismaClient: PrismaClient; - - constructor(prismaClient: PrismaClient = prisma) { - this.#prismaClient = prismaClient; - } - - public async call(id: string) { - const queue = await this.#prismaClient.jobQueue.findUnique({ - where: { id }, - include: { - runs: { - where: { - status: "QUEUED", - }, - orderBy: { - queuedAt: "asc", - }, - take: 1, - }, - }, - }); - - if (!queue) { - return; - } - - if (queue.runs.length === 0) { - return; - } - - if (queue.jobCount >= queue.maxJobs) { - return; - } - - const run = queue.runs[0]; - - if (!run) { - return; - } - - await workerQueue.enqueue( - "startRun", - { - id: run.id, - }, - { - queueName: `job-queue:${queue.id}`, - } - ); - } -} diff --git a/apps/webapp/app/services/runs/startRun.server.ts b/apps/webapp/app/services/runs/startRun.server.ts index f050dc5c7..b9b647534 100644 --- a/apps/webapp/app/services/runs/startRun.server.ts +++ b/apps/webapp/app/services/runs/startRun.server.ts @@ -1,7 +1,7 @@ import type { ConnectionType, Integration, IntegrationConnection } from "@trigger.dev/database"; -import { EXECUTE_JOB_RETRY_LIMIT, PREPROCESS_RETRY_LIMIT } from "~/consts"; import type { PrismaClient, PrismaClientOrTransaction } from "~/db.server"; import { prisma } from "~/db.server"; +import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server"; import { workerQueue } from "../worker.server"; type FoundRun = NonNullable>>; @@ -21,34 +21,20 @@ export class StartRunService { return; } - if (run.queue.jobCount >= run.queue.maxJobs) { - await this.#queueRun(id); - } else { - const runConnectionsByKey = await createRunConnections(this.#prismaClient, run); + const runConnectionsByKey = await createRunConnections(this.#prismaClient, run); - if (hasMissingConnections(runConnectionsByKey)) { - await this.#handleMissingConnections(id, runConnectionsByKey); - } else { - await this.#startRun(id, run, runConnectionsByKey); - } + if (hasMissingConnections(runConnectionsByKey)) { + await this.#handleMissingConnections(id, runConnectionsByKey); + } else { + await this.#startRun(id, run, runConnectionsByKey); } } #runIsStartable(run: FoundRun) { - const startableStatuses = ["PENDING", "QUEUED", "WAITING_ON_CONNECTIONS"] as const; + const startableStatuses = ["PENDING", "WAITING_ON_CONNECTIONS"] as const; return startableStatuses.includes(run.status); } - async #queueRun(id: string) { - await this.#prismaClient.jobRun.update({ - where: { id }, - data: { - status: "QUEUED", - queuedAt: new Date(), - }, - }); - } - async #startRun(id: string, run: FoundRun, runConnectionsByKey: RunConnectionsByKey) { const createRunConnections = Object.entries(runConnectionsByKey) .map(([key, runConnection]) => @@ -69,89 +55,35 @@ export class StartRunService { ) .filter(Boolean); - const updateRunAndCreateExecution = async () => { + const updateRun = async () => { if (run.preprocess) { // Start the jobRun and increment the jobCount - await this.#prismaClient.jobRun.update({ + return await this.#prismaClient.jobRun.update({ where: { id }, data: { status: "PREPROCESSING", - queue: { - update: { - jobCount: { - increment: 1, - }, - }, - }, runConnections: { create: createRunConnections, }, }, }); - - return await this.#prismaClient.jobRunExecution.create({ - data: { - run: { - connect: { - id, - }, - }, - status: "PENDING", - reason: "PREPROCESS", - retryLimit: PREPROCESS_RETRY_LIMIT, - }, - }); } else { - // Start the jobRun and increment the jobCount - await this.#prismaClient.jobRun.update({ + return await this.#prismaClient.jobRun.update({ where: { id }, data: { - status: "STARTED", - startedAt: new Date(), - queue: { - update: { - jobCount: { - increment: 1, - }, - }, - }, + status: "QUEUED", + queuedAt: new Date(), runConnections: { create: createRunConnections, }, }, }); - - return await this.#prismaClient.jobRunExecution.create({ - data: { - run: { - connect: { - id, - }, - }, - status: "PENDING", - reason: "EXECUTE_JOB", - retryLimit: EXECUTE_JOB_RETRY_LIMIT, - }, - }); } }; - const execution = await updateRunAndCreateExecution(); + const updatedRun = await updateRun(); - const job = await workerQueue.enqueue("performRunExecution", { - id: execution.id, - }); - - await this.#prismaClient.jobRunExecution.update({ - where: { id: execution.id }, - data: { - graphileJobId: job.id, - }, - }); - - await workerQueue.enqueue("startQueuedRuns", { - id: run.queueId, - }); + await enqueueRunExecutionV2(updatedRun, this.#prismaClient); } async #handleMissingConnections(id: string, runConnectionsByKey: RunConnectionsByKey) { diff --git a/apps/webapp/app/services/tasks/performTaskOperation.server.ts b/apps/webapp/app/services/tasks/performTaskOperation.server.ts index eeddc7f76..eb425bbf5 100644 --- a/apps/webapp/app/services/tasks/performTaskOperation.server.ts +++ b/apps/webapp/app/services/tasks/performTaskOperation.server.ts @@ -1,7 +1,3 @@ -import type { Task } from "@trigger.dev/database"; -import { EXECUTE_JOB_RETRY_LIMIT } from "~/consts"; -import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; -import { workerQueue } from "../worker.server"; import { FetchOperationSchema, FetchRequestInit, @@ -10,9 +6,13 @@ import { RedactString, calculateRetryAt, } from "@trigger.dev/core"; +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 { formatUnknownError } from "~/utils/formatErrors.server"; +import { workerQueue } from "../worker.server"; type FoundTask = Awaited>; @@ -192,7 +192,7 @@ export class PerformTaskOperationService { }); } - async #resumeTaskWithError(task: Task, output: any) { + async #resumeTaskWithError(task: NonNullable, output: any) { await $transaction(this.#prismaClient, async (tx) => { await tx.task.update({ where: { id: task.id }, @@ -243,34 +243,8 @@ export class PerformTaskOperationService { }); } - async #resumeRunExecution(task: Task, prisma: PrismaClientOrTransaction) { - await $transaction(prisma, async (tx) => { - const newJobExecution = await tx.jobRunExecution.create({ - data: { - runId: task.runId, - reason: "EXECUTE_JOB", - status: "PENDING", - retryLimit: EXECUTE_JOB_RETRY_LIMIT, - }, - }); - - const graphileJob = await workerQueue.enqueue( - "performRunExecution", - { - id: newJobExecution.id, - }, - { tx } - ); - - await tx.jobRunExecution.update({ - where: { - id: newJobExecution.id, - }, - data: { - graphileJobId: graphileJob.id, - }, - }); - }); + async #resumeRunExecution(task: NonNullable, prisma: PrismaClientOrTransaction) { + await enqueueRunExecutionV2(task.run, prisma); } } @@ -305,6 +279,11 @@ async function findTask(prisma: PrismaClient, id: string) { where: { id }, include: { attempts: true, + run: { + include: { + queue: true, + }, + }, }, }); } diff --git a/apps/webapp/app/services/worker.server.ts b/apps/webapp/app/services/worker.server.ts index 8e6134176..c7bfc9313 100644 --- a/apps/webapp/app/services/worker.server.ts +++ b/apps/webapp/app/services/worker.server.ts @@ -12,9 +12,8 @@ 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 { PerformRunExecutionService } from "./runs/performRunExecution.server"; -import { RunFinishedService } from "./runs/runFinished.server"; -import { StartQueuedRunsService } from "./runs/startQueuedRuns.server"; +import { PerformRunExecutionV1Service } from "./runs/performRunExecutionV1.server"; +import { PerformRunExecutionV2Service } from "./runs/performRunExecutionV2.server"; import { StartRunService } from "./runs/startRun.server"; import { DeliverScheduledEventService } from "./schedules/deliverScheduledEvent.server"; import { ActivateSourceService } from "./sources/activateSource.server"; @@ -30,13 +29,9 @@ const workerCatalog = { }), scheduleEmail: DeliverEmailSchema, startRun: z.object({ id: z.string() }), - performRunExecution: z.object({ - id: z.string(), - }), performTaskOperation: z.object({ id: z.string(), }), - runFinished: z.object({ id: z.string() }), deliverHttpSourceRequest: z.object({ id: z.string() }), refreshOAuthToken: z.object({ organizationId: z.string(), @@ -46,7 +41,7 @@ const workerCatalog = { id: z.string(), orphanedEvents: z.array(z.string()).optional(), }), - startQueuedRuns: z.object({ id: z.string() }), + deliverEvent: z.object({ id: z.string() }), "events.invokeDispatcher": z.object({ id: z.string(), @@ -64,10 +59,24 @@ const workerCatalog = { }), }; +const executionWorkerCatalog = { + performRunExecution: z.object({ + id: z.string(), + }), + performRunExecutionV2: z.object({ + id: z.string(), + reason: z.enum(["EXECUTE_JOB", "PREPROCESS"]), + resumeTaskId: z.string().optional(), + isRetry: z.boolean(), + }), +}; + let workerQueue: ZodWorker; +let executionWorker: ZodWorker; declare global { var __worker__: ZodWorker; + var __executionWorker__: ZodWorker; } // this is needed because in development we don't want to restart @@ -76,25 +85,41 @@ declare global { // in production we'll have a single connection to the DB. if (env.NODE_ENV === "production") { workerQueue = getWorkerQueue(); + executionWorker = getExecutionWorkerQueue(); } else { if (!global.__worker__) { global.__worker__ = getWorkerQueue(); } workerQueue = global.__worker__; + + if (!global.__executionWorker__) { + global.__executionWorker__ = getExecutionWorkerQueue(); + } + + executionWorker = global.__executionWorker__; } export async function init() { - await workerQueue.initialize(); + if (env.WORKER_ENABLED === "true") { + await workerQueue.initialize(); + } + + if (env.EXECUTION_WORKER_ENABLED === "true") { + await executionWorker.initialize(); + } } function getWorkerQueue() { return new ZodWorker({ + name: "workerQueue", prisma, runnerOptions: { connectionString: env.DATABASE_URL, - concurrency: 5, - pollInterval: 1000, + concurrency: env.WORKER_CONCURRENCY, + pollInterval: env.WORKER_POLL_INTERVAL, noPreparedStatements: env.DATABASE_URL !== env.DIRECT_URL, + schema: env.WORKER_SCHEMA, + maxPoolSize: env.WORKER_CONCURRENCY, }, schema: workerCatalog, recurringTasks: { @@ -124,6 +149,7 @@ function getWorkerQueue() { }, tasks: { "events.invokeDispatcher": { + priority: 0, // smaller number = higher priority maxAttempts: 3, handler: async (payload, job) => { const service = new InvokeDispatcherService(); @@ -132,6 +158,7 @@ function getWorkerQueue() { }, }, "events.deliverScheduled": { + priority: 0, // smaller number = higher priority maxAttempts: 5, handler: async ({ id, payload }, job) => { const service = new DeliverScheduledEventService(); @@ -140,6 +167,7 @@ function getWorkerQueue() { }, }, connectionCreated: { + priority: 10, // smaller number = higher priority maxAttempts: 3, handler: async (payload, job) => { const service = new IntegrationConnectionCreatedService(); @@ -148,6 +176,7 @@ function getWorkerQueue() { }, }, missingConnectionCreated: { + priority: 10, // smaller number = higher priority maxAttempts: 3, handler: async (payload, job) => { const service = new MissingConnectionCreatedService(); @@ -155,24 +184,8 @@ function getWorkerQueue() { await service.call(payload.id); }, }, - runFinished: { - maxAttempts: 3, - handler: async (payload, job) => { - const service = new RunFinishedService(); - - await service.call(payload.id); - }, - }, - startQueuedRuns: { - maxAttempts: 3, - queueName: (payload) => `queue:${payload.id}`, - handler: async (payload, job) => { - const service = new StartQueuedRunsService(); - - await service.call(payload.id); - }, - }, activateSource: { + priority: 10, // smaller number = higher priority maxAttempts: 3, handler: async (payload, job) => { const service = new ActivateSourceService(); @@ -181,6 +194,7 @@ function getWorkerQueue() { }, }, deliverHttpSourceRequest: { + priority: 1, // smaller number = higher priority maxAttempts: 25, handler: async (payload, job) => { const service = new DeliverHttpSourceRequestService(); @@ -189,6 +203,7 @@ function getWorkerQueue() { }, }, startRun: { + priority: 0, // smaller number = higher priority maxAttempts: 8, handler: async (payload, job) => { const service = new StartRunService(); @@ -196,16 +211,8 @@ function getWorkerQueue() { await service.call(payload.id); }, }, - performRunExecution: { - queueName: (payload) => `runs:${payload.id}`, - maxAttempts: 1, - handler: async (payload, job) => { - const service = new PerformRunExecutionService(); - - await service.call(payload.id); - }, - }, performTaskOperation: { + priority: 0, // smaller number = higher priority queueName: (payload) => `tasks:${payload.id}`, maxAttempts: 3, handler: async (payload, job) => { @@ -223,6 +230,7 @@ function getWorkerQueue() { }, }, indexEndpoint: { + priority: 1, // smaller number = higher priority handler: async (payload, job) => { const service = new IndexEndpointService(); @@ -230,6 +238,7 @@ function getWorkerQueue() { }, }, deliverEvent: { + priority: 0, // smaller number = higher priority handler: async (payload, job) => { const service = new DeliverEventService(); @@ -237,6 +246,7 @@ function getWorkerQueue() { }, }, refreshOAuthToken: { + priority: 8, // smaller number = higher priority queueName: "internal-queue", maxAttempts: 10, handler: async (payload, job) => { @@ -249,4 +259,42 @@ function getWorkerQueue() { }); } -export { workerQueue }; +function getExecutionWorkerQueue() { + return new ZodWorker({ + name: "executionWorker", + prisma, + runnerOptions: { + connectionString: env.DATABASE_URL, + concurrency: env.EXECUTION_WORKER_CONCURRENCY, + pollInterval: env.EXECUTION_WORKER_POLL_INTERVAL, + noPreparedStatements: env.DATABASE_URL !== env.DIRECT_URL, + schema: env.WORKER_SCHEMA, + maxPoolSize: env.EXECUTION_WORKER_CONCURRENCY, + }, + schema: executionWorkerCatalog, + tasks: { + performRunExecution: { + priority: 0, // smaller number = higher priority + maxAttempts: 1, + handler: async (payload, job) => { + // This is a legacy task that we don't use anymore, but needs to be here for backwards compatibility + // TODO: remove this once all performRunExecution tasks have been processed + const service = new PerformRunExecutionV1Service(); + + await service.call(payload.id); + }, + }, + performRunExecutionV2: { + priority: 0, // smaller number = higher priority + maxAttempts: 18, + handler: async (payload, job) => { + const service = new PerformRunExecutionV2Service(); + + await service.call(payload.id, payload.reason, payload.isRetry, payload.resumeTaskId); + }, + }, + }, + }); +} + +export { executionWorker, workerQueue }; diff --git a/apps/webapp/server.ts b/apps/webapp/server.ts index 9b2fbbee9..892a99430 100644 --- a/apps/webapp/server.ts +++ b/apps/webapp/server.ts @@ -54,14 +54,18 @@ app.all( const port = process.env.REMIX_APP_PORT || 3000; -const server = app.listen(port, () => { - // require the built app so we're ready when the first request comes in - require(BUILD_DIR); - console.log(`✅ app ready: http://localhost:${port}`); -}); +if (process.env.HTTP_SERVER_DISABLED !== "true") { + const server = app.listen(port, () => { + // require the built app so we're ready when the first request comes in + require(BUILD_DIR); + console.log(`✅ app ready: http://localhost:${port}`); + }); -// Handle shutdowns gracefully -createTerminus(server, { signals: ["SIGINT", "SIGTERM"], timeout: 5000 }); + // Handle shutdowns gracefully + createTerminus(server, { signals: ["SIGINT", "SIGTERM"], timeout: 5000 }); +} else { + console.log(`✅ app ready (skipping http server)`); +} function purgeRequireCache() { // purge require cache on requests for "server side HMR" this won't let diff --git a/docker/Dockerfile b/docker/Dockerfile index 525fa8aeb..2c7d3fc30 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -31,7 +31,8 @@ RUN corepack enable ENV NODE_ENV production RUN pnpm install --prod --no-frozen-lockfile COPY --from=pruner --chown=node:node /triggerdotdev/packages/database/prisma/schema.prisma /triggerdotdev/packages/database/prisma/schema.prisma -RUN pnpx prisma@5.1.1 generate --schema /triggerdotdev/packages/database/prisma/schema.prisma +# RUN pnpm add @prisma/client@5.1.1 -w +RUN pnpx prisma@4.16.0 generate --schema /triggerdotdev/packages/database/prisma/schema.prisma ## Builder (builds the webapp) FROM base AS builder diff --git a/docs/sdk/job.mdx b/docs/sdk/job.mdx index 2d4885d5b..a597620ec 100644 --- a/docs/sdk/job.mdx +++ b/docs/sdk/job.mdx @@ -114,12 +114,6 @@ client.defineJob({ Imports the specified integrations into the Job. The integrations will be available on the `io` object in the `run()` function with the same name as the key. For example: - - The `enabled` property is used to enable or disable the Job. If you disable a Job, it will not run. - - - The `queue` property is used to specify a custom queue. If you use an Object and specify the `maxConcurrent` option, you can control how many simulataneous runs can happen. - The `logLevel` property is an optional property that specifies the level of logging for the Job. The level is inherited from the client if you omit this property. diff --git a/examples/job-catalog/package.json b/examples/job-catalog/package.json index 35f12507f..287cdd97d 100644 --- a/examples/job-catalog/package.json +++ b/examples/job-catalog/package.json @@ -4,9 +4,14 @@ "private": true, "scripts": { "stripe": "nodemon --watch src/stripe.ts -r tsconfig-paths/register -r dotenv/config src/stripe.ts", + "slack": "nodemon --watch src/slack.ts -r tsconfig-paths/register -r dotenv/config src/slack.ts", + "openai": "nodemon --watch src/openai.ts -r tsconfig-paths/register -r dotenv/config src/openai.ts", "sendgrid": "nodemon --watch src/sendgrid.ts -r tsconfig-paths/register -r dotenv/config src/sendgrid.ts", "supabase": "nodemon --watch src/supabase.ts -r tsconfig-paths/register -r dotenv/config src/supabase.ts", "supabase:types": "npx supabase gen types typescript --project-id $SUPABASE_PROJECT_ID --schema public --schema auth --schema storage > src/supabase-types.ts", + "events": "nodemon --watch src/events.ts -r tsconfig-paths/register -r dotenv/config src/events.ts", + "stressTest": "nodemon --watch src/stressTest.ts -r tsconfig-paths/register -r dotenv/config src/stressTest.ts", + "delays": "nodemon --watch src/delays.ts -r tsconfig-paths/register -r dotenv/config src/delays.ts", "dev:trigger": "trigger-cli dev --port 8080" }, "dependencies": { @@ -37,4 +42,4 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^3.14.1" } -} +} \ No newline at end of file diff --git a/examples/job-catalog/src/delays.ts b/examples/job-catalog/src/delays.ts new file mode 100644 index 000000000..f41396f89 --- /dev/null +++ b/examples/job-catalog/src/delays.ts @@ -0,0 +1,36 @@ +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: "delays-example-1", + name: "Delays Example 1", + version: "1.0.0", + trigger: eventTrigger({ + name: "delays.example", + }), + run: async (payload, io, ctx) => { + await io.wait("wait-1", 60); + }, +}); + +client.defineJob({ + id: "delays-example-2", + name: "Delays Example 2 - Long Delay", + version: "1.0.0", + trigger: eventTrigger({ + name: "delays.example.long", + }), + run: async (payload, io, ctx) => { + await io.wait("wait-1", 60 * 30); + }, +}); + +createExpressServer(client); diff --git a/examples/job-catalog/src/events.ts b/examples/job-catalog/src/events.ts new file mode 100644 index 000000000..75b4e12fe --- /dev/null +++ b/examples/job-catalog/src/events.ts @@ -0,0 +1,32 @@ +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: "event-example-1", + name: "Event Example 1", + version: "1.0.0", + trigger: eventTrigger({ + name: "event.example", + }), + run: async (payload, io, ctx) => { + await io.runTask("task-example-1", { name: "Task 1" }, async () => { + return { + message: "Hello World", + }; + }); + + await io.wait("wait-1", 1); + + await io.logger.info("Hello World", { ctx }); + }, +}); + +createExpressServer(client); diff --git a/examples/job-catalog/src/openai.ts b/examples/job-catalog/src/openai.ts new file mode 100644 index 000000000..016da4e6e --- /dev/null +++ b/examples/job-catalog/src/openai.ts @@ -0,0 +1,80 @@ +import { createExpressServer } from "@trigger.dev/express"; +import { TriggerClient, eventTrigger } from "@trigger.dev/sdk"; +import { OpenAI } from "@trigger.dev/openai"; + +export const client = new TriggerClient({ + id: "job-catalog", + apiKey: process.env["TRIGGER_API_KEY"], + apiUrl: process.env["TRIGGER_API_URL"], + verbose: false, + ioLogLocalEnabled: true, +}); + +const openai = new OpenAI({ + id: "openai", + apiKey: process.env["OPENAI_API_KEY"]!, +}); + +client.defineJob({ + id: "openai-tasks", + name: "OpenAI Tasks", + version: "0.0.1", + trigger: eventTrigger({ + name: "openai.tasks", + }), + integrations: { + openai, + }, + run: async (payload, io, ctx) => { + const models = await io.openai.listModels("list-models"); + + if (models.data.length > 0) { + await io.openai.retrieveModel("get-model", { + model: models.data[0].id, + }); + } + + await io.openai.backgroundCreateChatCompletion("background-chat-completion", { + model: "gpt-3.5-turbo", + messages: [ + { + role: "user", + content: "Create a good programming joke about background jobs", + }, + ], + }); + + await io.openai.createChatCompletion("chat-completion", { + model: "gpt-3.5-turbo", + messages: [ + { + role: "user", + content: "Create a good programming joke about background jobs", + }, + ], + }); + + await io.openai.backgroundCreateCompletion("background-completion", { + model: "text-davinci-003", + prompt: "Create a good programming joke about Tasks", + }); + + await io.openai.createCompletion("completion", { + model: "text-davinci-003", + prompt: "Create a good programming joke about Tasks", + }); + + await io.openai.createEdit("edit", { + model: "text-davinci-edit-001", + input: "Thsi is ridddled with erors", + instruction: "Fix the spelling errors", + }); + + await io.openai.createEmbedding("embedding", { + model: "text-embedding-ada-002", + input: "The food was delicious and the waiter...", + }); + }, +}); + +createExpressServer(client); diff --git a/examples/job-catalog/src/slack.ts b/examples/job-catalog/src/slack.ts new file mode 100644 index 000000000..57ced7722 --- /dev/null +++ b/examples/job-catalog/src/slack.ts @@ -0,0 +1,33 @@ +import { createExpressServer } from "@trigger.dev/express"; +import { TriggerClient, eventTrigger } from "@trigger.dev/sdk"; +import { Slack } from "@trigger.dev/slack"; + +export const slack = new Slack({ id: "slack" }); + +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: "slack-example-1", + name: "Slack Example 1", + version: "1.0.0", + trigger: eventTrigger({ + name: "slack.example", + }), + integrations: { + slack, + }, + run: async (payload, io, ctx) => { + await io.slack.postMessage("Slack 📝", { + channel: "C04GWUTDC3W", + text: "Welcome to the team, Eric!", + }); + }, +}); + +createExpressServer(client); diff --git a/examples/job-catalog/src/stressTest.ts b/examples/job-catalog/src/stressTest.ts new file mode 100644 index 000000000..06949274e --- /dev/null +++ b/examples/job-catalog/src/stressTest.ts @@ -0,0 +1,41 @@ +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: "stress-test-1", + name: "Stress Test 1", + version: "1.0.0", + trigger: eventTrigger({ + name: "stress.test.1", + }), + run: async (payload, io, ctx) => { + // Run 10 tasks, each with a 300KB output + for (let i = 0; i < 10; i++) { + await io.runTask(`task-${i}`, { name: `Task ${i}` }, async (task) => { + return { + output: "a".repeat(300 * 1024), + }; + }); + } + + // Now run a single task with 5MB output + await io.runTask(`task-5mb`, { name: `Task 5MB` }, async (task) => { + return { + output: "a".repeat(5 * 1024 * 1024), + }; + }); + + // Now do a wait for 5 seconds + await io.wait("wait", 5); + }, +}); + +createExpressServer(client); diff --git a/package.json b/package.json index ae9ca86c0..45f508688 100644 --- a/package.json +++ b/package.json @@ -66,4 +66,4 @@ "@changesets/cli": "^2.26.0", "node-fetch": "2.6.x" } -} +} \ No newline at end of file diff --git a/packages/core/src/logger.ts b/packages/core/src/logger.ts index 1cd38c1f3..51a6417c6 100644 --- a/packages/core/src/logger.ts +++ b/packages/core/src/logger.ts @@ -166,6 +166,11 @@ function filterKeys(obj: unknown, keys: string[]): any { for (const [key, value] of Object.entries(obj)) { if (keys.includes(key)) { + if (value) { + filteredObj[key] = `[filtered ${prettyPrintBytes(value)}]`; + } else { + filteredObj[key] = value; + } continue; } @@ -174,3 +179,25 @@ function filterKeys(obj: unknown, keys: string[]): any { return filteredObj; } + +function prettyPrintBytes(value: unknown): string { + if (process.env.NODE_ENV === "production") { + return "skipped size"; + } + + const sizeInBytes = Buffer.byteLength(JSON.stringify(value), "utf8"); + + if (sizeInBytes < 1024) { + return `${sizeInBytes} bytes`; + } + + if (sizeInBytes < 1024 * 1024) { + return `${(sizeInBytes / 1024).toFixed(2)} KB`; + } + + if (sizeInBytes < 1024 * 1024 * 1024) { + return `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`; + } + + return `${(sizeInBytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; +} diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index f8d41cfa6..d90a06066 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -160,9 +160,8 @@ export const JobMetadataSchema = z.object({ trigger: TriggerMetadataSchema, integrations: z.record(IntegrationConfigSchema), internal: z.boolean().default(false), - queue: z.union([QueueOptionsSchema, z.string()]).optional(), - startPosition: z.enum(["initial", "latest"]), enabled: z.boolean(), + startPosition: z.enum(["initial", "latest"]), preprocessRuns: z.boolean(), }); diff --git a/packages/database/package.json b/packages/database/package.json index 173dec51e..0efc983dd 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -5,11 +5,11 @@ "main": "./src/index.ts", "types": "./src/index.ts", "dependencies": { - "@prisma/client": "5.1.1", + "@prisma/client": "4.16.0", "typescript": "^4.8.4" }, "devDependencies": { - "prisma": "^5.1.0" + "prisma": "4.16.0" }, "scripts": { "generate": "prisma generate", @@ -18,4 +18,4 @@ "db:studio": "prisma studio", "typecheck": "tsc --noEmit" } -} +} \ No newline at end of file diff --git a/packages/trigger-sdk/src/io.ts b/packages/trigger-sdk/src/io.ts index 1f16d1a27..66ce7ee77 100644 --- a/packages/trigger-sdk/src/io.ts +++ b/packages/trigger-sdk/src/io.ts @@ -69,7 +69,7 @@ export class IO { if (options.cachedTasks) { options.cachedTasks.forEach((task) => { - this._cachedTasks.set(task.id, task); + this._cachedTasks.set(task.idempotencyKey, task); }); } diff --git a/packages/trigger-sdk/src/job.ts b/packages/trigger-sdk/src/job.ts index c7aa0aebc..c9b142c82 100644 --- a/packages/trigger-sdk/src/job.ts +++ b/packages/trigger-sdk/src/job.ts @@ -42,9 +42,9 @@ export type JobOptions< }); ``` */ integrations?: TIntegrations; - /** The `queue` property is used to specify a custom queue. If you use an Object and specify the `maxConcurrent` option, you can control how many simulataneous runs can happen. */ + /** @deprecated This property is deprecated and no longer effects the execution of the Job + * */ queue?: QueueOptions | string; - startPosition?: "initial" | "latest"; /** The `enabled` property is used to enable or disable the Job. If you disable a Job, it will not run. */ enabled?: boolean; /** This function gets called automatically when a Run is Triggered. @@ -142,8 +142,7 @@ export class Job< event: this.trigger.event, trigger: this.trigger.toJSON(), integrations: this.integrations, - queue: this.options.queue, - startPosition: this.options.startPosition ?? "latest", + startPosition: "latest", // this is deprecated, leaving this for now to make sure newer clients work with older servers enabled: typeof this.options.enabled === "boolean" ? this.options.enabled : true, preprocessRuns: this.trigger.preprocessRuns, internal, diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index 9a30ff074..026f36377 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -508,11 +508,6 @@ export class TriggerClient { integrations: { integration: options.source.integration, }, - queue: { - name: options.key, - maxConcurrent: 1, - }, - startPosition: "initial", run: async (event, io, ctx) => { const updates = await options.source.register(options.params, event, io, ctx); diff --git a/perf/package.json b/perf/package.json new file mode 100644 index 000000000..d62f25191 --- /dev/null +++ b/perf/package.json @@ -0,0 +1,41 @@ +{ + "name": "perf", + "version": "1.0.0", + "description": "Performance testing for Trigger.dev", + "main": "src/index.ts", + "scripts": { + "start": "ts-node -r tsconfig-paths/register -r dotenv/config src/index.ts", + "server": "ts-node -r tsconfig-paths/register -r dotenv/config src/server.ts", + "dev:trigger": "trigger-cli dev --port ${PORT:-3000}" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@trigger.dev/express": "workspace:*", + "@trigger.dev/github": "workspace:*", + "@trigger.dev/openai": "workspace:*", + "@trigger.dev/plain": "workspace:*", + "@trigger.dev/resend": "workspace:*", + "@trigger.dev/sdk": "workspace:*", + "@trigger.dev/sendgrid": "workspace:*", + "@trigger.dev/slack": "workspace:*", + "@trigger.dev/stripe": "workspace:*", + "@trigger.dev/supabase": "workspace:*", + "@trigger.dev/typeform": "workspace:*", + "zod": "3.21.4" + }, + "devDependencies": { + "@trigger.dev/cli": "workspace:*", + "@trigger.dev/tsconfig": "workspace:*", + "@types/node": "^20.5.0", + "concurrently": "^8.2.0", + "dotenv": "^16.3.1", + "nodemon": "^3.0.1", + "ts-node": "^10.9.1", + "tsconfig-paths": "^3.14.1", + "typescript": "^5.1.6" + }, + "trigger.dev": { + "endpointId": "perf" + } +} \ No newline at end of file diff --git a/perf/src/index.ts b/perf/src/index.ts new file mode 100644 index 000000000..b2170e3f9 --- /dev/null +++ b/perf/src/index.ts @@ -0,0 +1,83 @@ +import { triggerClient } from "./trigger"; + +async function sendEvent() { + try { + return await triggerClient.sendEvent({ + name: "perf.test", + payload: { + string: "Hello, World!", + number: 42, + boolean: true, + nullValue: null, + array: [1, 2, 3], + object: { + nestedString: "Nested value", + nestedNumber: 3.14, + nestedArray: ["apple", "banana", "cherry"], + nestedObject: { + nestedBoolean: false, + nestedNull: null, + }, + }, + }, + }); + } catch (err) { + console.error(err); + } +} + +async function main() { + console.log("Preparing perf tests..."); + + // wait for 10 seconds + await new Promise((resolve) => setTimeout(resolve, 10000)); + + console.log("Starting perf tests in 1 second..."); + + // wait for 1 seconds + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Send 5 events per second for 30 seconds (1 event == 10 runs) + for (let i = 0; i < 30; i++) { + console.log("Sending 5 event..."); + + await sendEvent(); + await sendEvent(); + await sendEvent(); + await sendEvent(); + await sendEvent(); + + await new Promise((resolve) => setTimeout(resolve, 950)); + } + + // console.log("Sending 30 events..."); + // for (let i = 0; i < 30; i++) { + // await sendEvent(); + // } +} + +async function mainLong() { + console.log("Preparing long perf tests..."); + + // wait for 10 seconds + await new Promise((resolve) => setTimeout(resolve, 10000)); + + console.log("Starting long perf tests in 1 second..."); + + // wait for 1 seconds + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Send 1 events every 5 seconds for 30 minutes + for (let i = 0; i < 360; i++) { + console.log("Sending 1 events..."); + + await sendEvent(); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + } +} + +mainLong().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/perf/src/server.ts b/perf/src/server.ts new file mode 100644 index 000000000..1671e8cae --- /dev/null +++ b/perf/src/server.ts @@ -0,0 +1,7 @@ +import { triggerClient } from "./trigger"; +import { createExpressServer } from "@trigger.dev/express"; + +const app = createExpressServer( + triggerClient, + process.env.PORT ? parseInt(process.env.PORT) : 3000 +); diff --git a/perf/src/trigger.ts b/perf/src/trigger.ts new file mode 100644 index 000000000..b1c883faa --- /dev/null +++ b/perf/src/trigger.ts @@ -0,0 +1,37 @@ +import { TriggerClient, eventTrigger } from "@trigger.dev/sdk"; +import { z } from "zod"; + +export const triggerClient = new TriggerClient({ + id: "perf", + apiKey: process.env.TRIGGER_API_KEY!, + apiUrl: process.env.TRIGGER_API_URL!, +}); + +// Define 10 jobs in a for loop +for (let i = 0; i < 10; i++) { + triggerClient.defineJob({ + id: `perf-test-${i + 1}`, + name: `Perf Test ${i + 1}`, + version: "1.0.0", + trigger: eventTrigger({ + name: "perf.test", + }), + queue: { + name: "perf-test", + maxConcurrent: 50, + }, + run: async (payload, io, ctx) => { + await io.runTask("task-1", { name: "task 1" }, async (task) => { + return { + value: Math.random(), + }; + }); + + await io.runTask("task-2", { name: "task 2" }, async (task) => { + return { + value: Math.random(), + }; + }); + }, + }); +} diff --git a/perf/tsconfig.json b/perf/tsconfig.json new file mode 100644 index 000000000..a8977b78e --- /dev/null +++ b/perf/tsconfig.json @@ -0,0 +1,37 @@ +{ + "extends": "@trigger.dev/tsconfig/node18.json", + "include": ["./src/**/*.ts"], + "compilerOptions": { + "baseUrl": ".", + "lib": ["DOM", "DOM.Iterable"], + "paths": { + "@/*": ["./src/*"], + "@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"], + "@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"], + "@trigger.dev/express": ["../../packages/express/src/index"], + "@trigger.dev/express/*": ["../../packages/express/src/*"], + "@trigger.dev/core": ["../../packages/core/src/index"], + "@trigger.dev/core/*": ["../../packages/core/src/*"], + "@trigger.dev/integration-kit": ["../../packages/integration-kit/src/index"], + "@trigger.dev/integration-kit/*": ["../../packages/integration-kit/src/*"], + "@trigger.dev/github": ["../../integrations/github/src/index"], + "@trigger.dev/github/*": ["../../integrations/github/src/*"], + "@trigger.dev/slack": ["../../integrations/slack/src/index"], + "@trigger.dev/slack/*": ["../../integrations/slack/src/*"], + "@trigger.dev/openai": ["../../integrations/openai/src/index"], + "@trigger.dev/openai/*": ["../../integrations/openai/src/*"], + "@trigger.dev/resend": ["../../integrations/resend/src/index"], + "@trigger.dev/resend/*": ["../../integrations/resend/src/*"], + "@trigger.dev/typeform": ["../../integrations/typeform/src/index"], + "@trigger.dev/typeform/*": ["../../integrations/typeform/src/*"], + "@trigger.dev/plain": ["../../integrations/plain/src/index"], + "@trigger.dev/plain/*": ["../../integrations/plain/src/*"], + "@trigger.dev/supabase": ["../../integrations/supabase/src/index"], + "@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"], + "@trigger.dev/stripe": ["../../integrations/stripe/src/index"], + "@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"], + "@trigger.dev/sendgrid": ["../../integrations/sendgrid/src/index"], + "@trigger.dev/sendgrid/*": ["../../integrations/sendgrid/src/*"] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44a942a33..9ea874566 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -906,14 +906,14 @@ importers: packages/database: specifiers: - '@prisma/client': 5.1.1 - prisma: ^5.1.0 + '@prisma/client': 4.16.0 + prisma: 4.16.0 typescript: ^4.8.4 dependencies: - '@prisma/client': 5.1.1_prisma@5.1.1 + '@prisma/client': 4.16.0_prisma@4.16.0 typescript: 4.9.5 devDependencies: - prisma: 5.1.1 + prisma: 4.16.0 packages/emails: specifiers: @@ -974,10 +974,10 @@ importers: dependencies: requireindex: 1.2.0 devDependencies: - eslint: 8.47.0 - eslint-doc-generator: 1.4.3_eslint@8.47.0 - eslint-plugin-eslint-plugin: 5.1.1_eslint@8.47.0 - eslint-plugin-node: 11.1.0_eslint@8.47.0 + eslint: 8.45.0 + eslint-doc-generator: 1.4.3_eslint@8.45.0 + eslint-plugin-eslint-plugin: 5.1.1_eslint@8.45.0 + eslint-plugin-node: 11.1.0_eslint@8.45.0 mocha: 10.2.0 npm-run-all: 4.1.5 @@ -1142,6 +1142,53 @@ importers: tsx: 3.12.2 typescript: 4.9.5 + perf: + specifiers: + '@trigger.dev/cli': workspace:* + '@trigger.dev/express': workspace:* + '@trigger.dev/github': workspace:* + '@trigger.dev/openai': workspace:* + '@trigger.dev/plain': workspace:* + '@trigger.dev/resend': workspace:* + '@trigger.dev/sdk': workspace:* + '@trigger.dev/sendgrid': workspace:* + '@trigger.dev/slack': workspace:* + '@trigger.dev/stripe': workspace:* + '@trigger.dev/supabase': workspace:* + '@trigger.dev/tsconfig': workspace:* + '@trigger.dev/typeform': workspace:* + '@types/node': ^20.5.0 + concurrently: ^8.2.0 + dotenv: ^16.3.1 + nodemon: ^3.0.1 + ts-node: ^10.9.1 + tsconfig-paths: ^3.14.1 + typescript: ^5.1.6 + zod: 3.21.4 + dependencies: + '@trigger.dev/express': link:../packages/express + '@trigger.dev/github': link:../integrations/github + '@trigger.dev/openai': link:../integrations/openai + '@trigger.dev/plain': link:../integrations/plain + '@trigger.dev/resend': link:../integrations/resend + '@trigger.dev/sdk': link:../packages/trigger-sdk + '@trigger.dev/sendgrid': link:../integrations/sendgrid + '@trigger.dev/slack': link:../integrations/slack + '@trigger.dev/stripe': link:../integrations/stripe + '@trigger.dev/supabase': link:../integrations/supabase + '@trigger.dev/typeform': link:../integrations/typeform + zod: 3.21.4 + devDependencies: + '@trigger.dev/cli': link:../packages/cli + '@trigger.dev/tsconfig': link:../config-packages/tsconfig + '@types/node': 20.5.0 + concurrently: 8.2.0 + dotenv: 16.3.1 + nodemon: 3.0.1 + ts-node: 10.9.1_jch25vbq34sbabk3unb5q5rkqi + tsconfig-paths: 3.14.1 + typescript: 5.1.6 + packages: /@aashutoshrathi/word-wrap/1.2.6: @@ -1191,11 +1238,11 @@ packages: dependencies: '@ampproject/remapping': 2.2.0 '@babel/code-frame': 7.21.4 - '@babel/generator': 7.20.7 + '@babel/generator': 7.21.5 '@babel/helper-compilation-targets': 7.21.5_@babel+core@7.20.12 '@babel/helper-module-transforms': 7.20.11 '@babel/helpers': 7.20.7 - '@babel/parser': 7.20.7 + '@babel/parser': 7.21.8 '@babel/template': 7.20.7 '@babel/traverse': 7.20.12 '@babel/types': 7.21.5 @@ -1245,15 +1292,6 @@ packages: semver: 6.3.0 dev: true - /@babel/generator/7.20.7: - resolution: {integrity: sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - '@jridgewell/gen-mapping': 0.3.2 - jsesc: 2.5.2 - dev: true - /@babel/generator/7.21.5: resolution: {integrity: sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==} engines: {node: '>=6.9.0'} @@ -1288,7 +1326,7 @@ packages: '@babel/compat-data': 7.21.7 '@babel/core': 7.20.12 '@babel/helper-validator-option': 7.21.0 - browserslist: 4.21.10 + browserslist: 4.21.9 lru-cache: 5.1.1 semver: 6.3.0 dev: true @@ -1302,7 +1340,7 @@ packages: '@babel/compat-data': 7.21.7 '@babel/core': 7.21.8 '@babel/helper-validator-option': 7.21.0 - browserslist: 4.21.10 + browserslist: 4.21.9 lru-cache: 5.1.1 semver: 6.3.0 dev: true @@ -1619,14 +1657,6 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 - /@babel/parser/7.20.7: - resolution: {integrity: sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.21.5 - dev: true - /@babel/parser/7.21.8: resolution: {integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==} engines: {node: '>=6.0.0'} @@ -5024,7 +5054,7 @@ packages: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: eslint: 8.31.0 - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 3.4.2 dev: true /@eslint-community/eslint-utils/4.4.0_eslint@8.42.0: @@ -5034,7 +5064,7 @@ packages: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: eslint: 8.42.0 - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 3.4.2 dev: true /@eslint-community/eslint-utils/4.4.0_eslint@8.44.0: @@ -5044,7 +5074,7 @@ packages: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: eslint: 8.44.0 - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 3.4.2 dev: true /@eslint-community/eslint-utils/4.4.0_eslint@8.45.0: @@ -5054,28 +5084,12 @@ packages: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: eslint: 8.45.0 - eslint-visitor-keys: 3.4.3 - dev: false - - /@eslint-community/eslint-utils/4.4.0_eslint@8.47.0: - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.47.0 - eslint-visitor-keys: 3.4.3 - dev: true + eslint-visitor-keys: 3.4.2 /@eslint-community/regexpp/4.5.1: resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - /@eslint-community/regexpp/4.6.2: - resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - /@eslint/eslintrc/1.4.1: resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5099,7 +5113,7 @@ packages: dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.6.1 + espree: 9.6.0 globals: 13.19.0 ignore: 5.2.4 import-fresh: 3.3.0 @@ -5109,23 +5123,6 @@ packages: transitivePeerDependencies: - supports-color - /@eslint/eslintrc/2.1.2: - resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.6.1 - globals: 13.19.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - /@eslint/js/8.42.0: resolution: {integrity: sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5135,11 +5132,6 @@ packages: resolution: {integrity: sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /@eslint/js/8.47.0: - resolution: {integrity: sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - /@fal-works/esbuild-plugin-global-externals/2.1.2: resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} dev: true @@ -5603,7 +5595,7 @@ packages: graceful-fs: 4.2.10 jest-haste-map: 29.5.0 jest-regex-util: 29.4.3 - jest-util: 29.5.0 + jest-util: 29.6.2 micromatch: 4.0.5 pirates: 4.0.5 slash: 3.0.0 @@ -5819,7 +5811,7 @@ packages: react: '>=16' dependencies: '@types/mdx': 2.0.5 - '@types/react': 18.2.20 + '@types/react': 18.2.17 react: 18.2.0 dev: true @@ -7556,9 +7548,9 @@ packages: webpack: 5.80.0_uhpfu7q6noim4yjdo6qt2aajgu dev: true - /@prisma/client/5.1.1_prisma@5.1.1: - resolution: {integrity: sha512-fxcCeK5pMQGcgCqCrWsi+I2rpIbk0rAhdrN+ke7f34tIrgPwA68ensrpin+9+fZvuV2OtzHmuipwduSY6HswdA==} - engines: {node: '>=16.13'} + /@prisma/client/4.16.0_prisma@4.16.0: + resolution: {integrity: sha512-CBD+5IdZPiavhLkQokvsz1uz4r9ppixaqY/ajybWs4WXNnsDVMBKEqN3BiPzpSo79jiy22VKj/67pqt4VwIg9w==} + engines: {node: '>=14.17'} requiresBuild: true peerDependencies: prisma: '*' @@ -7566,16 +7558,16 @@ packages: prisma: optional: true dependencies: - '@prisma/engines-version': 5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e - prisma: 5.1.1 + '@prisma/engines-version': 4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c + prisma: 4.16.0 dev: false - /@prisma/engines-version/5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e: - resolution: {integrity: sha512-owZqbY/wucbr65bXJ/ljrHPgQU5xXTSkmcE/JcbqE1kusuAXV/TLN3/exmz21SZ5rJ7WDkyk70J2G/n68iogbQ==} + /@prisma/engines-version/4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c: + resolution: {integrity: sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA==} dev: false - /@prisma/engines/5.1.1: - resolution: {integrity: sha512-NV/4nVNWFZSJCCIA3HIFJbbDKO/NARc9ej0tX5S9k2EVbkrFJC4Xt9b0u4rNZWL4V+F5LAjvta8vzEUw0rw+HA==} + /@prisma/engines/4.16.0: + resolution: {integrity: sha512-M6XoMRXnqL0rqZGQS8ZpNiHYG4G1fKBdoqW/oBtHnr1in5UYgerZqal3CXchmd6OBD/770PE9dtjQuqcilZJUA==} requiresBuild: true /@protobufjs/aspromise/1.1.2: @@ -8980,7 +8972,7 @@ packages: '@slack/logger': 3.0.0 '@slack/types': 2.8.0 '@types/is-stream': 1.1.0 - '@types/node': 20.5.0 + '@types/node': 20.4.8 axios: 0.27.2 eventemitter3: 3.1.2 form-data: 2.5.1 @@ -9983,7 +9975,7 @@ packages: memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - semver: 7.5.0 + semver: 7.5.4 store2: 2.14.2 telejson: 7.1.0 ts-dedent: 2.2.0 @@ -11230,7 +11222,6 @@ packages: /@types/node/20.4.8: resolution: {integrity: sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==} - dev: true /@types/node/20.5.0: resolution: {integrity: sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q==} @@ -11291,7 +11282,7 @@ packages: /@types/react-dom/18.2.7: resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} dependencies: - '@types/react': 18.2.20 + '@types/react': 18.2.17 /@types/react/18.2.17: resolution: {integrity: sha512-u+e7OlgPPh+aryjOm5UJMX32OvB2E3QASOAqVMY6Ahs90djagxwv2ya0IctglNbNTexC12qCSMZG47KPfy1hAA==} @@ -11300,13 +11291,6 @@ packages: '@types/scheduler': 0.16.2 csstype: 3.1.1 - /@types/react/18.2.20: - resolution: {integrity: sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw==} - dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.2 - csstype: 3.1.1 - /@types/responselike/1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: @@ -11397,7 +11381,7 @@ packages: /@types/ws/8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 20.5.0 + '@types/node': 20.4.8 dev: true /@types/yargs-parser/21.0.0: @@ -11664,19 +11648,19 @@ packages: - supports-color dev: false - /@typescript-eslint/utils/5.59.6_eslint@8.47.0: + /@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} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0_eslint@8.47.0 + '@eslint-community/eslint-utils': 4.4.0_eslint@8.45.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 - eslint: 8.47.0 + eslint: 8.45.0 eslint-scope: 5.1.1 semver: 7.5.4 transitivePeerDependencies: @@ -11709,7 +11693,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: '@typescript-eslint/types': 5.59.6 - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 3.4.2 /@uiw/codemirror-extensions-basic-setup/4.19.5_wd2tsis3in55bkaiwnc2c46tom: resolution: {integrity: sha512-1zt7ZPJ01xKkSW/KDy0FZNga0bngN1fC594wCVG7FBi60ehfcAucpooQ+JSPScKXopxcb+ugPKZvVLzr9/OfzA==} @@ -12978,23 +12962,12 @@ packages: pako: 0.2.9 dev: true - /browserslist/4.21.10: - resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001521 - electron-to-chromium: 1.4.495 - node-releases: 2.0.13 - update-browserslist-db: 1.0.11_browserslist@4.21.10 - dev: true - /browserslist/4.21.4: resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001521 + caniuse-lite: 1.0.30001504 electron-to-chromium: 1.4.433 node-releases: 2.0.12 update-browserslist-db: 1.0.11_browserslist@4.21.4 @@ -13005,11 +12978,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001521 + caniuse-lite: 1.0.30001504 electron-to-chromium: 1.4.433 node-releases: 2.0.12 update-browserslist-db: 1.0.11_browserslist@4.21.9 - dev: false /bs-logger/0.2.6: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} @@ -13274,10 +13246,6 @@ packages: /caniuse-lite/1.0.30001504: resolution: {integrity: sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==} - dev: false - - /caniuse-lite/1.0.30001521: - resolution: {integrity: sha512-fnx1grfpEOvDGH+V17eccmNjucGUnCbP6KL+l5KqBIerp26WK/+RQ7CIDE37KGJjaPyqWXXlFUyKiWmvdNNKmQ==} /cartesian/1.0.1: resolution: {integrity: sha512-tR3qKRYpRJ6FXEGuoBwpuCYcwydrk1N2rduy7eWg1Msepi3i5fCxheryw4VBlCqjCbk3Vhjh3eg+IGHtl5H74A==} @@ -13769,7 +13737,7 @@ packages: /core-js-compat/3.27.1: resolution: {integrity: sha512-Dg91JFeCDA17FKnneN7oCMz4BkQ4TcffkgHP4OWwp9yx3pi7ubqMDXXSacfNak1PQqjc95skyt+YBLHQJnkJwA==} dependencies: - browserslist: 4.21.10 + browserslist: 4.21.9 dev: true /core-js-pure/3.30.2: @@ -13908,14 +13876,14 @@ packages: peerDependencies: webpack: ^5.0.0 dependencies: - icss-utils: 5.1.0_postcss@8.4.28 - postcss: 8.4.28 - postcss-modules-extract-imports: 3.0.0_postcss@8.4.28 - postcss-modules-local-by-default: 4.0.0_postcss@8.4.28 - postcss-modules-scope: 3.0.0_postcss@8.4.28 - postcss-modules-values: 4.0.0_postcss@8.4.28 + icss-utils: 5.1.0_postcss@8.4.27 + postcss: 8.4.27 + postcss-modules-extract-imports: 3.0.0_postcss@8.4.27 + postcss-modules-local-by-default: 4.0.0_postcss@8.4.27 + postcss-modules-scope: 3.0.0_postcss@8.4.27 + postcss-modules-values: 4.0.0_postcss@8.4.27 postcss-value-parser: 4.2.0 - semver: 7.5.0 + semver: 7.5.4 dev: true /css-loader/6.7.3_webpack@5.80.0: @@ -13924,14 +13892,14 @@ packages: peerDependencies: webpack: ^5.0.0 dependencies: - icss-utils: 5.1.0_postcss@8.4.28 - postcss: 8.4.28 - postcss-modules-extract-imports: 3.0.0_postcss@8.4.28 - postcss-modules-local-by-default: 4.0.0_postcss@8.4.28 - postcss-modules-scope: 3.0.0_postcss@8.4.28 - postcss-modules-values: 4.0.0_postcss@8.4.28 + icss-utils: 5.1.0_postcss@8.4.27 + postcss: 8.4.27 + postcss-modules-extract-imports: 3.0.0_postcss@8.4.27 + postcss-modules-local-by-default: 4.0.0_postcss@8.4.27 + postcss-modules-scope: 3.0.0_postcss@8.4.27 + postcss-modules-values: 4.0.0_postcss@8.4.27 postcss-value-parser: 4.2.0 - semver: 7.5.0 + semver: 7.5.4 webpack: 5.80.0_uhpfu7q6noim4yjdo6qt2aajgu dev: true @@ -14553,10 +14521,6 @@ packages: /electron-to-chromium/1.4.433: resolution: {integrity: sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==} - /electron-to-chromium/1.4.495: - resolution: {integrity: sha512-mwknuemBZnoOCths4GtpU/SDuVMp3uQHKa2UNJT9/aVD6WVRjGpXOxRGX7lm6ILIenTdGXPSTCTDaWos5tEU8Q==} - dev: true - /emittery/0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} @@ -15342,21 +15306,21 @@ packages: eslint-plugin-turbo: 1.10.12_eslint@8.31.0 dev: true - /eslint-doc-generator/1.4.3_eslint@8.47.0: + /eslint-doc-generator/1.4.3_eslint@8.45.0: resolution: {integrity: sha512-cn9KXE7xuKlxKi/9VbirR3cbz7W1geRObwWzZjJAnpTeNBoqA8Rj+lD8/HHHJ7PnOdaTrRyhhoYdCtxqq3U7Bw==} engines: {node: ^14.18.0 || ^16.0.0 || >=18.0.0} hasBin: true peerDependencies: eslint: '>= 7' dependencies: - '@typescript-eslint/utils': 5.59.6_eslint@8.47.0 + '@typescript-eslint/utils': 5.59.6_eslint@8.45.0 ajv: 8.12.0 boolean: 3.2.0 commander: 10.0.1 cosmiconfig: 8.1.3 deepmerge: 4.2.2 dot-prop: 7.2.0 - eslint: 8.47.0 + eslint: 8.45.0 jest-diff: 29.6.2 json-schema-traverse: 1.0.0 markdown-table: 3.0.3 @@ -15577,7 +15541,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.59.6_eslint@8.42.0 + '@typescript-eslint/parser': 5.59.6_binxsscxvozjxebftqdoazsxm4 debug: 3.2.7 eslint: 8.42.0 eslint-import-resolver-node: 0.3.7 @@ -15597,25 +15561,25 @@ packages: regexpp: 3.2.0 dev: true - /eslint-plugin-es/3.0.1_eslint@8.47.0: + /eslint-plugin-es/3.0.1_eslint@8.45.0: resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==} engines: {node: '>=8.10.0'} peerDependencies: eslint: '>=4.19.1' dependencies: - eslint: 8.47.0 + eslint: 8.45.0 eslint-utils: 2.1.0 regexpp: 3.2.0 dev: true - /eslint-plugin-eslint-plugin/5.1.1_eslint@8.47.0: + /eslint-plugin-eslint-plugin/5.1.1_eslint@8.45.0: resolution: {integrity: sha512-4MGDsG505Ot2TSDSYxFL0cpDo4Y+t6hKB8cfZw9Jx484VjXWDfiYC/A6cccWFtWoOOC0j+wGgQIIb11cdIAMBg==} engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0} peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.47.0 - eslint-utils: 3.0.0_eslint@8.47.0 + eslint: 8.45.0 + eslint-utils: 3.0.0_eslint@8.45.0 estraverse: 5.3.0 dev: true @@ -15662,7 +15626,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.59.6_eslint@8.42.0 + '@typescript-eslint/parser': 5.59.6_binxsscxvozjxebftqdoazsxm4 array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 @@ -15899,14 +15863,14 @@ packages: semver: 6.3.0 dev: true - /eslint-plugin-node/11.1.0_eslint@8.47.0: + /eslint-plugin-node/11.1.0_eslint@8.45.0: resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} engines: {node: '>=8.10.0'} peerDependencies: eslint: '>=5.16.0' dependencies: - eslint: 8.47.0 - eslint-plugin-es: 3.0.1_eslint@8.47.0 + eslint: 8.45.0 + eslint-plugin-es: 3.0.1_eslint@8.45.0 eslint-utils: 2.1.0 ignore: 5.2.4 minimatch: 3.1.2 @@ -16114,14 +16078,6 @@ packages: esrecurse: 4.3.0 estraverse: 5.3.0 - /eslint-scope/7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - /eslint-utils/2.1.0: resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} engines: {node: '>=6'} @@ -16139,13 +16095,13 @@ packages: eslint-visitor-keys: 2.1.0 dev: true - /eslint-utils/3.0.0_eslint@8.47.0: + /eslint-utils/3.0.0_eslint@8.45.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 8.47.0 + eslint: 8.45.0 eslint-visitor-keys: 2.1.0 dev: true @@ -16167,15 +16123,11 @@ packages: /eslint-visitor-keys/3.4.1: resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true /eslint-visitor-keys/3.4.2: resolution: {integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint-visitor-keys/3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} /eslint/8.31.0: resolution: {integrity: sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==} @@ -16340,7 +16292,7 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.0 - eslint-visitor-keys: 3.4.1 + eslint-visitor-keys: 3.4.2 espree: 9.6.0 esquery: 1.5.0 esutils: 2.0.3 @@ -16365,53 +16317,6 @@ packages: text-table: 0.2.0 transitivePeerDependencies: - supports-color - dev: false - - /eslint/8.47.0: - resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0_eslint@8.47.0 - '@eslint-community/regexpp': 4.6.2 - '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.47.0 - '@humanwhocodes/config-array': 0.11.10 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.19.0 - graphemer: 1.4.0 - ignore: 5.2.4 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.3 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true /espree/9.4.1: resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} @@ -16419,7 +16324,7 @@ packages: dependencies: acorn: 8.10.0 acorn-jsx: 5.3.2_acorn@8.10.0 - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 3.4.2 dev: true /espree/9.6.0: @@ -16428,15 +16333,7 @@ packages: dependencies: acorn: 8.10.0 acorn-jsx: 5.3.2_acorn@8.10.0 - eslint-visitor-keys: 3.4.3 - - /espree/9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.10.0 - acorn-jsx: 5.3.2_acorn@8.10.0 - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 3.4.2 /esprima/4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} @@ -18003,15 +17900,6 @@ packages: postcss: 8.4.27 dev: true - /icss-utils/5.1.0_postcss@8.4.28: - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - postcss: 8.4.28 - dev: true - /ieee754/1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -18889,7 +18777,7 @@ packages: graceful-fs: 4.2.10 jest-regex-util: 29.4.3 jest-util: 29.6.2 - jest-worker: 29.5.0 + jest-worker: 29.6.2 micromatch: 4.0.5 walker: 1.0.8 optionalDependencies: @@ -19094,18 +18982,6 @@ packages: - supports-color dev: true - /jest-util/29.5.0: - resolution: {integrity: sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.1 - '@types/node': 20.5.0 - chalk: 4.1.2 - ci-info: 3.7.1 - graceful-fs: 4.2.10 - picomatch: 2.3.1 - dev: true - /jest-util/29.6.2: resolution: {integrity: sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -19153,16 +19029,6 @@ packages: supports-color: 8.1.1 dev: true - /jest-worker/29.5.0: - resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@types/node': 20.5.0 - jest-util: 29.6.2 - merge-stream: 2.0.0 - supports-color: 8.1.1 - dev: true - /jest-worker/29.6.2: resolution: {integrity: sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -21085,10 +20951,6 @@ packages: /node-releases/2.0.12: resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==} - /node-releases/2.0.13: - resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} - dev: true - /nodemon/3.0.1: resolution: {integrity: sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==} engines: {node: '>=10'} @@ -21970,13 +21832,13 @@ packages: read-cache: 1.0.0 resolve: 1.22.2 - /postcss-import/15.1.0_postcss@8.4.28: + /postcss-import/15.1.0_postcss@8.4.27: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.4.28 + postcss: 8.4.27 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.2 @@ -22001,14 +21863,14 @@ packages: camelcase-css: 2.0.1 postcss: 8.4.23 - /postcss-js/4.0.1_postcss@8.4.28: + /postcss-js/4.0.1_postcss@8.4.27: resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 dependencies: camelcase-css: 2.0.1 - postcss: 8.4.28 + postcss: 8.4.27 dev: false /postcss-load-config/3.1.4: @@ -22077,7 +21939,7 @@ packages: ts-node: 10.9.1_fodzh64fuekdilycyvke2qmf2e yaml: 2.3.1 - /postcss-load-config/4.0.1_postcss@8.4.28: + /postcss-load-config/4.0.1_postcss@8.4.27: resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} peerDependencies: @@ -22090,7 +21952,7 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - postcss: 8.4.28 + postcss: 8.4.27 yaml: 2.3.1 dev: false @@ -22123,7 +21985,7 @@ packages: jiti: 1.18.2 klona: 2.0.6 postcss: 8.4.21 - semver: 7.5.0 + semver: 7.5.4 dev: true /postcss-modules-extract-imports/3.0.0_postcss@8.4.27: @@ -22135,15 +21997,6 @@ packages: postcss: 8.4.27 dev: true - /postcss-modules-extract-imports/3.0.0_postcss@8.4.28: - resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - postcss: 8.4.28 - dev: true - /postcss-modules-local-by-default/4.0.0_postcss@8.4.27: resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==} engines: {node: ^10 || ^12 || >= 14} @@ -22156,18 +22009,6 @@ packages: postcss-value-parser: 4.2.0 dev: true - /postcss-modules-local-by-default/4.0.0_postcss@8.4.28: - resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - icss-utils: 5.1.0_postcss@8.4.28 - postcss: 8.4.28 - postcss-selector-parser: 6.0.11 - postcss-value-parser: 4.2.0 - dev: true - /postcss-modules-scope/3.0.0_postcss@8.4.27: resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} engines: {node: ^10 || ^12 || >= 14} @@ -22178,16 +22019,6 @@ packages: postcss-selector-parser: 6.0.11 dev: true - /postcss-modules-scope/3.0.0_postcss@8.4.28: - resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - postcss: 8.4.28 - postcss-selector-parser: 6.0.11 - dev: true - /postcss-modules-values/4.0.0_postcss@8.4.27: resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} engines: {node: ^10 || ^12 || >= 14} @@ -22198,16 +22029,6 @@ packages: postcss: 8.4.27 dev: true - /postcss-modules-values/4.0.0_postcss@8.4.28: - resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - dependencies: - icss-utils: 5.1.0_postcss@8.4.28 - postcss: 8.4.28 - dev: true - /postcss-modules/6.0.0_postcss@8.4.27: resolution: {integrity: sha512-7DGfnlyi/ju82BRzTIjWS5C4Tafmzl3R79YP/PASiocj+aa6yYphHhhKUOEoXQToId5rgyFgJ88+ccOUydjBXQ==} peerDependencies: @@ -22243,13 +22064,13 @@ packages: postcss: 8.4.23 postcss-selector-parser: 6.0.11 - /postcss-nested/6.0.1_postcss@8.4.28: + /postcss-nested/6.0.1_postcss@8.4.27: resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.4.28 + postcss: 8.4.27 postcss-selector-parser: 6.0.11 dev: false @@ -22312,14 +22133,6 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 - /postcss/8.4.28: - resolution: {integrity: sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - /postgres-array/2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -22507,13 +22320,13 @@ packages: react: 18.2.0 dev: false - /prisma/5.1.1: - resolution: {integrity: sha512-WJFG/U7sMmcc6TjJTTifTfpI6Wjoh55xl4AzopVwAdyK68L9/ogNo8QQ2cxuUjJf/Wa82z/uhyh3wMzvRIBphg==} - engines: {node: '>=16.13'} + /prisma/4.16.0: + resolution: {integrity: sha512-kSCwbTm3LCephyGfZMJYqBXpPJXdJStg5xwfzeFmR5C05zfkOURK9pQpJF6uUQvFWm3lI9ZMSNkObmFkAPnB+g==} + engines: {node: '>=14.17'} hasBin: true requiresBuild: true dependencies: - '@prisma/engines': 5.1.1 + '@prisma/engines': 4.16.0 /prismjs/1.29.0: resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} @@ -23436,7 +23249,7 @@ packages: adjust-sourcemap-loader: 4.0.0 convert-source-map: 1.9.0 loader-utils: 2.0.4 - postcss: 8.4.28 + postcss: 8.4.27 source-map: 0.6.1 dev: true @@ -24422,7 +24235,7 @@ packages: resolution: {integrity: sha512-WrDlYH1p5jliY7uzSU5nLDY7OCIeRe6FkC0hhScpTGwMthP/Muk38WXGeggjDHKeXAGCs43jUheZ7Ud/NEAJdg==} engines: {node: '>=12.*'} dependencies: - '@types/node': 20.5.0 + '@types/node': 20.4.8 qs: 6.11.0 /striptags/2.2.1: @@ -24691,11 +24504,11 @@ packages: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.4.28 - postcss-import: 15.1.0_postcss@8.4.28 - postcss-js: 4.0.1_postcss@8.4.28 - postcss-load-config: 4.0.1_postcss@8.4.28 - postcss-nested: 6.0.1_postcss@8.4.28 + postcss: 8.4.27 + postcss-import: 15.1.0_postcss@8.4.27 + postcss-js: 4.0.1_postcss@8.4.27 + postcss-load-config: 4.0.1_postcss@8.4.27 + postcss-nested: 6.0.1_postcss@8.4.27 postcss-selector-parser: 6.0.11 resolve: 1.22.2 sucrase: 3.32.0 @@ -25043,7 +24856,7 @@ packages: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 jest: 29.6.2_@types+node@16.18.11 - jest-util: 29.5.0 + jest-util: 29.6.2 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -25097,6 +24910,37 @@ packages: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + /ts-node/10.9.1_jch25vbq34sbabk3unb5q5rkqi: + 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 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 20.5.0 + 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.1.6 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /ts-node/10.9.1_xj5cs2fmhcigm4w5bhhtewqeja: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true @@ -25869,17 +25713,6 @@ packages: setimmediate: 1.0.5 dev: false - /update-browserslist-db/1.0.11_browserslist@4.21.10: - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.10 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: true - /update-browserslist-db/1.0.11_browserslist@4.21.4: resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true @@ -25900,7 +25733,6 @@ packages: browserslist: 4.21.9 escalade: 3.1.1 picocolors: 1.0.0 - dev: false /uri-js/4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -26209,7 +26041,7 @@ packages: dependencies: '@types/node': 18.11.18 esbuild: 0.16.17 - postcss: 8.4.28 + postcss: 8.4.27 resolve: 1.22.1 rollup: 3.10.0 optionalDependencies: @@ -26243,7 +26075,7 @@ packages: dependencies: '@types/node': 18.17.1 esbuild: 0.16.17 - postcss: 8.4.28 + postcss: 8.4.27 resolve: 1.22.1 rollup: 3.10.0 optionalDependencies: @@ -26277,7 +26109,7 @@ packages: dependencies: '@types/node': 20.5.0 esbuild: 0.16.17 - postcss: 8.4.28 + postcss: 8.4.27 resolve: 1.22.1 rollup: 3.10.0 optionalDependencies: @@ -26441,7 +26273,7 @@ packages: '@webassemblyjs/wasm-parser': 1.11.5 acorn: 8.10.0 acorn-import-assertions: 1.8.0_acorn@8.10.0 - browserslist: 4.21.10 + browserslist: 4.21.9 chrome-trace-event: 1.0.3 enhanced-resolve: 5.13.0 es-module-lexer: 1.2.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7933c8f98..5c384a7ff 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,3 +5,4 @@ packages: - "apps/**" - "examples/*" - "docs" + - "perf"