From 629bdbbf508eb929d6c681b60a4b6513e78468fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20M?= Date: Thu, 28 Sep 2023 16:27:34 +0200 Subject: [PATCH] feat: dynamic graphQL schema generation based on user workspace (#1725) * wip: refacto and start creating custom resolver * feat: findMany & findUnique of a custom entity * feat: wip pagination * feat: initial metadata migration * feat: universal findAll with pagination * fix: clean small stuff in pagination * fix: test * fix: miss file * feat: rename custom into universal * feat: create metadata schema in default database * Multi-tenant db schemas POC fix tests and use query builders remove synchronize restore updatedAt remove unnecessary import use queryRunner fix camelcase add migrations for standard objects Multi-tenant db schemas POC fix tests and use query builders remove synchronize restore updatedAt remove unnecessary import use queryRunner fix camelcase add migrations for standard objects poc: conditional schema at runtime wip: try to create resolver in Nest.JS context fix * feat: wip add pg_graphql * feat: setup pg_graphql during database init * wip: dynamic resolver * poc: dynamic resolver and query using pg_graphql * feat: pg_graphql use ARG in Dockerfile * feat: clean findMany & findOne dynamic resolver * feat: get correct schema based on access token * fix: remove old file * fix: tests * fix: better comment * fix: e2e test not working, error format change due to yoga * remove typeorm entity generation + fix jwt + fix search_path + remove anon * fix conflict --------- Co-authored-by: Charles Bochet Co-authored-by: corentin --- infra/dev/docker-compose.yml | 4 +- infra/dev/postgres/Dockerfile | 12 +- infra/dev/postgres/init.sql | 41 +- server/package.json | 16 +- .../patches/@graphql-yoga+nestjs+2.1.0.patch | 329 +++++++++++++++ server/src/app.module.ts | 87 +++- .../services/workspace.service.spec.ts | 5 + .../workspace/services/workspace.service.ts | 5 + server/src/core/workspace/workspace.module.ts | 9 +- server/src/guards/jwt.auth.guard.ts | 4 +- .../entity-resolver/entity-resolver.module.ts | 12 + .../entity-resolver.service.spec.ts | 27 ++ .../entity-resolver.service.ts | 111 +++++ .../entity-resolver/entity-resolver.util.ts | 23 + .../data-source-metadata.service.ts | 9 +- .../data-source/data-source.service.ts | 43 +- .../entity-schema-generator.util.ts | 1 - .../field-metadata/field-metadata.entity.ts | 6 + .../tenant/metadata/metadata.controller.ts | 2 + .../migration-generator.service.ts | 5 - ...695717691800-alter-field-metadata-table.ts | 61 +++ .../tenant-migration.service.spec.ts | 4 +- .../graphql-types/connection.graphql-type.ts | 24 ++ .../graphql-types/edge.graphql-type.ts | 22 + .../graphql-types/object.graphql-type.ts | 99 +++++ .../graphql-types/page-info.graphql-type.ts | 19 + .../schema-generation.module.ts | 21 + .../schema-generation.service.spec.ts | 37 ++ .../schema-generation.service.ts | 150 +++++++ server/src/tenant/tenant.module.ts | 3 +- server/src/utils/pagination/paginated.ts | 5 +- server/src/utils/pascal-case.ts | 32 ++ server/test/company.e2e-spec.ts | 9 +- server/yarn.lock | 351 ++++++++++++++-- yarn.lock | 396 ++++++++++++++++++ 35 files changed, 1860 insertions(+), 124 deletions(-) create mode 100644 server/patches/@graphql-yoga+nestjs+2.1.0.patch create mode 100644 server/src/tenant/entity-resolver/entity-resolver.module.ts create mode 100644 server/src/tenant/entity-resolver/entity-resolver.service.spec.ts create mode 100644 server/src/tenant/entity-resolver/entity-resolver.service.ts create mode 100644 server/src/tenant/entity-resolver/entity-resolver.util.ts create mode 100644 server/src/tenant/metadata/migrations/1695717691800-alter-field-metadata-table.ts create mode 100644 server/src/tenant/schema-generation/graphql-types/connection.graphql-type.ts create mode 100644 server/src/tenant/schema-generation/graphql-types/edge.graphql-type.ts create mode 100644 server/src/tenant/schema-generation/graphql-types/object.graphql-type.ts create mode 100644 server/src/tenant/schema-generation/graphql-types/page-info.graphql-type.ts create mode 100644 server/src/tenant/schema-generation/schema-generation.module.ts create mode 100644 server/src/tenant/schema-generation/schema-generation.service.spec.ts create mode 100644 server/src/tenant/schema-generation/schema-generation.service.ts create mode 100644 server/src/utils/pascal-case.ts create mode 100644 yarn.lock diff --git a/infra/dev/docker-compose.yml b/infra/dev/docker-compose.yml index 0c260214d..7351607c0 100644 --- a/infra/dev/docker-compose.yml +++ b/infra/dev/docker-compose.yml @@ -28,7 +28,9 @@ services: volumes: - db_data:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD: twenty + - POSTGRES_USER=twenty + - POSTGRES_PASSWORD=twenty + - POSTGRES_DB=default ports: - "5432:5432" volumes: diff --git a/infra/dev/postgres/Dockerfile b/infra/dev/postgres/Dockerfile index 75ddc042d..f36394df5 100644 --- a/infra/dev/postgres/Dockerfile +++ b/infra/dev/postgres/Dockerfile @@ -1,5 +1,15 @@ -FROM postgres:13.7 as postgres +ARG PG_MAIN_VERSION=14 + +FROM postgres:${PG_MAIN_VERSION} as postgres + +ARG PG_MAIN_VERSION +ARG PG_GRAPHQL_VERSION=1.3.0 +ARG TARGETARCH=arm64 RUN apt update && apt install -y curl +# Install precompiled pg_graphql extensions +RUN curl -L "https://github.com/supabase/pg_graphql/releases/download/v${PG_GRAPHQL_VERSION}/pg_graphql-v${PG_GRAPHQL_VERSION}-pg${PG_MAIN_VERSION}-${TARGETARCH}-linux-gnu.deb" -o pg_graphql.deb +RUN dpkg --install pg_graphql.deb + COPY init.sql /docker-entrypoint-initdb.d/ diff --git a/infra/dev/postgres/init.sql b/infra/dev/postgres/init.sql index a0ffb4e2f..4478153eb 100644 --- a/infra/dev/postgres/init.sql +++ b/infra/dev/postgres/init.sql @@ -1,25 +1,46 @@ --- Create the default database for development -CREATE DATABASE "default"; - --- Create the tests database for e2e testing -CREATE DATABASE "test"; - --- Create a twenty user -CREATE USER twenty PASSWORD 'twenty'; -ALTER USER twenty CREATEDB; +-- Inflect names for pg_graphql +COMMENT ON SCHEMA "public" IS '@graphql({"inflect_names": true})'; -- Connect to the "default" database \c "default"; +-- Create extension +CREATE EXTENSION IF NOT EXISTS pg_graphql; + -- Create the metadata schema if it doesn't exist CREATE SCHEMA IF NOT EXISTS "metadata"; GRANT ALL ON SCHEMA metadata TO twenty; + +-- Create extension uuid-ossp CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; --- Connect to the "default" database +-- Create GraphQL Entrypoint +create function graphql( + "operationName" text default null, + query text default null, + variables jsonb default null, + extensions jsonb default null +) + returns jsonb + language sql +as $$ + select graphql.resolve( + query := query, + variables := coalesce(variables, '{}'), + "operationName" := "operationName", + extensions := extensions + ); +$$; + +-- Create the tests database for e2e testing +CREATE DATABASE "test"; + +-- Connect to the "test" database for e2e testing \c "test"; -- Create the metadata schema if it doesn't exist CREATE SCHEMA IF NOT EXISTS "metadata"; GRANT ALL ON SCHEMA metadata TO twenty; + +-- Create extension uuid-ossp CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/server/package.json b/server/package.json index 88d60b8ad..ade37b524 100644 --- a/server/package.json +++ b/server/package.json @@ -6,6 +6,7 @@ "private": true, "license": "UNLICENSED", "scripts": { + "postinstall": "patch-package", "prebuild": "rimraf dist", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", @@ -35,11 +36,12 @@ "@aws-sdk/credential-providers": "^3.363.0", "@casl/ability": "^6.5.0", "@casl/prisma": "1.4.0", + "@graphql-yoga/nestjs": "^2.1.0", "@nestjs/apollo": "^11.0.5", "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", - "@nestjs/graphql": "^11.0.6", + "@nestjs/graphql": "^12.0.8", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", @@ -62,12 +64,15 @@ "class-validator": "^0.14.0", "date-fns": "^2.30.0", "file-type": "16.5.4", - "graphql": "^16.7.1", + "graphql": "^16.8.0", + "graphql-fields": "^2.0.3", "graphql-type-json": "^0.3.2", "graphql-upload": "^13.0.0", + "graphql-yoga": "^4.0.4", "jest-mock-extended": "^3.0.4", "jsonwebtoken": "^9.0.0", "lodash.camelcase": "^4.3.0", + "lodash.isempty": "^4.4.0", "lodash.isobject": "^3.0.2", "lodash.kebabcase": "^4.1.1", "lodash.merge": "^4.6.2", @@ -77,7 +82,9 @@ "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", + "patch-package": "^8.0.0", "pg": "^8.11.3", + "postinstall-postinstall": "^2.1.0", "prisma-graphql-type-decimal": "^3.0.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", @@ -96,8 +103,10 @@ "@types/bytes": "^3.1.1", "@types/date-fns": "^2.6.0", "@types/express": "^4.17.13", + "@types/graphql-fields": "^1.3.6", "@types/graphql-upload": "^8.0.12", "@types/jest": "28.1.8", + "@types/lodash.isempty": "^4.4.7", "@types/lodash.isobject": "^3.0.7", "@types/lodash.kebabcase": "^4.1.7", "@types/lodash.snakecase": "^4.1.7", @@ -127,6 +136,9 @@ "tsconfig-paths": "4.1.0", "typescript": "^4.9.4" }, + "resolutions": { + "graphql": "16.8.0" + }, "prisma": { "schema": "src/database/schema.prisma", "seed": "ts-node src/database/seeds/index.ts" diff --git a/server/patches/@graphql-yoga+nestjs+2.1.0.patch b/server/patches/@graphql-yoga+nestjs+2.1.0.patch new file mode 100644 index 000000000..d41096c72 --- /dev/null +++ b/server/patches/@graphql-yoga+nestjs+2.1.0.patch @@ -0,0 +1,329 @@ +diff --git a/node_modules/@graphql-yoga/nestjs/dist/cjs/index.js b/node_modules/@graphql-yoga/nestjs/dist/cjs/index.js +index 1684394..8a92c3c 100644 +--- a/node_modules/@graphql-yoga/nestjs/dist/cjs/index.js ++++ b/node_modules/@graphql-yoga/nestjs/dist/cjs/index.js +@@ -5,6 +5,7 @@ const tslib_1 = require("tslib"); + const graphql_1 = require("graphql"); + const graphql_yoga_1 = require("graphql-yoga"); + const common_1 = require("@nestjs/common"); ++const schema_1 = require("@graphql-tools/schema"); + const graphql_2 = require("@nestjs/graphql"); + class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { + async start(options) { +@@ -27,7 +28,7 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { + async stop() { + // noop + } +- registerExpress(options, { preStartHook } = {}) { ++ registerExpress({ conditionalSchema, ...options }, { preStartHook } = {}) { + const app = this.httpAdapterHost.httpAdapter.getInstance(); + preStartHook?.(app); + // nest's logger doesnt have the info method +@@ -42,6 +43,21 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { + } + const yoga = (0, graphql_yoga_1.createYoga)({ + ...options, ++ schema: async (request) => { ++ const schemas = []; ++ if (options.schema) { ++ schemas.push(options.schema); ++ } ++ if (conditionalSchema) { ++ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; ++ if (conditionalSchemaResult) { ++ schemas.push(conditionalSchemaResult); ++ } ++ } ++ return (0, schema_1.mergeSchemas)({ ++ schemas, ++ }); ++ }, + graphqlEndpoint: options.path, + // disable logging by default + // however, if `true` use nest logger +@@ -54,11 +70,26 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { + this.yoga = yoga; + app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res })); + } +- registerFastify(options, { preStartHook } = {}) { ++ registerFastify({ conditionalSchema, ...options }, { preStartHook } = {}) { + const app = this.httpAdapterHost.httpAdapter.getInstance(); + preStartHook?.(app); + const yoga = (0, graphql_yoga_1.createYoga)({ + ...options, ++ schema: async (request) => { ++ const schemas = []; ++ if (options.schema) { ++ schemas.push(options.schema); ++ } ++ if (conditionalSchema) { ++ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; ++ if (conditionalSchemaResult) { ++ schemas.push(conditionalSchemaResult); ++ } ++ } ++ return (0, schema_1.mergeSchemas)({ ++ schemas, ++ }); ++ }, + graphqlEndpoint: options.path, + // disable logging by default + // however, if `true` use fastify logger +diff --git a/node_modules/@graphql-yoga/nestjs/dist/esm/index.js b/node_modules/@graphql-yoga/nestjs/dist/esm/index.js +index 7068c51..8ba5d2a 100644 +--- a/node_modules/@graphql-yoga/nestjs/dist/esm/index.js ++++ b/node_modules/@graphql-yoga/nestjs/dist/esm/index.js +@@ -2,6 +2,7 @@ import { __decorate } from "tslib"; + import { printSchema } from 'graphql'; + import { createYoga, filter, pipe } from 'graphql-yoga'; + import { Injectable, Logger } from '@nestjs/common'; ++import { mergeSchemas } from '@graphql-tools/schema'; + import { AbstractGraphQLDriver, GqlSubscriptionService, } from '@nestjs/graphql'; + export class AbstractYogaDriver extends AbstractGraphQLDriver { + async start(options) { +@@ -24,7 +25,7 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { + async stop() { + // noop + } +- registerExpress(options, { preStartHook } = {}) { ++ registerExpress({ conditionalSchema, ...options }, { preStartHook } = {}) { + const app = this.httpAdapterHost.httpAdapter.getInstance(); + preStartHook?.(app); + // nest's logger doesnt have the info method +@@ -39,6 +40,21 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { + } + const yoga = createYoga({ + ...options, ++ schema: async (request) => { ++ const schemas = []; ++ if (options.schema) { ++ schemas.push(options.schema); ++ } ++ if (conditionalSchema) { ++ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; ++ if (conditionalSchemaResult) { ++ schemas.push(conditionalSchemaResult); ++ } ++ } ++ return mergeSchemas({ ++ schemas, ++ }); ++ }, + graphqlEndpoint: options.path, + // disable logging by default + // however, if `true` use nest logger +@@ -51,11 +67,26 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { + this.yoga = yoga; + app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res })); + } +- registerFastify(options, { preStartHook } = {}) { ++ registerFastify({ conditionalSchema, ...options }, { preStartHook } = {}) { + const app = this.httpAdapterHost.httpAdapter.getInstance(); + preStartHook?.(app); + const yoga = createYoga({ + ...options, ++ schema: async (request) => { ++ const schemas = []; ++ if (options.schema) { ++ schemas.push(options.schema); ++ } ++ if (conditionalSchema) { ++ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; ++ if (conditionalSchemaResult) { ++ schemas.push(conditionalSchemaResult); ++ } ++ } ++ return mergeSchemas({ ++ schemas, ++ }); ++ }, + graphqlEndpoint: options.path, + // disable logging by default + // however, if `true` use fastify logger +diff --git a/node_modules/@graphql-yoga/nestjs/dist/typings/index.d.cts b/node_modules/@graphql-yoga/nestjs/dist/typings/index.d.cts +index 2c6a965..fd86dac 100644 +--- a/node_modules/@graphql-yoga/nestjs/dist/typings/index.d.cts ++++ b/node_modules/@graphql-yoga/nestjs/dist/typings/index.d.cts +@@ -1,7 +1,8 @@ + import type { Express, Request as ExpressRequest, Response as ExpressResponse } from 'express'; + import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +-import { YogaServerInstance, YogaServerOptions } from 'graphql-yoga'; ++import { YogaServerInstance, YogaServerOptions, GraphQLSchemaWithContext, PromiseOrValue, YogaInitialContext } from 'graphql-yoga'; + import { AbstractGraphQLDriver, GqlModuleOptions, SubscriptionConfig } from '@nestjs/graphql'; ++export type YogaSchemaDefinition = PromiseOrValue> | ((context: TContext & YogaInitialContext) => PromiseOrValue>); + export type YogaDriverPlatform = 'express' | 'fastify'; + export type YogaDriverServerContext = Platform extends 'fastify' ? { + req: FastifyRequest; +@@ -10,7 +11,9 @@ export type YogaDriverServerContext = Platf + req: ExpressRequest; + res: ExpressResponse; + }; +-export type YogaDriverServerOptions = Omit, never>, 'context' | 'schema'>; ++export type YogaDriverServerOptions = Omit, never>, 'context' | 'schema'> & { ++ conditionalSchema?: YogaSchemaDefinition> | undefined; ++}; + export type YogaDriverServerInstance = YogaServerInstance, never>; + export type YogaDriverConfig = GqlModuleOptions & YogaDriverServerOptions & { + /** +@@ -26,10 +29,10 @@ export declare abstract class AbstractYogaDriver; + start(options: YogaDriverConfig): Promise; + stop(): Promise; +- protected registerExpress(options: YogaDriverConfig<'express'>, { preStartHook }?: { ++ protected registerExpress({ conditionalSchema, ...options }: YogaDriverConfig<'express'>, { preStartHook }?: { + preStartHook?: (app: Express) => void; + }): void; +- protected registerFastify(options: YogaDriverConfig<'fastify'>, { preStartHook }?: { ++ protected registerFastify({ conditionalSchema, ...options }: YogaDriverConfig<'fastify'>, { preStartHook }?: { + preStartHook?: (app: FastifyInstance) => void; + }): void; + subscriptionWithFilter(instanceRef: unknown, filterFn: (payload: TPayload, variables: TVariables, context: TContext) => boolean | Promise, createSubscribeContext: Function): (args_0: TPayload, args_1: TVariables, args_2: TContext) => Promise>; +diff --git a/node_modules/@graphql-yoga/nestjs/dist/typings/index.d.ts b/node_modules/@graphql-yoga/nestjs/dist/typings/index.d.ts +index 2c6a965..fd86dac 100644 +--- a/node_modules/@graphql-yoga/nestjs/dist/typings/index.d.ts ++++ b/node_modules/@graphql-yoga/nestjs/dist/typings/index.d.ts +@@ -1,7 +1,8 @@ + import type { Express, Request as ExpressRequest, Response as ExpressResponse } from 'express'; + import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +-import { YogaServerInstance, YogaServerOptions } from 'graphql-yoga'; ++import { YogaServerInstance, YogaServerOptions, GraphQLSchemaWithContext, PromiseOrValue, YogaInitialContext } from 'graphql-yoga'; + import { AbstractGraphQLDriver, GqlModuleOptions, SubscriptionConfig } from '@nestjs/graphql'; ++export type YogaSchemaDefinition = PromiseOrValue> | ((context: TContext & YogaInitialContext) => PromiseOrValue>); + export type YogaDriverPlatform = 'express' | 'fastify'; + export type YogaDriverServerContext = Platform extends 'fastify' ? { + req: FastifyRequest; +@@ -10,7 +11,9 @@ export type YogaDriverServerContext = Platf + req: ExpressRequest; + res: ExpressResponse; + }; +-export type YogaDriverServerOptions = Omit, never>, 'context' | 'schema'>; ++export type YogaDriverServerOptions = Omit, never>, 'context' | 'schema'> & { ++ conditionalSchema?: YogaSchemaDefinition> | undefined; ++}; + export type YogaDriverServerInstance = YogaServerInstance, never>; + export type YogaDriverConfig = GqlModuleOptions & YogaDriverServerOptions & { + /** +@@ -26,10 +29,10 @@ export declare abstract class AbstractYogaDriver; + start(options: YogaDriverConfig): Promise; + stop(): Promise; +- protected registerExpress(options: YogaDriverConfig<'express'>, { preStartHook }?: { ++ protected registerExpress({ conditionalSchema, ...options }: YogaDriverConfig<'express'>, { preStartHook }?: { + preStartHook?: (app: Express) => void; + }): void; +- protected registerFastify(options: YogaDriverConfig<'fastify'>, { preStartHook }?: { ++ protected registerFastify({ conditionalSchema, ...options }: YogaDriverConfig<'fastify'>, { preStartHook }?: { + preStartHook?: (app: FastifyInstance) => void; + }): void; + subscriptionWithFilter(instanceRef: unknown, filterFn: (payload: TPayload, variables: TVariables, context: TContext) => boolean | Promise, createSubscribeContext: Function): (args_0: TPayload, args_1: TVariables, args_2: TContext) => Promise>; +diff --git a/node_modules/@graphql-yoga/nestjs/src/index.ts b/node_modules/@graphql-yoga/nestjs/src/index.ts +index ce142f6..cda4117 100644 +--- a/node_modules/@graphql-yoga/nestjs/src/index.ts ++++ b/node_modules/@graphql-yoga/nestjs/src/index.ts +@@ -1,9 +1,10 @@ + import type { Express, Request as ExpressRequest, Response as ExpressResponse } from 'express'; + import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +-import { printSchema } from 'graphql'; +-import { createYoga, filter, pipe, YogaServerInstance, YogaServerOptions } from 'graphql-yoga'; ++import { GraphQLSchema, printSchema } from 'graphql'; ++import { createYoga, filter, pipe, YogaServerInstance, YogaServerOptions, GraphQLSchemaWithContext, PromiseOrValue, YogaInitialContext } from 'graphql-yoga'; + import type { ExecutionParams } from 'subscriptions-transport-ws'; + import { Injectable, Logger } from '@nestjs/common'; ++import { mergeSchemas } from '@graphql-tools/schema'; + import { + AbstractGraphQLDriver, + GqlModuleOptions, +@@ -11,6 +12,12 @@ import { + SubscriptionConfig, + } from '@nestjs/graphql'; + ++export type YogaSchemaDefinition = ++ | PromiseOrValue> ++ | (( ++ context: TContext & YogaInitialContext, ++ ) => PromiseOrValue>); ++ + export type YogaDriverPlatform = 'express' | 'fastify'; + + export type YogaDriverServerContext = +@@ -27,7 +34,9 @@ export type YogaDriverServerContext = + export type YogaDriverServerOptions = Omit< + YogaServerOptions, never>, + 'context' | 'schema' +->; ++> & { ++ conditionalSchema?: YogaSchemaDefinition> | undefined; ++}; + + export type YogaDriverServerInstance = YogaServerInstance< + YogaDriverServerContext, +@@ -78,7 +87,7 @@ export abstract class AbstractYogaDriver< + } + + protected registerExpress( +- options: YogaDriverConfig<'express'>, ++ { conditionalSchema, ...options}: YogaDriverConfig<'express'>, + { preStartHook }: { preStartHook?: (app: Express) => void } = {}, + ) { + const app: Express = this.httpAdapterHost.httpAdapter.getInstance(); +@@ -98,6 +107,25 @@ export abstract class AbstractYogaDriver< + + const yoga = createYoga>({ + ...options, ++ schema: async request => { ++ const schemas: GraphQLSchema[] = []; ++ ++ if (options.schema) { ++ schemas.push(options.schema); ++ } ++ ++ if (conditionalSchema) { ++ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; ++ ++ if (conditionalSchemaResult) { ++ schemas.push(conditionalSchemaResult); ++ } ++ } ++ ++ return mergeSchemas({ ++ schemas, ++ }); ++ }, + graphqlEndpoint: options.path, + // disable logging by default + // however, if `true` use nest logger +@@ -115,7 +143,7 @@ export abstract class AbstractYogaDriver< + } + + protected registerFastify( +- options: YogaDriverConfig<'fastify'>, ++ { conditionalSchema, ...options }: YogaDriverConfig<'fastify'>, + { preStartHook }: { preStartHook?: (app: FastifyInstance) => void } = {}, + ) { + const app: FastifyInstance = this.httpAdapterHost.httpAdapter.getInstance(); +@@ -124,6 +152,25 @@ export abstract class AbstractYogaDriver< + + const yoga = createYoga>({ + ...options, ++ schema: async request => { ++ const schemas: GraphQLSchema[] = []; ++ ++ if (options.schema) { ++ schemas.push(options.schema); ++ } ++ ++ if (conditionalSchema) { ++ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; ++ ++ if (conditionalSchemaResult) { ++ schemas.push(conditionalSchemaResult); ++ } ++ } ++ ++ return mergeSchemas({ ++ schemas, ++ }); ++ }, + graphqlEndpoint: options.path, + // disable logging by default + // however, if `true` use fastify logger diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 9fb2e8b02..aaa7512d9 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -1,11 +1,13 @@ import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { ConfigModule } from '@nestjs/config'; -import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; +import { ModuleRef } from '@nestjs/core'; -import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default'; -import { GraphQLError } from 'graphql'; +import { YogaDriver, YogaDriverConfig } from '@graphql-yoga/nestjs'; import GraphQLJSON from 'graphql-type-json'; +import { GraphQLError, GraphQLSchema } from 'graphql'; +import { ExtractJwt } from 'passport-jwt'; +import { TokenExpiredError, verify } from 'jsonwebtoken'; import { AppService } from './app.service'; @@ -15,24 +17,77 @@ import { PrismaModule } from './database/prisma.module'; import { HealthModule } from './health/health.module'; import { AbilityModule } from './ability/ability.module'; import { TenantModule } from './tenant/tenant.module'; +import { SchemaGenerationService } from './tenant/schema-generation/schema-generation.service'; +import { EnvironmentService } from './integrations/environment/environment.service'; +import { + JwtAuthStrategy, + JwtPayload, +} from './core/auth/strategies/jwt.auth.strategy'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, }), - GraphQLModule.forRoot({ - playground: false, + GraphQLModule.forRoot({ context: ({ req }) => ({ req }), - driver: ApolloDriver, + driver: YogaDriver, autoSchemaFile: true, - resolvers: { JSON: GraphQLJSON }, - plugins: [ApolloServerPluginLandingPageLocalDefault()], - formatError: (error: GraphQLError) => { - error.extensions.stacktrace = undefined; - return error; + conditionalSchema: async (request) => { + try { + // Get the SchemaGenerationService from the AppModule + const service = AppModule.moduleRef.get(SchemaGenerationService, { + strict: false, + }); + + // Get the JwtAuthStrategy from the AppModule + const jwtStrategy = AppModule.moduleRef.get(JwtAuthStrategy, { + strict: false, + }); + + // Get the EnvironmentService from the AppModule + const environmentService = AppModule.moduleRef.get( + EnvironmentService, + { + strict: false, + }, + ); + + // Extract JWT from the request + const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request.req); + + // If there is no token, return an empty schema + if (!token) { + return new GraphQLSchema({}); + } + + // Verify and decode JWT + const decoded = verify( + token, + environmentService.getAccessTokenSecret(), + ); + + // Validate JWT + const { workspace } = await jwtStrategy.validate( + decoded as JwtPayload, + ); + + const conditionalSchema = await service.generateSchema(workspace.id); + + return conditionalSchema; + } catch (error) { + if (error instanceof TokenExpiredError) { + throw new GraphQLError('Unauthenticated', { + extensions: { + code: 'UNAUTHENTICATED', + }, + }); + } + throw error; + } }, - csrfPrevention: false, + resolvers: { JSON: GraphQLJSON }, + plugins: [], }), PrismaModule, HealthModule, @@ -43,4 +98,10 @@ import { TenantModule } from './tenant/tenant.module'; ], providers: [AppService], }) -export class AppModule {} +export class AppModule { + static moduleRef: ModuleRef; + + constructor(private moduleRef: ModuleRef) { + AppModule.moduleRef = this.moduleRef; + } +} diff --git a/server/src/core/workspace/services/workspace.service.spec.ts b/server/src/core/workspace/services/workspace.service.spec.ts index 1d6a760da..85873e0ae 100644 --- a/server/src/core/workspace/services/workspace.service.spec.ts +++ b/server/src/core/workspace/services/workspace.service.spec.ts @@ -8,6 +8,7 @@ import { PersonService } from 'src/core/person/person.service'; import { CompanyService } from 'src/core/company/company.service'; import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service'; import { ViewService } from 'src/core/view/services/view.service'; +import { DataSourceService } from 'src/tenant/metadata/data-source/data-source.service'; import { WorkspaceService } from './workspace.service'; @@ -46,6 +47,10 @@ describe('WorkspaceService', () => { provide: ViewService, useValue: {}, }, + { + provide: DataSourceService, + useValue: {}, + }, ], }).compile(); diff --git a/server/src/core/workspace/services/workspace.service.ts b/server/src/core/workspace/services/workspace.service.ts index 127fabb0e..f4cc63937 100644 --- a/server/src/core/workspace/services/workspace.service.ts +++ b/server/src/core/workspace/services/workspace.service.ts @@ -11,6 +11,7 @@ import { PipelineService } from 'src/core/pipeline/services/pipeline.service'; import { ViewService } from 'src/core/view/services/view.service'; import { PrismaService } from 'src/database/prisma.service'; import { assert } from 'src/utils/assert'; +import { DataSourceService } from 'src/tenant/metadata/data-source/data-source.service'; @Injectable() export class WorkspaceService { @@ -22,6 +23,7 @@ export class WorkspaceService { private readonly pipelineStageService: PipelineStageService, private readonly pipelineProgressService: PipelineProgressService, private readonly viewService: ViewService, + private readonly dataSourceService: DataSourceService, ) {} // Find @@ -63,6 +65,9 @@ export class WorkspaceService { }, }); + // Create workspace schema + await this.dataSourceService.createWorkspaceSchema(workspace.id); + // Create default companies const companies = await this.companyService.createDefaultCompanies({ workspaceId: workspace.id, diff --git a/server/src/core/workspace/workspace.module.ts b/server/src/core/workspace/workspace.module.ts index 62a53d775..474636e81 100644 --- a/server/src/core/workspace/workspace.module.ts +++ b/server/src/core/workspace/workspace.module.ts @@ -5,6 +5,7 @@ import { PipelineModule } from 'src/core/pipeline/pipeline.module'; import { CompanyModule } from 'src/core/company/company.module'; import { PersonModule } from 'src/core/person/person.module'; import { ViewModule } from 'src/core/view/view.module'; +import { DataSourceModule } from 'src/tenant/metadata/data-source/data-source.module'; import { WorkspaceService } from './services/workspace.service'; import { WorkspaceMemberService } from './services/workspace-member.service'; @@ -12,7 +13,13 @@ import { WorkspaceMemberResolver } from './resolvers/workspace-member.resolver'; import { WorkspaceResolver } from './resolvers/workspace.resolver'; @Module({ - imports: [PipelineModule, CompanyModule, PersonModule, ViewModule], + imports: [ + PipelineModule, + CompanyModule, + PersonModule, + ViewModule, + DataSourceModule, + ], providers: [ WorkspaceService, FileUploadService, diff --git a/server/src/guards/jwt.auth.guard.ts b/server/src/guards/jwt.auth.guard.ts index 66324d7ea..dff0a5376 100644 --- a/server/src/guards/jwt.auth.guard.ts +++ b/server/src/guards/jwt.auth.guard.ts @@ -17,9 +17,7 @@ export class JwtAuthGuard extends AuthGuard(['jwt']) { } getRequest(context: ExecutionContext) { - const request = getRequest(context); - - return request; + return getRequest(context); } handleRequest(err: any, user: any, info: any) { diff --git a/server/src/tenant/entity-resolver/entity-resolver.module.ts b/server/src/tenant/entity-resolver/entity-resolver.module.ts new file mode 100644 index 000000000..9f525ffff --- /dev/null +++ b/server/src/tenant/entity-resolver/entity-resolver.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; + +import { DataSourceModule } from 'src/tenant/metadata/data-source/data-source.module'; + +import { EntityResolverService } from './entity-resolver.service'; + +@Module({ + imports: [DataSourceModule], + providers: [EntityResolverService], + exports: [EntityResolverService], +}) +export class EntityResolverModule {} diff --git a/server/src/tenant/entity-resolver/entity-resolver.service.spec.ts b/server/src/tenant/entity-resolver/entity-resolver.service.spec.ts new file mode 100644 index 000000000..a75ba34c3 --- /dev/null +++ b/server/src/tenant/entity-resolver/entity-resolver.service.spec.ts @@ -0,0 +1,27 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { DataSourceService } from 'src/tenant/metadata/data-source/data-source.service'; + +import { EntityResolverService } from './entity-resolver.service'; + +describe('EntityResolverService', () => { + let service: EntityResolverService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + EntityResolverService, + { + provide: DataSourceService, + useValue: {}, + }, + ], + }).compile(); + + service = module.get(EntityResolverService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server/src/tenant/entity-resolver/entity-resolver.service.ts b/server/src/tenant/entity-resolver/entity-resolver.service.ts new file mode 100644 index 000000000..1bd331e0c --- /dev/null +++ b/server/src/tenant/entity-resolver/entity-resolver.service.ts @@ -0,0 +1,111 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; + +import { GraphQLResolveInfo } from 'graphql'; +import graphqlFields from 'graphql-fields'; + +import { DataSourceService } from 'src/tenant/metadata/data-source/data-source.service'; + +import { convertFieldsToGraphQL } from './entity-resolver.util'; + +@Injectable() +export class EntityResolverService { + constructor(private readonly dataSourceService: DataSourceService) {} + + async findAll( + entityName: string, + tableName: string, + workspaceId: string, + info: GraphQLResolveInfo, + fieldAliases: Record, + ) { + const workspaceDataSource = + await this.dataSourceService.connectToWorkspaceDataSource(workspaceId); + + const graphqlQuery = await this.prepareGrapQLQuery( + workspaceId, + info, + fieldAliases, + ); + + /* TODO: This is a temporary solution to set the schema before each raw query. + getSchemaName is used to avoid a call to metadata.data_source table, + this won't work when we won't be able to dynamically recompute the schema name from its workspace_id only (remote schemas for example) + */ + await workspaceDataSource?.query(` + SET search_path TO ${this.dataSourceService.getSchemaName(workspaceId)}; + `); + const graphqlResult = await workspaceDataSource?.query(` + SELECT graphql.resolve($$ + { + ${entityName}Collection: ${tableName}Collection { + ${graphqlQuery} + } + } + $$); + `); + + const result = + graphqlResult?.[0]?.resolve?.data?.[`${entityName}Collection`]; + + if (!result) { + throw new BadRequestException('Malformed result from GraphQL query'); + } + + return result; + } + + async findOne( + entityName: string, + tableName: string, + args: { id: string }, + workspaceId: string, + info: GraphQLResolveInfo, + fieldAliases: Record, + ) { + const workspaceDataSource = + await this.dataSourceService.connectToWorkspaceDataSource(workspaceId); + + const graphqlQuery = await this.prepareGrapQLQuery( + workspaceId, + info, + fieldAliases, + ); + + await workspaceDataSource?.query(` + SET search_path TO ${this.dataSourceService.getSchemaName(workspaceId)}; + `); + const graphqlResult = await workspaceDataSource?.query(` + SELECT graphql.resolve($$ + { + ${entityName}Collection: : ${tableName}Collection(filter: { id: { eq: "${args.id}" } }) { + ${graphqlQuery} + } + } + $$); + `); + + const result = + graphqlResult?.[0]?.resolve?.data?.[`${entityName}Collection`]; + + if (!result) { + return null; + } + + return result; + } + + private async prepareGrapQLQuery( + workspaceId: string, + info: GraphQLResolveInfo, + fieldAliases: Record, + ): Promise { + // Extract requested fields from GraphQL resolve info + const fields = graphqlFields(info); + + await this.dataSourceService.createWorkspaceSchema(workspaceId); + + const graphqlQuery = convertFieldsToGraphQL(fields, fieldAliases); + + return graphqlQuery; + } +} diff --git a/server/src/tenant/entity-resolver/entity-resolver.util.ts b/server/src/tenant/entity-resolver/entity-resolver.util.ts new file mode 100644 index 000000000..530bddca9 --- /dev/null +++ b/server/src/tenant/entity-resolver/entity-resolver.util.ts @@ -0,0 +1,23 @@ +import isEmpty from 'lodash.isempty'; + +export const convertFieldsToGraphQL = ( + fields: any, + fieldAliases: Record, + acc = '', +) => { + for (const [key, value] of Object.entries(fields)) { + if (value && !isEmpty(value)) { + acc += `${key} {\n`; + acc = convertFieldsToGraphQL(value, fieldAliases, acc); + acc += `}\n`; + } else { + if (fieldAliases[key]) { + acc += `${key}: ${fieldAliases[key]}\n`; + } else { + acc += `${key}\n`; + } + } + } + + return acc; +}; diff --git a/server/src/tenant/metadata/data-source-metadata/data-source-metadata.service.ts b/server/src/tenant/metadata/data-source-metadata/data-source-metadata.service.ts index 865c68fcc..e99813058 100644 --- a/server/src/tenant/metadata/data-source-metadata/data-source-metadata.service.ts +++ b/server/src/tenant/metadata/data-source-metadata/data-source-metadata.service.ts @@ -28,10 +28,17 @@ export class DataSourceMetadataService { }); } - getDataSourcesMetadataFromWorkspaceId(workspaceId: string) { + async getDataSourcesMetadataFromWorkspaceId(workspaceId: string) { return this.dataSourceMetadataRepository.find({ where: { workspaceId }, order: { createdAt: 'DESC' }, }); } + + async getLastDataSourceMetadataFromWorkspaceIdOrFail(workspaceId: string) { + return this.dataSourceMetadataRepository.findOneOrFail({ + where: { workspaceId }, + order: { createdAt: 'DESC' }, + }); + } } diff --git a/server/src/tenant/metadata/data-source/data-source.service.ts b/server/src/tenant/metadata/data-source/data-source.service.ts index f0e915364..8941e5aa0 100644 --- a/server/src/tenant/metadata/data-source/data-source.service.ts +++ b/server/src/tenant/metadata/data-source/data-source.service.ts @@ -1,9 +1,4 @@ -import { - Injectable, - NotFoundException, - OnModuleDestroy, - OnModuleInit, -} from '@nestjs/common'; +import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { DataSource, QueryRunner, Table } from 'typeorm'; @@ -37,15 +32,14 @@ export class DataSourceService implements OnModuleInit, OnModuleDestroy { * @param workspaceId * @returns Promise */ - public async createWorkspaceSchema(workspaceId: string): Promise { + public async createWorkspaceSchema(workspaceId: string): Promise { const schemaName = this.getSchemaName(workspaceId); const queryRunner = this.mainDataSource.createQueryRunner(); const schemaAlreadyExists = await queryRunner.hasSchema(schemaName); + if (schemaAlreadyExists) { - throw new Error( - `Schema ${schemaName} already exists for workspace ${workspaceId}`, - ); + return schemaName; } await queryRunner.createSchema(schemaName, true); @@ -56,6 +50,8 @@ export class DataSourceService implements OnModuleInit, OnModuleDestroy { workspaceId, schemaName, ); + + return schemaName; } private async createMigrationTable( @@ -105,20 +101,12 @@ export class DataSourceService implements OnModuleInit, OnModuleDestroy { return cachedDataSource; } - const dataSourcesMetadata = - await this.dataSourceMetadataService.getDataSourcesMetadataFromWorkspaceId( - workspaceId, - ); - - if (dataSourcesMetadata.length === 0) { - throw new NotFoundException( - `We can't find any data source for this workspace id (${workspaceId}).`, - ); - } - // We only want the first one for now, we will handle multiple data sources later with remote datasources. // However, we will need to differentiate the data sources because we won't run migrations on remote data sources for example. - const dataSourceMetadata = dataSourcesMetadata[0]; + const dataSourceMetadata = + await this.dataSourceMetadataService.getLastDataSourceMetadataFromWorkspaceIdOrFail( + workspaceId, + ); const schema = dataSourceMetadata.schema; // Probably not needed as we will ask for the schema name OR store public by default if it's remote @@ -128,11 +116,6 @@ export class DataSourceService implements OnModuleInit, OnModuleDestroy { ); } - const entities = - await this.entitySchemaGeneratorService.getTypeORMEntitiesByDataSourceId( - dataSourceMetadata.id, - ); - const workspaceDataSource = new DataSource({ // TODO: We should use later dataSourceMetadata.type and use a switch case condition to create the right data source url: dataSourceMetadata.url ?? this.environmentService.getPGDatabaseUrl(), @@ -141,15 +124,17 @@ export class DataSourceService implements OnModuleInit, OnModuleDestroy { schema, entities: { TenantMigration, - ...entities, }, }); await workspaceDataSource.initialize(); + // Set search path to workspace schema for raw queries + await workspaceDataSource?.query(`SET search_path TO ${schema};`); + this.dataSources.set(workspaceId, workspaceDataSource); - return this.dataSources.get(workspaceId); + return workspaceDataSource; } /** diff --git a/server/src/tenant/metadata/entity-schema-generator/entity-schema-generator.util.ts b/server/src/tenant/metadata/entity-schema-generator/entity-schema-generator.util.ts index f337fd62c..9c1687377 100644 --- a/server/src/tenant/metadata/entity-schema-generator/entity-schema-generator.util.ts +++ b/server/src/tenant/metadata/entity-schema-generator/entity-schema-generator.util.ts @@ -31,7 +31,6 @@ export const sanitizeColumnName = (columnName: string): string => export const convertFieldTypeToPostgresType = (fieldType: string): string => { switch (fieldType) { case 'text': - return 'text'; case 'url': return 'text'; case 'number': diff --git a/server/src/tenant/metadata/field-metadata/field-metadata.entity.ts b/server/src/tenant/metadata/field-metadata/field-metadata.entity.ts index e025e6143..a110fedb6 100644 --- a/server/src/tenant/metadata/field-metadata/field-metadata.entity.ts +++ b/server/src/tenant/metadata/field-metadata/field-metadata.entity.ts @@ -27,9 +27,15 @@ export class FieldMetadata { @Column({ nullable: false, name: 'target_column_name' }) targetColumnName: string; + @Column('text', { nullable: true, array: true }) + enums: string[]; + @Column({ default: false, name: 'is_custom' }) isCustom: boolean; + @Column({ nullable: true, default: true, name: 'is_nullable' }) + isNullable: boolean; + @Column({ nullable: false, name: 'workspace_id' }) workspaceId: string; diff --git a/server/src/tenant/metadata/metadata.controller.ts b/server/src/tenant/metadata/metadata.controller.ts index c2da837a7..082630a34 100644 --- a/server/src/tenant/metadata/metadata.controller.ts +++ b/server/src/tenant/metadata/metadata.controller.ts @@ -41,6 +41,8 @@ export class MetadataController { entities.push(...dataSourceEntities); } + this.dataSourceService.createWorkspaceSchema(workspace.id); + await this.migrationGenerator.executeMigrationFromPendingMigrations( workspace.id, ); diff --git a/server/src/tenant/metadata/migration-generator/migration-generator.service.ts b/server/src/tenant/metadata/migration-generator/migration-generator.service.ts index 00f60b320..b37e63874 100644 --- a/server/src/tenant/metadata/migration-generator/migration-generator.service.ts +++ b/server/src/tenant/metadata/migration-generator/migration-generator.service.ts @@ -58,11 +58,6 @@ export class MigrationGeneratorService { ); }); - await queryRunner.release(); - // We want to destroy all connections to the workspace data source and invalidate the cache - // so that the next request will create a new connection and get the latest entities - await this.dataSourceService.disconnectFromWorkspaceDataSource(workspaceId); - return flattenedPendingMigrations; } diff --git a/server/src/tenant/metadata/migrations/1695717691800-alter-field-metadata-table.ts b/server/src/tenant/metadata/migrations/1695717691800-alter-field-metadata-table.ts new file mode 100644 index 000000000..4483db8df --- /dev/null +++ b/server/src/tenant/metadata/migrations/1695717691800-alter-field-metadata-table.ts @@ -0,0 +1,61 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterFieldMetadataTable1695717691800 + implements MigrationInterface +{ + name = 'AlterFieldMetadataTable1695717691800'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "metadata"."field_metadata" ADD "enums" text array`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."field_metadata" ADD "is_nullable" boolean DEFAULT true`, + ); + await queryRunner.query( + `ALTER TYPE "metadata"."data_source_metadata_type_enum" RENAME TO "data_source_metadata_type_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "metadata"."data_source_metadata_type_enum" AS ENUM('postgres')`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" TYPE "metadata"."data_source_metadata_type_enum" USING "type"::"text"::"metadata"."data_source_metadata_type_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" SET DEFAULT 'postgres'`, + ); + await queryRunner.query( + `DROP TYPE "metadata"."data_source_metadata_type_enum_old"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "metadata"."data_source_metadata_type_enum_old" AS ENUM('postgres', 'mysql')`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" TYPE "metadata"."data_source_metadata_type_enum_old" USING "type"::"text"::"metadata"."data_source_metadata_type_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" SET DEFAULT 'postgres'`, + ); + await queryRunner.query( + `DROP TYPE "metadata"."data_source_metadata_type_enum"`, + ); + await queryRunner.query( + `ALTER TYPE "metadata"."data_source_metadata_type_enum_old" RENAME TO "data_source_metadata_type_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."field_metadata" DROP COLUMN "is_nullable"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."field_metadata" DROP COLUMN "enums"`, + ); + } +} diff --git a/server/src/tenant/metadata/tenant-migration/tenant-migration.service.spec.ts b/server/src/tenant/metadata/tenant-migration/tenant-migration.service.spec.ts index a356ce821..4816110ba 100644 --- a/server/src/tenant/metadata/tenant-migration/tenant-migration.service.spec.ts +++ b/server/src/tenant/metadata/tenant-migration/tenant-migration.service.spec.ts @@ -1,7 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { TenantMigrationService } from './tenant-migration.service'; + import { DataSourceService } from 'src/tenant/metadata/data-source/data-source.service'; +import { TenantMigrationService } from './tenant-migration.service'; + describe('TenantMigrationService', () => { let service: TenantMigrationService; diff --git a/server/src/tenant/schema-generation/graphql-types/connection.graphql-type.ts b/server/src/tenant/schema-generation/graphql-types/connection.graphql-type.ts new file mode 100644 index 000000000..2db8a18d9 --- /dev/null +++ b/server/src/tenant/schema-generation/graphql-types/connection.graphql-type.ts @@ -0,0 +1,24 @@ +import { GraphQLList, GraphQLNonNull, GraphQLObjectType } from 'graphql'; + +import { PageInfoType } from './page-info.graphql-type'; + +/** + * Generate a GraphQL connection type based on the EdgeType. + * @param EdgeType Edge type to be used in the connection. + * @returns GraphQL connection type. + */ +export const generateConnectionType = ( + EdgeType: T, +): GraphQLObjectType => { + return new GraphQLObjectType({ + name: `${EdgeType.name.slice(0, -4)}Connection`, // Removing 'Edge' from the name + fields: { + edges: { + type: new GraphQLList(EdgeType), + }, + pageInfo: { + type: new GraphQLNonNull(PageInfoType), + }, + }, + }); +}; diff --git a/server/src/tenant/schema-generation/graphql-types/edge.graphql-type.ts b/server/src/tenant/schema-generation/graphql-types/edge.graphql-type.ts new file mode 100644 index 000000000..d344a48af --- /dev/null +++ b/server/src/tenant/schema-generation/graphql-types/edge.graphql-type.ts @@ -0,0 +1,22 @@ +import { GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql'; + +/** + * Generate a GraphQL edge type based on the ObjectType. + * @param ObjectType Object type to be used in the Edge. + * @returns GraphQL edge type. + */ +export const generateEdgeType = ( + ObjectType: T, +): GraphQLObjectType => { + return new GraphQLObjectType({ + name: `${ObjectType.name}Edge`, + fields: { + node: { + type: ObjectType, + }, + cursor: { + type: new GraphQLNonNull(GraphQLString), + }, + }, + }); +}; diff --git a/server/src/tenant/schema-generation/graphql-types/object.graphql-type.ts b/server/src/tenant/schema-generation/graphql-types/object.graphql-type.ts new file mode 100644 index 000000000..0b9090fc1 --- /dev/null +++ b/server/src/tenant/schema-generation/graphql-types/object.graphql-type.ts @@ -0,0 +1,99 @@ +import { + GraphQLBoolean, + GraphQLEnumType, + GraphQLID, + GraphQLInt, + GraphQLNonNull, + GraphQLObjectType, + GraphQLString, +} from 'graphql'; + +import { FieldMetadata } from 'src/tenant/metadata/field-metadata/field-metadata.entity'; +import { ObjectMetadata } from 'src/tenant/metadata/object-metadata/object-metadata.entity'; +import { pascalCase } from 'src/utils/pascal-case'; + +/** + * Map the column type from field-metadata to its corresponding GraphQL type. + * @param columnType Type of the column in the database. + */ +const mapColumnTypeToGraphQLType = (column: FieldMetadata): any => { + switch (column.type) { + case 'uuid': + return GraphQLID; + case 'text': + case 'url': + case 'date': + return GraphQLString; + case 'boolean': + return GraphQLBoolean; + case 'number': + return GraphQLInt; + case 'enum': { + if (column.enums && column.enums.length > 0) { + const enumName = `${pascalCase(column.objectId)}${pascalCase( + column.displayName, + )}Enum`; + + return new GraphQLEnumType({ + name: enumName, + values: Object.fromEntries( + column.enums.map((value) => [value, { value }]), + ), + }); + } + } + default: + return GraphQLString; + } +}; + +/** + * Generate a GraphQL object type based on the name and columns. + * @param name Name for the GraphQL object. + * @param columns Array of FieldMetadata columns. + */ +export const generateObjectType = ( + name: string, + columns: FieldMetadata[], +): GraphQLObjectType => { + const fields: Record = { + // Default fields + id: { type: new GraphQLNonNull(GraphQLID) }, + createdAt: { type: new GraphQLNonNull(GraphQLString) }, + updatedAt: { type: new GraphQLNonNull(GraphQLString) }, + }; + + columns.forEach((column) => { + let graphqlType = mapColumnTypeToGraphQLType(column); + + if (!column.isNullable) { + graphqlType = new GraphQLNonNull(graphqlType); + } + + fields[column.displayName] = { + type: graphqlType, + description: column.targetColumnName, + }; + }); + + return new GraphQLObjectType({ + name: pascalCase(name), + fields, + }); +}; + +/** + * Generate multiple GraphQL object types based on an array of object metadata. + * @param objectMetadata Array of ObjectMetadata. + */ +export const generateObjectTypes = (objectMetadata: ObjectMetadata[]) => { + const objectTypes: Record = {}; + + for (const object of objectMetadata) { + const ObjectType = generateObjectType(object.displayName, object.fields); + + objectTypes[object.displayName] = ObjectType; + } + + return objectTypes; +}; diff --git a/server/src/tenant/schema-generation/graphql-types/page-info.graphql-type.ts b/server/src/tenant/schema-generation/graphql-types/page-info.graphql-type.ts new file mode 100644 index 000000000..74a1a0d98 --- /dev/null +++ b/server/src/tenant/schema-generation/graphql-types/page-info.graphql-type.ts @@ -0,0 +1,19 @@ +import { + GraphQLBoolean, + GraphQLNonNull, + GraphQLObjectType, + GraphQLString, +} from 'graphql'; + +/** + * GraphQL PageInfo type. + */ +export const PageInfoType = new GraphQLObjectType({ + name: 'PageInfo', + fields: { + startCursor: { type: GraphQLString }, + endCursor: { type: GraphQLString }, + hasNextPage: { type: new GraphQLNonNull(GraphQLBoolean) }, + hasPreviousPage: { type: new GraphQLNonNull(GraphQLBoolean) }, + }, +}); diff --git a/server/src/tenant/schema-generation/schema-generation.module.ts b/server/src/tenant/schema-generation/schema-generation.module.ts new file mode 100644 index 000000000..7340f3bc2 --- /dev/null +++ b/server/src/tenant/schema-generation/schema-generation.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; + +import { EntityResolverModule } from 'src/tenant/entity-resolver/entity-resolver.module'; +import { JwtAuthGuard } from 'src/guards/jwt.auth.guard'; +import { DataSourceMetadataModule } from 'src/tenant/metadata/data-source-metadata/data-source-metadata.module'; +import { EntitySchemaGeneratorModule } from 'src/tenant/metadata/entity-schema-generator/entity-schema-generator.module'; +import { ObjectMetadataModule } from 'src/tenant/metadata/object-metadata/object-metadata.module'; + +import { SchemaGenerationService } from './schema-generation.service'; + +@Module({ + imports: [ + EntityResolverModule, + DataSourceMetadataModule, + EntitySchemaGeneratorModule, + ObjectMetadataModule, + ], + providers: [SchemaGenerationService, JwtAuthGuard], + exports: [SchemaGenerationService], +}) +export class SchemaGenerationModule {} diff --git a/server/src/tenant/schema-generation/schema-generation.service.spec.ts b/server/src/tenant/schema-generation/schema-generation.service.spec.ts new file mode 100644 index 000000000..bb309e92e --- /dev/null +++ b/server/src/tenant/schema-generation/schema-generation.service.spec.ts @@ -0,0 +1,37 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { DataSourceMetadataService } from 'src/tenant/metadata/data-source-metadata/data-source-metadata.service'; +import { ObjectMetadataService } from 'src/tenant/metadata/object-metadata/object-metadata.service'; +import { EntityResolverService } from 'src/tenant/entity-resolver/entity-resolver.service'; + +import { SchemaGenerationService } from './schema-generation.service'; + +describe('SchemaGenerationService', () => { + let service: SchemaGenerationService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + SchemaGenerationService, + { + provide: DataSourceMetadataService, + useValue: {}, + }, + { + provide: ObjectMetadataService, + useValue: {}, + }, + { + provide: EntityResolverService, + useValue: {}, + }, + ], + }).compile(); + + service = module.get(SchemaGenerationService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server/src/tenant/schema-generation/schema-generation.service.ts b/server/src/tenant/schema-generation/schema-generation.service.ts new file mode 100644 index 000000000..1f06794e6 --- /dev/null +++ b/server/src/tenant/schema-generation/schema-generation.service.ts @@ -0,0 +1,150 @@ +import { Injectable, InternalServerErrorException } from '@nestjs/common'; + +import { + GraphQLID, + GraphQLNonNull, + GraphQLObjectType, + GraphQLResolveInfo, + GraphQLSchema, +} from 'graphql'; + +import { EntityResolverService } from 'src/tenant/entity-resolver/entity-resolver.service'; +import { DataSourceMetadataService } from 'src/tenant/metadata/data-source-metadata/data-source-metadata.service'; +import { pascalCase } from 'src/utils/pascal-case'; +import { ObjectMetadataService } from 'src/tenant/metadata/object-metadata/object-metadata.service'; +import { ObjectMetadata } from 'src/tenant/metadata/object-metadata/object-metadata.entity'; + +import { generateEdgeType } from './graphql-types/edge.graphql-type'; +import { generateConnectionType } from './graphql-types/connection.graphql-type'; +import { generateObjectTypes } from './graphql-types/object.graphql-type'; + +@Injectable() +export class SchemaGenerationService { + constructor( + private readonly dataSourceMetadataService: DataSourceMetadataService, + private readonly objectMetadataService: ObjectMetadataService, + private readonly entityResolverService: EntityResolverService, + ) {} + + private generateQueryFieldForEntity( + entityName: string, + tableName: string, + ObjectType: GraphQLObjectType, + objectDefinition: ObjectMetadata, + workspaceId: string, + ) { + const fieldAliases = + objectDefinition?.fields.reduce( + (acc, field) => ({ + ...acc, + [field.displayName]: field.targetColumnName, + }), + {}, + ) || {}; + + const EdgeType = generateEdgeType(ObjectType); + const ConnectionType = generateConnectionType(EdgeType); + + return { + [`findAll${pascalCase(entityName)}`]: { + type: ConnectionType, + resolve: async (root, args, context, info: GraphQLResolveInfo) => { + return this.entityResolverService.findAll( + entityName, + tableName, + workspaceId, + info, + fieldAliases, + ); + }, + }, + [`findOne${pascalCase(entityName)}`]: { + type: ObjectType, + args: { + id: { type: new GraphQLNonNull(GraphQLID) }, + }, + resolve: (root, args, context, info) => { + return this.entityResolverService.findOne( + entityName, + tableName, + args, + workspaceId, + info, + fieldAliases, + ); + }, + }, + }; + } + + private generateQueryType( + ObjectTypes: Record, + objectMetadata: ObjectMetadata[], + workspaceId: string, + ): GraphQLObjectType { + const fields: any = {}; + + for (const [entityName, ObjectType] of Object.entries(ObjectTypes)) { + const objectDefinition = objectMetadata.find( + (object) => object.displayName === entityName, + ); + const tableName = objectDefinition?.targetTableName ?? ''; + + if (!objectDefinition) { + throw new InternalServerErrorException('Object definition not found'); + } + + Object.assign( + fields, + this.generateQueryFieldForEntity( + entityName, + tableName, + ObjectType, + objectDefinition, + workspaceId, + ), + ); + } + + return new GraphQLObjectType({ + name: 'Query', + fields, + }); + } + + async generateSchema( + workspaceId: string | undefined, + ): Promise { + if (!workspaceId) { + return new GraphQLSchema({}); + } + + const dataSourcesMetadata = + await this.dataSourceMetadataService.getDataSourcesMetadataFromWorkspaceId( + workspaceId, + ); + + // Can'f find any data sources for this workspace + if (!dataSourcesMetadata || dataSourcesMetadata.length === 0) { + return new GraphQLSchema({}); + } + + const dataSourceMetadata = dataSourcesMetadata[0]; + + const objectMetadata = + await this.objectMetadataService.getObjectMetadataFromDataSourceId( + dataSourceMetadata.id, + ); + + const ObjectTypes = generateObjectTypes(objectMetadata); + const QueryType = this.generateQueryType( + ObjectTypes, + objectMetadata, + workspaceId, + ); + + return new GraphQLSchema({ + query: QueryType, + }); + } +} diff --git a/server/src/tenant/tenant.module.ts b/server/src/tenant/tenant.module.ts index f31d9daf3..b5a7b8b63 100644 --- a/server/src/tenant/tenant.module.ts +++ b/server/src/tenant/tenant.module.ts @@ -2,8 +2,9 @@ import { Module } from '@nestjs/common'; import { MetadataModule } from './metadata/metadata.module'; import { UniversalModule } from './universal/universal.module'; +import { SchemaGenerationModule } from './schema-generation/schema-generation.module'; @Module({ - imports: [MetadataModule, UniversalModule], + imports: [MetadataModule, UniversalModule, SchemaGenerationModule], }) export class TenantModule {} diff --git a/server/src/utils/pagination/paginated.ts b/server/src/utils/pagination/paginated.ts index ff11f4779..5124ad99a 100644 --- a/server/src/utils/pagination/paginated.ts +++ b/server/src/utils/pagination/paginated.ts @@ -1,5 +1,5 @@ import { Type } from '@nestjs/common'; -import { ArgsType, Directive, Field, ObjectType } from '@nestjs/graphql'; +import { ArgsType, Field, ObjectType } from '@nestjs/graphql'; import { IsNumber, IsOptional, IsString } from 'class-validator'; @@ -50,7 +50,6 @@ export function Paginated(classRef: Type): Type> { public cursor!: ConnectionCursor; @Field(() => classRef, { nullable: true }) - @Directive(`@cacheControl(inheritMaxAge: true)`) public node!: T; } @@ -59,11 +58,9 @@ export function Paginated(classRef: Type): Type> { public name = `${classRef.name}Connection`; @Field(() => [Edge], { nullable: true }) - @Directive(`@cacheControl(inheritMaxAge: true)`) public edges!: IEdge[]; @Field(() => PageInfo, { nullable: true }) - @Directive(`@cacheControl(inheritMaxAge: true)`) public pageInfo!: IPageInfo; @Field() diff --git a/server/src/utils/pascal-case.ts b/server/src/utils/pascal-case.ts new file mode 100644 index 000000000..92f5b67ab --- /dev/null +++ b/server/src/utils/pascal-case.ts @@ -0,0 +1,32 @@ +import isObject from 'lodash.isobject'; +import lodashCamelCase from 'lodash.camelcase'; +import { PascalCase, PascalCasedPropertiesDeep } from 'type-fest'; + +export const capitalizeFirstLetter = (str: string) => { + return str.charAt(0).toUpperCase() + str.slice(1); +}; + +export const pascalCase = (text: T) => + capitalizeFirstLetter( + lodashCamelCase(text as unknown as string), + ) as PascalCase; + +export const pascalCaseDeep = (value: T): PascalCasedPropertiesDeep => { + // Check if it's an array + if (Array.isArray(value)) { + return value.map(pascalCaseDeep) as PascalCasedPropertiesDeep; + } + + // Check if it's an object + if (isObject(value)) { + const result: Record = {}; + + for (const key in value) { + result[pascalCase(key)] = pascalCaseDeep(value[key]); + } + + return result as PascalCasedPropertiesDeep; + } + + return value as PascalCasedPropertiesDeep; +}; diff --git a/server/test/company.e2e-spec.ts b/server/test/company.e2e-spec.ts index 988cca0b8..cb6ea18f5 100644 --- a/server/test/company.e2e-spec.ts +++ b/server/test/company.e2e-spec.ts @@ -160,8 +160,7 @@ describe('CompanyResolver (e2e)', () => { const error = errors?.[0]; expect(error).toBeDefined(); - expect(error.extensions.code).toBe('FORBIDDEN'); - expect(error.extensions.originalError.statusCode).toBe(403); + expect(error.message).toBe('Forbidden resource'); }); }); @@ -235,8 +234,7 @@ describe('CompanyResolver (e2e)', () => { const error = errors?.[0]; expect(error).toBeDefined(); - expect(error.extensions.code).toBe('FORBIDDEN'); - expect(error.extensions.originalError.statusCode).toBe(403); + expect(error.message).toBe('Forbidden resource'); }); }); @@ -291,8 +289,7 @@ describe('CompanyResolver (e2e)', () => { const error = errors?.[0]; expect(error).toBeDefined(); - expect(error.extensions.code).toBe('FORBIDDEN'); - expect(error.extensions.originalError.statusCode).toBe(403); + expect(error.message).toBe('Forbidden resource'); }); }); }); diff --git a/server/yarn.lock b/server/yarn.lock index d3afc2e30..8e6b937f8 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -1282,6 +1282,21 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@envelop/core@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@envelop/core/-/core-4.0.1.tgz#0f3eb33a396e9f4527d7fa8079055ec151eb73aa" + integrity sha512-uBLI7ql3hZopz7vMi9UDAb9HWzKw4STKiqg4QT+lb+tu5ZNaeuJ4fom2rrmgITz38B85QZOhZrGyVrlJXxfDzw== + dependencies: + "@envelop/types" "4.0.1" + tslib "^2.5.0" + +"@envelop/types@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@envelop/types/-/types-4.0.1.tgz#145690d8266a003cdb06dd58fa1236e3c80050a9" + integrity sha512-ULo27/doEsP7uUhm2iTnElx13qTO6I5FKvmLoX41cpfuw8x6e0NUFknoqhEsLzAbgz8xVS5mjwcxGCXh4lDYzg== + dependencies: + tslib "^2.5.0" + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" @@ -1314,6 +1329,17 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz" integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== +"@graphql-tools/executor@^1.0.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/executor/-/executor-1.2.0.tgz#6c45f4add765769d9820c4c4405b76957ba39c79" + integrity sha512-SKlIcMA71Dha5JnEWlw4XxcaJ+YupuXg0QCZgl2TOLFz4SkGCwU/geAsJvUJFwK2RbVLpQv/UMq67lOaBuwDtg== + dependencies: + "@graphql-tools/utils" "^10.0.0" + "@graphql-typed-document-node/core" "3.2.0" + "@repeaterjs/repeater" "^3.0.4" + tslib "^2.4.0" + value-or-promise "^1.0.12" + "@graphql-tools/merge@8.3.1": version "8.3.1" resolved "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.3.1.tgz" @@ -1348,7 +1374,7 @@ fast-json-stable-stringify "^2.1.0" tslib "^2.4.0" -"@graphql-tools/schema@10.0.0": +"@graphql-tools/schema@10.0.0", "@graphql-tools/schema@^10.0.0": version "10.0.0" resolved "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.0.tgz" integrity sha512-kf3qOXMFcMs2f/S8Y3A8fm/2w+GaHAkfr3Gnhh2LOug/JgpY/ywgFVxO3jOeSpSEdoYcDKLcXVjMigNbY4AdQg== @@ -1378,10 +1404,10 @@ tslib "^2.4.0" value-or-promise "^1.0.12" -"@graphql-tools/utils@10.0.0": - version "10.0.0" - resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.0.tgz" - integrity sha512-ndBPc6zgR+eGU/jHLpuojrs61kYN3Z89JyMLwK3GCRkPv4EQn9EOr1UWqF1JO0iM+/jAVHY0mvfUxyrFFN9DUQ== +"@graphql-tools/utils@10.0.1", "@graphql-tools/utils@^10.0.0": + version "10.0.1" + resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.1.tgz" + integrity sha512-i1FozbDGHgdsFA47V/JvQZ0FE8NAy0Eiz7HGCJO2MkNdZAKNnwei66gOq0JWYVFztwpwbVQ09GkKhq7Kjcq5Cw== dependencies: "@graphql-typed-document-node/core" "^3.1.1" tslib "^2.4.0" @@ -1393,14 +1419,6 @@ dependencies: tslib "^2.4.0" -"@graphql-tools/utils@^10.0.0": - version "10.0.1" - resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.1.tgz" - integrity sha512-i1FozbDGHgdsFA47V/JvQZ0FE8NAy0Eiz7HGCJO2MkNdZAKNnwei66gOq0JWYVFztwpwbVQ09GkKhq7Kjcq5Cw== - dependencies: - "@graphql-typed-document-node/core" "^3.1.1" - tslib "^2.4.0" - "@graphql-tools/utils@^9.2.1": version "9.2.1" resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz" @@ -1409,11 +1427,41 @@ "@graphql-typed-document-node/core" "^3.1.1" tslib "^2.4.0" -"@graphql-typed-document-node/core@^3.1.1": +"@graphql-typed-document-node/core@3.2.0", "@graphql-typed-document-node/core@^3.1.1": version "3.2.0" resolved "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz" integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== +"@graphql-yoga/logger@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@graphql-yoga/logger/-/logger-1.0.0.tgz#0fba12edd8c4b0b9c0f0a74b0d101f1646c3780e" + integrity sha512-JYoxwnPggH2BfO+dWlWZkDeFhyFZqaTRGLvFhy+Pjp2UxitEW6nDrw+pEDw/K9tJwMjIFMmTT9VfTqrnESmBHg== + dependencies: + tslib "^2.5.2" + +"@graphql-yoga/nestjs@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@graphql-yoga/nestjs/-/nestjs-2.1.0.tgz#e429906f84a391a9d7bcfd4661987425896938b2" + integrity sha512-LaKdPJrLSG8QLNm8Xp7f6xWO8tGLfHZyPrNKNpqClvk4X9DByraeMHArwEyP1YDScFbsIEKkLxTaM7JoLkLkpg== + +"@graphql-yoga/subscription@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@graphql-yoga/subscription/-/subscription-4.0.0.tgz#2bf5844ce8aeff46332650ad642218250201dcc5" + integrity sha512-0qsN/BPPZNMoC2CZ8i+P6PgiJyHh1H35aKDt37qARBDaIOKDQuvEOq7+4txUKElcmXi7DYFo109FkhSQoEajrg== + dependencies: + "@graphql-yoga/typed-event-target" "^2.0.0" + "@repeaterjs/repeater" "^3.0.4" + "@whatwg-node/events" "^0.1.0" + tslib "^2.5.2" + +"@graphql-yoga/typed-event-target@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@graphql-yoga/typed-event-target/-/typed-event-target-2.0.0.tgz#41809fc8c101c27c61a5427d74e0d0ce824044db" + integrity sha512-oA/VGxGmaSDym1glOHrltw43qZsFwLLjBwvh57B79UKX/vo3+UQcRgOyE44c5RP7DCYjkrC2tuArZmb6jCzysw== + dependencies: + "@repeaterjs/repeater" "^3.0.4" + tslib "^2.5.2" + "@humanwhocodes/config-array@^0.11.10": version "0.11.10" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz" @@ -1794,23 +1842,23 @@ path-to-regexp "3.2.0" tslib "2.5.3" -"@nestjs/graphql@^11.0.6": - version "11.0.6" - resolved "https://registry.npmjs.org/@nestjs/graphql/-/graphql-11.0.6.tgz" - integrity sha512-EGE4fhLHrQLPAAgER+AwIa034IM9FNJto3xg49cZmI6c7s1yo6pLw/JULCn2jE2hUDbg5Q3IORZMMG8EUiUyMw== +"@nestjs/graphql@^12.0.8": + version "12.0.8" + resolved "https://registry.yarnpkg.com/@nestjs/graphql/-/graphql-12.0.8.tgz#15143b76dfb5fa4dc880d68a1bf2f7159ea077b6" + integrity sha512-odYDHUdLOMCxjC5VSEmF/23r8cY40N1KCwBkWaCmI1IF76Ffe3srWRDv8HS9tcai9eSmOeSWuyLBEg2OSru0cQ== dependencies: "@graphql-tools/merge" "9.0.0" "@graphql-tools/schema" "10.0.0" - "@graphql-tools/utils" "10.0.0" - "@nestjs/mapped-types" "1.2.2" + "@graphql-tools/utils" "10.0.1" + "@nestjs/mapped-types" "2.0.2" chokidar "3.5.3" fast-glob "3.2.12" graphql-tag "2.12.6" - graphql-ws "5.13.1" + graphql-ws "5.14.0" lodash "4.17.21" normalize-path "3.0.0" subscriptions-transport-ws "0.11.0" - tslib "2.5.2" + tslib "2.6.0" uuid "9.0.0" ws "8.13.0" @@ -1822,10 +1870,10 @@ "@types/jsonwebtoken" "9.0.1" jsonwebtoken "9.0.0" -"@nestjs/mapped-types@1.2.2": - version "1.2.2" - resolved "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.2.2.tgz" - integrity sha512-3dHxLXs3M0GPiriAcCFFJQHoDFUuzTD5w6JDhE7TyfT89YKpe6tcCCIqOZWdXmt9AZjjK30RkHRSFF+QEnWFQg== +"@nestjs/mapped-types@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz#c8a090a8d22145b85ed977414c158534210f2e4f" + integrity sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg== "@nestjs/passport@^9.0.3": version "9.0.3" @@ -2158,6 +2206,11 @@ resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@repeaterjs/repeater@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.4.tgz#a04d63f4d1bf5540a41b01a921c9a7fddc3bd1ca" + integrity sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA== + "@sentry-internal/tracing@7.66.0": version "7.66.0" resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.66.0.tgz#45ea607917d55a5bcaa3229341387ff6ed9b3a2b" @@ -2834,6 +2887,13 @@ dependencies: "@types/node" "*" +"@types/graphql-fields@^1.3.6": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@types/graphql-fields/-/graphql-fields-1.3.6.tgz#fc73326082f142ac63394f8167d272c21604f74e" + integrity sha512-tLiskj9g5ftYgiVxq8VlvBEYDRow23IwY/LMnhljXfWvBhi+LIo9YEMa6AACjgIa3rG+edS9xh3v6Rja2UPbDg== + dependencies: + graphql "*" + "@types/graphql-upload@^8.0.12": version "8.0.12" resolved "https://registry.yarnpkg.com/@types/graphql-upload/-/graphql-upload-8.0.12.tgz#224738b8885bad8d50fb690b67bbe10bbcdef032" @@ -2938,6 +2998,13 @@ dependencies: "@types/lodash" "*" +"@types/lodash.isempty@^4.4.7": + version "4.4.7" + resolved "https://registry.yarnpkg.com/@types/lodash.isempty/-/lodash.isempty-4.4.7.tgz#b1015d1adba560daf560ad04f294848939e75317" + integrity sha512-YOzlpoIn9jrfHzjIukKnu9Le3tmi+0PhUdOt2rMpJW/4J6jX7s0HeBatXdh9QckLga8qt4EKBxVIEqtEq6pzLg== + dependencies: + "@types/lodash" "*" + "@types/lodash.isobject@^3.0.7": version "3.0.7" resolved "https://registry.yarnpkg.com/@types/lodash.isobject/-/lodash.isobject-3.0.7.tgz#8a37beea56512f0ae86f8d48ea01e2ea9b79c185" @@ -3384,6 +3451,38 @@ "@webassemblyjs/ast" "1.11.6" "@xtuc/long" "4.2.2" +"@whatwg-node/events@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@whatwg-node/events/-/events-0.1.1.tgz#0ca718508249419587e130da26d40e29d99b5356" + integrity sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w== + +"@whatwg-node/fetch@^0.9.10", "@whatwg-node/fetch@^0.9.7": + version "0.9.12" + resolved "https://registry.yarnpkg.com/@whatwg-node/fetch/-/fetch-0.9.12.tgz#3b20ac8f286a2196003976b6f1452c2c513fad00" + integrity sha512-zNUkPJNfM1v9Jhy3Vmi2a7lQxaNIDTSiAb1NKO5eMsSdo05XoddBEj/CHj1xu4IOMU68VerDvuBVwzPjxBl12g== + dependencies: + "@whatwg-node/node-fetch" "^0.4.17" + urlpattern-polyfill "^9.0.0" + +"@whatwg-node/node-fetch@^0.4.17": + version "0.4.18" + resolved "https://registry.yarnpkg.com/@whatwg-node/node-fetch/-/node-fetch-0.4.18.tgz#ff15beb1ecd03eb7428286435674c8bd583f159a" + integrity sha512-zdey6buMKCqDVDq+tMqcjopO75Fb6iLqWo+g6cWwN5kiwctEHtVcbws2lJUFhCbo+TLZeH6bMDRUXEo5bkPtcQ== + dependencies: + "@whatwg-node/events" "^0.1.0" + busboy "^1.6.0" + fast-querystring "^1.1.1" + fast-url-parser "^1.1.3" + tslib "^2.3.1" + +"@whatwg-node/server@^0.9.1": + version "0.9.14" + resolved "https://registry.yarnpkg.com/@whatwg-node/server/-/server-0.9.14.tgz#54c47b50c370e46fabdcbbed06be3d84686b8a91" + integrity sha512-I8TT0NoCP+xThLBuGlU6dgq5wpExkphNMo2geZwQW0vAmEPtc3MNMZMIYqg5GyNmpv5Nf7fnxb8tVOIHbDvuDA== + dependencies: + "@whatwg-node/fetch" "^0.9.10" + tslib "^2.3.1" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" @@ -3394,6 +3493,11 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -3794,6 +3898,11 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -4061,7 +4170,7 @@ busboy@^0.3.1: dependencies: dicer "0.3.0" -busboy@^1.0.0: +busboy@^1.0.0, busboy@^1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz" integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== @@ -4176,7 +4285,7 @@ chrome-trace-event@^1.0.2: resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -ci-info@3.8.0, ci-info@^3.2.0: +ci-info@3.8.0, ci-info@^3.2.0, ci-info@^3.7.0: version "3.8.0" resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== @@ -4684,6 +4793,11 @@ dotenv@^16.0.3: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== +dset@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.2.tgz#89c436ca6450398396dc6538ea00abc0c54cd45a" + integrity sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q== + ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" @@ -5130,6 +5244,11 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" +fast-decode-uri-component@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" + integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -5161,11 +5280,25 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-querystring@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fast-querystring/-/fast-querystring-1.1.2.tgz#a6d24937b4fc6f791b4ee31dcb6f53aeafb89f53" + integrity sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg== + dependencies: + fast-decode-uri-component "^1.0.1" + fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== +fast-url-parser@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" + integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== + dependencies: + punycode "^1.3.2" + fast-write-atomic@0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz" @@ -5274,6 +5407,13 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" @@ -5398,6 +5538,16 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-jetpack@5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-5.1.0.tgz" @@ -5614,7 +5764,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5629,6 +5779,11 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +graphql-fields@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/graphql-fields/-/graphql-fields-2.0.3.tgz#5e68dff7afbb202be4f4f40623e983b22c96ab8f" + integrity sha512-x3VE5lUcR4XCOxPIqaO4CE+bTK8u6gVouOdpQX9+EKHr+scqtK5Pp/l8nIGqIpN1TUlkKE6jDCCycm/WtLRAwA== + graphql-parse-resolve-info@^4.13.0: version "4.13.0" resolved "https://registry.npmjs.org/graphql-parse-resolve-info/-/graphql-parse-resolve-info-4.13.0.tgz" @@ -5659,15 +5814,32 @@ graphql-upload@^13.0.0: http-errors "^1.8.1" object-path "^0.11.8" -graphql-ws@5.13.1: - version "5.13.1" - resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.13.1.tgz" - integrity sha512-eiX7ES/ZQr0q7hSM5UBOEIFfaAUmAY9/CSDyAnsETuybByU7l/v46drRg9DQoTvVABEHp3QnrvwgTRMhqy7zxQ== +graphql-ws@5.14.0: + version "5.14.0" + resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.14.0.tgz#766f249f3974fc2c48fae0d1fb20c2c4c79cd591" + integrity sha512-itrUTQZP/TgswR4GSSYuwWUzrE/w5GhbwM2GX3ic2U7aw33jgEsayfIlvaj7/GcIvZgNMzsPTrE5hqPuFUiE5g== -"graphql@0.13.1 - 16", graphql@^16.7.1: - version "16.7.1" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.7.1.tgz#11475b74a7bff2aefd4691df52a0eca0abd9b642" - integrity sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg== +graphql-yoga@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/graphql-yoga/-/graphql-yoga-4.0.4.tgz#00f388b6c38560ad4e5662c66cd2671befb58229" + integrity sha512-MvCLhFecYNIKuxAZisPjpIL9lxRYbpgPSNKENDO/8CV3oiFlsLJHZb5dp2sVAeLafXHeZ9TgkijLthUBc1+Jag== + dependencies: + "@envelop/core" "^4.0.0" + "@graphql-tools/executor" "^1.0.0" + "@graphql-tools/schema" "^10.0.0" + "@graphql-tools/utils" "^10.0.0" + "@graphql-yoga/logger" "^1.0.0" + "@graphql-yoga/subscription" "^4.0.0" + "@whatwg-node/fetch" "^0.9.7" + "@whatwg-node/server" "^0.9.1" + dset "^3.1.1" + lru-cache "^10.0.0" + tslib "^2.5.2" + +graphql@*, "graphql@0.13.1 - 16", graphql@16.8.0, graphql@^16.8.0: + version "16.8.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.0.tgz#374478b7f27b2dc6153c8f42c1b80157f79d79d4" + integrity sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg== has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" @@ -6613,6 +6785,13 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stable-stringify@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz#e06f23128e0bbe342dc996ed5a19e28b57b580e0" + integrity sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g== + dependencies: + jsonify "^0.0.1" + json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -6639,6 +6818,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== + jsonwebtoken@9.0.0, jsonwebtoken@^9.0.0: version "9.0.0" resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz" @@ -6666,6 +6850,13 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + kleur@4.1.5: version "4.1.5" resolved "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz" @@ -6745,6 +6936,11 @@ lodash.flatten@^4.4.0: resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz" integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg== + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" @@ -6818,6 +7014,11 @@ long@^4.0.0: resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz" integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== +lru-cache@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -6910,7 +7111,7 @@ methods@^1.1.2, methods@~1.1.2: resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.0, micromatch@^4.0.4: +micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -7277,7 +7478,7 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@7.4.2: +open@7.4.2, open@^7.4.2: version "7.4.2" resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz" integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== @@ -7481,6 +7682,27 @@ passport@^0.6.0: pause "0.0.1" utils-merge "^1.0.1" +patch-package@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61" + integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^4.1.2" + ci-info "^3.7.0" + cross-spawn "^7.0.3" + find-yarn-workspace-root "^2.0.0" + fs-extra "^9.0.0" + json-stable-stringify "^1.0.2" + klaw-sync "^6.0.0" + minimist "^1.2.6" + open "^7.4.2" + rimraf "^2.6.3" + semver "^7.5.3" + slash "^2.0.0" + tmp "^0.0.33" + yaml "^2.2.2" + path-browserify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz" @@ -7651,6 +7873,11 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +postinstall-postinstall@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" + integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== + prebuild-install@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" @@ -7772,6 +7999,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + punycode@^2.1.0: version "2.3.0" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" @@ -8021,6 +8253,13 @@ rimraf@4.4.1: dependencies: glob "^9.2.0" +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + run-async@^2.4.0: version "2.4.1" resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" @@ -8107,6 +8346,13 @@ semver@^7.5.0: dependencies: lru-cache "^6.0.0" +semver@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + send@0.18.0: version "0.18.0" resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" @@ -8236,6 +8482,11 @@ sisteransi@^1.0.5: resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" @@ -8836,17 +9087,17 @@ tslib@2.5.3, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz" integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== +tslib@2.6.0, tslib@^2.3.1, tslib@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" + integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== + tslib@^1.11.1, tslib@^1.8.1: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.3.1, tslib@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" - integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== - -"tslib@^2.4.1 || ^1.9.3": +"tslib@^2.4.1 || ^1.9.3", tslib@^2.5.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -9039,6 +9290,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +urlpattern-polyfill@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz#bc7e386bb12fd7898b58d1509df21d3c29ab3460" + integrity sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g== + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" @@ -9306,6 +9562,11 @@ yaml@^1.10.0: resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.2.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" + integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== + yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..a1de5f449 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,396 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@mapbox/node-pre-gyp@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bcrypt@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2" + integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.11" + node-addon-api "^5.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +semver@^6.0.0: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.5: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +signal-exit@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +tar@^6.1.11: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==