[AUDIT] Run knip over twenty-server (#21159)

# Introduction
Run [knip](https://knip.dev/) over twenty-server
Used config:
```json
{
  "$schema": "https://unpkg.com/knip@5/schema.json",
  "workspaces": {
    "packages/twenty-server": {
      "entry": [
        "src/main.ts",
        "src/command/command.ts",
        "src/queue-worker/queue-worker.ts",
        "src/database/scripts/setup-db.ts",
        "src/database/scripts/truncate-db.ts",
        "src/database/clickHouse/migrations/run-migrations.ts",
        "src/database/clickHouse/seeds/run-seeds.ts",
        "src/instrument.ts",
        "lingui.config.ts",
        "test/integration/graphql/codegen/index.ts",
        "test/integration/utils/setup-test.ts",
        "test/integration/utils/teardown-test.ts",
        "scripts/**/*.ts",
        "**/*.spec.ts",
        "**/*.integration-spec.ts"
      ],
      "project": ["src/**/*.ts", "test/**/*.ts", "scripts/**/*.ts"],
      "ignore": [
        "src/database/typeorm/**/migrations/**",
        "src/database/typeorm/**/*.entity.ts",
        "**/*.workspace-entity.ts",
        "**/logic-function-resource/constants/seed-project/**"
      ],
      "ignoreDependencies": ["@types/psl", "@types/aws-lambda"],
      "ignoreBinaries": ["nest", "lingui", "typeorm"]
    }
  }
}
```
This commit is contained in:
Paul Rastoin
2026-06-04 12:05:22 +02:00
committed by GitHub
parent 4ad8d8e98e
commit 3d49642d12
140 changed files with 55 additions and 4643 deletions
@@ -1,209 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
type ClickHouseFilterResult = {
whereClause: string;
params: Record<string, unknown>;
};
type FilterOperator =
| 'eq'
| 'neq'
| 'gt'
| 'gte'
| 'lt'
| 'lte'
| 'in'
| 'is'
| 'like'
| 'ilike'
| 'startsWith'
| 'endsWith'
| 'contains';
const getClickHouseType = (value: unknown): string => {
if (typeof value === 'string') {
return 'String';
}
if (typeof value === 'number') {
return Number.isInteger(value) ? 'Int64' : 'Float64';
}
if (typeof value === 'boolean') {
return 'Bool';
}
return 'String';
};
const buildOperatorCondition = (
fieldName: string,
operator: FilterOperator,
paramName: string,
paramType: string,
): string => {
switch (operator) {
case 'eq':
return `"${fieldName}" = {${paramName}:${paramType}}`;
case 'neq':
return `"${fieldName}" != {${paramName}:${paramType}}`;
case 'gt':
return `"${fieldName}" > {${paramName}:${paramType}}`;
case 'gte':
return `"${fieldName}" >= {${paramName}:${paramType}}`;
case 'lt':
return `"${fieldName}" < {${paramName}:${paramType}}`;
case 'lte':
return `"${fieldName}" <= {${paramName}:${paramType}}`;
case 'in':
return `"${fieldName}" IN {${paramName}:Array(${paramType})}`;
case 'is':
return `"${fieldName}" IS NULL`;
case 'like':
return `"${fieldName}" LIKE {${paramName}:${paramType}}`;
case 'ilike':
return `lower("${fieldName}") LIKE lower({${paramName}:${paramType}})`;
case 'startsWith':
return `"${fieldName}" LIKE concat({${paramName}:${paramType}}, '%')`;
case 'endsWith':
return `"${fieldName}" LIKE concat('%', {${paramName}:${paramType}})`;
case 'contains':
return `"${fieldName}" LIKE concat('%', {${paramName}:${paramType}}, '%')`;
default:
return `"${fieldName}" = {${paramName}:${paramType}}`;
}
};
const parseFilterValue = (
fieldName: string,
filterValue: unknown,
paramIndex: number,
): { conditions: string[]; params: Record<string, unknown> } => {
const conditions: string[] = [];
const params: Record<string, unknown> = {};
if (!isDefined(filterValue) || typeof filterValue !== 'object') {
return { conditions, params };
}
const filterObj = filterValue as Record<string, unknown>;
for (const [operator, value] of Object.entries(filterObj)) {
if (!isDefined(value)) {
continue;
}
const paramName = `${fieldName}_${paramIndex}_${operator}`;
if (operator === 'is') {
if (value === 'NULL') {
conditions.push(`"${fieldName}" IS NULL`);
} else if (value === 'NOT_NULL') {
conditions.push(`"${fieldName}" IS NOT NULL`);
}
continue;
}
const paramType = getClickHouseType(value);
conditions.push(
buildOperatorCondition(
fieldName,
operator as FilterOperator,
paramName,
paramType,
),
);
params[paramName] = value;
}
return { conditions, params };
};
export const parseClickHouseFilter = (
filter: ObjectRecordFilter | undefined,
): ClickHouseFilterResult => {
if (!isDefined(filter) || Object.keys(filter).length === 0) {
return { whereClause: '', params: {} };
}
const allConditions: string[] = [];
const allParams: Record<string, unknown> = {};
let paramIndex = 0;
// Handle 'and' operator
if ('and' in filter && Array.isArray(filter.and)) {
const andConditions: string[] = [];
for (const subFilter of filter.and) {
const { whereClause, params } = parseClickHouseFilter(
subFilter as ObjectRecordFilter,
);
if (whereClause) {
andConditions.push(`(${whereClause})`);
Object.assign(allParams, params);
}
}
if (andConditions.length > 0) {
allConditions.push(andConditions.join(' AND '));
}
}
// Handle 'or' operator
if ('or' in filter && Array.isArray(filter.or)) {
const orConditions: string[] = [];
for (const subFilter of filter.or) {
const { whereClause, params } = parseClickHouseFilter(
subFilter as ObjectRecordFilter,
);
if (whereClause) {
orConditions.push(`(${whereClause})`);
Object.assign(allParams, params);
}
}
if (orConditions.length > 0) {
allConditions.push(`(${orConditions.join(' OR ')})`);
}
}
// Handle 'not' operator
if ('not' in filter && isDefined(filter.not)) {
const { whereClause, params } = parseClickHouseFilter(
filter.not as ObjectRecordFilter,
);
if (whereClause) {
allConditions.push(`NOT (${whereClause})`);
Object.assign(allParams, params);
}
}
// Handle field-level filters
for (const [fieldName, filterValue] of Object.entries(filter)) {
if (['and', 'or', 'not'].includes(fieldName)) {
continue;
}
const { conditions, params } = parseFilterValue(
fieldName,
filterValue,
paramIndex++,
);
allConditions.push(...conditions);
Object.assign(allParams, params);
}
return {
whereClause: allConditions.join(' AND '),
params: allParams,
};
};
@@ -1,28 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
export const parseClickHouseOrderBy = (
orderBy: Array<Record<string, string>> | undefined,
): string => {
if (!isDefined(orderBy) || orderBy.length === 0) {
return '';
}
const orderClauses: string[] = [];
for (const orderItem of orderBy) {
for (const [fieldName, direction] of Object.entries(orderItem)) {
const normalizedDirection = direction
.toUpperCase()
.replace('NULLS_FIRST', 'NULLS FIRST')
.replace('NULLS_LAST', 'NULLS LAST')
.replace('ASC_NULLS_FIRST', 'ASC NULLS FIRST')
.replace('ASC_NULLS_LAST', 'ASC NULLS LAST')
.replace('DESC_NULLS_FIRST', 'DESC NULLS FIRST')
.replace('DESC_NULLS_LAST', 'DESC NULLS LAST');
orderClauses.push(`"${fieldName}" ${normalizedDirection}`);
}
}
return orderClauses.length > 0 ? `ORDER BY ${orderClauses.join(', ')}` : '';
};
@@ -1 +0,0 @@
export type CompositeFieldGroupByDefinition = Record<string, boolean>;
@@ -1,8 +0,0 @@
import { type CompositeFieldGroupByDefinition } from 'src/engine/api/common/common-args-processors/group-by-arg-processor/types/composite-field-group-by-definition.type';
import { type DateFieldGroupByDefinition } from 'src/engine/api/common/common-args-processors/group-by-arg-processor/types/date-field-group-by-definition.type';
export type FieldGroupByDefinition =
| boolean
| CompositeFieldGroupByDefinition
| DateFieldGroupByDefinition
| undefined;
@@ -1,31 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import { MAX_DEPTH } from 'src/engine/api/rest/input-request-parsers/constants/max-depth.constant';
import { type Depth } from 'src/engine/api/rest/input-request-parsers/types/depth.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export const getShouldRecurseIntoRelation = ({
depth,
flatField,
}: {
depth: Depth | undefined;
flatField: FlatFieldMetadata;
}): boolean => {
const flatFieldIsJoinColumn =
isDefined(flatField.settings) &&
'junctionTargetFieldId' in flatField.settings;
// TODO: refactor this when we remove hard-coded activity relations
const flatFieldIsActivityTarget =
flatField.name === 'noteTargets' || flatField.name === 'taskTargets';
const shouldGoOneLevelDeeper =
depth === MAX_DEPTH && isDefined(flatField.relationTargetObjectMetadataId);
const shouldRecurseIntoRelation =
shouldGoOneLevelDeeper ||
flatFieldIsActivityTarget ||
flatFieldIsJoinColumn;
return shouldRecurseIntoRelation;
};
@@ -1,34 +0,0 @@
import { AggregateOperations, FieldMetadataType } from 'twenty-shared/types';
export const computeIsNumericReturningAggregate = (
operation: AggregateOperations,
fromFieldType: FieldMetadataType,
): boolean => {
if (
operation === AggregateOperations.COUNT ||
operation === AggregateOperations.COUNT_UNIQUE_VALUES ||
operation === AggregateOperations.COUNT_EMPTY ||
operation === AggregateOperations.COUNT_NOT_EMPTY ||
operation === AggregateOperations.COUNT_TRUE ||
operation === AggregateOperations.COUNT_FALSE ||
operation === AggregateOperations.PERCENTAGE_EMPTY ||
operation === AggregateOperations.PERCENTAGE_NOT_EMPTY
) {
return true;
}
if (
operation === AggregateOperations.MIN ||
operation === AggregateOperations.MAX ||
operation === AggregateOperations.AVG ||
operation === AggregateOperations.SUM
) {
return [
FieldMetadataType.NUMBER,
FieldMetadataType.NUMERIC,
FieldMetadataType.CURRENCY,
].includes(fromFieldType);
}
return false;
};
@@ -1,97 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { getTargetObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-target-object-metadata.util';
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util';
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
type GetFieldMetadataFromGraphQLFieldArgs = {
flatObjectMetadata: FlatObjectMetadata;
graphQLField: string;
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
};
export function getFieldMetadataFromGraphQLField({
flatObjectMetadata,
graphQLField,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
}: GetFieldMetadataFromGraphQLFieldArgs): FlatFieldMetadata | undefined {
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
flatFieldMetadataMaps,
flatObjectMetadata,
);
const sourceFieldMetadataId = fieldIdByName[graphQLField];
let sourceFieldMetadata = sourceFieldMetadataId
? findFlatEntityByIdInFlatEntityMaps({
flatEntityId: sourceFieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
})
: undefined;
// If empty, it could be a morph relation
if (!isDefined(sourceFieldMetadata)) {
const morphRelationsWithTargetObjectMetadata =
getFlatFieldsFromFlatObjectMetadata(
flatObjectMetadata,
flatFieldMetadataMaps,
)
.filter(
(fieldMetadata) =>
fieldMetadata.type === FieldMetadataType.MORPH_RELATION,
)
.map((fieldMetadata) => {
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
fieldMetadata,
flatObjectMetadataMaps,
);
return {
fieldMetadata,
targetObjectMetadata,
};
});
const possibleGraphQLFieldNames: {
graphQLField: string;
fieldMetadata: FlatFieldMetadata;
targetObjectMetadata: FlatObjectMetadata;
}[] = [];
morphRelationsWithTargetObjectMetadata.map((morphRelation) => {
if (
!isFlatFieldMetadataOfType(
morphRelation.fieldMetadata,
FieldMetadataType.MORPH_RELATION,
) ||
!morphRelation.fieldMetadata.settings?.relationType
) {
return;
}
possibleGraphQLFieldNames.push({
graphQLField: morphRelation.fieldMetadata.name,
fieldMetadata: morphRelation.fieldMetadata,
targetObjectMetadata: morphRelation.targetObjectMetadata,
});
});
const fieldMetdata = possibleGraphQLFieldNames.find(
(possibleGraphQLFieldName) =>
possibleGraphQLFieldName.graphQLField === graphQLField,
)?.fieldMetadata;
if (fieldMetdata) {
sourceFieldMetadata = fieldMetdata;
}
}
return sourceFieldMetadata;
}
@@ -1,101 +0,0 @@
import {
Kind,
type FieldNode,
type GraphQLResolveInfo,
type InlineFragmentNode,
type SelectionNode,
type SelectionSetNode,
type ValueNode,
} from 'graphql';
const isFieldNode = (node: SelectionNode): node is FieldNode =>
node.kind === Kind.FIELD;
const isInlineFragmentNode = (
node: SelectionNode,
): node is InlineFragmentNode => node.kind === Kind.INLINE_FRAGMENT;
const findFieldNode = (
selectionSet: SelectionSetNode | undefined,
key: string,
): FieldNode | null => {
if (!selectionSet) return null;
let field: FieldNode | null = null;
for (const selection of selectionSet.selections) {
// We've found the field
if (isFieldNode(selection) && selection.name.value === key) {
return selection;
}
// Recursively search for the field in nested selections
if (
(isFieldNode(selection) || isInlineFragmentNode(selection)) &&
selection.selectionSet
) {
field = findFieldNode(selection.selectionSet, key);
// If we find the field in a nested selection, stop searching
if (field) break;
}
}
return field;
};
// @ts-expect-error legacy noImplicitAny
const parseValueNode = (
valueNode: ValueNode,
variables: GraphQLResolveInfo['variableValues'],
) => {
switch (valueNode.kind) {
case Kind.VARIABLE:
return variables[valueNode.name.value];
case Kind.INT:
case Kind.FLOAT:
return Number(valueNode.value);
case Kind.STRING:
case Kind.BOOLEAN:
case Kind.ENUM:
return valueNode.value;
case Kind.LIST:
// @ts-expect-error legacy noImplicitAny
return valueNode.values.map((value) => parseValueNode(value, variables));
case Kind.OBJECT:
return valueNode.fields.reduce((obj, field) => {
// @ts-expect-error legacy noImplicitAny
obj[field.name.value] = parseValueNode(field.value, variables);
return obj;
}, {});
default:
return null;
}
};
export const getFieldArgumentsByKey = (
info: GraphQLResolveInfo,
fieldKey: string,
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
): Record<string, any> => {
// Start from the first top-level field node and search recursively
const targetField = findFieldNode(info.fieldNodes[0].selectionSet, fieldKey);
// If the field is not found, throw an error
if (!targetField) {
throw new Error(`Field "${fieldKey}" not found.`);
}
// Extract the arguments from the field we've found
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
const args: Record<string, any> = {};
if (targetField.arguments && targetField.arguments.length) {
for (const arg of targetField.arguments) {
args[arg.name.value] = parseValueNode(arg.value, info.variableValues);
}
}
return args;
};
@@ -1,79 +0,0 @@
import {
WorkspaceQueryRunnerException,
WorkspaceQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
export type PgGraphQLConfig = {
atMost: number;
};
interface PgGraphQLErrorMapping {
[key: string]: (
command: string,
objectName: string,
pgGraphqlConfig: PgGraphQLConfig,
) => WorkspaceQueryRunnerException;
}
const pgGraphQLCommandMapping = {
insertInto: 'insert',
update: 'update',
deleteFrom: 'delete',
};
const pgGraphQLErrorMapping: PgGraphQLErrorMapping = {
'delete impacts too many records': (_, objectName, pgGraphqlConfig) =>
new WorkspaceQueryRunnerException(
`Cannot delete ${objectName} because it impacts too many records (more than ${pgGraphqlConfig?.atMost}).`,
WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED,
),
'update impacts too many records': (_, objectName, pgGraphqlConfig) =>
new WorkspaceQueryRunnerException(
`Cannot update ${objectName} because it impacts too many records (more than ${pgGraphqlConfig?.atMost}).`,
WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED,
),
'duplicate key value violates unique constraint': (command, objectName, _) =>
new WorkspaceQueryRunnerException(
`Cannot ${
// @ts-expect-error legacy noImplicitAny
pgGraphQLCommandMapping[command] ?? command
} ${objectName} because it violates a uniqueness constraint.`,
WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT,
),
'violates foreign key constraint': (command, objectName, _) =>
new WorkspaceQueryRunnerException(
`Cannot ${
// @ts-expect-error legacy noImplicitAny
pgGraphQLCommandMapping[command] ?? command
} ${objectName} because it violates a foreign key constraint.`,
WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT,
),
};
export const computePgGraphQLError = (
command: string,
objectName: string,
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
errors: any[],
pgGraphqlConfig: PgGraphQLConfig,
) => {
const error = errors[0];
const errorMessage = error?.message;
const mappedErrorKey = Object.keys(pgGraphQLErrorMapping).find((key) =>
errorMessage?.includes(key),
);
const mappedError = mappedErrorKey
? pgGraphQLErrorMapping[mappedErrorKey]
: null;
if (mappedError) {
return mappedError(command, objectName, pgGraphqlConfig);
}
return new WorkspaceQueryRunnerException(
`GraphQL errors on ${command}${objectName}: ${JSON.stringify(error)}`,
WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR,
);
};
@@ -1,29 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
export const withSoftDeleted = <T extends ObjectRecordFilter>(
filter: T | undefined | null,
): boolean => {
if (!isDefined(filter)) {
return false;
}
if (Array.isArray(filter)) {
return filter.some((item) => withSoftDeleted(item));
}
for (const [key, value] of Object.entries(filter)) {
if (key === 'deletedAt') {
return true;
}
if (typeof value === 'object' && value !== null) {
if (withSoftDeleted(value)) {
return true;
}
}
}
return false;
};
@@ -1,11 +0,0 @@
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
export const CONNECTION_METHOD_NAMES = new Set<string>([
RESOLVER_METHOD_NAMES.FIND_MANY,
RESOLVER_METHOD_NAMES.FIND_DUPLICATES,
RESOLVER_METHOD_NAMES.CREATE_MANY,
RESOLVER_METHOD_NAMES.UPDATE_MANY,
RESOLVER_METHOD_NAMES.DELETE_MANY,
RESOLVER_METHOD_NAMES.DESTROY_MANY,
RESOLVER_METHOD_NAMES.RESTORE_MANY,
]);
@@ -1,18 +0,0 @@
import { Injectable } from '@nestjs/common';
import { GraphQLNamedType } from 'graphql';
import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
@Injectable()
export class OrphanedTypesGenerator {
constructor(private readonly gqlTypesStorage: GqlTypesStorage) {}
fetchOrphanedTypes(): GraphQLNamedType[] {
return this.gqlTypesStorage.getAllGqlTypesExcept([
GqlOperation.Query,
GqlOperation.Mutation,
]);
}
}
@@ -1,19 +0,0 @@
import { GraphQLISODateTime } from '@nestjs/graphql';
import { GraphQLInputObjectType, GraphQLList, GraphQLNonNull } from 'graphql';
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
export const DateTimeFilterType = new GraphQLInputObjectType({
name: 'DateTimeFilter',
fields: {
eq: { type: GraphQLISODateTime },
gt: { type: GraphQLISODateTime },
gte: { type: GraphQLISODateTime },
in: { type: new GraphQLList(new GraphQLNonNull(GraphQLISODateTime)) },
lt: { type: GraphQLISODateTime },
lte: { type: GraphQLISODateTime },
neq: { type: GraphQLISODateTime },
is: { type: FilterIs },
},
});
@@ -1,11 +0,0 @@
import {
type FieldMetadataType,
type CompositeProperty,
} from 'twenty-shared/types';
export const computeCompositePropertyTarget = (
type: FieldMetadataType,
compositeProperty: CompositeProperty,
): string => {
return `${type.toString()}->${compositeProperty.name}`;
};
@@ -1,17 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
export const isFieldMetadataRelationOrMorphRelation = (
fieldMetadata: FieldMetadataEntity<FieldMetadataType>,
): fieldMetadata is FieldMetadataEntity &
(
| FieldMetadataEntity<FieldMetadataType.RELATION>
| FieldMetadataEntity<FieldMetadataType.MORPH_RELATION>
) => {
return (
isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.RELATION) ||
isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.MORPH_RELATION)
);
};
@@ -1,30 +0,0 @@
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
import { type JSONSchema7 } from 'json-schema';
class ValidationSchemaManager {
private static instance: ValidationSchemaManager;
private schemas: Record<string, JSONSchema7> | null = null;
private constructor() {}
public static getInstance(): ValidationSchemaManager {
if (!ValidationSchemaManager.instance) {
ValidationSchemaManager.instance = new ValidationSchemaManager();
}
return ValidationSchemaManager.instance;
}
public getSchemas(): Record<string, JSONSchema7> {
if (!this.schemas) {
this.schemas = validationMetadatasToSchemas() as Record<
string,
JSONSchema7
>;
}
return this.schemas;
}
}
export const validationSchemaManager = ValidationSchemaManager.getInstance();
@@ -1,13 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import { type AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
export const parseOmitNullValuesRestRequest = (
request: AuthenticatedRequest,
): boolean => {
if (!isDefined(request.query.omit_null_values)) {
return false;
}
return request.query.omit_null_values === 'true';
};