Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NonExposed parameter decorator #1395

Merged
merged 6 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@boostercloud/framework-core",
"comment": "Add NonExposed decorator to hide GraphQL fields",
"type": "minor"
}
],
"packageName": "@boostercloud/framework-core"
}
136 changes: 71 additions & 65 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/framework-core/src/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './role'
export * from './scheduled-command'
export * from './schema-migration'
export * from './sequenced-by'
export * from './non-exposed'
33 changes: 33 additions & 0 deletions packages/framework-core/src/decorators/non-exposed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Booster } from '../booster'
import { AnyClass } from '@boostercloud/framework-types'
import { getFunctionArguments } from './metadata'

export function NonExposed(
target: AnyClass | InstanceType<AnyClass>,
methodName: string | undefined,
parameterIndex?: number
) {
Booster.configureCurrentEnv((config): void => {
const className = target.name ?? target.constructor.name
const value: Array<string> = config.nonExposedGraphQLMetadataKey[className] || []

const fieldName = getFieldName(methodName, target, parameterIndex)
config.nonExposedGraphQLMetadataKey[className] = [...value, fieldName]
})
}

function getFieldName(
methodName: string | undefined,
target: AnyClass | InstanceType<AnyClass>,
parameterIndex: number | undefined
): string {
if (methodName) {
return methodName
}
if (!parameterIndex) {
throw new Error(`We could not get field name information in ${target} for method ${methodName}`)
}

const argumentNames = getFunctionArguments(target)
return argumentNames[parameterIndex]
}
7 changes: 7 additions & 0 deletions packages/framework-core/src/services/graphql/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,10 @@ export const buildGraphqlSimpleEnumFor = (enumName: string, values: Array<string
}, {} as GraphQLEnumValueConfigMap),
})
}

export function nonExcludedFields(
fields: Array<PropertyMetadata>,
excludeProps?: Array<string>
): Array<PropertyMetadata> {
return excludeProps ? fields.filter((field: PropertyMetadata) => !excludeProps.includes(field.name)) : fields
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,17 @@ export class GraphQLGenerator {
const mutationGenerator = new GraphQLMutationGenerator(
config.commandHandlers,
typeInformer,
this.commandResolverBuilder.bind(this)
this.commandResolverBuilder.bind(this),
config
)

const subscriptionGenerator = new GraphQLSubscriptionGenerator(
Object.values(config.readModels).map((m) => m.class),
typeInformer,
this.subscriptionByIDResolverBuilder.bind(this, config),
this.subscriptionResolverBuilder.bind(this, config),
generatedFiltersByTypeName
generatedFiltersByTypeName,
config
)

this.schema = new GraphQLSchema({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ import { ResolverBuilder, TargetTypesMap } from './common'
import { GraphQLTypeInformer } from './graphql-type-informer'
import { GraphQLObjectType } from 'graphql'
import { GraphQLHandledFieldsGenerator } from './query-helpers/graphql-handled-fields-generator'
import { BoosterConfig } from '@boostercloud/framework-types'

export class GraphQLMutationGenerator {
public constructor(
private readonly targetTypes: TargetTypesMap,
private readonly typeInformer: GraphQLTypeInformer,
private readonly mutationResolver: ResolverBuilder
private readonly mutationResolver: ResolverBuilder,
private readonly config: BoosterConfig
) {}

public generate(): GraphQLObjectType | undefined {
const graphqlGenerateHandledFields = new GraphQLHandledFieldsGenerator(
this.targetTypes,
this.typeInformer,
this.mutationResolver
this.mutationResolver,
this.config
)
const mutations = graphqlGenerateHandledFields.generateFields()
if (Object.keys(mutations).length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ export class GraphQLQueryGenerator {
typeInformer,
byIDResolverBuilder
)
this.graphqlQueryGenerator = new GraphqlQueryGenerator(targetTypes, typeInformer, queryResolverBuilder)
this.graphqlQueryGenerator = new GraphqlQueryGenerator(targetTypes, typeInformer, queryResolverBuilder, config)
this.graphqlQueryFiltersGenerator = new GraphqlQueryFiltersGenerator(
readModels,
typeInformer,
filterResolverBuilder,
generatedFiltersByTypeName
generatedFiltersByTypeName,
config
)
this.graphqlQueryListedGenerator = new GraphqlQueryListedGenerator(
readModels,
typeInformer,
filterResolverBuilder,
generatedFiltersByTypeName
generatedFiltersByTypeName,
config
)
this.graphqlQueryEventsGenerator = new GraphqlQueryEventsGenerator(config, byIDResolverBuilder, eventsResolver)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { GraphQLFieldConfigMap, GraphQLID, GraphQLInputObjectType, GraphQLNonNul
import { ResolverBuilder } from './common'
import { GraphQLTypeInformer } from './graphql-type-informer'
import * as inflected from 'inflected'
import { AnyClass } from '@boostercloud/framework-types'
import { AnyClass, BoosterConfig } from '@boostercloud/framework-types'
import { GraphqlQueryFilterFieldsBuilder } from './query-helpers/graphql-query-filter-fields-builder'

export class GraphQLSubscriptionGenerator {
Expand All @@ -13,9 +13,14 @@ export class GraphQLSubscriptionGenerator {
private readonly typeInformer: GraphQLTypeInformer,
private readonly byIDResolverBuilder: ResolverBuilder,
private readonly filterResolverBuilder: ResolverBuilder,
protected generatedFiltersByTypeName: Record<string, GraphQLInputObjectType> = {}
protected generatedFiltersByTypeName: Record<string, GraphQLInputObjectType> = {},
private readonly config: BoosterConfig
) {
this.graphqlQueryFilterFieldsBuilder = new GraphqlQueryFilterFieldsBuilder(typeInformer, generatedFiltersByTypeName)
this.graphqlQueryFilterFieldsBuilder = new GraphqlQueryFilterFieldsBuilder(
typeInformer,
generatedFiltersByTypeName,
config
)
}

public generate(): GraphQLObjectType | undefined {
Expand All @@ -34,7 +39,8 @@ export class GraphQLSubscriptionGenerator {
private generateByIDSubscriptions(): GraphQLFieldConfigMap<any, any> {
const subscriptions: GraphQLFieldConfigMap<any, any> = {}
for (const readModel of this.readModels) {
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel)
const excludeProps = this.config.nonExposedGraphQLMetadataKey[readModel.name]
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel, excludeProps)
subscriptions[readModel.name] = {
type: graphQLType,
args: {
Expand All @@ -50,12 +56,14 @@ export class GraphQLSubscriptionGenerator {
private generateFilterSubscriptions(): GraphQLFieldConfigMap<any, any> {
const subscriptions: GraphQLFieldConfigMap<any, any> = {}
for (const readModel of this.readModels) {
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel)
const excludeProps = this.config.nonExposedGraphQLMetadataKey[readModel.name]
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel, excludeProps)
subscriptions[inflected.pluralize(readModel.name)] = {
type: graphQLType,
args: this.graphqlQueryFilterFieldsBuilder.generateFilterQueriesFields(
`${readModel.name}Subscription`,
readModel
readModel,
excludeProps
),
resolve: (source) => source,
subscribe: this.filterResolverBuilder(readModel),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@ import {
GraphQLType,
} from 'graphql'
import { GraphQLJSON } from 'graphql-scalars'
import { ClassMetadata, ClassType, TypeMetadata } from '@boostercloud/metadata-booster'
import { DateScalar, isExternalType } from './common'
import { ClassMetadata, ClassType, PropertyMetadata, TypeMetadata } from '@boostercloud/metadata-booster'
import { DateScalar, isExternalType, nonExcludedFields } from './common'
import { Logger } from '@boostercloud/framework-types'

export class GraphQLTypeInformer {
private graphQLTypes: Record<string, GraphQLType> = {}

constructor(private logger: Logger) {}

public generateGraphQLTypeForClass(type: ClassType, inputType: true): GraphQLInputType
public generateGraphQLTypeForClass(type: ClassType, inputType?: false): GraphQLOutputType
public generateGraphQLTypeForClass(type: ClassType, inputType: boolean): GraphQLType
public generateGraphQLTypeForClass(type: ClassType, inputType = false): GraphQLType {
public generateGraphQLTypeForClass(type: ClassType, excludeProps: Array<string>, inputType: true): GraphQLInputType
public generateGraphQLTypeForClass(type: ClassType, excludeProps: Array<string>, inputType?: false): GraphQLOutputType
public generateGraphQLTypeForClass(type: ClassType, excludeProps: Array<string>, inputType: boolean): GraphQLType
public generateGraphQLTypeForClass(type: ClassType, excludeProps: Array<string>, inputType = false): GraphQLType {
this.logger.debug(`Generate GraphQL ${inputType ? 'input' : 'output'} type for class ${type.name}`)
const metadata = getClassMetadata(type)
return this.getOrCreateObjectType(metadata, inputType)
return this.getOrCreateObjectType(metadata, inputType, excludeProps)
}

public getOrCreateGraphQLType(typeMetadata: TypeMetadata, inputType: true): GraphQLInputType
Expand Down Expand Up @@ -102,19 +102,28 @@ export class GraphQLTypeInformer {
return new GraphQLList(GraphQLPropType)
}

private getOrCreateObjectType(classMetadata: ClassMetadata, inputType: boolean): GraphQLType {
private getOrCreateObjectType(
classMetadata: ClassMetadata,
inputType: boolean,
excludeProps: Array<string>
): GraphQLType {
const typeName = classMetadata.name + (inputType ? 'Input' : '')
if (typeName && this.graphQLTypes[typeName]) return this.graphQLTypes[typeName]
const createdGraphQLType = this.createObjectType(classMetadata, inputType)
const createdGraphQLType = this.createObjectType(classMetadata, inputType, excludeProps)
if (typeName) this.graphQLTypes[typeName] = createdGraphQLType
return createdGraphQLType
}

private createObjectType(classMetadata: ClassMetadata, inputType: boolean): GraphQLType {
private createObjectType(
classMetadata: ClassMetadata,
inputType: boolean,
excludeProps?: Array<string>
): GraphQLType {
const finalFields: Array<PropertyMetadata> = nonExcludedFields(classMetadata.fields, excludeProps)
if (inputType) {
return new GraphQLInputObjectType({
name: classMetadata.name + 'Input',
fields: classMetadata.fields?.reduce((obj, prop) => {
fields: finalFields?.reduce((obj, prop) => {
this.logger.debug(`Get or create GraphQL input type for property ${prop.name}`)
return {
...obj,
Expand All @@ -125,7 +134,7 @@ export class GraphQLTypeInformer {
}
return new GraphQLObjectType({
name: classMetadata.name,
fields: classMetadata.fields?.reduce((obj, prop) => {
fields: finalFields?.reduce((obj, prop) => {
this.logger.debug(`Get or create GraphQL output type for property ${prop.name}`)
return {
...obj,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ export class GraphqlQueryByKeysGenerator {
}

private generateByIdQuery(readModel: AnyClass): GraphQLFieldConfig<unknown, GraphQLResolverContext> {
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel)
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(
readModel,
this.config.nonExposedGraphQLMetadataKey[readModel.name]
)
return {
type: graphQLType,
args: {
Expand All @@ -40,7 +43,10 @@ export class GraphqlQueryByKeysGenerator {
readModel: AnyClass,
sequenceKeyName: string
): GraphQLFieldConfig<unknown, GraphQLResolverContext> {
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel)
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(
readModel,
this.config.nonExposedGraphQLMetadataKey[readModel.name]
)
return {
type: new GraphQLList(graphQLType),
args: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GraphQLResolverContext, ResolverBuilder } from '../common'
import * as inflected from 'inflected'
import { GraphQLTypeInformer } from '../graphql-type-informer'
import { GraphqlQueryFilterFieldsBuilder } from '../query-helpers/graphql-query-filter-fields-builder'
import { AnyClass } from '@boostercloud/framework-types'
import { AnyClass, BoosterConfig } from '@boostercloud/framework-types'

export class GraphqlQueryFiltersGenerator {
private graphqlQueryFilterFieldsBuilder: GraphqlQueryFilterFieldsBuilder
Expand All @@ -12,18 +12,24 @@ export class GraphqlQueryFiltersGenerator {
private readonly readModels: AnyClass[],
private readonly typeInformer: GraphQLTypeInformer,
private readonly filterResolverBuilder: ResolverBuilder,
protected generatedFiltersByTypeName: Record<string, GraphQLInputObjectType> = {}
protected generatedFiltersByTypeName: Record<string, GraphQLInputObjectType> = {},
private readonly config: BoosterConfig
) {
this.graphqlQueryFilterFieldsBuilder = new GraphqlQueryFilterFieldsBuilder(typeInformer, generatedFiltersByTypeName)
this.graphqlQueryFilterFieldsBuilder = new GraphqlQueryFilterFieldsBuilder(
typeInformer,
generatedFiltersByTypeName,
config
)
}

public generateFilterQueries(): GraphQLFieldConfigMap<unknown, GraphQLResolverContext> {
const queries: GraphQLFieldConfigMap<unknown, GraphQLResolverContext> = {}
for (const readModel of this.readModels) {
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel)
const excludeProp = this.config.nonExposedGraphQLMetadataKey[readModel.name]
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel, excludeProp)
queries[inflected.pluralize(readModel.name)] = {
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(graphQLType))),
args: this.graphqlQueryFilterFieldsBuilder.generateFilterQueriesFields(readModel.name, readModel),
args: this.graphqlQueryFilterFieldsBuilder.generateFilterQueriesFields(readModel.name, readModel, excludeProp),
resolve: this.filterResolverBuilder(readModel),
deprecationReason: 'Method is deprecated. Use List* methods',
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ import { GraphQLTypeInformer } from '../graphql-type-informer'
import { GraphQLResolverContext, ResolverBuilder, TargetTypesMap } from '../common'
import { GraphQLFieldConfigMap } from 'graphql'
import { GraphQLHandledFieldsGenerator } from '../query-helpers/graphql-handled-fields-generator'
import { BoosterConfig } from '@boostercloud/framework-types'

export class GraphqlQueryGenerator {
public constructor(
private readonly targetTypes: TargetTypesMap,
private readonly typeInformer: GraphQLTypeInformer,
private readonly queryResolveBuilder: ResolverBuilder
private readonly queryResolveBuilder: ResolverBuilder,
private readonly config: BoosterConfig
) {}

public generateQueries(): GraphQLFieldConfigMap<unknown, GraphQLResolverContext> {
const graphqlGenerateHandledFields = new GraphQLHandledFieldsGenerator(
this.targetTypes,
this.typeInformer,
this.queryResolveBuilder
this.queryResolveBuilder,
this.config
)
return graphqlGenerateHandledFields.generateFields(false)
}
Expand Down
Loading