diff --git a/spec/regression/regression-suite.ts b/spec/regression/regression-suite.ts index 42e635b8..c92e5f77 100644 --- a/spec/regression/regression-suite.ts +++ b/spec/regression/regression-suite.ts @@ -71,7 +71,7 @@ export class RegressionSuite { authRoles: context.authRoles, flexSearchMaxFilterableAndSortableAmount: context.flexSearchMaxFilterableAndSortableAmount }), - modelValidationOptions: { + modelOptions: { forbiddenRootEntityNames: [] }, ...options, diff --git a/src/config/interfaces.ts b/src/config/interfaces.ts index 17cc435f..43b12271 100644 --- a/src/config/interfaces.ts +++ b/src/config/interfaces.ts @@ -25,13 +25,6 @@ export interface SchemaOptions { readonly maxOrderByRootEntityDepth?: number; } -export interface ModelValidationOptions { - /** - * A list of root entity names that are not allowed. - */ - readonly forbiddenRootEntityNames?: ReadonlyArray; -} - export interface ModelOptions { /** * Determines whether a slash in a source name indicates the target namespace for that source @@ -39,14 +32,32 @@ export interface ModelOptions { * Defaults to true. Explicitly specify false to disable this. */ readonly useSourceDirectoriesAsNamespaces?: boolean; + + /** + * Specifies the default for case-sensitiveness of flexSearch fields (can be overridden with the decorator) + * + * Default is true + */ + readonly isFlexSearchIndexCaseSensitiveByDefault?: boolean; + + /** + * A list of root entity names that are not allowed. + */ + readonly forbiddenRootEntityNames?: ReadonlyArray; } +export type ModelValidationOptions = ModelOptions; + export interface ProjectOptions { readonly loggerProvider?: LoggerProvider; readonly profileConsumer?: (profile: RequestProfile) => void; readonly getExecutionOptions?: (args: ExecutionOptionsCallbackArgs) => ExecutionOptions; readonly schemaOptions?: SchemaOptions; + + /** + * @deprecated use modelOptions instead + */ readonly modelValidationOptions?: ModelValidationOptions; readonly modelOptions?: ModelOptions; diff --git a/src/model/config/model.ts b/src/model/config/model.ts index e03477a3..70bcfef1 100644 --- a/src/model/config/model.ts +++ b/src/model/config/model.ts @@ -1,4 +1,4 @@ -import { ModelValidationOptions } from '../../config/interfaces'; +import { ModelOptions, ModelValidationOptions } from '../../config/interfaces'; import { ValidationMessage } from '../validation'; import { BillingConfig } from './billing'; import { LocalizationConfig } from './i18n'; @@ -11,6 +11,6 @@ export interface ModelConfig { readonly validationMessages?: ReadonlyArray; readonly i18n?: ReadonlyArray; readonly billing?: BillingConfig; - readonly modelValidationOptions?: ModelValidationOptions; readonly timeToLiveConfigs?: ReadonlyArray; + readonly options?: ModelOptions; } diff --git a/src/model/create-model.ts b/src/model/create-model.ts index 49178bd9..7bbf8c4c 100644 --- a/src/model/create-model.ts +++ b/src/model/create-model.ts @@ -16,7 +16,7 @@ import { TypeDefinitionNode, valueFromAST } from 'graphql'; -import { ModelValidationOptions } from '../config/interfaces'; +import { ModelOptions, ModelValidationOptions, ProjectOptions } from '../config/interfaces'; import { ParsedGraphQLProjectSource, ParsedObjectProjectSource, @@ -115,16 +115,16 @@ import { parseI18nConfigs } from './parse-i18n'; import { parseTTLConfigs } from './parse-ttl'; import { ValidationContext, ValidationMessage } from './validation'; -export function createModel(parsedProject: ParsedProject, modelValidationOptions?: ModelValidationOptions): Model { +export function createModel(parsedProject: ParsedProject, options?: ModelOptions): Model { const validationContext = new ValidationContext(); return new Model({ - types: createTypeInputs(parsedProject, validationContext), + types: createTypeInputs(parsedProject, validationContext, options ?? {}), permissionProfiles: extractPermissionProfiles(parsedProject), i18n: extractI18n(parsedProject), validationMessages: validationContext.validationMessages, billing: extractBilling(parsedProject), - modelValidationOptions, - timeToLiveConfigs: extractTimeToLive(parsedProject) + timeToLiveConfigs: extractTimeToLive(parsedProject), + options }); } @@ -144,7 +144,11 @@ const VALIDATION_ERROR_MISSING_OBJECT_TYPE_DIRECTIVE = `Add one of @${ROOT_ENTIT const VALIDATION_ERROR_INVALID_DEFINITION_KIND = 'This kind of definition is not allowed. Only object and enum type definitions are allowed.'; -function createTypeInputs(parsedProject: ParsedProject, context: ValidationContext): ReadonlyArray { +function createTypeInputs( + parsedProject: ParsedProject, + context: ValidationContext, + options: ModelOptions +): ReadonlyArray { const graphQLSchemaParts = parsedProject.sources.filter( parsedSource => parsedSource.kind === ParsedProjectSourceBaseKind.GRAPHQL ) as ReadonlyArray; @@ -173,7 +177,7 @@ function createTypeInputs(parsedProject: ParsedProject, context: ValidationConte }; return enumTypeInput; case OBJECT_TYPE_DEFINITION: - return createObjectTypeInput(definition, schemaPart, context); + return createObjectTypeInput(definition, schemaPart, context, options); default: return undefined; } @@ -196,7 +200,8 @@ function createEnumValues(valueNodes: ReadonlyArray): R function createObjectTypeInput( definition: ObjectTypeDefinitionNode, schemaPart: ParsedGraphQLProjectSource, - context: ValidationContext + context: ValidationContext, + options: ModelOptions ): ObjectTypeConfig { const entityType = getKindOfObjectTypeNode(definition, context); @@ -204,7 +209,7 @@ function createObjectTypeInput( name: definition.name.value, description: definition.description ? definition.description.value : undefined, astNode: definition, - fields: (definition.fields || []).map(field => createFieldInput(field, context)), + fields: (definition.fields || []).map(field => createFieldInput(field, context, options)), namespacePath: getNamespacePath(definition, schemaPart.namespacePath), flexSearchLanguage: getDefaultLanguage(definition, context) }; @@ -457,7 +462,11 @@ function getLanguage(fieldNode: FieldDefinitionNode, context: ValidationContext) } } -function createFieldInput(fieldNode: FieldDefinitionNode, context: ValidationContext): FieldConfig { +function createFieldInput( + fieldNode: FieldDefinitionNode, + context: ValidationContext, + options: ModelOptions +): FieldConfig { const inverseOfASTNode = getInverseOfASTNode(fieldNode, context); const relationDeleteActionASTNode = getRelationDeleteActionASTNode(fieldNode, context); const referenceDirectiveASTNode = findDirectiveWithName(fieldNode, REFERENCE_DIRECTIVE); @@ -500,7 +509,7 @@ function createFieldInput(fieldNode: FieldDefinitionNode, context: ValidationCon isFlexSearchIndexCaseSensitive: flexSearchIndexCaseSensitiveNode?.value.kind === 'BooleanValue' ? flexSearchIndexCaseSensitiveNode.value.value - : undefined, + : options.isFlexSearchIndexCaseSensitiveByDefault, isFlexSearchIndexedASTNode: findDirectiveWithName(fieldNode, FLEX_SEARCH_INDEXED_DIRECTIVE), isFlexSearchFulltextIndexed: hasDirectiveWithName(fieldNode, FLEX_SEARCH_FULLTEXT_INDEXED_DIRECTIVE), isFlexSearchFulltextIndexedASTNode: findDirectiveWithName(fieldNode, FLEX_SEARCH_FULLTEXT_INDEXED_DIRECTIVE), diff --git a/src/model/implementation/model.ts b/src/model/implementation/model.ts index 5bb46fdc..2a27d48d 100644 --- a/src/model/implementation/model.ts +++ b/src/model/implementation/model.ts @@ -1,6 +1,6 @@ import { groupBy, uniqBy } from 'lodash'; import memorize from 'memorize-decorator'; -import { ModelValidationOptions } from '../../config/interfaces'; +import { ModelOptions } from '../../config/interfaces'; import { flatMap, objectEntries, objectValues } from '../../utils/utils'; import { ModelConfig, TypeKind } from '../config'; import { NamespacedPermissionProfileConfigMap } from '../index'; @@ -31,7 +31,11 @@ export class Model implements ModelComponent { readonly i18n: ModelI18n; readonly permissionProfiles: ReadonlyArray; readonly billingEntityTypes: ReadonlyArray; - readonly modelValidationOptions?: ModelValidationOptions; + /** + * @deprecated use options + */ + readonly modelValidationOptions?: ModelOptions; + readonly options?: ModelOptions; readonly timeToLiveTypes: ReadonlyArray; constructor(private input: ModelConfig) { @@ -50,7 +54,8 @@ export class Model implements ModelComponent { this.billingEntityTypes = input.billing ? input.billing.billingEntities.map(value => new BillingEntityType(value, this)) : []; - this.modelValidationOptions = input.modelValidationOptions; + this.options = input.options; + this.modelValidationOptions = input.options; this.timeToLiveTypes = input.timeToLiveConfigs ? input.timeToLiveConfigs.map(ttlConfig => new TimeToLiveType(ttlConfig, this)) : []; @@ -243,10 +248,10 @@ export class Model implements ModelComponent { } get forbiddenRootEntityNames(): ReadonlyArray { - if (!this.modelValidationOptions || !this.modelValidationOptions.forbiddenRootEntityNames) { + if (!this.options || !this.options.forbiddenRootEntityNames) { return ['BillingEntity']; } - return this.modelValidationOptions!.forbiddenRootEntityNames; + return this.options!.forbiddenRootEntityNames; } } diff --git a/src/schema/schema-builder.ts b/src/schema/schema-builder.ts index e3bea1af..4035bfe6 100644 --- a/src/schema/schema-builder.ts +++ b/src/schema/schema-builder.ts @@ -81,7 +81,10 @@ export function validateAndPrepareSchema(project: Project): { validationResult: const preparedProject = executePreMergeTransformationPipeline({ sources: validParsedSources }); - const model = createModel(preparedProject, project.options.modelValidationOptions); + const model = createModel(preparedProject, { + ...project.options.modelValidationOptions, + ...project.options.modelOptions + }); const mergedSchema: DocumentNode = mergeSchemaDefinition(preparedProject);