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
This commit is contained in:
@ -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"] }
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
"trailingComma": "all",
|
||||
"brakeBeforeElse": false
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -11,24 +11,28 @@ export class ApiRestController {
|
||||
@Get()
|
||||
async handleApiGet(@Req() request: Request): Promise<object> {
|
||||
const result = await this.apiRestService.get(request);
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
@Delete()
|
||||
async handleApiDelete(@Req() request: Request): Promise<object> {
|
||||
const result = await this.apiRestService.delete(request);
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async handleApiPost(@Req() request: Request): Promise<object> {
|
||||
const result = await this.apiRestService.create(request);
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
@Put()
|
||||
async handleApiPut(@Req() request: Request): Promise<object> {
|
||||
const result = await this.apiRestService.update(request);
|
||||
|
||||
return result.data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: [
|
||||
|
||||
@ -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 <RecordOrderBy>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}` } };
|
||||
|
||||
@ -47,6 +47,7 @@ export class AuthResolver {
|
||||
const { exists } = await this.authService.checkUserExists(
|
||||
checkUserExistsInput.email,
|
||||
);
|
||||
|
||||
return { exists };
|
||||
}
|
||||
|
||||
|
||||
@ -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<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.isAuthGoogleEnabled()) {
|
||||
throw new NotFoundException('Google auth is not enabled');
|
||||
}
|
||||
|
||||
new GoogleStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ export class TokenService {
|
||||
|
||||
async generateAccessToken(userId: string): Promise<AuthToken> {
|
||||
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<AuthToken> {
|
||||
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<AuthToken> {
|
||||
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 };
|
||||
}
|
||||
|
||||
|
||||
@ -59,6 +59,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||
picture: photos?.[0]?.value,
|
||||
workspaceInviteHash: state.workspaceInviteHash,
|
||||
};
|
||||
|
||||
done(null, user);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}`);
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ export class BeforeCreateOneRefreshToken<T extends RefreshToken>
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,6 +72,7 @@ export class UserService extends TypeOrmQueryService<User> {
|
||||
const user = await this.userRepository.findOneBy({
|
||||
id: userId,
|
||||
});
|
||||
|
||||
assert(user, 'User not found');
|
||||
|
||||
await this.userRepository.delete(user.id);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
|
||||
async deleteWorkspace(id: string) {
|
||||
const workspace = await this.workspaceRepository.findOneBy({ id });
|
||||
|
||||
assert(workspace, 'Workspace not found');
|
||||
|
||||
await this.workspaceManagerService.delete(id);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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<string>('PG_DATABASE_URL'),
|
||||
type: 'postgres',
|
||||
|
||||
@ -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<string>('PG_DATABASE_URL'),
|
||||
type: 'postgres',
|
||||
|
||||
@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -13,5 +13,6 @@ const toBoolean = (value: any) => {
|
||||
if (['false', 'off', 'no', '0'].includes(value.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
@ -10,5 +10,6 @@ const toNumber = (value: any) => {
|
||||
if (typeof value === 'string') {
|
||||
return isNaN(+value) ? undefined : toNumber(+value);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,6 +159,7 @@ export const validate = (config: Record<string, unknown>) => {
|
||||
const validatedConfig = plainToClass(EnvironmentVariables, config);
|
||||
|
||||
const errors = validateSync(validatedConfig);
|
||||
|
||||
assert(!errors.length, errors.toString());
|
||||
|
||||
return validatedConfig;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -67,6 +67,7 @@ const loggerModuleFactory = async (
|
||||
environmentService: EnvironmentService,
|
||||
): Promise<LoggerModuleOptions> => {
|
||||
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: {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -7,6 +7,7 @@ export class MemoryStorageDefaultSerializer<T>
|
||||
if (typeof item !== 'string') {
|
||||
throw new Error('DefaultSerializer can only serialize strings');
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ export class PgBossDriver implements MessageQueueDriver {
|
||||
constructor(options: PgBossDriverOptions) {
|
||||
this.pgBoss = new PgBoss(options);
|
||||
}
|
||||
|
||||
async stop() {
|
||||
await this.pgBoss.stop();
|
||||
}
|
||||
|
||||
@ -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 || [],
|
||||
|
||||
@ -38,6 +38,7 @@ const bootstrap = async () => {
|
||||
}),
|
||||
);
|
||||
const loggerService = app.get(LoggerService);
|
||||
|
||||
app.useLogger(loggerService);
|
||||
app.useLogger(app.get(EnvironmentService).getLogLevels());
|
||||
|
||||
|
||||
@ -129,6 +129,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
if (position > acc) {
|
||||
return position;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, -1);
|
||||
|
||||
|
||||
@ -16,11 +16,13 @@ export class BeforeCreateOneField<T extends CreateFieldInput>
|
||||
context: any,
|
||||
): Promise<CreateOneInputType<T>> {
|
||||
const workspaceId = context?.req?.user?.workspace?.id;
|
||||
|
||||
if (!workspaceId) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
instance.input.workspaceId = workspaceId;
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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()}'`);
|
||||
});
|
||||
});
|
||||
|
||||
@ -36,6 +36,7 @@ export class BeforeCreateOneObject<T extends CreateObjectInput>
|
||||
);
|
||||
}
|
||||
instance.input.workspaceId = workspaceId;
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,6 +197,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
if (fieldMetadata.type === FieldMetadataType.RELATION) {
|
||||
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
@ -282,6 +283,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
("objectMetadataId", "type", "name")
|
||||
VALUES ('${createdObjectMetadata.id}', 'table', 'All ${createdObjectMetadata.namePlural}') RETURNING *`,
|
||||
);
|
||||
|
||||
createdObjectMetadata.fields.map(async (field, index) => {
|
||||
if (field.name === 'id') {
|
||||
return;
|
||||
|
||||
@ -22,6 +22,7 @@ export class BeforeCreateOneRelation<T extends CreateRelationInput>
|
||||
}
|
||||
|
||||
instance.input.workspaceId = workspaceId;
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,6 +97,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
|
||||
const objectMetadataMap = objectMetadataEntries.reduce((acc, curr) => {
|
||||
acc[curr.id] = curr;
|
||||
|
||||
return acc;
|
||||
}, {} as { [key: string]: ObjectMetadataEntity });
|
||||
|
||||
@ -169,6 +170,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
if (fieldMetadata.type === FieldMetadataType.RELATION) {
|
||||
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
||||
@ -94,11 +94,13 @@ export class WorkspaceMigrationFactory {
|
||||
action: WorkspaceMigrationColumnActionType.CREATE,
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
): WorkspaceMigrationColumnAction[];
|
||||
|
||||
createColumnActions(
|
||||
action: WorkspaceMigrationColumnActionType.ALTER,
|
||||
previousFieldMetadata: FieldMetadataInterface,
|
||||
nextFieldMetadata: FieldMetadataInterface,
|
||||
): WorkspaceMigrationColumnAction[];
|
||||
|
||||
createColumnActions(
|
||||
action:
|
||||
| WorkspaceMigrationColumnActionType.CREATE
|
||||
|
||||
@ -31,6 +31,7 @@ export class WorkspaceMigrationService {
|
||||
const insertedStandardMigrationsMapByName =
|
||||
insertedStandardMigrations.reduce((acc, migration) => {
|
||||
acc[migration.name] = migration;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ describe('getResolverName', () => {
|
||||
|
||||
it('should throw an error for an unknown resolver type', () => {
|
||||
const unknownType = 'unknownType';
|
||||
|
||||
expect(() =>
|
||||
getResolverName(
|
||||
metadata,
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ export const viewPrefillData = async (
|
||||
|
||||
const viewIdMap = createdViews.raw.reduce((acc, view) => {
|
||||
acc[view.name] = view.id;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
||||
@ -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;
|
||||
}, {});
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ export const viewPrefillData = async (
|
||||
|
||||
const viewIdMap = createdViews.raw.reduce((acc, view) => {
|
||||
acc[view.name] = view.id;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ export class MetadataParser {
|
||||
|
||||
if (objectMetadata) {
|
||||
const fields = Object.values(fieldMetadata);
|
||||
|
||||
return {
|
||||
...objectMetadata,
|
||||
workspaceId,
|
||||
|
||||
@ -33,9 +33,11 @@ export const mapObjectMetadataByUniqueIdentifier = (
|
||||
...curr,
|
||||
fields: curr.fields.reduce((acc, curr) => {
|
||||
acc[curr.name] = curr;
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -9,6 +9,7 @@ import { ArgsAliasFactory } from './args-alias.factory';
|
||||
@Injectable()
|
||||
export class ArgsStringFactory {
|
||||
constructor(private readonly argsAliasFactory: ArgsAliasFactory) {}
|
||||
|
||||
create(
|
||||
initialArgs: Record<string, any> | undefined,
|
||||
fieldMetadataCollection: FieldMetadataInterface[],
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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[],
|
||||
|
||||
@ -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[],
|
||||
|
||||
@ -15,6 +15,7 @@ export const BigFloatScalarType = new GraphQLScalarType({
|
||||
if (ast.kind === Kind.FLOAT || ast.kind === Kind.INT) {
|
||||
return String(ast.value);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@ -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}`,
|
||||
);
|
||||
|
||||
@ -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}`);
|
||||
|
||||
@ -73,6 +73,7 @@ export class WorkspaceFactory {
|
||||
objectMetadataCollection,
|
||||
workspaceResolverBuilderMethodNames,
|
||||
);
|
||||
|
||||
usedScalarNames =
|
||||
this.scalarsExplorerService.getUsedScalarNames(autoGeneratedSchema);
|
||||
typeDefs = printSchema(autoGeneratedSchema);
|
||||
|
||||
107
server/yarn.lock
107
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"
|
||||
|
||||
Reference in New Issue
Block a user