From 9df83c9a5a20ecbdf89395a4af75233af36a47aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20M?= Date: Wed, 6 Dec 2023 12:19:00 +0100 Subject: [PATCH] feat: better server lint (#2850) * feat: add stylistic eslint plugin * feat: add missing line return * feat: secure line-break style * feat: disallow break before else * feat: line between class members * feat: better new line lint rule --- server/.eslintrc.js | 13 ++- server/.prettierrc | 5 +- server/package.json | 1 + server/src/app.module.ts | 1 + .../src/core/api-rest/api-rest.controller.ts | 4 + .../core/api-rest/api-rest.service.spec.ts | 1 + server/src/core/api-rest/api-rest.service.ts | 42 +++++++ server/src/core/auth/auth.resolver.ts | 1 + .../guards/google-provider-enabled.guard.ts | 2 + server/src/core/auth/services/auth.service.ts | 3 + .../src/core/auth/services/token.service.ts | 5 + .../auth/strategies/google.auth.strategy.ts | 1 + .../core/auth/strategies/jwt.auth.strategy.ts | 2 + .../core/file/controllers/file.controller.ts | 1 + .../core/file/services/file-upload.service.ts | 1 + .../before-create-one-refresh-token.hook.ts | 1 + server/src/core/user/services/user.service.ts | 1 + server/src/core/user/user.resolver.ts | 4 + .../workspace/services/workspace.service.ts | 1 + .../src/core/workspace/workspace.resolver.ts | 2 + .../data-seed-demo-workspace.command.ts | 2 + .../data-seed-dev-workspace.command.ts | 2 + .../database/typeorm-seeds/core/demo/index.ts | 2 + .../src/database/typeorm-seeds/core/index.ts | 2 + .../database/typeorm-seeds/metadata/index.ts | 1 + .../database/typeorm/core/core.datasource.ts | 1 + .../typeorm/metadata/metadata.datasource.ts | 1 + .../1700663879152-addEnumOptions.ts | 37 +++--- server/src/guards/optional-jwt.auth.guard.ts | 2 + .../cast-to-log-level-array.decorator.spec.ts | 8 ++ .../cast-to-positive-number.decorator.spec.ts | 7 ++ .../decorators/cast-to-boolean.decorator.ts | 1 + .../cast-to-positive-number.decorator.ts | 1 + .../decorators/is-aws-region.decorator.ts | 1 + .../decorators/is-duration.decorator.ts | 1 + .../environment/environment.validation.ts | 1 + .../file-storage/file-storage.module.ts | 1 + .../src/integrations/integrations.module.ts | 3 + .../src/integrations/logger/logger.module.ts | 1 + .../serializers/default.serializer.ts | 1 + .../message-queue/drivers/bullmq.driver.ts | 2 + .../message-queue/drivers/pg-boss.driver.ts | 1 + .../message-queue/message-queue.module.ts | 4 + server/src/main.ts | 1 + .../field-metadata/field-metadata.service.ts | 1 + .../hooks/before-create-one-field.hook.ts | 2 + .../generate-target-column-map.spec.ts | 3 + .../__tests__/serialize-default-value.spec.ts | 1 + .../hooks/before-create-one-object.hook.ts | 1 + .../object-metadata.service.ts | 2 + .../hooks/before-create-one-relation.hook.ts | 1 + .../relation-metadata.service.ts | 2 + .../workspace-migration.factory.ts | 2 + .../workspace-migration.service.ts | 1 + .../services/scalars-explorer.service.ts | 3 + .../utils/__tests__/get-resolver-name.spec.ts | 1 + .../demo-objects-prefill-data.ts | 3 + .../demo-objects-prefill-data/opportunity.ts | 2 + .../demo-objects-prefill-data/view.ts | 1 + .../standard-objects-prefill-data.ts | 2 + .../standard-objects-prefill-data/view.ts | 1 + .../utils/metadata.parser.ts | 1 + .../utils/sync-metadata.util.ts | 2 + .../workspace-manager.service.ts | 8 ++ .../factories/args-string.factory.ts | 1 + .../stringify-without-key-quote.spec.ts | 5 + .../utils/get-field-arguments-by-key.util.ts | 1 + .../factories/mutation-type.factory.ts | 1 + .../factories/query-type.factory.ts | 1 + .../graphql-types/scalars/big-float.scalar.ts | 1 + .../graphql-types/scalars/cursor.scalar.ts | 3 + .../__tests__/get-field-metadata-type.spec.ts | 1 + .../utils/__tests__/get-resolver-args.spec.ts | 1 + server/src/workspace/workspace.factory.ts | 1 + server/yarn.lock | 107 +++++++++++++++++- 75 files changed, 318 insertions(+), 21 deletions(-) diff --git a/server/.eslintrc.js b/server/.eslintrc.js index a4ad35d63..bcf38f34a 100644 --- a/server/.eslintrc.js +++ b/server/.eslintrc.js @@ -5,7 +5,7 @@ module.exports = { tsconfigRootDir : __dirname, sourceType: 'module', }, - plugins: ['@typescript-eslint/eslint-plugin', 'import', 'unused-imports'], + plugins: ['@typescript-eslint/eslint-plugin', 'import', 'unused-imports', '@stylistic'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', @@ -77,5 +77,16 @@ module.exports = { 'import/no-duplicates': ["error", {"considerQueryString": true}], 'unused-imports/no-unused-imports': 'warn', "@typescript-eslint/consistent-type-imports": ["error", { "prefer": "no-type-imports" }], + "@stylistic/linebreak-style": ["error", "unix"], + "@stylistic/lines-between-class-members": ["error", { "enforce": [ + { blankLine: "always", prev: "method", next: "method" } + ]}], + "@stylistic/padding-line-between-statements": [ + "error", + { blankLine: "always", prev: "*", next: "return" }, + { blankLine: "always", prev: ["const", "let", "var"], next: "*"}, + { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }, + { blankLine: "always", prev: "*", next: ["interface", "type"] } + ] }, }; diff --git a/server/.prettierrc b/server/.prettierrc index dcb72794f..3519b0676 100644 --- a/server/.prettierrc +++ b/server/.prettierrc @@ -1,4 +1,5 @@ { "singleQuote": true, - "trailingComma": "all" -} \ No newline at end of file + "trailingComma": "all", + "brakeBeforeElse": false +} diff --git a/server/package.json b/server/package.json index 4814217f4..acc533b79 100644 --- a/server/package.json +++ b/server/package.json @@ -106,6 +106,7 @@ "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", + "@stylistic/eslint-plugin": "^1.5.0", "@types/bcrypt": "^5.0.0", "@types/bytes": "^3.1.1", "@types/express": "^4.17.13", diff --git a/server/src/app.module.ts b/server/src/app.module.ts index da2bc4e25..f6439d0b6 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -69,6 +69,7 @@ import { ExceptionFilter } from './filters/exception.filter'; ); const contextId = ContextIdFactory.create(); + AppModule.moduleRef.registerRequestByContextId(request, contextId); // Get the SchemaGenerationService from the AppModule diff --git a/server/src/core/api-rest/api-rest.controller.ts b/server/src/core/api-rest/api-rest.controller.ts index 019c993ff..e5f43f9da 100644 --- a/server/src/core/api-rest/api-rest.controller.ts +++ b/server/src/core/api-rest/api-rest.controller.ts @@ -11,24 +11,28 @@ export class ApiRestController { @Get() async handleApiGet(@Req() request: Request): Promise { const result = await this.apiRestService.get(request); + return result.data; } @Delete() async handleApiDelete(@Req() request: Request): Promise { const result = await this.apiRestService.delete(request); + return result.data; } @Post() async handleApiPost(@Req() request: Request): Promise { const result = await this.apiRestService.create(request); + return result.data; } @Put() async handleApiPut(@Req() request: Request): Promise { const result = await this.apiRestService.update(request); + return result.data; } } diff --git a/server/src/core/api-rest/api-rest.service.spec.ts b/server/src/core/api-rest/api-rest.service.spec.ts index 013977d12..8cb35d188 100644 --- a/server/src/core/api-rest/api-rest.service.spec.ts +++ b/server/src/core/api-rest/api-rest.service.spec.ts @@ -7,6 +7,7 @@ import { EnvironmentService } from 'src/integrations/environment/environment.ser describe('ApiRestService', () => { let service: ApiRestService; const objectMetadataItem = { fields: [{ name: 'field', type: 'NUMBER' }] }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ diff --git a/server/src/core/api-rest/api-rest.service.ts b/server/src/core/api-rest/api-rest.service.ts index 5f3da6311..b9fdf4b17 100644 --- a/server/src/core/api-rest/api-rest.service.ts +++ b/server/src/core/api-rest/api-rest.service.ts @@ -24,6 +24,7 @@ enum FILTER_COMPARATORS { const ALLOWED_DEPTH_VALUES = [1, 2]; const DEFAULT_DEPTH_VALUE = 2; const DEFAULT_ORDER_DIRECTION = OrderByDirection.AscNullsFirst; + enum CONJUNCTIONS { or = 'or', and = 'and', @@ -275,16 +276,19 @@ export class ApiRestService { const [objectMetadata] = objectMetadataItems.filter( (object) => object.namePlural === parsedObject, ); + if (!objectMetadata) { const [wrongObjectMetadata] = objectMetadataItems.filter( (object) => object.nameSingular === parsedObject, ); let hint = 'eg: companies'; + if (wrongObjectMetadata) { hint = `Did you mean '${wrongObjectMetadata.namePlural}'?`; } throw Error(`object '${parsedObject}' not found. ${hint}`); } + return { objectMetadataItems, objectMetadataItem: objectMetadata, @@ -295,6 +299,7 @@ export class ApiRestService { if (!(filterQuery.includes('(') && filterQuery.includes(')'))) { return `${DEFAULT_FILTER_CONJUNCTION}(${filterQuery})`; } + return filterQuery; } @@ -302,6 +307,7 @@ export class ApiRestService { const countOpenedBrackets = (filterQuery.match(/\(/g) || []).length; const countClosedBrackets = (filterQuery.match(/\)/g) || []).length; const diff = countOpenedBrackets - countClosedBrackets; + if (diff !== 0) { const hint = diff > 0 @@ -309,8 +315,10 @@ export class ApiRestService { : `${Math.abs(diff)} close bracket${ Math.abs(diff) > 1 ? 's are' : ' is' }`; + throw Error(`'filter' invalid. ${hint} missing in the query`); } + return; } @@ -318,6 +326,7 @@ export class ApiRestService { let parenthesisCounter = 0; const predicates: string[] = []; let currentPredicates = ''; + for (const c of filterQuery) { if (c === '(') { parenthesisCounter++; @@ -337,6 +346,7 @@ export class ApiRestService { if (currentPredicates.length) { predicates.push(currentPredicates); } + return predicates; } @@ -345,11 +355,13 @@ export class ApiRestService { const match = filterQuery.match( `^(${Object.values(CONJUNCTIONS).join('|')})((.+))$`, ); + if (match) { const conjunction = match[1]; const subResult = this.parseFilterQueryContent(filterQuery).map((elem) => this.parseStringFilter(elem, objectMetadataItem), ); + if (conjunction === CONJUNCTIONS.not) { if (subResult.length > 1) { throw Error( @@ -360,8 +372,10 @@ export class ApiRestService { } else { result[conjunction] = subResult; } + return result; } + return this.parseSimpleFilter(filterQuery, objectMetadataItem); } @@ -376,6 +390,7 @@ export class ApiRestService { } const [fieldAndComparator, value] = filterString.split(':'); const [field, comparator] = fieldAndComparator.replace(']', '').split('['); + if (!Object.keys(FILTER_COMPARATORS).includes(comparator)) { throw Error( `'filter' invalid for '${filterString}', comparator ${comparator} not in ${Object.keys( @@ -384,9 +399,11 @@ export class ApiRestService { ); } const fields = field.split('.'); + this.checkFields(objectMetadataItem, fields, 'filter'); const fieldType = this.getFieldType(objectMetadataItem, fields[0]); const formattedValue = this.formatFieldValue(value, fieldType); + return fields.reverse().reduce( (acc, currentValue) => { return { [currentValue]: acc }; @@ -402,35 +419,42 @@ export class ApiRestService { if (fieldType === 'BOOLEAN') { return value.toLowerCase() === 'true'; } + return value; } parseFilter(request, objectMetadataItem) { const parsedObjectId = this.parseObject(request)[1]; + if (parsedObjectId) { return { id: { eq: parsedObjectId } }; } const rawFilterQuery = request.query.filter; + if (typeof rawFilterQuery !== 'string') { return {}; } this.checkFilterQuery(rawFilterQuery); const filterQuery = this.addDefaultConjunctionIfMissing(rawFilterQuery); + return this.parseStringFilter(filterQuery, objectMetadataItem); } parseOrderBy(request, objectMetadataItem) { //?order_by=field_1[AscNullsFirst],field_2[DescNullsLast],field_3 const orderByQuery = request.query.order_by; + if (typeof orderByQuery !== 'string') { return {}; } const orderByItems = orderByQuery.split(','); const result = {}; + for (const orderByItem of orderByItems) { // orderByItem -> field_1[AscNullsFirst] if (orderByItem.includes('[') && orderByItem.includes(']')) { const [field, direction] = orderByItem.replace(']', '').split('['); + // field -> field_1 ; direction -> AscNullsFirst if (!(direction in OrderByDirection)) { throw Error( @@ -448,6 +472,7 @@ export class ApiRestService { } } this.checkFields(objectMetadataItem, Object.keys(result), 'order_by'); + return result; } @@ -482,21 +507,26 @@ export class ApiRestService { parseLimit(request) { const limitQuery = request.query.limit; + if (typeof limitQuery !== 'string') { return 60; } const limitParsed = parseInt(limitQuery); + if (!Number.isInteger(limitParsed)) { throw Error(`limit '${limitQuery}' is invalid. Should be an integer`); } + return limitParsed; } parseCursor(request) { const cursorQuery = request.query.last_cursor; + if (typeof cursorQuery !== 'string') { return undefined; } + return cursorQuery; } @@ -511,6 +541,7 @@ export class ApiRestService { parseObject(request) { const queryAction = request.path.replace('/rest/', '').split('/'); + if (queryAction.length > 2) { throw Error( `Query path '${request.path}' invalid. Valid examples: /rest/companies/id or /rest/companies`, @@ -519,14 +550,17 @@ export class ApiRestService { if (queryAction.length === 1) { return [queryAction[0], undefined]; } + return queryAction; } extractWorkspaceId(request: Request) { const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request); + if (!token) { throw Error('missing authentication token'); } + return verify(token, this.environmentService.getAccessTokenSecret())[ 'workspaceId' ]; @@ -537,6 +571,7 @@ export class ApiRestService { typeof request.query.depth === 'string' ? parseInt(request.query.depth) : DEFAULT_DEPTH_VALUE; + if (!ALLOWED_DEPTH_VALUES.includes(depth)) { throw Error( `'depth=${depth}' parameter invalid. Allowed values are ${ALLOWED_DEPTH_VALUES.join( @@ -544,6 +579,7 @@ export class ApiRestService { )}`, ); } + return depth; } @@ -583,6 +619,7 @@ export class ApiRestService { objectMetadata.objectMetadataItem, ), }; + return await this.callGraphql(request, data); } catch (err) { return { data: { error: `${err}` } }; @@ -593,6 +630,7 @@ export class ApiRestService { try { const objectMetadata = await this.getObjectMetadata(request); const id = this.parseObject(request)[1]; + if (!id) { return { data: { @@ -606,6 +644,7 @@ export class ApiRestService { id: this.parseObject(request)[1], }, }; + return await this.callGraphql(request, data); } catch (err) { return { data: { error: `${err}` } }; @@ -626,6 +665,7 @@ export class ApiRestService { data: request.body, }, }; + return await this.callGraphql(request, data); } catch (err) { return { data: { error: `${err}` } }; @@ -637,6 +677,7 @@ export class ApiRestService { const objectMetadata = await this.getObjectMetadata(request); const depth = this.computeDepth(request); const id = this.parseObject(request)[1]; + if (!id) { return { data: { @@ -655,6 +696,7 @@ export class ApiRestService { data: request.body, }, }; + return await this.callGraphql(request, data); } catch (err) { return { data: { error: `${err}` } }; diff --git a/server/src/core/auth/auth.resolver.ts b/server/src/core/auth/auth.resolver.ts index 74a86cc0f..df7ee6181 100644 --- a/server/src/core/auth/auth.resolver.ts +++ b/server/src/core/auth/auth.resolver.ts @@ -47,6 +47,7 @@ export class AuthResolver { const { exists } = await this.authService.checkUserExists( checkUserExistsInput.email, ); + return { exists }; } diff --git a/server/src/core/auth/guards/google-provider-enabled.guard.ts b/server/src/core/auth/guards/google-provider-enabled.guard.ts index 614e7e165..b6095fc95 100644 --- a/server/src/core/auth/guards/google-provider-enabled.guard.ts +++ b/server/src/core/auth/guards/google-provider-enabled.guard.ts @@ -8,12 +8,14 @@ import { GoogleStrategy } from 'src/core/auth/strategies/google.auth.strategy'; @Injectable() export class GoogleProviderEnabledGuard implements CanActivate { constructor(private readonly environmentService: EnvironmentService) {} + canActivate(): boolean | Promise | Observable { if (!this.environmentService.isAuthGoogleEnabled()) { throw new NotFoundException('Google auth is not enabled'); } new GoogleStrategy(this.environmentService); + return true; } } diff --git a/server/src/core/auth/services/auth.service.ts b/server/src/core/auth/services/auth.service.ts index 2ceca4253..37d6a72b8 100644 --- a/server/src/core/auth/services/auth.service.ts +++ b/server/src/core/auth/services/auth.service.ts @@ -89,10 +89,12 @@ export class AuthService { const existingUser = await this.userRepository.findOneBy({ email: email, }); + assert(!existingUser, 'This user already exists', ForbiddenException); if (password) { const isPasswordValid = PASSWORD_REGEX.test(password); + assert(isPasswordValid, 'Password too weak', BadRequestException); } @@ -115,6 +117,7 @@ export class AuthService { domainName: '', inviteHash: v4(), }); + workspace = await this.workspaceRepository.save(workspaceToCreate); await this.workspaceManagerService.init(workspace.id); } diff --git a/server/src/core/auth/services/token.service.ts b/server/src/core/auth/services/token.service.ts index 1b36fe4a5..072ec5221 100644 --- a/server/src/core/auth/services/token.service.ts +++ b/server/src/core/auth/services/token.service.ts @@ -34,6 +34,7 @@ export class TokenService { async generateAccessToken(userId: string): Promise { const expiresIn = this.environmentService.getAccessTokenExpiresIn(); + assert(expiresIn, '', InternalServerErrorException); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); @@ -64,6 +65,7 @@ export class TokenService { async generateRefreshToken(userId: string): Promise { const secret = this.environmentService.getRefreshTokenSecret(); const expiresIn = this.environmentService.getRefreshTokenExpiresIn(); + assert(expiresIn, '', InternalServerErrorException); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); @@ -94,6 +96,7 @@ export class TokenService { async generateLoginToken(email: string): Promise { const secret = this.environmentService.getLoginTokenSecret(); const expiresIn = this.environmentService.getLoginTokenExpiresIn(); + assert(expiresIn, '', InternalServerErrorException); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); const jwtPayload = { @@ -122,6 +125,7 @@ export class TokenService { }; const secret = this.environmentService.getAccessTokenSecret(); let expiresIn: string | number; + if (expiresAt) { expiresIn = Math.floor( (new Date(expiresAt).getTime() - new Date().getTime()) / 1000, @@ -134,6 +138,7 @@ export class TokenService { expiresIn, jwtid: apiKeyId, }); + return { token }; } diff --git a/server/src/core/auth/strategies/google.auth.strategy.ts b/server/src/core/auth/strategies/google.auth.strategy.ts index 22a109e4e..d8a7c6e5c 100644 --- a/server/src/core/auth/strategies/google.auth.strategy.ts +++ b/server/src/core/auth/strategies/google.auth.strategy.ts @@ -59,6 +59,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { picture: photos?.[0]?.value, workspaceInviteHash: state.workspaceInviteHash, }; + done(null, user); } } diff --git a/server/src/core/auth/strategies/jwt.auth.strategy.ts b/server/src/core/auth/strategies/jwt.auth.strategy.ts index 429b1be19..e18e1ff45 100644 --- a/server/src/core/auth/strategies/jwt.auth.strategy.ts +++ b/server/src/core/auth/strategies/jwt.auth.strategy.ts @@ -41,6 +41,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') { const workspace = await this.workspaceRepository.findOneBy({ id: payload.workspaceId ?? payload.sub, }); + if (!workspace) { throw new UnauthorizedException(); } @@ -66,6 +67,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') { } let user; + if (payload.workspaceId) { user = await this.userRepository.findOneBy({ id: payload.sub, diff --git a/server/src/core/file/controllers/file.controller.ts b/server/src/core/file/controllers/file.controller.ts index ead58094d..e237af763 100644 --- a/server/src/core/file/controllers/file.controller.ts +++ b/server/src/core/file/controllers/file.controller.ts @@ -9,6 +9,7 @@ import { FileService } from 'src/core/file/services/file.service'; @Controller('files') export class FileController { constructor(private readonly fileService: FileService) {} + /** * Serve files from local storage * We recommend using an s3 bucket for production diff --git a/server/src/core/file/services/file-upload.service.ts b/server/src/core/file/services/file-upload.service.ts index 7825f78fb..3294f4178 100644 --- a/server/src/core/file/services/file-upload.service.ts +++ b/server/src/core/file/services/file-upload.service.ts @@ -77,6 +77,7 @@ export class FileUploadService { const name = `${id}${ext ? `.${ext}` : ''}`; const cropSizes = settings.storage.imageCropSizes[fileFolder]; + if (!cropSizes) { throw new Error(`No crop sizes found for ${fileFolder}`); } diff --git a/server/src/core/refresh-token/hooks/before-create-one-refresh-token.hook.ts b/server/src/core/refresh-token/hooks/before-create-one-refresh-token.hook.ts index ce9e3470c..7399e402f 100644 --- a/server/src/core/refresh-token/hooks/before-create-one-refresh-token.hook.ts +++ b/server/src/core/refresh-token/hooks/before-create-one-refresh-token.hook.ts @@ -19,6 +19,7 @@ export class BeforeCreateOneRefreshToken // FIXME: These fields should be autogenerated, we need to run a migration for this instance.input.id = uuidv4(); instance.input.updatedAt = new Date(); + return instance; } } diff --git a/server/src/core/user/services/user.service.ts b/server/src/core/user/services/user.service.ts index c0b1c9098..0feb56bad 100644 --- a/server/src/core/user/services/user.service.ts +++ b/server/src/core/user/services/user.service.ts @@ -72,6 +72,7 @@ export class UserService extends TypeOrmQueryService { const user = await this.userRepository.findOneBy({ id: userId, }); + assert(user, 'User not found'); await this.userRepository.delete(user.id); diff --git a/server/src/core/user/user.resolver.ts b/server/src/core/user/user.resolver.ts index 37ccaad11..b4a0e2268 100644 --- a/server/src/core/user/user.resolver.ts +++ b/server/src/core/user/user.resolver.ts @@ -30,6 +30,7 @@ const getHMACKey = (email?: string, key?: string | null) => { if (!email || !key) return null; const hmac = crypto.createHmac('sha256', key); + return hmac.update(email).digest('hex'); }; @@ -47,7 +48,9 @@ export class UserResolver { const user = await this.userService.findById(id, { relations: [{ name: 'defaultWorkspace', query: {} }], }); + assert(user, 'User not found'); + return user; } @@ -66,6 +69,7 @@ export class UserResolver { return null; } const key = this.environmentService.getSupportFrontHMACKey(); + return getHMACKey(parent.email, key); } diff --git a/server/src/core/workspace/services/workspace.service.ts b/server/src/core/workspace/services/workspace.service.ts index 87c73fe9c..ebe4a6f12 100644 --- a/server/src/core/workspace/services/workspace.service.ts +++ b/server/src/core/workspace/services/workspace.service.ts @@ -19,6 +19,7 @@ export class WorkspaceService extends TypeOrmQueryService { async deleteWorkspace(id: string) { const workspace = await this.workspaceRepository.findOneBy({ id }); + assert(workspace, 'Workspace not found'); await this.workspaceManagerService.delete(id); diff --git a/server/src/core/workspace/workspace.resolver.ts b/server/src/core/workspace/workspace.resolver.ts index 790179dd2..13c5fc92c 100644 --- a/server/src/core/workspace/workspace.resolver.ts +++ b/server/src/core/workspace/workspace.resolver.ts @@ -27,7 +27,9 @@ export class WorkspaceResolver { @Query(() => Workspace) async currentWorkspace(@AuthWorkspace() { id }: Workspace) { const workspace = await this.workspaceService.findById(id); + assert(workspace, 'User not found'); + return workspace; } diff --git a/server/src/database/commands/data-seed-demo-workspace.command.ts b/server/src/database/commands/data-seed-demo-workspace.command.ts index 9b7a94c8a..c1c17612c 100644 --- a/server/src/database/commands/data-seed-demo-workspace.command.ts +++ b/server/src/database/commands/data-seed-demo-workspace.command.ts @@ -29,6 +29,7 @@ export class DataSeedDemoWorkspaceCommand extends CommandRunner { logging: true, schema: 'public', }); + await dataSource.initialize(); const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); @@ -46,6 +47,7 @@ export class DataSeedDemoWorkspaceCommand extends CommandRunner { } } catch (error) { console.error(error); + return; } } diff --git a/server/src/database/commands/data-seed-dev-workspace.command.ts b/server/src/database/commands/data-seed-dev-workspace.command.ts index f9e2b1714..5207ca3bc 100644 --- a/server/src/database/commands/data-seed-dev-workspace.command.ts +++ b/server/src/database/commands/data-seed-dev-workspace.command.ts @@ -43,12 +43,14 @@ export class DataSeedWorkspaceCommand extends CommandRunner { logging: true, schema: 'public', }); + await dataSource.initialize(); await seedCoreSchema(dataSource, this.workspaceId); await seedMetadataSchema(dataSource); } catch (error) { console.error(error); + return; } diff --git a/server/src/database/typeorm-seeds/core/demo/index.ts b/server/src/database/typeorm-seeds/core/demo/index.ts index 9216a1cad..9494324e0 100644 --- a/server/src/database/typeorm-seeds/core/demo/index.ts +++ b/server/src/database/typeorm-seeds/core/demo/index.ts @@ -18,6 +18,7 @@ export const seedCoreSchema = async ( workspaceId: string, ) => { const schemaName = 'core'; + await seedWorkspaces(workspaceDataSource, schemaName, workspaceId); await seedUsers(workspaceDataSource, schemaName, workspaceId); await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId); @@ -28,6 +29,7 @@ export const deleteCoreSchema = async ( workspaceId: string, ) => { const schemaName = 'core'; + await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId); await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId); // deleteWorkspaces should be last diff --git a/server/src/database/typeorm-seeds/core/index.ts b/server/src/database/typeorm-seeds/core/index.ts index 8d98cde2c..b33dce403 100644 --- a/server/src/database/typeorm-seeds/core/index.ts +++ b/server/src/database/typeorm-seeds/core/index.ts @@ -18,6 +18,7 @@ export const seedCoreSchema = async ( workspaceId: string, ) => { const schemaName = 'core'; + await seedWorkspaces(workspaceDataSource, schemaName, workspaceId); await seedUsers(workspaceDataSource, schemaName, workspaceId); await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId); @@ -28,6 +29,7 @@ export const deleteCoreSchema = async ( workspaceId: string, ) => { const schemaName = 'core'; + await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId); await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId); // deleteWorkspaces should be last diff --git a/server/src/database/typeorm-seeds/metadata/index.ts b/server/src/database/typeorm-seeds/metadata/index.ts index e5a26dff3..5d47f02d7 100644 --- a/server/src/database/typeorm-seeds/metadata/index.ts +++ b/server/src/database/typeorm-seeds/metadata/index.ts @@ -27,6 +27,7 @@ import { seedWebhookFieldMetadata } from 'src/database/typeorm-seeds/metadata/fi export const seedMetadataSchema = async (workspaceDataSource: DataSource) => { const schemaName = 'metadata'; + await seedDataSource(workspaceDataSource, schemaName); await seedObjectMetadata(workspaceDataSource, schemaName); diff --git a/server/src/database/typeorm/core/core.datasource.ts b/server/src/database/typeorm/core/core.datasource.ts index 5966fa038..7125558e4 100644 --- a/server/src/database/typeorm/core/core.datasource.ts +++ b/server/src/database/typeorm/core/core.datasource.ts @@ -5,6 +5,7 @@ import { DataSource, DataSourceOptions } from 'typeorm'; import { config } from 'dotenv'; config(); const configService = new ConfigService(); + export const typeORMCoreModuleOptions: TypeOrmModuleOptions = { url: configService.get('PG_DATABASE_URL'), type: 'postgres', diff --git a/server/src/database/typeorm/metadata/metadata.datasource.ts b/server/src/database/typeorm/metadata/metadata.datasource.ts index 139cd2a98..92a75a873 100644 --- a/server/src/database/typeorm/metadata/metadata.datasource.ts +++ b/server/src/database/typeorm/metadata/metadata.datasource.ts @@ -5,6 +5,7 @@ import { DataSource, DataSourceOptions } from 'typeorm'; import { config } from 'dotenv'; config(); const configService = new ConfigService(); + export const typeORMMetadataModuleOptions: TypeOrmModuleOptions = { url: configService.get('PG_DATABASE_URL'), type: 'postgres', diff --git a/server/src/database/typeorm/metadata/migrations/1700663879152-addEnumOptions.ts b/server/src/database/typeorm/metadata/migrations/1700663879152-addEnumOptions.ts index 0f0774e07..469ccf20b 100644 --- a/server/src/database/typeorm/metadata/migrations/1700663879152-addEnumOptions.ts +++ b/server/src/database/typeorm/metadata/migrations/1700663879152-addEnumOptions.ts @@ -1,18 +1,29 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; export class AddEnumOptions1700663879152 implements MigrationInterface { - name = 'AddEnumOptions1700663879152' + name = 'AddEnumOptions1700663879152'; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" RENAME COLUMN "enums" TO "options"`); - await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "options"`); - await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" ADD "options" jsonb`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "options"`); - await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" ADD "options" text array`); - await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" RENAME COLUMN "options" TO "enums"`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "metadata"."fieldMetadata" RENAME COLUMN "enums" TO "options"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "options"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."fieldMetadata" ADD "options" jsonb`, + ); + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "options"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."fieldMetadata" ADD "options" text array`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."fieldMetadata" RENAME COLUMN "options" TO "enums"`, + ); + } } diff --git a/server/src/guards/optional-jwt.auth.guard.ts b/server/src/guards/optional-jwt.auth.guard.ts index 52df71d81..6090a55c6 100644 --- a/server/src/guards/optional-jwt.auth.guard.ts +++ b/server/src/guards/optional-jwt.auth.guard.ts @@ -11,11 +11,13 @@ export class OptionalJwtAuthGuard extends AuthGuard(['jwt']) { getRequest(context: ExecutionContext) { const request = getRequest(context); + return request; } handleRequest(err, user, info) { if (err || info) return null; + return user; } } diff --git a/server/src/integrations/environment/decorators/__tests__/cast-to-log-level-array.decorator.spec.ts b/server/src/integrations/environment/decorators/__tests__/cast-to-log-level-array.decorator.spec.ts index 267688fe0..26ba667e4 100644 --- a/server/src/integrations/environment/decorators/__tests__/cast-to-log-level-array.decorator.spec.ts +++ b/server/src/integrations/environment/decorators/__tests__/cast-to-log-level-array.decorator.spec.ts @@ -10,26 +10,31 @@ class TestClass { describe('CastToLogLevelArray Decorator', () => { it('should cast "log" to ["log"]', () => { const transformedClass = plainToClass(TestClass, { logLevels: 'log' }); + expect(transformedClass.logLevels).toStrictEqual(['log']); }); it('should cast "error" to ["error"]', () => { const transformedClass = plainToClass(TestClass, { logLevels: 'error' }); + expect(transformedClass.logLevels).toStrictEqual(['error']); }); it('should cast "warn" to ["warn"]', () => { const transformedClass = plainToClass(TestClass, { logLevels: 'warn' }); + expect(transformedClass.logLevels).toStrictEqual(['warn']); }); it('should cast "debug" to ["debug"]', () => { const transformedClass = plainToClass(TestClass, { logLevels: 'debug' }); + expect(transformedClass.logLevels).toStrictEqual(['debug']); }); it('should cast "verbose" to ["verbose"]', () => { const transformedClass = plainToClass(TestClass, { logLevels: 'verbose' }); + expect(transformedClass.logLevels).toStrictEqual(['verbose']); }); @@ -37,6 +42,7 @@ describe('CastToLogLevelArray Decorator', () => { const transformedClass = plainToClass(TestClass, { logLevels: 'verbose,error,warn', }); + expect(transformedClass.logLevels).toStrictEqual([ 'verbose', 'error', @@ -46,6 +52,7 @@ describe('CastToLogLevelArray Decorator', () => { it('should cast "toto" to undefined', () => { const transformedClass = plainToClass(TestClass, { logLevels: 'toto' }); + expect(transformedClass.logLevels).toBeUndefined(); }); @@ -53,6 +60,7 @@ describe('CastToLogLevelArray Decorator', () => { const transformedClass = plainToClass(TestClass, { logLevels: 'verbose,error,toto', }); + expect(transformedClass.logLevels).toBeUndefined(); }); }); diff --git a/server/src/integrations/environment/decorators/__tests__/cast-to-positive-number.decorator.spec.ts b/server/src/integrations/environment/decorators/__tests__/cast-to-positive-number.decorator.spec.ts index 0a65d3a90..b82940493 100644 --- a/server/src/integrations/environment/decorators/__tests__/cast-to-positive-number.decorator.spec.ts +++ b/server/src/integrations/environment/decorators/__tests__/cast-to-positive-number.decorator.spec.ts @@ -10,21 +10,25 @@ class TestClass { describe('CastToPositiveNumber Decorator', () => { it('should cast number to number', () => { const transformedClass = plainToClass(TestClass, { numberProperty: 123 }); + expect(transformedClass.numberProperty).toBe(123); }); it('should cast string to number', () => { const transformedClass = plainToClass(TestClass, { numberProperty: '123' }); + expect(transformedClass.numberProperty).toBe(123); }); it('should cast null to undefined', () => { const transformedClass = plainToClass(TestClass, { numberProperty: null }); + expect(transformedClass.numberProperty).toBe(undefined); }); it('should cast negative number to undefined', () => { const transformedClass = plainToClass(TestClass, { numberProperty: -12 }); + expect(transformedClass.numberProperty).toBe(undefined); }); @@ -32,6 +36,7 @@ describe('CastToPositiveNumber Decorator', () => { const transformedClass = plainToClass(TestClass, { numberProperty: undefined, }); + expect(transformedClass.numberProperty).toBe(undefined); }); @@ -39,6 +44,7 @@ describe('CastToPositiveNumber Decorator', () => { const transformedClass = plainToClass(TestClass, { numberProperty: 'toto', }); + expect(transformedClass.numberProperty).toBe(undefined); }); @@ -46,6 +52,7 @@ describe('CastToPositiveNumber Decorator', () => { const transformedClass = plainToClass(TestClass, { numberProperty: '-123', }); + expect(transformedClass.numberProperty).toBe(undefined); }); }); diff --git a/server/src/integrations/environment/decorators/cast-to-boolean.decorator.ts b/server/src/integrations/environment/decorators/cast-to-boolean.decorator.ts index f358e0b21..8b1942a10 100644 --- a/server/src/integrations/environment/decorators/cast-to-boolean.decorator.ts +++ b/server/src/integrations/environment/decorators/cast-to-boolean.decorator.ts @@ -13,5 +13,6 @@ const toBoolean = (value: any) => { if (['false', 'off', 'no', '0'].includes(value.toLowerCase())) { return false; } + return undefined; }; diff --git a/server/src/integrations/environment/decorators/cast-to-positive-number.decorator.ts b/server/src/integrations/environment/decorators/cast-to-positive-number.decorator.ts index b961f79e3..1f532d966 100644 --- a/server/src/integrations/environment/decorators/cast-to-positive-number.decorator.ts +++ b/server/src/integrations/environment/decorators/cast-to-positive-number.decorator.ts @@ -10,5 +10,6 @@ const toNumber = (value: any) => { if (typeof value === 'string') { return isNaN(+value) ? undefined : toNumber(+value); } + return undefined; }; diff --git a/server/src/integrations/environment/decorators/is-aws-region.decorator.ts b/server/src/integrations/environment/decorators/is-aws-region.decorator.ts index 57be66dfd..3eb886d51 100644 --- a/server/src/integrations/environment/decorators/is-aws-region.decorator.ts +++ b/server/src/integrations/environment/decorators/is-aws-region.decorator.ts @@ -9,6 +9,7 @@ import { export class IsAWSRegionConstraint implements ValidatorConstraintInterface { validate(region: string) { const regex = /^[a-z]{2}-[a-z]+-\d{1}$/; + return regex.test(region); // Returns true if region matches regex } } diff --git a/server/src/integrations/environment/decorators/is-duration.decorator.ts b/server/src/integrations/environment/decorators/is-duration.decorator.ts index 9f1ea1668..a6092da32 100644 --- a/server/src/integrations/environment/decorators/is-duration.decorator.ts +++ b/server/src/integrations/environment/decorators/is-duration.decorator.ts @@ -10,6 +10,7 @@ export class IsDurationConstraint implements ValidatorConstraintInterface { validate(duration: string) { const regex = /^-?[0-9]+(.[0-9]+)?(m(illiseconds?)?|s(econds?)?|h((ou)?rs?)?|d(ays?)?|w(eeks?)?|M(onths?)?|y(ears?)?)?$/; + return regex.test(duration); // Returns true if duration matches regex } } diff --git a/server/src/integrations/environment/environment.validation.ts b/server/src/integrations/environment/environment.validation.ts index 4360700e4..a7113ac3d 100644 --- a/server/src/integrations/environment/environment.validation.ts +++ b/server/src/integrations/environment/environment.validation.ts @@ -159,6 +159,7 @@ export const validate = (config: Record) => { const validatedConfig = plainToClass(EnvironmentVariables, config); const errors = validateSync(validatedConfig); + assert(!errors.length, errors.toString()); return validatedConfig; diff --git a/server/src/integrations/file-storage/file-storage.module.ts b/server/src/integrations/file-storage/file-storage.module.ts index f6ed5c731..d1f0f64de 100644 --- a/server/src/integrations/file-storage/file-storage.module.ts +++ b/server/src/integrations/file-storage/file-storage.module.ts @@ -33,6 +33,7 @@ export class FileStorageModule { provide: STORAGE_DRIVER, useFactory: async (...args: any[]) => { const config = await options.useFactory(...args); + return config?.type === 's3' ? new S3Driver(config.options) : new LocalDriver(config.options); diff --git a/server/src/integrations/integrations.module.ts b/server/src/integrations/integrations.module.ts index e12af15c7..4e2a8f6c6 100644 --- a/server/src/integrations/integrations.module.ts +++ b/server/src/integrations/integrations.module.ts @@ -67,6 +67,7 @@ const loggerModuleFactory = async ( environmentService: EnvironmentService, ): Promise => { const type = environmentService.getLoggerDriver(); + switch (type) { case LoggerDriver.Console: { return { @@ -100,6 +101,7 @@ const messageQueueModuleFactory = async ( switch (type) { case MessageQueueType.PgBoss: { const connectionString = environmentService.getPGDatabaseUrl(); + return { type: MessageQueueType.PgBoss, options: { @@ -110,6 +112,7 @@ const messageQueueModuleFactory = async ( case MessageQueueType.BullMQ: { const host = environmentService.getRedisHost(); const port = environmentService.getRedisPort(); + return { type: MessageQueueType.BullMQ, options: { diff --git a/server/src/integrations/logger/logger.module.ts b/server/src/integrations/logger/logger.module.ts index 5d9ff0085..6e2c8c5d5 100644 --- a/server/src/integrations/logger/logger.module.ts +++ b/server/src/integrations/logger/logger.module.ts @@ -32,6 +32,7 @@ export class LoggerModule { provide: LOGGER_DRIVER, useFactory: async (...args: any[]) => { const config = await options.useFactory(...args); + return config?.type === LoggerDriver.Console ? new ConsoleLogger() : new SentryDriver(config.options); diff --git a/server/src/integrations/memory-storage/serializers/default.serializer.ts b/server/src/integrations/memory-storage/serializers/default.serializer.ts index 729d065dd..06475f71e 100644 --- a/server/src/integrations/memory-storage/serializers/default.serializer.ts +++ b/server/src/integrations/memory-storage/serializers/default.serializer.ts @@ -7,6 +7,7 @@ export class MemoryStorageDefaultSerializer if (typeof item !== 'string') { throw new Error('DefaultSerializer can only serialize strings'); } + return item; } diff --git a/server/src/integrations/message-queue/drivers/bullmq.driver.ts b/server/src/integrations/message-queue/drivers/bullmq.driver.ts index bb6fd7b3c..4f619125c 100644 --- a/server/src/integrations/message-queue/drivers/bullmq.driver.ts +++ b/server/src/integrations/message-queue/drivers/bullmq.driver.ts @@ -27,6 +27,7 @@ export class BullMQDriver implements MessageQueueDriver { async stop() { const workers = Object.values(this.workerMap); const queues = Object.values(this.queueMap); + await Promise.all([ ...queues.map((q) => q.close()), ...workers.map((w) => w.close()), @@ -40,6 +41,7 @@ export class BullMQDriver implements MessageQueueDriver { const worker = new Worker(queueName, async (job) => { await handler(job as { data: T; id: string }); }); + this.workerMap[queueName] = worker; } diff --git a/server/src/integrations/message-queue/drivers/pg-boss.driver.ts b/server/src/integrations/message-queue/drivers/pg-boss.driver.ts index b08213888..6755c2cb3 100644 --- a/server/src/integrations/message-queue/drivers/pg-boss.driver.ts +++ b/server/src/integrations/message-queue/drivers/pg-boss.driver.ts @@ -12,6 +12,7 @@ export class PgBossDriver implements MessageQueueDriver { constructor(options: PgBossDriverOptions) { this.pgBoss = new PgBoss(options); } + async stop() { await this.pgBoss.stop(); } diff --git a/server/src/integrations/message-queue/message-queue.module.ts b/server/src/integrations/message-queue/message-queue.module.ts index 05e08c0ea..dce9da800 100644 --- a/server/src/integrations/message-queue/message-queue.module.ts +++ b/server/src/integrations/message-queue/message-queue.module.ts @@ -30,11 +30,15 @@ export class MessageQueueModule { provide: QUEUE_DRIVER, useFactory: async (...args: any[]) => { const config = await options.useFactory(...args); + if (config.type === MessageQueueType.PgBoss) { const boss = new PgBossDriver(config.options); + await boss.init(); + return boss; } + return new BullMQDriver(config.options); }, inject: options.inject || [], diff --git a/server/src/main.ts b/server/src/main.ts index 4524d9d5d..19791d6f6 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -38,6 +38,7 @@ const bootstrap = async () => { }), ); const loggerService = app.get(LoggerService); + app.useLogger(loggerService); app.useLogger(app.get(EnvironmentService).getLogLevels()); diff --git a/server/src/metadata/field-metadata/field-metadata.service.ts b/server/src/metadata/field-metadata/field-metadata.service.ts index 0fd4c7d66..89af0e9fc 100644 --- a/server/src/metadata/field-metadata/field-metadata.service.ts +++ b/server/src/metadata/field-metadata/field-metadata.service.ts @@ -129,6 +129,7 @@ export class FieldMetadataService extends TypeOrmQueryService acc) { return position; } + return acc; }, -1); diff --git a/server/src/metadata/field-metadata/hooks/before-create-one-field.hook.ts b/server/src/metadata/field-metadata/hooks/before-create-one-field.hook.ts index 9eb09c592..ff8833c8c 100644 --- a/server/src/metadata/field-metadata/hooks/before-create-one-field.hook.ts +++ b/server/src/metadata/field-metadata/hooks/before-create-one-field.hook.ts @@ -16,11 +16,13 @@ export class BeforeCreateOneField context: any, ): Promise> { const workspaceId = context?.req?.user?.workspace?.id; + if (!workspaceId) { throw new UnauthorizedException(); } instance.input.workspaceId = workspaceId; + return instance; } } diff --git a/server/src/metadata/field-metadata/utils/__tests__/generate-target-column-map.spec.ts b/server/src/metadata/field-metadata/utils/__tests__/generate-target-column-map.spec.ts index 3910cbf71..dbedbe8be 100644 --- a/server/src/metadata/field-metadata/utils/__tests__/generate-target-column-map.spec.ts +++ b/server/src/metadata/field-metadata/utils/__tests__/generate-target-column-map.spec.ts @@ -10,6 +10,7 @@ describe('generateTargetColumnMap', () => { false, 'name', ); + expect(textMap).toEqual({ value: 'name' }); const linkMap = generateTargetColumnMap( @@ -17,6 +18,7 @@ describe('generateTargetColumnMap', () => { false, 'website', ); + expect(linkMap).toEqual({ label: 'websiteLabel', url: 'websiteUrl' }); const currencyMap = generateTargetColumnMap( @@ -24,6 +26,7 @@ describe('generateTargetColumnMap', () => { true, 'price', ); + expect(currencyMap).toEqual({ amountMicros: '_priceAmountMicros', currencyCode: '_priceCurrencyCode', diff --git a/server/src/metadata/field-metadata/utils/__tests__/serialize-default-value.spec.ts b/server/src/metadata/field-metadata/utils/__tests__/serialize-default-value.spec.ts index 1bd493390..5df7b0319 100644 --- a/server/src/metadata/field-metadata/utils/__tests__/serialize-default-value.spec.ts +++ b/server/src/metadata/field-metadata/utils/__tests__/serialize-default-value.spec.ts @@ -40,6 +40,7 @@ describe('serializeDefaultValue', () => { it('should handle Date static default value', () => { const date = new Date('2023-01-01'); + expect(serializeDefaultValue(date)).toBe(`'${date.toISOString()}'`); }); }); diff --git a/server/src/metadata/object-metadata/hooks/before-create-one-object.hook.ts b/server/src/metadata/object-metadata/hooks/before-create-one-object.hook.ts index b8ce7b638..08d6b86fa 100644 --- a/server/src/metadata/object-metadata/hooks/before-create-one-object.hook.ts +++ b/server/src/metadata/object-metadata/hooks/before-create-one-object.hook.ts @@ -36,6 +36,7 @@ export class BeforeCreateOneObject ); } instance.input.workspaceId = workspaceId; + return instance; } } diff --git a/server/src/metadata/object-metadata/object-metadata.service.ts b/server/src/metadata/object-metadata/object-metadata.service.ts index 84a1a8e02..8e0c9bdf6 100644 --- a/server/src/metadata/object-metadata/object-metadata.service.ts +++ b/server/src/metadata/object-metadata/object-metadata.service.ts @@ -197,6 +197,7 @@ export class ObjectMetadataService extends TypeOrmQueryService { if (field.name === 'id') { return; diff --git a/server/src/metadata/relation-metadata/hooks/before-create-one-relation.hook.ts b/server/src/metadata/relation-metadata/hooks/before-create-one-relation.hook.ts index 71f309b42..57f2dd9f9 100644 --- a/server/src/metadata/relation-metadata/hooks/before-create-one-relation.hook.ts +++ b/server/src/metadata/relation-metadata/hooks/before-create-one-relation.hook.ts @@ -22,6 +22,7 @@ export class BeforeCreateOneRelation } instance.input.workspaceId = workspaceId; + return instance; } } diff --git a/server/src/metadata/relation-metadata/relation-metadata.service.ts b/server/src/metadata/relation-metadata/relation-metadata.service.ts index 51be1bda6..505bc77dc 100644 --- a/server/src/metadata/relation-metadata/relation-metadata.service.ts +++ b/server/src/metadata/relation-metadata/relation-metadata.service.ts @@ -97,6 +97,7 @@ export class RelationMetadataService extends TypeOrmQueryService { acc[curr.id] = curr; + return acc; }, {} as { [key: string]: ObjectMetadataEntity }); @@ -169,6 +170,7 @@ export class RelationMetadataService extends TypeOrmQueryService { acc[migration.name] = migration; + return acc; }, {}); diff --git a/server/src/workspace/services/scalars-explorer.service.ts b/server/src/workspace/services/scalars-explorer.service.ts index 31ca6c08e..a3576ed80 100644 --- a/server/src/workspace/services/scalars-explorer.service.ts +++ b/server/src/workspace/services/scalars-explorer.service.ts @@ -11,6 +11,7 @@ export class ScalarsExplorerService { constructor() { this.scalarImplementations = scalars.reduce((acc, scalar) => { acc[scalar.name] = scalar; + return acc; }, {}); } @@ -25,6 +26,7 @@ export class ScalarsExplorerService { for (const typeName in typeMap) { const type = typeMap[typeName]; + if (isScalarType(type) && !typeName.startsWith('__')) { usedScalarNames.push(type.name); } @@ -40,6 +42,7 @@ export class ScalarsExplorerService { for (const scalarName of usedScalarNames) { const scalarImplementation = this.getScalarImplementation(scalarName); + if (scalarImplementation) { scalarResolvers[scalarName] = scalarImplementation; } diff --git a/server/src/workspace/utils/__tests__/get-resolver-name.spec.ts b/server/src/workspace/utils/__tests__/get-resolver-name.spec.ts index 2e9d4dbac..413cee626 100644 --- a/server/src/workspace/utils/__tests__/get-resolver-name.spec.ts +++ b/server/src/workspace/utils/__tests__/get-resolver-name.spec.ts @@ -23,6 +23,7 @@ describe('getResolverName', () => { it('should throw an error for an unknown resolver type', () => { const unknownType = 'unknownType'; + expect(() => getResolverName( metadata, diff --git a/server/src/workspace/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data.ts b/server/src/workspace/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data.ts index 1a208848b..4fd2fd646 100644 --- a/server/src/workspace/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data.ts +++ b/server/src/workspace/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data.ts @@ -18,14 +18,17 @@ export const demoObjectsPrefillData = async ( id: object.id, fields: object.fields.reduce((acc, field) => { acc[field.name] = field.id; + return acc; }, {}), }; + return acc; }, {}); // TODO: udnerstand why only with this createQueryRunner transaction below works const queryRunner = workspaceDataSource.createQueryRunner(); + await queryRunner.connect(); workspaceDataSource.transaction(async (entityManager: EntityManager) => { diff --git a/server/src/workspace/workspace-manager/demo-objects-prefill-data/opportunity.ts b/server/src/workspace/workspace-manager/demo-objects-prefill-data/opportunity.ts index 79227a879..b14eee434 100644 --- a/server/src/workspace/workspace-manager/demo-objects-prefill-data/opportunity.ts +++ b/server/src/workspace/workspace-manager/demo-objects-prefill-data/opportunity.ts @@ -5,6 +5,7 @@ const tableName = 'opportunity'; const getRandomProbability = () => { const firstDigit = Math.floor(Math.random() * 9) + 1; + return firstDigit / 10; }; @@ -13,6 +14,7 @@ const getRandomPipelineStepId = (pipelineStepIds: { id: string }[]) => const generateRandomAmountMicros = () => { const firstDigit = Math.floor(Math.random() * 9) + 1; + return firstDigit * 10000000000; }; diff --git a/server/src/workspace/workspace-manager/demo-objects-prefill-data/view.ts b/server/src/workspace/workspace-manager/demo-objects-prefill-data/view.ts index ac5e7a25d..7e40b47ba 100644 --- a/server/src/workspace/workspace-manager/demo-objects-prefill-data/view.ts +++ b/server/src/workspace/workspace-manager/demo-objects-prefill-data/view.ts @@ -35,6 +35,7 @@ export const viewPrefillData = async ( const viewIdMap = createdViews.raw.reduce((acc, view) => { acc[view.name] = view.id; + return acc; }, {}); diff --git a/server/src/workspace/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts b/server/src/workspace/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts index 566be4e9a..c4c801a9a 100644 --- a/server/src/workspace/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts +++ b/server/src/workspace/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts @@ -16,9 +16,11 @@ export const standardObjectsPrefillData = async ( id: object.id, fields: object.fields.reduce((acc, field) => { acc[field.name] = field.id; + return acc; }, {}), }; + return acc; }, {}); diff --git a/server/src/workspace/workspace-manager/standard-objects-prefill-data/view.ts b/server/src/workspace/workspace-manager/standard-objects-prefill-data/view.ts index ac5e7a25d..7e40b47ba 100644 --- a/server/src/workspace/workspace-manager/standard-objects-prefill-data/view.ts +++ b/server/src/workspace/workspace-manager/standard-objects-prefill-data/view.ts @@ -35,6 +35,7 @@ export const viewPrefillData = async ( const viewIdMap = createdViews.raw.reduce((acc, view) => { acc[view.name] = view.id; + return acc; }, {}); diff --git a/server/src/workspace/workspace-manager/utils/metadata.parser.ts b/server/src/workspace/workspace-manager/utils/metadata.parser.ts index 73f26cb8c..bc3148c4f 100644 --- a/server/src/workspace/workspace-manager/utils/metadata.parser.ts +++ b/server/src/workspace/workspace-manager/utils/metadata.parser.ts @@ -12,6 +12,7 @@ export class MetadataParser { if (objectMetadata) { const fields = Object.values(fieldMetadata); + return { ...objectMetadata, workspaceId, diff --git a/server/src/workspace/workspace-manager/utils/sync-metadata.util.ts b/server/src/workspace/workspace-manager/utils/sync-metadata.util.ts index 18c96742f..d2000bbf1 100644 --- a/server/src/workspace/workspace-manager/utils/sync-metadata.util.ts +++ b/server/src/workspace/workspace-manager/utils/sync-metadata.util.ts @@ -33,9 +33,11 @@ export const mapObjectMetadataByUniqueIdentifier = ( ...curr, fields: curr.fields.reduce((acc, curr) => { acc[curr.name] = curr; + return acc; }, {}), }; + return acc; }, {}); }; diff --git a/server/src/workspace/workspace-manager/workspace-manager.service.ts b/server/src/workspace/workspace-manager/workspace-manager.service.ts index c97dec498..7355b6bd4 100644 --- a/server/src/workspace/workspace-manager/workspace-manager.service.ts +++ b/server/src/workspace/workspace-manager/workspace-manager.service.ts @@ -182,6 +182,7 @@ export class WorkspaceManagerService { const createdObjectMetadataByNameSingular = createdObjectMetadata.reduce( (acc, curr) => { acc[curr.nameSingular] = curr; + return acc; }, {}, @@ -326,6 +327,7 @@ export class WorkspaceManagerService { if (value === null || typeof value !== 'object') { return [key, value]; } + return [key, filterIgnoredProperties(value, fieldPropertiesToIgnore)]; }), ); @@ -346,6 +348,7 @@ export class WorkspaceManagerService { // We only handle CHANGE here as REMOVE and CREATE are handled earlier. if (diff.type === 'CHANGE') { const property = diff.path[0]; + objectsToUpdate[objectInDB.id] = { ...objectsToUpdate[objectInDB.id], [property]: diff.value, @@ -357,12 +360,14 @@ export class WorkspaceManagerService { if (diff.type === 'CREATE') { const fieldName = diff.path[0]; const fieldMetadata = standardObjectFields[fieldName]; + fieldsToCreate.push(fieldMetadata); } if (diff.type === 'CHANGE') { const fieldName = diff.path[0]; const property = diff.path[diff.path.length - 1]; const fieldMetadata = objectInDBFields[fieldName]; + fieldsToUpdate[fieldMetadata.id] = { ...fieldsToUpdate[fieldMetadata.id], [property]: diff.value, @@ -371,6 +376,7 @@ export class WorkspaceManagerService { if (diff.type === 'REMOVE') { const fieldName = diff.path[0]; const fieldMetadata = objectInDBFields[fieldName]; + fieldsToDelete.push(fieldMetadata); } } @@ -429,6 +435,7 @@ export class WorkspaceManagerService { await this.workspaceDataSourceService.connectToWorkspaceDataSource( workspaceId, ); + await workspaceDataSource.query( `comment on schema ${schemaName} is e'@graphql({"max_rows": 60})'`, ); @@ -460,6 +467,7 @@ export class WorkspaceManagerService { createdObjectMetadata, ); } + /** * * We are prefilling a few demo objects with data to make it easier for the user to get started. diff --git a/server/src/workspace/workspace-query-builder/factories/args-string.factory.ts b/server/src/workspace/workspace-query-builder/factories/args-string.factory.ts index 9e06e4532..35a116ac0 100644 --- a/server/src/workspace/workspace-query-builder/factories/args-string.factory.ts +++ b/server/src/workspace/workspace-query-builder/factories/args-string.factory.ts @@ -9,6 +9,7 @@ import { ArgsAliasFactory } from './args-alias.factory'; @Injectable() export class ArgsStringFactory { constructor(private readonly argsAliasFactory: ArgsAliasFactory) {} + create( initialArgs: Record | undefined, fieldMetadataCollection: FieldMetadataInterface[], diff --git a/server/src/workspace/workspace-query-builder/utils/__tests__/stringify-without-key-quote.spec.ts b/server/src/workspace/workspace-query-builder/utils/__tests__/stringify-without-key-quote.spec.ts index 56b10aaef..0bfad1574 100644 --- a/server/src/workspace/workspace-query-builder/utils/__tests__/stringify-without-key-quote.spec.ts +++ b/server/src/workspace/workspace-query-builder/utils/__tests__/stringify-without-key-quote.spec.ts @@ -4,6 +4,7 @@ describe('stringifyWithoutKeyQuote', () => { test('should stringify object correctly without quotes around keys', () => { const obj = { name: 'John', age: 30, isAdmin: false }; const result = stringifyWithoutKeyQuote(obj); + expect(result).toBe('{name:"John",age:30,isAdmin:false}'); }); @@ -14,6 +15,7 @@ describe('stringifyWithoutKeyQuote', () => { address: { city: 'New York', zipCode: 10001 }, }; const result = stringifyWithoutKeyQuote(obj); + expect(result).toBe( '{name:"John",age:30,address:{city:"New York",zipCode:10001}}', ); @@ -26,6 +28,7 @@ describe('stringifyWithoutKeyQuote', () => { hobbies: ['reading', 'movies', 'hiking'], }; const result = stringifyWithoutKeyQuote(obj); + expect(result).toBe( '{name:"John",age:30,hobbies:["reading","movies","hiking"]}', ); @@ -34,6 +37,7 @@ describe('stringifyWithoutKeyQuote', () => { test('should handle empty objects', () => { const obj = {}; const result = stringifyWithoutKeyQuote(obj); + expect(result).toBe('{}'); }); @@ -41,6 +45,7 @@ describe('stringifyWithoutKeyQuote', () => { const num = 10; const str = 'Hello'; const bool = false; + expect(stringifyWithoutKeyQuote(num)).toBe('10'); expect(stringifyWithoutKeyQuote(str)).toBe('"Hello"'); expect(stringifyWithoutKeyQuote(bool)).toBe('false'); diff --git a/server/src/workspace/workspace-query-builder/utils/get-field-arguments-by-key.util.ts b/server/src/workspace/workspace-query-builder/utils/get-field-arguments-by-key.util.ts index b2854f20e..33e0b761b 100644 --- a/server/src/workspace/workspace-query-builder/utils/get-field-arguments-by-key.util.ts +++ b/server/src/workspace/workspace-query-builder/utils/get-field-arguments-by-key.util.ts @@ -63,6 +63,7 @@ const parseValueNode = ( case Kind.OBJECT: return valueNode.fields.reduce((obj, field) => { obj[field.name.value] = parseValueNode(field.value, variables); + return obj; }, {}); default: diff --git a/server/src/workspace/workspace-schema-builder/factories/mutation-type.factory.ts b/server/src/workspace/workspace-schema-builder/factories/mutation-type.factory.ts index a5940ad9d..5a5fbddcd 100644 --- a/server/src/workspace/workspace-schema-builder/factories/mutation-type.factory.ts +++ b/server/src/workspace/workspace-schema-builder/factories/mutation-type.factory.ts @@ -11,6 +11,7 @@ import { ObjectTypeName, RootTypeFactory } from './root-type.factory'; @Injectable() export class MutationTypeFactory { constructor(private readonly rootTypeFactory: RootTypeFactory) {} + create( objectMetadataCollection: ObjectMetadataInterface[], workspaceResolverMethodNames: WorkspaceResolverBuilderMutationMethodNames[], diff --git a/server/src/workspace/workspace-schema-builder/factories/query-type.factory.ts b/server/src/workspace/workspace-schema-builder/factories/query-type.factory.ts index fec7594b9..b837db179 100644 --- a/server/src/workspace/workspace-schema-builder/factories/query-type.factory.ts +++ b/server/src/workspace/workspace-schema-builder/factories/query-type.factory.ts @@ -11,6 +11,7 @@ import { ObjectTypeName, RootTypeFactory } from './root-type.factory'; @Injectable() export class QueryTypeFactory { constructor(private readonly rootTypeFactory: RootTypeFactory) {} + create( objectMetadataCollection: ObjectMetadataInterface[], workspaceResolverMethodNames: WorkspaceResolverBuilderQueryMethodNames[], diff --git a/server/src/workspace/workspace-schema-builder/graphql-types/scalars/big-float.scalar.ts b/server/src/workspace/workspace-schema-builder/graphql-types/scalars/big-float.scalar.ts index 17025f41f..a83d08805 100644 --- a/server/src/workspace/workspace-schema-builder/graphql-types/scalars/big-float.scalar.ts +++ b/server/src/workspace/workspace-schema-builder/graphql-types/scalars/big-float.scalar.ts @@ -15,6 +15,7 @@ export const BigFloatScalarType = new GraphQLScalarType({ if (ast.kind === Kind.FLOAT || ast.kind === Kind.INT) { return String(ast.value); } + return null; }, }); diff --git a/server/src/workspace/workspace-schema-builder/graphql-types/scalars/cursor.scalar.ts b/server/src/workspace/workspace-schema-builder/graphql-types/scalars/cursor.scalar.ts index abb4d3855..882913460 100644 --- a/server/src/workspace/workspace-schema-builder/graphql-types/scalars/cursor.scalar.ts +++ b/server/src/workspace/workspace-schema-builder/graphql-types/scalars/cursor.scalar.ts @@ -7,18 +7,21 @@ export const CursorScalarType = new GraphQLScalarType({ if (typeof value !== 'string') { throw new Error('Cursor must be a string'); } + return value; }, parseValue(value) { if (typeof value !== 'string') { throw new Error('Cursor must be a string'); } + return value; }, parseLiteral(ast) { if (ast.kind !== Kind.STRING) { throw new Error('Cursor must be a string'); } + return ast.value; }, }); diff --git a/server/src/workspace/workspace-schema-builder/utils/__tests__/get-field-metadata-type.spec.ts b/server/src/workspace/workspace-schema-builder/utils/__tests__/get-field-metadata-type.spec.ts index 8c2a729a7..ee579c723 100644 --- a/server/src/workspace/workspace-schema-builder/utils/__tests__/get-field-metadata-type.spec.ts +++ b/server/src/workspace/workspace-schema-builder/utils/__tests__/get-field-metadata-type.spec.ts @@ -14,6 +14,7 @@ describe('getFieldMetadataType', () => { it('should throw an error for an unknown type', () => { const unknownType = 'unknownType'; + expect(() => getFieldMetadataType(unknownType)).toThrow( `Unknown type ${unknownType}`, ); diff --git a/server/src/workspace/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts b/server/src/workspace/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts index c90ab63d3..cdb0c6b6b 100644 --- a/server/src/workspace/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts +++ b/server/src/workspace/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts @@ -48,6 +48,7 @@ describe('getResolverArgs', () => { // Test for an unknown resolver type it('should throw an error for an unknown resolver type', () => { const unknownType = 'unknownType'; + expect(() => getResolverArgs(unknownType as WorkspaceResolverBuilderMethodNames), ).toThrow(`Unknown resolver type: ${unknownType}`); diff --git a/server/src/workspace/workspace.factory.ts b/server/src/workspace/workspace.factory.ts index 88e1b0f79..699bb2b47 100644 --- a/server/src/workspace/workspace.factory.ts +++ b/server/src/workspace/workspace.factory.ts @@ -73,6 +73,7 @@ export class WorkspaceFactory { objectMetadataCollection, workspaceResolverBuilderMethodNames, ); + usedScalarNames = this.scalarsExplorerService.getUsedScalarNames(autoGeneratedSchema); typeDefs = printSchema(autoGeneratedSchema); diff --git a/server/yarn.lock b/server/yarn.lock index 3de8ba2ac..c25bf43e2 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -1270,7 +1270,7 @@ dependencies: tslib "^2.5.0" -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== @@ -2622,6 +2622,51 @@ resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== +"@stylistic/eslint-plugin-js@1.5.0", "@stylistic/eslint-plugin-js@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.5.0.tgz#d502d2470f114a4a2b682930ade6ca4eeebaa9f8" + integrity sha512-TuGQv1bsIshkbJUInCewp4IUWy24W5RFiVNMV0quPSkuZ8gsYoqq6kLHvvaxpjxN9TvwSoOIwnhgrYKei2Tgcw== + dependencies: + acorn "^8.11.2" + escape-string-regexp "^4.0.0" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + graphemer "^1.4.0" + +"@stylistic/eslint-plugin-jsx@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-1.5.0.tgz#f8fcb23dcc75b455650426887b24711c4831bee6" + integrity sha512-sqFdA1mS0jwovAatS8xFAiwxPbcy69S2AUjrGMxyhxaKbELPjvqbxPYJL+35ylT0xqirUlm118xZIFDooC8koQ== + dependencies: + "@stylistic/eslint-plugin-js" "^1.5.0" + estraverse "^5.3.0" + +"@stylistic/eslint-plugin-plus@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-1.5.0.tgz#49ad5560bb6b699c1b5d2940586a4048ebc35b1c" + integrity sha512-+A4qXFuM6V7x25Hj+xqfVIUbEckG+MUSvL6m83M6YtRq3d5zLW+giKKEL7eSCAw12MwnoDwPcEhqIJK6BRDR3w== + dependencies: + "@typescript-eslint/utils" "^6.13.2" + +"@stylistic/eslint-plugin-ts@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-1.5.0.tgz#ed84ffe3c2809748337079b6309f2906ff268e02" + integrity sha512-OusNGWRXnOV+ywnoXmBFoMtU6Ig/MX1bEu5Jigqmy2cIT8GRMMn7jUl/bXevkv2o66MYnC7PT1Q/3GvN7t0/eg== + dependencies: + "@stylistic/eslint-plugin-js" "1.5.0" + "@typescript-eslint/utils" "^6.13.2" + graphemer "^1.4.0" + +"@stylistic/eslint-plugin@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-1.5.0.tgz#d89c813c9f2121a33e1e6d3d3c521ff015964e61" + integrity sha512-XmlB5nxk06nlnx1/ka0l+WNqHcjnnXfDts4ZaCvrpCY/6l8lNtHwLwdCKF/UpBYNuRWI/HLWCTtQc0jjfwrfBA== + dependencies: + "@stylistic/eslint-plugin-js" "1.5.0" + "@stylistic/eslint-plugin-jsx" "1.5.0" + "@stylistic/eslint-plugin-plus" "1.5.0" + "@stylistic/eslint-plugin-ts" "1.5.0" + "@tokenizer/token@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" @@ -2868,7 +2913,7 @@ expect "^28.0.0" pretty-format "^28.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -3086,7 +3131,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/semver@^7.3.12": +"@types/semver@^7.3.12", "@types/semver@^7.5.0": version "7.5.6" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== @@ -3184,6 +3229,14 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" +"@typescript-eslint/scope-manager@6.13.2": + version "6.13.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.13.2.tgz#5fa4e4adace028dafac212c770640b94e7b61052" + integrity sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA== + dependencies: + "@typescript-eslint/types" "6.13.2" + "@typescript-eslint/visitor-keys" "6.13.2" + "@typescript-eslint/type-utils@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" @@ -3199,6 +3252,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== +"@typescript-eslint/types@6.13.2": + version "6.13.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.13.2.tgz#c044aac24c2f6cefb8e921e397acad5417dd0ae6" + integrity sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg== + "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" @@ -3212,6 +3270,19 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@6.13.2": + version "6.13.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.2.tgz#ae556ee154c1acf025b48d37c3ef95a1d55da258" + integrity sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w== + dependencies: + "@typescript-eslint/types" "6.13.2" + "@typescript-eslint/visitor-keys" "6.13.2" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/utils@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" @@ -3226,6 +3297,19 @@ eslint-scope "^5.1.1" semver "^7.3.7" +"@typescript-eslint/utils@^6.13.2": + version "6.13.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.13.2.tgz#8eb89e53adc6d703a879b131e528807245486f89" + integrity sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.13.2" + "@typescript-eslint/types" "6.13.2" + "@typescript-eslint/typescript-estree" "6.13.2" + semver "^7.5.4" + "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" @@ -3234,6 +3318,14 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" +"@typescript-eslint/visitor-keys@6.13.2": + version "6.13.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz#e0a4a80cf842bb08e6127b903284166ac4a5594c" + integrity sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw== + dependencies: + "@typescript-eslint/types" "6.13.2" + eslint-visitor-keys "^3.4.1" + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -3435,7 +3527,7 @@ acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f" integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== -acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.11.2, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: version "8.11.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== @@ -4981,7 +5073,7 @@ estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.1.0, estraverse@^5.2.0: +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== @@ -8717,6 +8809,11 @@ tree-kill@1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + ts-essentials@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38"