diff --git a/controlplane/package.json b/controlplane/package.json index 567f126082..39e0177355 100644 --- a/controlplane/package.json +++ b/controlplane/package.json @@ -90,6 +90,7 @@ "stream-json": "^1.8.0", "stripe": "^14.19.0", "tiny-lru": "^11.2.11", + "tinypool": "^2.0.0", "uid": "^2.0.2", "uuid": "^10.0.0", "zod": "^3.22.4", diff --git a/controlplane/src/core/bufservices/contract/createContract.ts b/controlplane/src/core/bufservices/contract/createContract.ts index ea7cb44888..d27abc0b68 100644 --- a/controlplane/src/core/bufservices/contract/createContract.ts +++ b/controlplane/src/core/bufservices/contract/createContract.ts @@ -206,6 +206,7 @@ export function createContract( chClient: opts.chClient!, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), federatedGraphs: [{ ...contractGraph, contract }], + composeWorkerPool: opts.composeWorkerPool, }); compositionErrors.push(...composition.compositionErrors); diff --git a/controlplane/src/core/bufservices/contract/updateContract.ts b/controlplane/src/core/bufservices/contract/updateContract.ts index aa64ed6ef4..f9ae4d5abd 100644 --- a/controlplane/src/core/bufservices/contract/updateContract.ts +++ b/controlplane/src/core/bufservices/contract/updateContract.ts @@ -140,6 +140,7 @@ export function updateContract( }, labelMatchers: [], chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), }); @@ -155,6 +156,7 @@ export function updateContract( }, blobStorage: opts.blobStorage, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), federatedGraphs: [ { diff --git a/controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts b/controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts index 3738f6db2b..1bc7f71124 100644 --- a/controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts +++ b/controlplane/src/core/bufservices/feature-flag/createFeatureFlag.ts @@ -205,6 +205,7 @@ export function createFeatureFlag( }, blobStorage: opts.blobStorage, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), federatedGraphs, }); diff --git a/controlplane/src/core/bufservices/feature-flag/deleteFeatureFlag.ts b/controlplane/src/core/bufservices/feature-flag/deleteFeatureFlag.ts index baffe0b1ce..c666d1726f 100644 --- a/controlplane/src/core/bufservices/feature-flag/deleteFeatureFlag.ts +++ b/controlplane/src/core/bufservices/feature-flag/deleteFeatureFlag.ts @@ -135,6 +135,7 @@ export function deleteFeatureFlag( }, blobStorage: opts.blobStorage, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), federatedGraphs, }); diff --git a/controlplane/src/core/bufservices/feature-flag/enableFeatureFlag.ts b/controlplane/src/core/bufservices/feature-flag/enableFeatureFlag.ts index 0720f81951..4161d60274 100644 --- a/controlplane/src/core/bufservices/feature-flag/enableFeatureFlag.ts +++ b/controlplane/src/core/bufservices/feature-flag/enableFeatureFlag.ts @@ -119,6 +119,7 @@ export function enableFeatureFlag( }, blobStorage: opts.blobStorage, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), federatedGraphs, }); diff --git a/controlplane/src/core/bufservices/feature-flag/updateFeatureFlag.ts b/controlplane/src/core/bufservices/feature-flag/updateFeatureFlag.ts index 314bf76d92..14a1a21e0c 100644 --- a/controlplane/src/core/bufservices/feature-flag/updateFeatureFlag.ts +++ b/controlplane/src/core/bufservices/feature-flag/updateFeatureFlag.ts @@ -174,6 +174,7 @@ export function updateFeatureFlag( }, blobStorage: opts.blobStorage, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), federatedGraphs: allFederatedGraphsToCompose, }); diff --git a/controlplane/src/core/bufservices/federated-graph/createFederatedGraph.ts b/controlplane/src/core/bufservices/federated-graph/createFederatedGraph.ts index 9ea483d09d..763659bb61 100644 --- a/controlplane/src/core/bufservices/federated-graph/createFederatedGraph.ts +++ b/controlplane/src/core/bufservices/federated-graph/createFederatedGraph.ts @@ -232,6 +232,7 @@ export function createFederatedGraph( }, blobStorage: opts.blobStorage, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), federatedGraphs: [federatedGraph], }); diff --git a/controlplane/src/core/bufservices/federated-graph/migrateFromApollo.ts b/controlplane/src/core/bufservices/federated-graph/migrateFromApollo.ts index d9089182af..bae87a5f5f 100644 --- a/controlplane/src/core/bufservices/federated-graph/migrateFromApollo.ts +++ b/controlplane/src/core/bufservices/federated-graph/migrateFromApollo.ts @@ -156,6 +156,7 @@ export function migrateFromApollo( webhookJWTSecret: opts.admissionWebhookJWTSecret, }, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: { disableResolvabilityValidation: true, }, diff --git a/controlplane/src/core/bufservices/federated-graph/moveFederatedGraph.ts b/controlplane/src/core/bufservices/federated-graph/moveFederatedGraph.ts index 7889ef6ddb..c016f6fa89 100644 --- a/controlplane/src/core/bufservices/federated-graph/moveFederatedGraph.ts +++ b/controlplane/src/core/bufservices/federated-graph/moveFederatedGraph.ts @@ -125,6 +125,7 @@ export function moveFederatedGraph( jwtSecret: opts.admissionWebhookJWTSecret, }, opts.chClient!, + opts.composeWorkerPool, ); const allDeploymentErrors: PlainMessage[] = []; @@ -163,6 +164,7 @@ export function moveFederatedGraph( jwtSecret: opts.admissionWebhookJWTSecret, }, opts.chClient!, + opts.composeWorkerPool, ); allCompositionErrors.push(...contractErrors); diff --git a/controlplane/src/core/bufservices/federated-graph/updateFederatedGraph.ts b/controlplane/src/core/bufservices/federated-graph/updateFederatedGraph.ts index 5533403d16..3943ed4d2b 100644 --- a/controlplane/src/core/bufservices/federated-graph/updateFederatedGraph.ts +++ b/controlplane/src/core/bufservices/federated-graph/updateFederatedGraph.ts @@ -119,6 +119,7 @@ export function updateFederatedGraph( admissionWebhookURL: req.admissionWebhookURL, blobStorage: opts.blobStorage, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), labelMatchers: req.labelMatchers, namespaceId: federatedGraph.namespaceId, diff --git a/controlplane/src/core/bufservices/graph/setGraphRouterCompatibilityVersion.ts b/controlplane/src/core/bufservices/graph/setGraphRouterCompatibilityVersion.ts index 4b995a3f99..70220059b4 100644 --- a/controlplane/src/core/bufservices/graph/setGraphRouterCompatibilityVersion.ts +++ b/controlplane/src/core/bufservices/graph/setGraphRouterCompatibilityVersion.ts @@ -140,6 +140,7 @@ export function setGraphRouterCompatibilityVersion( }, blobStorage: opts.blobStorage, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), federatedGraphs: [federatedGraph], }); diff --git a/controlplane/src/core/bufservices/monograph/publishMonograph.ts b/controlplane/src/core/bufservices/monograph/publishMonograph.ts index 9fd48dfeba..ed06a4a129 100644 --- a/controlplane/src/core/bufservices/monograph/publishMonograph.ts +++ b/controlplane/src/core/bufservices/monograph/publishMonograph.ts @@ -152,6 +152,7 @@ export function publishMonograph( webhookJWTSecret: opts.admissionWebhookJWTSecret, }, opts.chClient!, + opts.composeWorkerPool, ); for (const graph of updatedFederatedGraphs) { diff --git a/controlplane/src/core/bufservices/monograph/updateMonograph.ts b/controlplane/src/core/bufservices/monograph/updateMonograph.ts index 9443ede21c..78cfa0f595 100644 --- a/controlplane/src/core/bufservices/monograph/updateMonograph.ts +++ b/controlplane/src/core/bufservices/monograph/updateMonograph.ts @@ -150,6 +150,7 @@ export function updateMonograph( admissionWebhookURL: req.admissionWebhookURL, admissionWebhookSecret: req.admissionWebhookSecret, chClient: opts.chClient!, + composeWorkerPool: opts.composeWorkerPool, }); await subgraphRepo.update( @@ -173,6 +174,7 @@ export function updateMonograph( webhookJWTSecret: opts.admissionWebhookJWTSecret, }, opts.chClient!, + opts.composeWorkerPool, ); await auditLogRepo.addAuditLog({ diff --git a/controlplane/src/core/bufservices/subgraph/deleteFederatedSubgraph.ts b/controlplane/src/core/bufservices/subgraph/deleteFederatedSubgraph.ts index 69c145bee1..65421c964b 100644 --- a/controlplane/src/core/bufservices/subgraph/deleteFederatedSubgraph.ts +++ b/controlplane/src/core/bufservices/subgraph/deleteFederatedSubgraph.ts @@ -186,6 +186,7 @@ export function deleteFederatedSubgraph( chClient: opts.chClient!, compositionOptions: newCompositionOptions(req.disableResolvabilityValidation), federatedGraphs: affectedFederatedGraphs, + composeWorkerPool: opts.composeWorkerPool, }); return { affectedFederatedGraphs, compositionErrors, deploymentErrors, compositionWarnings }; diff --git a/controlplane/src/core/bufservices/subgraph/fixSubgraphSchema.ts b/controlplane/src/core/bufservices/subgraph/fixSubgraphSchema.ts index 78663cd491..4b0d2f6820 100644 --- a/controlplane/src/core/bufservices/subgraph/fixSubgraphSchema.ts +++ b/controlplane/src/core/bufservices/subgraph/fixSubgraphSchema.ts @@ -182,7 +182,7 @@ export function fixSubgraphSchema( if (composition.errors.length > 0) { for (const error of composition.errors) { compositionErrors.push({ - message: error.message, + message: error, federatedGraphName: composition.name, namespace: composition.namespace, featureFlag: '', diff --git a/controlplane/src/core/bufservices/subgraph/moveSubgraph.ts b/controlplane/src/core/bufservices/subgraph/moveSubgraph.ts index 1784431735..ecd49784d0 100644 --- a/controlplane/src/core/bufservices/subgraph/moveSubgraph.ts +++ b/controlplane/src/core/bufservices/subgraph/moveSubgraph.ts @@ -117,6 +117,7 @@ export function moveSubgraph( jwtSecret: opts.admissionWebhookJWTSecret, }, opts.chClient!, + opts.composeWorkerPool, newCompositionOptions(req.disableResolvabilityValidation), ); diff --git a/controlplane/src/core/bufservices/subgraph/publishFederatedSubgraph.ts b/controlplane/src/core/bufservices/subgraph/publishFederatedSubgraph.ts index 5bf4c15c7f..b7e34d64b3 100644 --- a/controlplane/src/core/bufservices/subgraph/publishFederatedSubgraph.ts +++ b/controlplane/src/core/bufservices/subgraph/publishFederatedSubgraph.ts @@ -574,6 +574,7 @@ export function publishFederatedSubgraph( webhookJWTSecret: opts.admissionWebhookJWTSecret, }, opts.chClient!, + opts.composeWorkerPool, newCompositionOptions(req.disableResolvabilityValidation), ); diff --git a/controlplane/src/core/bufservices/subgraph/updateSubgraph.ts b/controlplane/src/core/bufservices/subgraph/updateSubgraph.ts index 049eadcb7c..96e11942c0 100644 --- a/controlplane/src/core/bufservices/subgraph/updateSubgraph.ts +++ b/controlplane/src/core/bufservices/subgraph/updateSubgraph.ts @@ -184,6 +184,7 @@ export function updateSubgraph( webhookJWTSecret: opts.admissionWebhookJWTSecret, }, opts.chClient!, + opts.composeWorkerPool, newCompositionOptions(req.disableResolvabilityValidation), ); diff --git a/controlplane/src/core/build-server.ts b/controlplane/src/core/build-server.ts index 4ecb4e8173..ba8557653d 100644 --- a/controlplane/src/core/build-server.ts +++ b/controlplane/src/core/build-server.ts @@ -1,3 +1,4 @@ +import { join } from 'node:path'; import Fastify, { FastifyBaseLogger } from 'fastify'; import { S3Client } from '@aws-sdk/client-s3'; import { fastifyConnectPlugin } from '@connectrpc/connect-fastify'; @@ -8,6 +9,7 @@ import { compressionBrotli, compressionGzip } from '@connectrpc/connect-node'; import fastifyGracefulShutdown from 'fastify-graceful-shutdown'; import { App } from 'octokit'; import { Worker } from 'bullmq'; +import { Tinypool } from 'tinypool'; import routes from './routes.js'; import fastifyHealth from './plugins/health.js'; import fastifyMetrics, { MetricsPluginOptions } from './plugins/metrics.js'; @@ -136,6 +138,9 @@ export interface BuildConfig { key?: string; // e.g. string or '/path/to/my/client-key.pem' }; }; + composeWorkers?: { + maxCount: number; + }; } export interface MetricsOptions { @@ -469,6 +474,11 @@ export default async function build(opts: BuildConfig) { keycloakRealm: opts.keycloak.realm, }); + const composeWorkerPool = new Tinypool({ + filename: join(process.cwd(), 'dist/workers/compose.js'), + maxThreads: opts.composeWorkers?.maxCount, + }); + // Must be registered after custom fastify routes // Because it registers an all-catch route for connect handlers @@ -502,6 +512,7 @@ export default async function build(opts: BuildConfig) { stripeSecretKey: opts.stripe?.secret, admissionWebhookJWTSecret: opts.admissionWebhook.secret, cdnBaseUrl: opts.cdnBaseUrl, + composeWorkerPool, }), contextValues(req) { return createContextValues().set({ id: fastifyLoggerId, defaultValue: req.log }, req.log); @@ -522,6 +533,12 @@ export default async function build(opts: BuildConfig) { }); fastify.gracefulShutdown(async () => { + fastify.log.debug('Shutting down compose worker pool'); + + await composeWorkerPool.destroy(); + + fastify.log.debug('Compose worker pool shut down'); + fastify.log.debug('Shutting down bull workers'); await Promise.all(bullWorkers.map((worker) => worker.close())); diff --git a/controlplane/src/core/composition/composer.ts b/controlplane/src/core/composition/composer.ts index afc22b2237..5c602ae8ee 100644 --- a/controlplane/src/core/composition/composer.ts +++ b/controlplane/src/core/composition/composer.ts @@ -222,10 +222,10 @@ export function mapResultToComposedGraph( composedSchema: result.success ? printSchemaWithDirectives(result.federatedGraphSchema) : undefined, federatedClientSchema: result.success ? printSchemaWithDirectives(result.federatedGraphClientSchema) : undefined, shouldIncludeClientSchema: result.success ? result.shouldIncludeClientSchema : false, - errors: result.success ? [] : result.errors, + errors: result.success ? [] : result.errors.map((e) => e.message), subgraphs: subgraphDTOsToComposedSubgraphs(federatedGraph.organizationId, subgraphs, result), fieldConfigurations: result.success ? result.fieldConfigurations : [], - warnings: result.warnings, + warnings: result.warnings.map((e) => e.message), }; } @@ -236,12 +236,12 @@ export interface ComposedFederatedGraph { namespace: string; namespaceId: string; composedSchema?: string; - errors: Error[]; + errors: string[]; subgraphs: ComposedSubgraph[]; fieldConfigurations: FieldConfiguration[]; federatedClientSchema?: string; shouldIncludeClientSchema?: boolean; - warnings: Warning[]; + warnings: string[]; } export interface CompositionDeployResult { diff --git a/controlplane/src/core/repositories/FederatedGraphRepository.ts b/controlplane/src/core/repositories/FederatedGraphRepository.ts index eea8a089a9..b47cff03ce 100644 --- a/controlplane/src/core/repositories/FederatedGraphRepository.ts +++ b/controlplane/src/core/repositories/FederatedGraphRepository.ts @@ -1,7 +1,8 @@ /* eslint-disable no-labels */ import { KeyObject, randomUUID } from 'node:crypto'; +import { MessageChannel } from 'node:worker_threads'; import { PlainMessage } from '@bufbuild/protobuf'; -import { FeatureFlagRouterExecutionConfig } from '@wundergraph/cosmo-connect/dist/node/v1/node_pb'; +import { FeatureFlagRouterExecutionConfig, RouterConfig } from '@wundergraph/cosmo-connect/dist/node/v1/node_pb'; import { CompositionError, CompositionWarning, @@ -29,6 +30,7 @@ import { PostgresJsDatabase } from 'drizzle-orm/postgres-js'; import { FastifyBaseLogger } from 'fastify'; import { parse } from 'graphql'; import { generateKeyPair, importPKCS8, SignJWT } from 'jose'; +import { Tinypool } from 'tinypool'; import { uid } from 'uid/secure'; import { ContractTagOptions, @@ -37,6 +39,12 @@ import { newContractTagOptionsFromArrays, Warning, } from '@wundergraph/composition'; +import { + Outputs as ComposeOutputs, + Inputs as ComposeInputs, + BaseMessage as ComposeBaseMessage, + BaseResult as ComposeBaseResult, +} from '../../workers/compose.js'; import * as schema from '../../db/schema.js'; import { federatedGraphs, @@ -79,7 +87,6 @@ import { normalizeLabelMatchers, normalizeLabels, } from '../util.js'; -import { unsuccessfulBaseCompositionError } from '../errors/errors.js'; import { ClickHouseClient } from '../clickhouse/index.js'; import { RBACEvaluator } from '../services/RBACEvaluator.js'; import { ContractRepository } from './ContractRepository.js'; @@ -205,6 +212,7 @@ export class FederatedGraphRepository { targetId: string; updatedBy: string; chClient: ClickHouseClient; + composeWorkerPool: Tinypool; admissionWebhookSecret?: string; admissionWebhookURL?: string; compositionOptions?: CompositionOptions; @@ -337,6 +345,7 @@ export class FederatedGraphRepository { actorId: data.updatedBy, chClient: data.chClient, compositionOptions: data.compositionOptions, + composeWorkerPool: data.composeWorkerPool, }); return { @@ -369,6 +378,7 @@ export class FederatedGraphRepository { cdnBaseUrl: string; }, chClient: ClickHouseClient, + composeWorkerPool: Tinypool, compositionOptions?: CompositionOptions, ): Promise<{ compositionErrors: PlainMessage[]; @@ -430,6 +440,7 @@ export class FederatedGraphRepository { chClient, compositionOptions, federatedGraphs: [movedContractGraph], + composeWorkerPool, }); return { @@ -449,6 +460,7 @@ export class FederatedGraphRepository { }, chClient, compositionOptions, + composeWorkerPool, }); return { @@ -803,8 +815,8 @@ export class FederatedGraphRepository { schemaVersionId: string; composedSDL?: string; clientSchema?: string; - compositionErrors?: Error[]; - compositionWarnings?: Warning[]; + compositionErrors?: string[]; + compositionWarnings?: string[]; composedSubgraphs: ComposedSubgraph[]; composedById: string; isFeatureFlagComposition: boolean; @@ -822,11 +834,11 @@ export class FederatedGraphRepository { let compositionWarningString = ''; if (compositionErrors && compositionErrors.length > 0) { - compositionErrorString = compositionErrors.map((e) => e.toString()).join('\n'); + compositionErrorString = compositionErrors.join('\n'); } if (compositionWarnings && compositionWarnings.length > 0) { - compositionWarningString = compositionWarnings.map((w) => w.toString()).join('\n'); + compositionWarningString = compositionWarnings.join('\n'); } const insertedVersion = await tx @@ -1505,6 +1517,7 @@ export class FederatedGraphRepository { chClient, blobStorage, federatedGraphs, + composeWorkerPool, }: { actorId: string; admissionConfig: { @@ -1515,6 +1528,7 @@ export class FederatedGraphRepository { chClient: ClickHouseClient; federatedGraphs: FederatedGraphDTO[]; compositionOptions?: CompositionOptions; + composeWorkerPool: Tinypool; }) => { return this.db.transaction(async (tx) => { const subgraphRepo = new SubgraphRepository(this.logger, tx, this.organizationId); @@ -1524,7 +1538,7 @@ export class FederatedGraphRepository { const graphCompositionRepo = new GraphCompositionRepository(this.logger, tx); const composer = new Composer( this.logger, - this.db, + tx, fedGraphRepo, subgraphRepo, contractRepo, @@ -1577,60 +1591,85 @@ export class FederatedGraphRepository { const contractBaseCompositionDataByContractId = new Map(); for (const subgraphsToCompose of allSubgraphsToCompose) { - const result: FederationResult | FederationResultWithContracts = getFederationResultWithPotentialContracts( + const { port1: portMain, port2: portWorker } = new MessageChannel(); + const inputs: ComposeInputs = { federatedGraph, subgraphsToCompose, tagOptionsByContractName, compositionOptions, - ); - - if (!result.success) { - // Collect all composition errors - allCompositionErrors.push( - ...result.errors.map((e) => ({ - federatedGraphName: federatedGraph.name, - namespace: federatedGraph.namespace, - message: e.message, - featureFlag: subgraphsToCompose.featureFlagName || '', - })), - ); - } - - // Collect all composition warnings - allCompositionWarnings.push( - ...result.warnings.map((w) => ({ - federatedGraphName: federatedGraph.name, - namespace: federatedGraph.namespace, - message: w.message, - featureFlag: subgraphsToCompose.featureFlagName || '', - })), - ); - - if (!subgraphsToCompose.isFeatureFlagComposition && !result.success && !federatedGraph.contract) { - allCompositionErrors.push(unsuccessfulBaseCompositionError(federatedGraph.name, federatedGraph.namespace)); - } - - const composedGraph = mapResultToComposedGraph(federatedGraph, subgraphsToCompose.subgraphs, result); + port: portWorker, + }; + const outputPromise: Promise = composeWorkerPool.run(inputs, { transferList: [portWorker] }); + + portMain.on('message', async (result: ComposeBaseMessage) => { + const routerConfig: RouterConfig | undefined = result.routerConfig + ? RouterConfig.fromJsonString(result.routerConfig) + : undefined; + + const errorResult: ComposeBaseResult = { + compositionDeployResult: { + schemaVersionId: '', + }, + contractGraphs: new Map(), + }; + + try { + const baseComposition = await composer.saveComposition({ + composedGraph: result.composedGraph, + composedById: actorId, + isFeatureFlagComposition: subgraphsToCompose.isFeatureFlagComposition, + federatedSchemaVersionId: result.federatedSchemaVersionId, + routerExecutionConfig: routerConfig, + featureFlagId: subgraphsToCompose.featureFlagId, + }); - const federatedSchemaVersionId = randomUUID(); + if (!result.resultSuccess || !baseComposition.schemaVersionId || !routerConfig) { + if (!subgraphsToCompose.isFeatureFlagComposition) { + portMain.postMessage(errorResult); + return; + } + } else if (subgraphsToCompose.isFeatureFlagComposition) { + baseCompositionData.featureFlagRouterExecutionConfigByFeatureFlagName.set( + subgraphsToCompose.featureFlagName, + routerConfigToFeatureFlagExecutionConfig(routerConfig), + ); + } else { + baseCompositionData.schemaVersionId = baseComposition.schemaVersionId; + baseCompositionData.routerExecutionConfig = routerConfig; + } + + const contractGraphs: Map = new Map(); + for (const contractName of result.contractNames) { + const contractGraph = await fedGraphRepo.byName(contractName, federatedGraph.namespace); + if (!contractGraph) { + portMain.postMessage(errorResult); + + throw new Error(`The contract graph "${contractName}" was not found.`); + } + contractGraphs.set(contractName, contractGraph); + } + + const baseResult: ComposeBaseResult = { + compositionDeployResult: baseComposition, + contractGraphs, + }; + + portMain.postMessage(baseResult); + } catch (e: any) { + portMain.postMessage(errorResult); + throw e; + } + }); - // Build the router execution config if the composed schema is valid - const routerExecutionConfig = buildRouterExecutionConfig( - composedGraph, - federatedSchemaVersionId, - federatedGraph.routerCompatibilityVersion, - ); + const outputs = await outputPromise; + portMain.close(); + portWorker.close(); - const baseComposition = await composer.saveComposition({ - composedGraph, - composedById: actorId, - isFeatureFlagComposition: subgraphsToCompose.isFeatureFlagComposition, - federatedSchemaVersionId, - routerExecutionConfig, - featureFlagId: subgraphsToCompose.featureFlagId, - }); + const routerConfig: RouterConfig | undefined = outputs.routerConfig + ? RouterConfig.fromJsonString(outputs.routerConfig) + : undefined; - if (!result.success || !baseComposition.schemaVersionId || !routerExecutionConfig) { + if (!outputs.resultSuccess || !outputs.schemaVersionId || !routerConfig) { /* If the base composition failed to compose or deploy, return to the parent loop, because * contracts are not composed if the base composition fails. */ @@ -1641,69 +1680,34 @@ export class FederatedGraphRepository { } else if (subgraphsToCompose.isFeatureFlagComposition) { baseCompositionData.featureFlagRouterExecutionConfigByFeatureFlagName.set( subgraphsToCompose.featureFlagName, - routerConfigToFeatureFlagExecutionConfig(routerExecutionConfig), + routerConfigToFeatureFlagExecutionConfig(routerConfig), ); // Otherwise, this is the base composition, so store the schema version id } else { - baseCompositionData.schemaVersionId = baseComposition.schemaVersionId; - baseCompositionData.routerExecutionConfig = routerExecutionConfig; + baseCompositionData.schemaVersionId = outputs.schemaVersionId; + baseCompositionData.routerExecutionConfig = routerConfig; } // If there are no contracts, there is nothing further to do - if (!('federationResultByContractName' in result)) { + if (outputs.contractGraphs.length === 0) { continue; } - for (const [contractName, contractResult] of result.federationResultByContractName) { - const contractGraph = await fedGraphRepo.byName(contractName, federatedGraph.namespace); - if (!contractGraph) { - throw new Error(`The contract graph "${contractName}" was not found.`); - } - if (!contractResult.success) { - allCompositionErrors.push( - ...contractResult.errors.map((e) => ({ - federatedGraphName: contractGraph.name, - namespace: contractGraph.namespace, - message: e.message, - featureFlag: subgraphsToCompose.featureFlagName, - })), - ); - } - - allCompositionWarnings.push( - ...contractResult.warnings.map((w) => ({ - federatedGraphName: contractGraph.name, - namespace: contractGraph.namespace, - message: w.message, - featureFlag: subgraphsToCompose.featureFlagName, - })), - ); - - const composedContract = mapResultToComposedGraph( - contractGraph, - subgraphsToCompose.subgraphs, - contractResult, - ); - - const contractSchemaVersionId = randomUUID(); - - // Build the router execution config if the composed schema is valid - const contractRouterExecutionConfig = buildRouterExecutionConfig( - composedContract, - contractSchemaVersionId, - federatedGraph.routerCompatibilityVersion, - ); + for (const contractGraph of outputs.contractGraphs) { + const contractRouterConfig: RouterConfig | undefined = contractGraph.contractRouterConfig + ? RouterConfig.fromJsonString(contractGraph.contractRouterConfig) + : undefined; const contractComposition = await composer.saveComposition({ - composedGraph: composedContract, + composedGraph: contractGraph.composedContract, composedById: actorId, isFeatureFlagComposition: subgraphsToCompose.isFeatureFlagComposition, - federatedSchemaVersionId: contractSchemaVersionId, - routerExecutionConfig: contractRouterExecutionConfig, + federatedSchemaVersionId: contractGraph.contractSchemaVersionId, + routerExecutionConfig: contractRouterConfig, featureFlagId: subgraphsToCompose.featureFlagId, }); - if (!contractResult.success || !contractComposition.schemaVersionId || !contractRouterExecutionConfig) { + if (!contractGraph.contractResultSuccess || !contractComposition.schemaVersionId || !contractRouterConfig) { continue; } @@ -1712,9 +1716,9 @@ export class FederatedGraphRepository { * The base composition is always the first item in the subgraphsToCompose array. * */ if (!subgraphsToCompose.isFeatureFlagComposition) { - contractBaseCompositionDataByContractId.set(contractGraph.id, { + contractBaseCompositionDataByContractId.set(contractGraph.contractGraphId, { schemaVersionId: contractComposition.schemaVersionId, - routerExecutionConfig: contractRouterExecutionConfig, + routerExecutionConfig: contractRouterConfig, featureFlagRouterExecutionConfigByFeatureFlagName: new Map(), }); continue; @@ -1723,7 +1727,9 @@ export class FederatedGraphRepository { /* If the contract has a feature flag, get the current array feature flag versions (or set a new one), * and then push the current schema version to the array * */ - const existingContractBaseCompositionData = contractBaseCompositionDataByContractId.get(contractGraph.id); + const existingContractBaseCompositionData = contractBaseCompositionDataByContractId.get( + contractGraph.contractGraphId, + ); /* If the existingContractSchemaVersions is undefined, it means the contract base composition failed. * In this case, simply continue, because when iterating a feature flag for the source graph composition, * there may not be any errors for the feature flag. @@ -1733,9 +1739,12 @@ export class FederatedGraphRepository { } existingContractBaseCompositionData.featureFlagRouterExecutionConfigByFeatureFlagName.set( subgraphsToCompose.featureFlagName, - routerConfigToFeatureFlagExecutionConfig(contractRouterExecutionConfig), + routerConfigToFeatureFlagExecutionConfig(contractRouterConfig), ); } + + allCompositionErrors.push(...outputs.allCompositionErrors); + allCompositionWarnings.push(...outputs.allCompositionWarnings); } const federatedGraphDTO = await this.byId(federatedGraph.id); diff --git a/controlplane/src/core/repositories/SchemaCheckRepository.ts b/controlplane/src/core/repositories/SchemaCheckRepository.ts index 9da31f4c84..2c33b1600a 100644 --- a/controlplane/src/core/repositories/SchemaCheckRepository.ts +++ b/controlplane/src/core/repositories/SchemaCheckRepository.ts @@ -1016,7 +1016,7 @@ export class SchemaCheckRepository { for (const composition of composedGraphs) { for (const error of composition.errors) { compositionErrors.push({ - message: error.message, + message: error, federatedGraphName: composition.name, namespace: composition.namespace, featureFlag: '', @@ -1025,7 +1025,7 @@ export class SchemaCheckRepository { for (const warning of composition.warnings) { compositionWarnings.push({ - message: warning.message, + message: warning, federatedGraphName: composition.name, namespace: composition.namespace, featureFlag: '', diff --git a/controlplane/src/core/repositories/SubgraphRepository.ts b/controlplane/src/core/repositories/SubgraphRepository.ts index 60ab3815a8..75b61ef4b0 100644 --- a/controlplane/src/core/repositories/SubgraphRepository.ts +++ b/controlplane/src/core/repositories/SubgraphRepository.ts @@ -13,6 +13,7 @@ import { and, asc, count, desc, eq, getTableName, gt, inArray, like, lt, notInAr import { PostgresJsDatabase } from 'drizzle-orm/postgres-js'; import { FastifyBaseLogger } from 'fastify'; import { GraphQLSchema } from 'graphql'; +import { Tinypool } from 'tinypool'; import { DBSubgraphType, WebsocketSubprotocol } from '../../db/models.js'; import * as schema from '../../db/schema.js'; import { @@ -242,6 +243,7 @@ export class SubgraphRepository { cdnBaseUrl: string; }, chClient: ClickHouseClient, + composeWorkerPool: Tinypool, compositionOptions?: CompositionOptions, ): Promise<{ compositionErrors: PlainMessage[]; @@ -470,6 +472,7 @@ export class SubgraphRepository { chClient, compositionOptions, federatedGraphs: updatedFederatedGraphs.filter((g) => !g.contract), + composeWorkerPool, }); compositionErrors.push(...cErrors); @@ -501,6 +504,7 @@ export class SubgraphRepository { cdnBaseUrl: string; }, chClient: ClickHouseClient, + composeWorkerPool: Tinypool, compositionOptions?: CompositionOptions, ): Promise<{ compositionErrors: PlainMessage[]; @@ -554,6 +558,7 @@ export class SubgraphRepository { actorId: data.updatedBy, chClient, compositionOptions, + composeWorkerPool, }); return { compositionErrors, updatedFederatedGraphs, deploymentErrors, compositionWarnings }; @@ -2014,7 +2019,7 @@ export class SubgraphRepository { for (const composedGraph of composedGraphs) { for (const error of composedGraph.errors) { compositionErrors.push({ - message: error.message, + message: error, federatedGraphName: composedGraph.name, namespace: composedGraph.namespace, featureFlag: '', @@ -2023,7 +2028,7 @@ export class SubgraphRepository { for (const warning of composedGraph.warnings) { compositionWarnings.push({ - message: warning.message, + message: warning, federatedGraphName: composedGraph.name, namespace: composedGraph.namespace, featureFlag: '', diff --git a/controlplane/src/core/routes.ts b/controlplane/src/core/routes.ts index d08fe04f42..ce497d508c 100644 --- a/controlplane/src/core/routes.ts +++ b/controlplane/src/core/routes.ts @@ -5,6 +5,7 @@ import { PlatformService } from '@wundergraph/cosmo-connect/dist/platform/v1/pla import { PostgresJsDatabase } from 'drizzle-orm/postgres-js'; import pino from 'pino'; import { App } from 'octokit'; +import { Tinypool } from 'tinypool'; import * as schema from '../db/schema.js'; import NodeServiceImpl from './bufservices/NodeService.js'; import PlatformServiceImpl from './bufservices/PlatformService.js'; @@ -51,6 +52,7 @@ export interface RouterOptions { }; stripeSecretKey?: string; cdnBaseUrl: string; + composeWorkerPool: Tinypool; } const handlerOptions: Partial = { maxTimeoutMs: 80_000, diff --git a/controlplane/src/core/util.ts b/controlplane/src/core/util.ts index 4018396f53..9a1bcce353 100644 --- a/controlplane/src/core/util.ts +++ b/controlplane/src/core/util.ts @@ -5,6 +5,7 @@ import { GraphQLSubscriptionProtocol, GraphQLWebsocketSubprotocol, } from '@wundergraph/cosmo-connect/dist/common/common_pb'; +import { SubgraphType, ProposalOrigin } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; import { joinLabel, splitLabel } from '@wundergraph/cosmo-shared'; import { AxiosError } from 'axios'; import { isNetworkError, isRetryableError } from 'axios-retry'; @@ -19,7 +20,10 @@ import { LATEST_ROUTER_COMPATIBILITY_VERSION, newContractTagOptionsFromArrays, } from '@wundergraph/composition'; -import { SubgraphType, ProposalOrigin } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { + composeFederatedContract, + composeFederatedGraphWithPotentialContracts, +} from '../core/composition/composition.js'; import { MemberRole, WebsocketSubprotocol, ProposalOrigin as ProposalOriginEnum } from '../db/models.js'; import { AuthContext, @@ -32,7 +36,6 @@ import { } from '../types/index.js'; import { isAuthenticationError, isAuthorizationError, isPublicError } from './errors/errors.js'; import { GraphKeyAuthContext } from './services/GraphApiTokenAuthenticator.js'; -import { composeFederatedContract, composeFederatedGraphWithPotentialContracts } from './composition/composition.js'; import { SubgraphsToCompose } from './repositories/FeatureFlagRepository.js'; const labelRegex = /^[\dA-Za-z](?:[\w.-]{0,61}[\dA-Za-z])?$/; @@ -546,6 +549,7 @@ export function getFederationResultWithPotentialContracts( compositionOptions, ); } + return composeFederatedGraphWithPotentialContracts( subgraphsToCompose.compositionSubgraphs, tagOptionsByContractName, diff --git a/controlplane/src/workers/compose.ts b/controlplane/src/workers/compose.ts new file mode 100644 index 0000000000..dcd3dcaaef --- /dev/null +++ b/controlplane/src/workers/compose.ts @@ -0,0 +1,226 @@ +/** + * This file is intended to be used with a worker thread. + * In watch mode changes aren't immediately applied. You have to rebuild the project with tsc before changes are applied. + */ +import { randomUUID } from 'node:crypto'; +import { MessagePort } from 'node:worker_threads'; +import { PlainMessage } from '@bufbuild/protobuf'; +import { RouterConfig } from '@wundergraph/cosmo-connect/dist/node/v1/node_pb'; +import { CompositionError, CompositionWarning } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; +import { ContractTagOptions, FederationResult, FederationResultWithContracts } from '@wundergraph/composition'; +import { getFederationResultWithPotentialContracts } from '../core/util.js'; +import { CompositionOptions, FederatedGraphDTO } from '../types/index.js'; +import { + buildRouterExecutionConfig, + ComposedFederatedGraph, + CompositionDeployResult, + mapResultToComposedGraph, +} from '../core/composition/composer.js'; +import { SubgraphsToCompose } from '../core/repositories/FeatureFlagRepository.js'; +import { unsuccessfulBaseCompositionError } from '../core/errors/errors.js'; + +export interface Inputs { + federatedGraph: FederatedGraphDTO; + subgraphsToCompose: SubgraphsToCompose; + tagOptionsByContractName: Map; + compositionOptions?: CompositionOptions; + port: MessagePort; +} + +export interface ContractGraph { + composedContract: ComposedFederatedGraph; + contractResultSuccess: boolean; + contractGraphId: string; + contractSchemaVersionId: `${string}-${string}-${string}-${string}-${string}`; + contractRouterConfig: string; +} + +export interface Outputs { + resultSuccess: boolean; + composedGraph: ComposedFederatedGraph; + federatedSchemaVersionId: `${string}-${string}-${string}-${string}-${string}`; + schemaVersionId: string; + routerConfig: string; + allCompositionErrors: PlainMessage[]; + allCompositionWarnings: PlainMessage[]; + contractGraphs: ContractGraph[]; +} + +export interface BaseMessage { + composedGraph: ComposedFederatedGraph; + contractNames: string[]; + federatedSchemaVersionId: `${string}-${string}-${string}-${string}-${string}`; + routerConfig: string; + resultSuccess: boolean; +} + +export interface BaseResult { + compositionDeployResult: CompositionDeployResult; + contractGraphs: Map; +} + +export default function ({ + federatedGraph, + subgraphsToCompose, + tagOptionsByContractName, + compositionOptions, + port, +}: Inputs): Promise { + return new Promise((resolve) => { + const allCompositionErrors: PlainMessage[] = []; + const allCompositionWarnings: PlainMessage[] = []; + + const result: FederationResult | FederationResultWithContracts = getFederationResultWithPotentialContracts( + federatedGraph, + subgraphsToCompose, + tagOptionsByContractName, + compositionOptions, + ); + + if (!result.success) { + allCompositionErrors.push( + ...result.errors.map((e) => ({ + federatedGraphName: federatedGraph.name, + namespace: federatedGraph.namespace, + message: e.message, + featureFlag: subgraphsToCompose.featureFlagName || '', + })), + ); + } + + allCompositionWarnings.push( + ...result.warnings.map((w) => ({ + federatedGraphName: federatedGraph.name, + namespace: federatedGraph.namespace, + message: w.message, + featureFlag: subgraphsToCompose.featureFlagName || '', + })), + ); + + if (!subgraphsToCompose.isFeatureFlagComposition && !result.success && !federatedGraph.contract) { + allCompositionErrors.push(unsuccessfulBaseCompositionError(federatedGraph.name, federatedGraph.namespace)); + } + + const composedGraph = mapResultToComposedGraph(federatedGraph, subgraphsToCompose.subgraphs, result); + + const federatedSchemaVersionId = randomUUID(); + + const routerExecutionConfig = buildRouterExecutionConfig( + composedGraph, + federatedSchemaVersionId, + federatedGraph.routerCompatibilityVersion, + ); + + const routerConfig = routerExecutionConfig ? routerExecutionConfig.toJsonString() : ''; + + const contractNames: string[] = []; + + if ('federationResultByContractName' in result) { + for (const [contractName, _] of result.federationResultByContractName) { + contractNames.push(contractName); + } + } + + for (const subgraph of composedGraph.subgraphs) { + if ('schema' in subgraph) { + subgraph.schema = undefined; + } + + if ('mapping' in subgraph) { + subgraph.mapping = JSON.parse(JSON.stringify(subgraph.mapping)); + } + } + + const message: BaseMessage = { + composedGraph, + federatedSchemaVersionId, + routerConfig, + contractNames, + resultSuccess: result.success, + }; + + port.postMessage(message); + + port.on('message', (baseResult: BaseResult) => { + if (!result.success || !baseResult.compositionDeployResult.schemaVersionId || !routerExecutionConfig) { + const outputs: Outputs = { + resultSuccess: result.success, + composedGraph, + federatedSchemaVersionId, + schemaVersionId: baseResult.compositionDeployResult.schemaVersionId, + routerConfig, + contractGraphs: [], + allCompositionErrors, + allCompositionWarnings, + }; + resolve(outputs); + } + + const contractGraphs: ContractGraph[] = []; + + if ('federationResultByContractName' in result) { + for (const [contractName, contractGraph] of baseResult.contractGraphs.entries()) { + const contractResult = result.federationResultByContractName.get(contractName)!; // This must always exist, as the `baseResult.contractGraphs` is generated from `message.contractNames`. + if (!contractResult.success) { + allCompositionErrors.push( + ...contractResult.errors.map((e) => ({ + federatedGraphName: contractGraph.name, + namespace: contractGraph.namespace, + message: e.message, + featureFlag: subgraphsToCompose.featureFlagName, + })), + ); + } + + allCompositionWarnings.push( + ...contractResult.warnings.map((w) => ({ + federatedGraphName: contractGraph.name, + namespace: contractGraph.namespace, + message: w.message, + featureFlag: subgraphsToCompose.featureFlagName, + })), + ); + + const composedContract = mapResultToComposedGraph( + contractGraph, + subgraphsToCompose.subgraphs, + contractResult, + ); + + const contractSchemaVersionId = randomUUID(); + + const contractRouterExecutionConfig = buildRouterExecutionConfig( + composedContract, + contractSchemaVersionId, + federatedGraph.routerCompatibilityVersion, + ); + + const contractRouterConfig = contractRouterExecutionConfig + ? contractRouterExecutionConfig.toJsonString() + : ''; + + contractGraphs.push({ + composedContract, + contractResultSuccess: contractResult.success, + contractGraphId: contractGraph.id, + contractSchemaVersionId, + contractRouterConfig, + }); + } + } + + const outputs: Outputs = { + resultSuccess: result.success, + composedGraph, + federatedSchemaVersionId, + schemaVersionId: baseResult.compositionDeployResult.schemaVersionId, + routerConfig, + contractGraphs, + allCompositionErrors, + allCompositionWarnings, + }; + + resolve(outputs); + }); + }); +} diff --git a/controlplane/test/test-util.ts b/controlplane/test/test-util.ts index 224ac1fc00..6e749826c6 100644 --- a/controlplane/test/test-util.ts +++ b/controlplane/test/test-util.ts @@ -12,6 +12,7 @@ import { drizzle } from 'drizzle-orm/postgres-js'; import Fastify from 'fastify'; import { pino } from 'pino'; import postgres from 'postgres'; +import { Tinypool } from 'tinypool'; import { expect } from 'vitest'; import { BlobNotFoundError, BlobObject, BlobStorage } from '../src/core/blobstorage/index.js'; import { ClickHouseClient } from '../src/core/clickhouse/index.js'; @@ -152,6 +153,11 @@ export const SetupTest = async function ({ const deleteUserQueue = new DeleteUserQueue(log, server.redisForQueue); const reactivateOrganizationQueue = new ReactivateOrganizationQueue(log, server.redisForQueue); + const composeWorkerPool = new Tinypool({ + filename: join(process.cwd(), 'dist/workers/compose.js'), + maxThreads: 1, + }); + const blobStorage = new InMemoryBlobStorage(); await server.register(fastifyConnectPlugin, { routes: routes({ @@ -182,6 +188,7 @@ export const SetupTest = async function ({ reactivateOrganizationQueue, deleteUserQueue, }, + composeWorkerPool, }), }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ae96c56ea..835ab23434 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -607,6 +607,9 @@ importers: tiny-lru: specifier: ^11.2.11 version: 11.2.11 + tinypool: + specifier: ^2.0.0 + version: 2.0.0 uid: specifier: ^2.0.2 version: 2.0.2 @@ -1502,8 +1505,8 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} '@babel/core@7.24.5': @@ -1518,8 +1521,8 @@ packages: resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.0': - resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -1546,8 +1549,8 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1596,12 +1599,16 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.2': - resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} '@babel/highlight@7.23.4': @@ -1637,8 +1644,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.28.0': - resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1680,6 +1687,10 @@ packages: resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + '@babel/template@7.24.0': resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} engines: {node: '>=6.9.0'} @@ -1700,8 +1711,8 @@ packages: resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.0': - resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} '@babel/types@7.23.4': @@ -1724,8 +1735,8 @@ packages: resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} '@bufbuild/buf-darwin-arm64@1.32.2': @@ -2327,8 +2338,8 @@ packages: '@codemirror/state@6.5.2': resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} - '@codemirror/view@6.38.1': - resolution: {integrity: sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==} + '@codemirror/view@6.38.6': + resolution: {integrity: sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==} '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} @@ -2490,8 +2501,8 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@csstools/color-helpers@5.0.2': - resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} '@csstools/css-calc@2.1.4': @@ -2501,8 +2512,8 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-color-parser@3.0.10': - resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==} + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} engines: {node: '>=18'} peerDependencies: '@csstools/css-parser-algorithms': ^3.0.5 @@ -4082,6 +4093,9 @@ packages: '@jridgewell/trace-mapping@0.3.30': resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -4137,14 +4151,14 @@ packages: resolution: {integrity: sha512-UQUFCFr+T8mp+ghpqdJXuAWz5+zScB/4zpoQAigLBbazzqSa+Xy+yIL5f1p8h8ql4b58HahUzQW0moSPp24ApA==} engines: {node: ^20.17.0 || >=22.9.0} - '@lezer/common@1.2.3': - resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + '@lezer/common@1.3.0': + resolution: {integrity: sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==} - '@lezer/highlight@1.2.1': - resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} - '@lezer/lr@1.4.2': - resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + '@lezer/lr@1.4.3': + resolution: {integrity: sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==} '@libsql/client-wasm@0.14.0': resolution: {integrity: sha512-gB/jtz0xuwrqAHApBv9e9JSew2030Fhj2edyZ83InZ4yPj/Q2LTUlEhaspEYT0T0xsAGqPy38uGrmq/OGS+DdQ==} @@ -7589,6 +7603,9 @@ packages: '@types/node@18.19.122': resolution: {integrity: sha512-yzegtT82dwTNEe/9y+CM8cgb42WrUfMMCg2QqSddzO1J6uPmBD7qKCZ7dOHZP2Yrpm/kb0eqdNMn2MUyEiqBmA==} + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@18.19.21': resolution: {integrity: sha512-2Q2NeB6BmiTFQi4DHBzncSoq/cJMLDdhPaAoJFnFCyD9a8VPZRf7a1GAwp1Edb7ROaZc5Jz/tnZyL6EsWMRaqw==} @@ -8225,6 +8242,10 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} + baseline-browser-mapping@2.8.28: + resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==} + hasBin: true + before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} @@ -8305,6 +8326,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-alloc-unsafe@1.1.0: resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} @@ -8435,6 +8461,9 @@ packages: caniuse-lite@1.0.30001735: resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + canvas-confetti@1.6.0: resolution: {integrity: sha512-ej+w/m8Jzpv9Z7W7uJZer14Ke8P2ogsjg4ZMGIuq4iqUOqY2Jq8BNW42iGmNfRwREaaEfFIczLuZZiEVSYNHAA==} @@ -9455,6 +9484,9 @@ packages: electron-to-chromium@1.5.200: resolution: {integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==} + electron-to-chromium@1.5.250: + resolution: {integrity: sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==} + emoji-regex@10.3.0: resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} @@ -11882,6 +11914,9 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + nodemailer@6.9.11: resolution: {integrity: sha512-UiAkgiERuG94kl/3bKfE8o10epvDnl0vokNEtZDPTq9BWzIl6EFT9336SbIT4oaTBD8NmmUTLsQyXHV82eXSWg==} engines: {node: '>=6.0.0'} @@ -12673,14 +12708,14 @@ packages: prosemirror-dropcursor@1.8.2: resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} - prosemirror-gapcursor@1.3.2: - resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==} + prosemirror-gapcursor@1.4.0: + resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==} - prosemirror-history@1.4.1: - resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==} + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} - prosemirror-inputrules@1.5.0: - resolution: {integrity: sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==} + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} prosemirror-keymap@1.2.3: resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} @@ -12697,8 +12732,8 @@ packages: prosemirror-model@1.21.0: resolution: {integrity: sha512-zLpS1mVCZLA7VTp82P+BfMiYVPcX1/z0Mf3gsjKZtzMWubwn2pN7CceMV0DycjlgE5JeXPR7UF4hJPbBV98oWA==} - prosemirror-model@1.25.3: - resolution: {integrity: sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA==} + prosemirror-model@1.25.4: + resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} prosemirror-schema-basic@1.2.4: resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} @@ -12706,11 +12741,11 @@ packages: prosemirror-schema-list@1.5.1: resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} - prosemirror-state@1.4.3: - resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} - prosemirror-tables@1.7.1: - resolution: {integrity: sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==} + prosemirror-tables@1.8.1: + resolution: {integrity: sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==} prosemirror-trailing-node@2.0.9: resolution: {integrity: sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==} @@ -12719,11 +12754,11 @@ packages: prosemirror-state: ^1.4.2 prosemirror-view: ^1.33.8 - prosemirror-transform@1.10.4: - resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==} + prosemirror-transform@1.10.5: + resolution: {integrity: sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==} - prosemirror-view@1.40.1: - resolution: {integrity: sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA==} + prosemirror-view@1.41.3: + resolution: {integrity: sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==} proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -13160,6 +13195,11 @@ packages: engines: {node: '>= 0.4'} hasBin: true + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + resolve@1.22.2: resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} hasBin: true @@ -13664,8 +13704,8 @@ packages: engines: {node: '>=4'} hasBin: true - style-mod@4.1.2: - resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} style-to-object@1.0.5: resolution: {integrity: sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ==} @@ -13845,6 +13885,10 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} + tinypool@2.0.0: + resolution: {integrity: sha512-/RX9RzeH2xU5ADE7n2Ykvmi9ED3FBGPAjw9u3zucrNNaEBIO0HPSYgL0NT7+3p147ojeSdaVu08F6hjpv31HJg==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} @@ -14218,6 +14262,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + upper-case-first@2.0.2: resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} @@ -14773,7 +14823,7 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@apm-js-collab/code-transformer@0.8.2': {} @@ -14794,7 +14844,7 @@ snapshots: '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 @@ -15337,20 +15387,20 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.0': {} + '@babel/compat-data@7.28.5': {} '@babel/core@7.24.5': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.24.5) - '@babel/helpers': 7.28.2 - '@babel/parser': 7.28.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.24.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 convert-source-map: 2.0.0 debug: 4.4.1 gensync: 1.0.0-beta.2 @@ -15374,19 +15424,19 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 - '@babel/generator@7.28.0': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.0 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.2 + browserslist: 4.28.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -15405,17 +15455,17 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.24.5)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -15443,12 +15493,14 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.2': + '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.2 + '@babel/types': 7.28.5 '@babel/highlight@7.23.4': dependencies: @@ -15483,9 +15535,9 @@ snapshots: dependencies: '@babel/types': 7.27.0 - '@babel/parser@7.28.0': + '@babel/parser@7.28.5': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.5 '@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.24.5)': dependencies: @@ -15516,6 +15568,8 @@ snapshots: '@babel/runtime@7.28.2': {} + '@babel/runtime@7.28.4': {} + '@babel/template@7.24.0': dependencies: '@babel/code-frame': 7.24.2 @@ -15531,8 +15585,8 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@babel/traverse@7.23.7': dependencies: @@ -15561,14 +15615,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/traverse@7.28.0': + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.2 + '@babel/types': 7.28.5 debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -15601,10 +15655,10 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/types@7.28.2': + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@bufbuild/buf-darwin-arm64@1.32.2': optional: true @@ -16408,21 +16462,21 @@ snapshots: '@codemirror/language@6.0.0': dependencies: '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.1 - '@lezer/common': 1.2.3 - '@lezer/highlight': 1.2.1 - '@lezer/lr': 1.4.2 - style-mod: 4.1.2 + '@codemirror/view': 6.38.6 + '@lezer/common': 1.3.0 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.3 + style-mod: 4.1.3 '@codemirror/state@6.5.2': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.38.1': + '@codemirror/view@6.38.6': dependencies: '@codemirror/state': 6.5.2 crelt: 1.0.6 - style-mod: 4.1.2 + style-mod: 4.1.3 w3c-keyname: 2.2.8 '@colors/colors@1.5.0': @@ -16630,16 +16684,16 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@5.0.2': {} + '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@csstools/color-helpers': 5.0.2 + '@csstools/color-helpers': 5.1.0 '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 @@ -17821,7 +17875,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/gen-mapping@0.3.3': dependencies: @@ -17850,7 +17904,7 @@ snapshots: '@jridgewell/source-map@0.3.11': dependencies: '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/source-map@0.3.6': dependencies: @@ -17875,6 +17929,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -18053,15 +18112,15 @@ snapshots: - supports-color - typescript - '@lezer/common@1.2.3': {} + '@lezer/common@1.3.0': {} - '@lezer/highlight@1.2.1': + '@lezer/highlight@1.2.3': dependencies: - '@lezer/common': 1.2.3 + '@lezer/common': 1.3.0 - '@lezer/lr@1.4.2': + '@lezer/lr@1.4.3': dependencies: - '@lezer/common': 1.2.3 + '@lezer/common': 1.3.0 '@libsql/client-wasm@0.14.0': dependencies: @@ -21821,20 +21880,20 @@ snapshots: prosemirror-collab: 1.3.1 prosemirror-commands: 1.7.1 prosemirror-dropcursor: 1.8.2 - prosemirror-gapcursor: 1.3.2 - prosemirror-history: 1.4.1 - prosemirror-inputrules: 1.5.0 + prosemirror-gapcursor: 1.4.0 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 prosemirror-keymap: 1.2.3 prosemirror-markdown: 1.13.2 prosemirror-menu: 1.2.5 - prosemirror-model: 1.25.3 + prosemirror-model: 1.25.4 prosemirror-schema-basic: 1.2.4 prosemirror-schema-list: 1.5.1 - prosemirror-state: 1.4.3 - prosemirror-tables: 1.7.1 - prosemirror-trailing-node: 2.0.9(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.1) - prosemirror-transform: 1.10.4 - prosemirror-view: 1.40.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.1 + prosemirror-trailing-node: 2.0.9(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3) + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 '@tiptap/react@2.1.13(@tiptap/core@2.4.0(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -22205,6 +22264,11 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + optional: true + '@types/node@18.19.21': dependencies: undici-types: 5.26.5 @@ -22987,9 +23051,9 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 cosmiconfig: 7.1.0 - resolve: 1.22.10 + resolve: 1.22.11 bail@2.0.2: {} @@ -22999,6 +23063,8 @@ snapshots: base64id@2.0.0: {} + baseline-browser-mapping@2.8.28: {} + before-after-hook@3.0.2: {} before-after-hook@4.0.0: {} @@ -23105,6 +23171,14 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.2) + browserslist@4.28.0: + dependencies: + baseline-browser-mapping: 2.8.28 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.250 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) + buffer-alloc-unsafe@1.1.0: {} buffer-alloc@1.2.0: @@ -23148,7 +23222,7 @@ snapshots: bun-types@1.2.12: dependencies: - '@types/node': 20.12.12 + '@types/node': 18.19.130 optional: true bun-types@1.2.3: @@ -23250,6 +23324,8 @@ snapshots: caniuse-lite@1.0.30001735: {} + caniuse-lite@1.0.30001754: {} + canvas-confetti@1.6.0: {} capital-case@1.0.4: @@ -24180,6 +24256,8 @@ snapshots: electron-to-chromium@1.5.200: {} + electron-to-chromium@1.5.250: {} + emoji-regex@10.3.0: {} emoji-regex@8.0.0: {} @@ -27325,6 +27403,8 @@ snapshots: node-releases@2.0.19: {} + node-releases@2.0.27: {} + nodemailer@6.9.11: {} nopt@8.1.0: @@ -28113,46 +28193,46 @@ snapshots: prosemirror-changeset@2.3.1: dependencies: - prosemirror-transform: 1.10.4 + prosemirror-transform: 1.10.5 prosemirror-collab@1.3.1: dependencies: - prosemirror-state: 1.4.3 + prosemirror-state: 1.4.4 prosemirror-commands@1.7.1: dependencies: - prosemirror-model: 1.25.3 - prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.4 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 prosemirror-dropcursor@1.8.2: dependencies: - prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.4 - prosemirror-view: 1.40.1 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 - prosemirror-gapcursor@1.3.2: + prosemirror-gapcursor@1.4.0: dependencies: prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.3 - prosemirror-state: 1.4.3 - prosemirror-view: 1.40.1 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.3 - prosemirror-history@1.4.1: + prosemirror-history@1.5.0: dependencies: - prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.4 - prosemirror-view: 1.40.1 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 rope-sequence: 1.3.4 - prosemirror-inputrules@1.5.0: + prosemirror-inputrules@1.5.1: dependencies: - prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 prosemirror-keymap@1.2.3: dependencies: - prosemirror-state: 1.4.3 + prosemirror-state: 1.4.4 w3c-keyname: 2.2.8 prosemirror-markdown@1.12.0: @@ -28164,64 +28244,64 @@ snapshots: dependencies: '@types/markdown-it': 14.1.2 markdown-it: 14.1.0 - prosemirror-model: 1.25.3 + prosemirror-model: 1.25.4 prosemirror-menu@1.2.5: dependencies: crelt: 1.0.6 prosemirror-commands: 1.7.1 - prosemirror-history: 1.4.1 - prosemirror-state: 1.4.3 + prosemirror-history: 1.5.0 + prosemirror-state: 1.4.4 prosemirror-model@1.21.0: dependencies: orderedmap: 2.1.1 - prosemirror-model@1.25.3: + prosemirror-model@1.25.4: dependencies: orderedmap: 2.1.1 prosemirror-schema-basic@1.2.4: dependencies: - prosemirror-model: 1.25.3 + prosemirror-model: 1.25.4 prosemirror-schema-list@1.5.1: dependencies: - prosemirror-model: 1.25.3 - prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.4 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 - prosemirror-state@1.4.3: + prosemirror-state@1.4.4: dependencies: - prosemirror-model: 1.25.3 - prosemirror-transform: 1.10.4 - prosemirror-view: 1.40.1 + prosemirror-model: 1.25.4 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 - prosemirror-tables@1.7.1: + prosemirror-tables@1.8.1: dependencies: prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.3 - prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.4 - prosemirror-view: 1.40.1 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 - prosemirror-trailing-node@2.0.9(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.1): + prosemirror-trailing-node@2.0.9(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3): dependencies: '@remirror/core-constants': 2.0.2 escape-string-regexp: 4.0.0 - prosemirror-model: 1.25.3 - prosemirror-state: 1.4.3 - prosemirror-view: 1.40.1 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.3 - prosemirror-transform@1.10.4: + prosemirror-transform@1.10.5: dependencies: - prosemirror-model: 1.25.3 + prosemirror-model: 1.25.4 - prosemirror-view@1.40.1: + prosemirror-view@1.41.3: dependencies: - prosemirror-model: 1.25.3 - prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.4 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 proto-list@1.2.4: {} @@ -28756,6 +28836,12 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + resolve@1.22.2: dependencies: is-core-module: 2.12.1 @@ -29398,7 +29484,7 @@ snapshots: minimist: 1.2.8 through: 2.3.8 - style-mod@4.1.2: {} + style-mod@4.1.3: {} style-to-object@1.0.5: dependencies: @@ -29646,6 +29732,8 @@ snapshots: tinypool@1.1.1: {} + tinypool@2.0.0: {} + tinyrainbow@2.0.0: {} tinyspy@4.0.3: {} @@ -30059,6 +30147,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.1.4(browserslist@4.28.0): + dependencies: + browserslist: 4.28.0 + escalade: 3.2.0 + picocolors: 1.1.1 + upper-case-first@2.0.2: dependencies: tslib: 2.8.1