From 372c8e357d93c1af6f99e7f06eafa12422e83579 Mon Sep 17 00:00:00 2001 From: Nicolas Scheers Date: Wed, 4 Sep 2024 11:05:22 +0200 Subject: [PATCH 1/7] fix: minor graphql generation config changes --- .../src/decorators/read-model.ts | 30 +++++++++---------- .../graphql-query-by-keys-generator.ts | 13 +++++--- .../graphql-query-listed-generator.ts | 13 +++++--- .../src/concepts/read-model.ts | 11 ++++--- 4 files changed, 39 insertions(+), 28 deletions(-) diff --git a/packages/framework-core/src/decorators/read-model.ts b/packages/framework-core/src/decorators/read-model.ts index d1d377eca9..b61503f851 100644 --- a/packages/framework-core/src/decorators/read-model.ts +++ b/packages/framework-core/src/decorators/read-model.ts @@ -2,8 +2,7 @@ import { Class, ReadModelAuthorizer, ReadModelFilterHooks, - ReadModelGraphqlQueryGeneratorConfig, - ReadModelGraphqlSubscriptionGeneratorConfig, + ReadModelGraphqlGenerationConfig, ReadModelInterface, ReadModelRoleAccess, } from '@boostercloud/framework-types' @@ -16,7 +15,8 @@ import { getClassMetadata } from './metadata' * @param attributes */ export function ReadModel( - attributes: ReadModelRoleAccess & ReadModelFilterHooks, graphQLQueryConfiguration: ReadModelGraphqlQueryGeneratorConfig, graphQLSubscriptionConfiguration: ReadModelGraphqlSubscriptionGeneratorConfig + attributes: ReadModelRoleAccess & ReadModelFilterHooks, + graphQLGenerationConfiguration: ReadModelGraphqlGenerationConfig ): (readModelClass: Class, context?: ClassDecoratorContext) => void { return (readModelClass) => { Booster.configureCurrentEnv((config): void => { @@ -27,30 +27,28 @@ export function ReadModel( const enableAutomaticGraphQLQueryGenerationFromReadModels = config.enableAutomaticGraphQLQueryGenerationFromReadModels; if (enableAutomaticGraphQLQueryGenerationFromReadModels) { - if (!graphQLQueryConfiguration) { - graphQLQueryConfiguration = 'GRAPHQL_LIST_AND_SINGLE_QUERIES'; + if (!graphQLGenerationConfiguration.queryGeneration) { + graphQLGenerationConfiguration.queryGeneration = 'GRAPHQL_LIST_AND_SINGLE' } - if (!graphQLSubscriptionConfiguration) { - graphQLSubscriptionConfiguration = 'GRAPHQL_LIST_AND_SINGLE_SUBSCRIPTION'; + if (!graphQLGenerationConfiguration.subscriptionGeneration) { + graphQLGenerationConfiguration.subscriptionGeneration = 'GRAPHQL_LIST_AND_SINGLE' } - } - if (!enableAutomaticGraphQLQueryGenerationFromReadModels) { - if (!graphQLQueryConfiguration) { - graphQLQueryConfiguration = 'NO_GRAPHQL_QUERIES'; + } else { + if (!graphQLGenerationConfiguration.queryGeneration) { + graphQLGenerationConfiguration.queryGeneration = 'NO_GRAPHQL' } - if (!graphQLSubscriptionConfiguration) { - graphQLSubscriptionConfiguration = 'NO_GRAPHQL_SUBSCRIPTIONS'; + if (!graphQLGenerationConfiguration.subscriptionGeneration) { + graphQLGenerationConfiguration.subscriptionGeneration = 'NO_GRAPHQL' } } - + const authorizer = BoosterAuthorizer.build(attributes) as ReadModelAuthorizer config.readModels[readModelClass.name] = { class: readModelClass, properties: getClassMetadata(readModelClass).fields, authorizer, before: attributes.before ?? [], - graphqlQueryGenerationConfig: graphQLQueryConfiguration, - graphqlSubscriptionGenerationConfig: graphQLSubscriptionConfiguration, + graphqlGenerationConfig: graphQLGenerationConfiguration, } }) } diff --git a/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts b/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts index abbea7eec0..2f4d0bc18b 100644 --- a/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts +++ b/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts @@ -13,7 +13,10 @@ export class GraphqlQueryByKeysGenerator { public generateByKeysQueries(): GraphQLFieldConfigMap { const queries: GraphQLFieldConfigMap = {} - const readModelsThatRequireGraphQLQueries = this.filterReadModelsThatRequireGraphQLQueries(this.readModels, this.config); + const readModelsThatRequireGraphQLQueries = this.filterReadModelsThatRequireGraphQLQueries( + this.readModels, + this.config + ) for (const readModel of readModelsThatRequireGraphQLQueries) { const readModelName = readModel.name @@ -29,9 +32,11 @@ export class GraphqlQueryByKeysGenerator { private filterReadModelsThatRequireGraphQLQueries(readModels: any[], config: any): any[] { return readModels.filter((readModel) => { - const graphqlQueryGenerationConfig = config.readModels[readModel.name].graphqlQueryGenerationConfig; - return graphqlQueryGenerationConfig === 'GRAPHQL_LIST_AND_SINGLE_QUERIES' || graphqlQueryGenerationConfig === 'GRAPHQL_SINGLE_QUERY'; - }); + const graphqlQueryGenerationConfig = config.readModels[readModel.name].graphqlGenerationConfig.queryGeneration + return ( + graphqlQueryGenerationConfig === 'GRAPHQL_LIST_AND_SINGLE' || graphqlQueryGenerationConfig === 'GRAPHQL_LIST' + ) + }) } private generateByIdQuery(readModel: AnyClass): GraphQLFieldConfig { diff --git a/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts b/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts index 19e252b9a3..ecc9d6e945 100644 --- a/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts +++ b/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts @@ -37,7 +37,10 @@ export class GraphqlQueryListedGenerator { public generateListedQueries(): GraphQLFieldConfigMap { const queries: GraphQLFieldConfigMap = {} - const readModelsThatRequireGraphQLQueries = this.filterReadModelsThatRequireGraphQLQueries(this.readModels, this.config); + const readModelsThatRequireGraphQLQueries = this.filterReadModelsThatRequireGraphQLQueries( + this.readModels, + this.config + ) for (const readModel of readModelsThatRequireGraphQLQueries) { const excludeProp = this.config.nonExposedGraphQLMetadataKey[readModel.name] const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel, excludeProp) @@ -61,9 +64,11 @@ export class GraphqlQueryListedGenerator { private filterReadModelsThatRequireGraphQLQueries(readModels: any[], config: any): any[] { return readModels.filter((readModel) => { - const graphqlQueryGenerationConfig = config.readModels[readModel.name].graphqlQueryGenerationConfig; - return graphqlQueryGenerationConfig === 'GRAPHQL_LIST_AND_SINGLE_QUERIES' || graphqlQueryGenerationConfig === 'GRAPHQL_LIST_QUERY'; - }); + const graphqlQueryGenerationConfig = config.readModels[readModel.name].graphqlGenerationConfig.queryGeneration + return ( + graphqlQueryGenerationConfig === 'GRAPHQL_LIST_AND_SINGLE' || graphqlQueryGenerationConfig === 'GRAPHQL_LIST' + ) + }) } private generateListedQueriesFields( diff --git a/packages/framework-types/src/concepts/read-model.ts b/packages/framework-types/src/concepts/read-model.ts index 2471e30f2c..fabad275aa 100644 --- a/packages/framework-types/src/concepts/read-model.ts +++ b/packages/framework-types/src/concepts/read-model.ts @@ -22,14 +22,17 @@ export interface ReadModelInterface { [key: string]: any } -export type ReadModelGraphqlQueryGeneratorConfig = 'GRAPHQL_LIST_AND_SINGLE_QUERIES' | 'GRAPHQL_LIST_QUERY' | 'GRAPHQL_SINGLE_QUERY' | 'NO_GRAPHQL_QUERIES'; -export type ReadModelGraphqlSubscriptionGeneratorConfig = 'GRAPHQL_LIST_AND_SINGLE_SUBSCRIPTION' | 'GRAPHQL_LIST_SUBSCRIPTION' | 'GRAPHQL_SINGLE_SUBSCRIPTION' | 'NO_GRAPHQL_SUBSCRIPTIONS'; +export type ReadModelGraphqlGenerationConfig = { + queryGeneration: GraphqlGenerationConfig + subscriptionGeneration: GraphqlGenerationConfig +} + +export type GraphqlGenerationConfig = 'GRAPHQL_LIST_AND_SINGLE' | 'GRAPHQL_LIST' | 'GRAPHQL_SINGLE' | 'NO_GRAPHQL' export interface ReadModelMetadata { readonly class: Class readonly properties: Array readonly authorizer: ReadModelAuthorizer readonly before: NonNullable['before']> - readonly graphqlQueryGenerationConfig: ReadModelGraphqlQueryGeneratorConfig - readonly graphqlSubscriptionGenerationConfig: ReadModelGraphqlSubscriptionGeneratorConfig + readonly graphqlGenerationConfig: ReadModelGraphqlGenerationConfig } From fe956a1b1db2046934005f7381a2f257343c6c5e Mon Sep 17 00:00:00 2001 From: Nicolas Scheers Date: Thu, 5 Sep 2024 10:50:45 +0200 Subject: [PATCH 2/7] fix: typing and completing graphql generation --- .../src/decorators/read-model.ts | 27 ++++++++++--------- .../src/services/graphql/common.ts | 22 +++++++++++++++ .../graphql/graphql-subcriptions-generator.ts | 18 ++++++++----- .../graphql-query-by-keys-generator.ts | 16 ++--------- .../graphql-query-listed-generator.ts | 16 ++--------- .../src/concepts/read-model.ts | 12 ++++----- 6 files changed, 58 insertions(+), 53 deletions(-) diff --git a/packages/framework-core/src/decorators/read-model.ts b/packages/framework-core/src/decorators/read-model.ts index b61503f851..20d805faa5 100644 --- a/packages/framework-core/src/decorators/read-model.ts +++ b/packages/framework-core/src/decorators/read-model.ts @@ -2,7 +2,7 @@ import { Class, ReadModelAuthorizer, ReadModelFilterHooks, - ReadModelGraphqlGenerationConfig, + GenerationStrategy, ReadModelInterface, ReadModelRoleAccess, } from '@boostercloud/framework-types' @@ -16,7 +16,8 @@ import { getClassMetadata } from './metadata' */ export function ReadModel( attributes: ReadModelRoleAccess & ReadModelFilterHooks, - graphQLGenerationConfiguration: ReadModelGraphqlGenerationConfig + queryGeneration: GenerationStrategy[] = [], + subscriptionGeneration: GenerationStrategy[] = [] ): (readModelClass: Class, context?: ClassDecoratorContext) => void { return (readModelClass) => { Booster.configureCurrentEnv((config): void => { @@ -25,20 +26,21 @@ export function ReadModel( If you think that this is an error, try performing a clean build.`) } - const enableAutomaticGraphQLQueryGenerationFromReadModels = config.enableAutomaticGraphQLQueryGenerationFromReadModels; + const enableAutomaticGraphQLQueryGenerationFromReadModels = + config.enableAutomaticGraphQLQueryGenerationFromReadModels if (enableAutomaticGraphQLQueryGenerationFromReadModels) { - if (!graphQLGenerationConfiguration.queryGeneration) { - graphQLGenerationConfiguration.queryGeneration = 'GRAPHQL_LIST_AND_SINGLE' + if (!queryGeneration) { + queryGeneration = [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE] } - if (!graphQLGenerationConfiguration.subscriptionGeneration) { - graphQLGenerationConfiguration.subscriptionGeneration = 'GRAPHQL_LIST_AND_SINGLE' + if (!subscriptionGeneration) { + subscriptionGeneration = [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE] } } else { - if (!graphQLGenerationConfiguration.queryGeneration) { - graphQLGenerationConfiguration.queryGeneration = 'NO_GRAPHQL' + if (!queryGeneration) { + queryGeneration = [GenerationStrategy.NO_GRAPHQL] } - if (!graphQLGenerationConfiguration.subscriptionGeneration) { - graphQLGenerationConfiguration.subscriptionGeneration = 'NO_GRAPHQL' + if (!subscriptionGeneration) { + subscriptionGeneration = [GenerationStrategy.NO_GRAPHQL] } } @@ -48,7 +50,8 @@ export function ReadModel( properties: getClassMetadata(readModelClass).fields, authorizer, before: attributes.before ?? [], - graphqlGenerationConfig: graphQLGenerationConfiguration, + queryGeneration, + subscriptionGeneration, } }) } diff --git a/packages/framework-core/src/services/graphql/common.ts b/packages/framework-core/src/services/graphql/common.ts index a153ac3aae..98ae5e23be 100644 --- a/packages/framework-core/src/services/graphql/common.ts +++ b/packages/framework-core/src/services/graphql/common.ts @@ -6,6 +6,8 @@ import { GraphQLOperation, ReadModelInterface, ContextEnvelope, + BoosterConfig, + GenerationStrategy, } from '@boostercloud/framework-types' import { GraphQLEnumType, @@ -88,6 +90,26 @@ export const buildGraphqlSimpleEnumFor = (enumName: string, values: Array { + return readModels.filter((readModel) => { + const graphqlQueryGenerationConfig = config.readModels[readModel.name].queryGeneration + return graphqlQueryGenerationConfig.includes(GenerationStrategy.GRAPHQL_LIST) + }) +} + +export const filterReadModelsThatRequireGraphQLSubscriptions = ( + readModels: AnyClass[], + config: BoosterConfig +): AnyClass[] => { + return readModels.filter((readModel) => { + const graphqlSubscriptionGenerationConfig = config.readModels[readModel.name].subscriptionGeneration + return graphqlSubscriptionGenerationConfig.includes(GenerationStrategy.GRAPHQL_LIST) + }) +} + export function nonExcludedFields( fields: Array, excludeProps?: Array diff --git a/packages/framework-core/src/services/graphql/graphql-subcriptions-generator.ts b/packages/framework-core/src/services/graphql/graphql-subcriptions-generator.ts index 1768230f4d..a92060bca3 100644 --- a/packages/framework-core/src/services/graphql/graphql-subcriptions-generator.ts +++ b/packages/framework-core/src/services/graphql/graphql-subcriptions-generator.ts @@ -1,5 +1,5 @@ import { GraphQLFieldConfigMap, GraphQLID, GraphQLInputObjectType, GraphQLNonNull, GraphQLObjectType } from 'graphql' -import { ResolverBuilder } from './common' +import { filterReadModelsThatRequireGraphQLSubscriptions, ResolverBuilder } from './common' import { GraphQLTypeInformer } from './graphql-type-informer' import * as inflected from 'inflected' import { AnyClass, BoosterConfig } from '@boostercloud/framework-types' @@ -24,8 +24,12 @@ export class GraphQLSubscriptionGenerator { } public generate(): GraphQLObjectType | undefined { - const byIDSubscriptions = this.generateByIDSubscriptions() - const filterSubscriptions = this.generateFilterSubscriptions() + const readModelsRequiringSubscriptionGeneration = filterReadModelsThatRequireGraphQLSubscriptions( + this.readModels, + this.config + ) + const byIDSubscriptions = this.generateByIDSubscriptions(readModelsRequiringSubscriptionGeneration) + const filterSubscriptions = this.generateFilterSubscriptions(readModelsRequiringSubscriptionGeneration) const fields = { ...byIDSubscriptions, ...filterSubscriptions } if (Object.keys(fields).length === 0) { return undefined @@ -36,9 +40,9 @@ export class GraphQLSubscriptionGenerator { }) } - private generateByIDSubscriptions(): GraphQLFieldConfigMap { + private generateByIDSubscriptions(readModels: AnyClass[]): GraphQLFieldConfigMap { const subscriptions: GraphQLFieldConfigMap = {} - for (const readModel of this.readModels) { + for (const readModel of readModels) { const excludeProps = this.config.nonExposedGraphQLMetadataKey[readModel.name] const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel, excludeProps) subscriptions[readModel.name] = { @@ -53,9 +57,9 @@ export class GraphQLSubscriptionGenerator { return subscriptions } - private generateFilterSubscriptions(): GraphQLFieldConfigMap { + private generateFilterSubscriptions(readModels: AnyClass[]): GraphQLFieldConfigMap { const subscriptions: GraphQLFieldConfigMap = {} - for (const readModel of this.readModels) { + for (const readModel of readModels) { const excludeProps = this.config.nonExposedGraphQLMetadataKey[readModel.name] const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel, excludeProps) subscriptions[inflected.pluralize(readModel.name)] = { diff --git a/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts b/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts index 2f4d0bc18b..fb07edc2dc 100644 --- a/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts +++ b/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts @@ -1,5 +1,5 @@ import { GraphQLFieldConfig, GraphQLFieldConfigMap, GraphQLID, GraphQLList, GraphQLNonNull } from 'graphql' -import { GraphQLResolverContext, ResolverBuilder } from '../common' +import { filterReadModelsThatRequireGraphQLQueries, GraphQLResolverContext, ResolverBuilder } from '../common' import { AnyClass, BoosterConfig } from '@boostercloud/framework-types' import { GraphQLTypeInformer } from '../graphql-type-informer' @@ -13,10 +13,7 @@ export class GraphqlQueryByKeysGenerator { public generateByKeysQueries(): GraphQLFieldConfigMap { const queries: GraphQLFieldConfigMap = {} - const readModelsThatRequireGraphQLQueries = this.filterReadModelsThatRequireGraphQLQueries( - this.readModels, - this.config - ) + const readModelsThatRequireGraphQLQueries = filterReadModelsThatRequireGraphQLQueries(this.readModels, this.config) for (const readModel of readModelsThatRequireGraphQLQueries) { const readModelName = readModel.name @@ -30,15 +27,6 @@ export class GraphqlQueryByKeysGenerator { return queries } - private filterReadModelsThatRequireGraphQLQueries(readModels: any[], config: any): any[] { - return readModels.filter((readModel) => { - const graphqlQueryGenerationConfig = config.readModels[readModel.name].graphqlGenerationConfig.queryGeneration - return ( - graphqlQueryGenerationConfig === 'GRAPHQL_LIST_AND_SINGLE' || graphqlQueryGenerationConfig === 'GRAPHQL_LIST' - ) - }) - } - private generateByIdQuery(readModel: AnyClass): GraphQLFieldConfig { const graphQLType = this.typeInformer.generateGraphQLTypeForClass( readModel, diff --git a/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts b/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts index ecc9d6e945..1b53a1d0da 100644 --- a/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts +++ b/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts @@ -7,7 +7,7 @@ import { GraphQLNonNull, GraphQLObjectType, } from 'graphql' -import { GraphQLResolverContext, ResolverBuilder } from '../common' +import { filterReadModelsThatRequireGraphQLQueries, GraphQLResolverContext, ResolverBuilder } from '../common' import * as inflected from 'inflected' import { GraphQLJSON } from 'graphql-scalars' @@ -37,10 +37,7 @@ export class GraphqlQueryListedGenerator { public generateListedQueries(): GraphQLFieldConfigMap { const queries: GraphQLFieldConfigMap = {} - const readModelsThatRequireGraphQLQueries = this.filterReadModelsThatRequireGraphQLQueries( - this.readModels, - this.config - ) + const readModelsThatRequireGraphQLQueries = filterReadModelsThatRequireGraphQLQueries(this.readModels, this.config) for (const readModel of readModelsThatRequireGraphQLQueries) { const excludeProp = this.config.nonExposedGraphQLMetadataKey[readModel.name] const graphQLType = this.typeInformer.generateGraphQLTypeForClass(readModel, excludeProp) @@ -62,15 +59,6 @@ export class GraphqlQueryListedGenerator { return queries } - private filterReadModelsThatRequireGraphQLQueries(readModels: any[], config: any): any[] { - return readModels.filter((readModel) => { - const graphqlQueryGenerationConfig = config.readModels[readModel.name].graphqlGenerationConfig.queryGeneration - return ( - graphqlQueryGenerationConfig === 'GRAPHQL_LIST_AND_SINGLE' || graphqlQueryGenerationConfig === 'GRAPHQL_LIST' - ) - }) - } - private generateListedQueriesFields( name: string, type: AnyClass, diff --git a/packages/framework-types/src/concepts/read-model.ts b/packages/framework-types/src/concepts/read-model.ts index fabad275aa..06262b7337 100644 --- a/packages/framework-types/src/concepts/read-model.ts +++ b/packages/framework-types/src/concepts/read-model.ts @@ -22,17 +22,17 @@ export interface ReadModelInterface { [key: string]: any } -export type ReadModelGraphqlGenerationConfig = { - queryGeneration: GraphqlGenerationConfig - subscriptionGeneration: GraphqlGenerationConfig +export enum GenerationStrategy { + GRAPHQL_LIST, + GRAPHQL_SINGLE, + NO_GRAPHQL, } -export type GraphqlGenerationConfig = 'GRAPHQL_LIST_AND_SINGLE' | 'GRAPHQL_LIST' | 'GRAPHQL_SINGLE' | 'NO_GRAPHQL' - export interface ReadModelMetadata { readonly class: Class readonly properties: Array readonly authorizer: ReadModelAuthorizer readonly before: NonNullable['before']> - readonly graphqlGenerationConfig: ReadModelGraphqlGenerationConfig + readonly queryGeneration: GenerationStrategy[] + readonly subscriptionGeneration: GenerationStrategy[] } From efe0cb8fc129b6bc01659b936246753334edeaa4 Mon Sep 17 00:00:00 2001 From: Nicolas Scheers Date: Thu, 5 Sep 2024 13:28:18 +0200 Subject: [PATCH 3/7] fix: read model annotation --- .../src/decorators/read-model.ts | 25 +++++++++---------- .../src/entities/another-stock.ts | 9 +++++++ .../read-models/another-stock-read-model.ts | 18 +++++++++++++ 3 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 packages/framework-integration-tests/src/entities/another-stock.ts create mode 100644 packages/framework-integration-tests/src/read-models/another-stock-read-model.ts diff --git a/packages/framework-core/src/decorators/read-model.ts b/packages/framework-core/src/decorators/read-model.ts index 20d805faa5..6986de06c1 100644 --- a/packages/framework-core/src/decorators/read-model.ts +++ b/packages/framework-core/src/decorators/read-model.ts @@ -16,8 +16,10 @@ import { getClassMetadata } from './metadata' */ export function ReadModel( attributes: ReadModelRoleAccess & ReadModelFilterHooks, - queryGeneration: GenerationStrategy[] = [], - subscriptionGeneration: GenerationStrategy[] = [] + graphQlGeneration?: { + queryGeneration: GenerationStrategy[] + subscriptionGeneration: GenerationStrategy[] + } ): (readModelClass: Class, context?: ClassDecoratorContext) => void { return (readModelClass) => { Booster.configureCurrentEnv((config): void => { @@ -28,20 +30,17 @@ export function ReadModel( const enableAutomaticGraphQLQueryGenerationFromReadModels = config.enableAutomaticGraphQLQueryGenerationFromReadModels - if (enableAutomaticGraphQLQueryGenerationFromReadModels) { - if (!queryGeneration) { + let queryGeneration: GenerationStrategy[] = [] + let subscriptionGeneration: GenerationStrategy[] = [] + + if (graphQlGeneration) { + queryGeneration = graphQlGeneration.queryGeneration + subscriptionGeneration = graphQlGeneration.subscriptionGeneration + } else { + if (enableAutomaticGraphQLQueryGenerationFromReadModels) { queryGeneration = [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE] - } - if (!subscriptionGeneration) { subscriptionGeneration = [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE] } - } else { - if (!queryGeneration) { - queryGeneration = [GenerationStrategy.NO_GRAPHQL] - } - if (!subscriptionGeneration) { - subscriptionGeneration = [GenerationStrategy.NO_GRAPHQL] - } } const authorizer = BoosterAuthorizer.build(attributes) as ReadModelAuthorizer diff --git a/packages/framework-integration-tests/src/entities/another-stock.ts b/packages/framework-integration-tests/src/entities/another-stock.ts new file mode 100644 index 0000000000..a905e6e9ad --- /dev/null +++ b/packages/framework-integration-tests/src/entities/another-stock.ts @@ -0,0 +1,9 @@ +import { Entity } from '@boostercloud/framework-core' +import { UUID } from '@boostercloud/framework-types' + +@Entity({ + authorizeReadEvents: 'all', +}) +export class AnotherStock { + public constructor(readonly id: UUID, readonly warehouses: Record) {} +} diff --git a/packages/framework-integration-tests/src/read-models/another-stock-read-model.ts b/packages/framework-integration-tests/src/read-models/another-stock-read-model.ts new file mode 100644 index 0000000000..046f4399fb --- /dev/null +++ b/packages/framework-integration-tests/src/read-models/another-stock-read-model.ts @@ -0,0 +1,18 @@ +import { GenerationStrategy, ProjectionResult, UUID } from '@boostercloud/framework-types' +import { Projects, ReadModel } from '@boostercloud/framework-core' +import { AnotherStock } from '../entities/another-stock' + +@ReadModel( + { + authorize: 'all', + }, + { queryGeneration: [GenerationStrategy.NO_GRAPHQL], subscriptionGeneration: [GenerationStrategy.NO_GRAPHQL] } +) +export class AnotherStockReadModel { + public constructor(readonly id: UUID, readonly warehouses: Record) {} + + @Projects(AnotherStock, 'id') + public static projectStock(entity: AnotherStock): ProjectionResult { + return new AnotherStockReadModel(entity.id, entity.warehouses) + } +} From e7186b5984cf7d20e7ce7fb618b1a5908fe7932c Mon Sep 17 00:00:00 2001 From: Nicolas Scheers Date: Thu, 3 Oct 2024 10:44:22 +0200 Subject: [PATCH 4/7] fix: typo --- website/docs/12_contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/12_contributing.md b/website/docs/12_contributing.md index 2a428f3c09..5a1cda32d8 100644 --- a/website/docs/12_contributing.md +++ b/website/docs/12_contributing.md @@ -207,7 +207,7 @@ To start contributing to the project you would need to set up the project in you - Add your contribution -- Make sure everything works by [executing the unit tests](#running-unit-tests): `rush rest` +- Make sure everything works by [executing the unit tests](#running-unit-tests): `rush test` > **DISCLAIMER**: The integration test process changed, feel free to chime in into our Discord for more info From 6aef7935799b126640ce3310197c74baefb382d8 Mon Sep 17 00:00:00 2001 From: Nicolas Scheers Date: Thu, 3 Oct 2024 11:17:40 +0200 Subject: [PATCH 5/7] fix: test config --- .../framework-core/test/services/read-model-store.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/framework-core/test/services/read-model-store.test.ts b/packages/framework-core/test/services/read-model-store.test.ts index 2dea5faf92..9f32bf2ff5 100644 --- a/packages/framework-core/test/services/read-model-store.test.ts +++ b/packages/framework-core/test/services/read-model-store.test.ts @@ -6,6 +6,7 @@ import { createInstance } from '@boostercloud/framework-common-helpers' import { BoosterConfig, EntitySnapshotEnvelope, + GenerationStrategy, Level, OptimisticConcurrencyUnexpectedVersionError, ProjectionMetadata, @@ -115,12 +116,16 @@ describe('ReadModelStore', () => { authorizer: BoosterAuthorizer.allowAccess, properties: [], before: [], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } config.readModels[AnotherReadModel.name] = { class: AnotherReadModel, authorizer: BoosterAuthorizer.allowAccess, properties: [], before: [], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } config.projections[AnImportantEntity.name] = [ { From 4762294d3cabead120db975975fb8ad868b511c6 Mon Sep 17 00:00:00 2001 From: Nicolas Scheers Date: Tue, 8 Oct 2024 13:28:10 +0200 Subject: [PATCH 6/7] feat: testing --- package-lock.json | 18 +++++++++++++ package.json | 5 ++++ .../src/services/graphql/common.ts | 6 +++-- .../test/booster-read-models-reader.test.ts | 17 ++++++++++++ .../test/decorators/read-model.test.ts | 4 +++ .../end-to-end/read-models.integration.ts | 27 +++++++++++++++++++ .../src/config/config.ts | 1 + 7 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..d422e3bd81 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,18 @@ +{ + "name": "booster", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@types/mocha": "^10.0.9" + } + }, + "node_modules/@types/mocha": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.9.tgz", + "integrity": "sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..20f52a2db8 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@types/mocha": "^10.0.9" + } +} diff --git a/packages/framework-core/src/services/graphql/common.ts b/packages/framework-core/src/services/graphql/common.ts index 98ae5e23be..ce5709a3a5 100644 --- a/packages/framework-core/src/services/graphql/common.ts +++ b/packages/framework-core/src/services/graphql/common.ts @@ -95,7 +95,8 @@ export const filterReadModelsThatRequireGraphQLQueries = ( config: BoosterConfig ): AnyClass[] => { return readModels.filter((readModel) => { - const graphqlQueryGenerationConfig = config.readModels[readModel.name].queryGeneration + const graphqlQueryGenerationConfig = config.readModels[readModel.name]?.queryGeneration + if (!graphqlQueryGenerationConfig) return config.enableAutomaticGraphQLQueryGenerationFromReadModels return graphqlQueryGenerationConfig.includes(GenerationStrategy.GRAPHQL_LIST) }) } @@ -105,7 +106,8 @@ export const filterReadModelsThatRequireGraphQLSubscriptions = ( config: BoosterConfig ): AnyClass[] => { return readModels.filter((readModel) => { - const graphqlSubscriptionGenerationConfig = config.readModels[readModel.name].subscriptionGeneration + const graphqlSubscriptionGenerationConfig = config.readModels[readModel.name]?.subscriptionGeneration + if (!graphqlSubscriptionGenerationConfig) return config.enableAutomaticGraphQLQueryGenerationFromReadModels return graphqlSubscriptionGenerationConfig.includes(GenerationStrategy.GRAPHQL_LIST) }) } diff --git a/packages/framework-core/test/booster-read-models-reader.test.ts b/packages/framework-core/test/booster-read-models-reader.test.ts index 52b0ebff75..ce9d240f11 100644 --- a/packages/framework-core/test/booster-read-models-reader.test.ts +++ b/packages/framework-core/test/booster-read-models-reader.test.ts @@ -3,6 +3,7 @@ import { expect } from './expect' import { BoosterConfig, FilterFor, + GenerationStrategy, GraphQLOperation, InvalidParameterError, NotAuthorizedError, @@ -69,12 +70,16 @@ describe('BoosterReadModelReader', () => { authorizer: BoosterAuthorizer.authorizeRoles.bind(null, [UserRole]), properties: [], before: [], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } config.readModels[SequencedReadModel.name] = { class: SequencedReadModel, authorizer: BoosterAuthorizer.authorizeRoles.bind(null, [UserRole]), properties: [], before: [], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } }) @@ -262,6 +267,8 @@ describe('BoosterReadModelReader', () => { authorizer: BoosterAuthorizer.authorizeRoles.bind(null, [UserRole]), properties: [], before: [], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } }) @@ -358,6 +365,8 @@ describe('BoosterReadModelReader', () => { authorizer: BoosterAuthorizer.authorizeRoles.bind(null, [UserRole]), properties: [], before: [], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } migratorStub = stub(ReadModelSchemaMigrator.prototype, 'migrate') }) @@ -529,6 +538,8 @@ describe('BoosterReadModelReader', () => { authorizer: BoosterAuthorizer.authorizeRoles.bind(null, [UserRole]), properties: [], before: [fakeBeforeFn], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } replace(Booster, 'config', config) // Needed because the function `Booster.readModel` references `this.config` from `searchFunction` @@ -564,6 +575,8 @@ describe('BoosterReadModelReader', () => { authorizer: BoosterAuthorizer.authorizeRoles.bind(null, [UserRole]), properties: [], before: [beforeFnSpy, beforeFnV2Spy], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } replace(Booster, 'config', config) // Needed because the function `Booster.readModel` references `this.config` from `searchFunction` @@ -610,6 +623,8 @@ describe('BoosterReadModelReader', () => { authorizer: BoosterAuthorizer.authorizeRoles.bind(null, [UserRole]), properties: [], before: [], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } replace(config.provider.readModels, 'subscribe', providerSubscribeFunctionFake) @@ -643,6 +658,8 @@ describe('BoosterReadModelReader', () => { authorizer: BoosterAuthorizer.authorizeRoles.bind(null, [UserRole]), properties: [], before: [beforeFn, beforeFnV2], + queryGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], + subscriptionGeneration: [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE], } replace(config.provider.readModels, 'subscribe', providerSubscribeFunctionFake) diff --git a/packages/framework-core/test/decorators/read-model.test.ts b/packages/framework-core/test/decorators/read-model.test.ts index fe998dd272..077673a1e7 100644 --- a/packages/framework-core/test/decorators/read-model.test.ts +++ b/packages/framework-core/test/decorators/read-model.test.ts @@ -54,6 +54,8 @@ describe('the `ReadModel` decorator', () => { }, }, ], + queryGeneration: [0, 1], + subscriptionGeneration: [0, 1], }) }) }) @@ -156,6 +158,8 @@ describe('the `ReadModel` decorator', () => { }, }, ], + queryGeneration: [0, 1], + subscriptionGeneration: [0, 1], }) }) }) diff --git a/packages/framework-integration-tests/integration/provider-unaware/end-to-end/read-models.integration.ts b/packages/framework-integration-tests/integration/provider-unaware/end-to-end/read-models.integration.ts index 1505477fa6..638073747a 100644 --- a/packages/framework-integration-tests/integration/provider-unaware/end-to-end/read-models.integration.ts +++ b/packages/framework-integration-tests/integration/provider-unaware/end-to-end/read-models.integration.ts @@ -18,6 +18,7 @@ describe('Read models end-to-end tests', () => { describe('Query read models', () => { context('1 cart item', () => { const mockCartId: string = random.uuid() + const mockStockId: string = random.uuid() const mockProductId: string = random.uuid() const mockQuantity: number = random.number({ min: 1 }) @@ -71,6 +72,32 @@ describe('Read models end-to-end tests', () => { }) }) + it('should not exist', async () => { + const queryResult = await waitForIt( + () => { + return client.query({ + variables: { + id: mockStockId, + }, + query: gql` + query AnotherStockReadModel($id: ID!) { + AnotherStockReadModel(id: $id) { + id + cartItems { + productId + quantity + } + } + } + `, + }) + }, + (result) => result?.data?.CartReadModel != null + ) + + await expect(queryResult).to.eventually.be.rejectedWith(/This query does not exist/) + }) + it('should apply modified filter by before hooks', async () => { // We create a cart with id 'before-fn-test-modified', but we query for // 'before-fn-test', which will then change the filter after two "before" functions diff --git a/packages/framework-integration-tests/src/config/config.ts b/packages/framework-integration-tests/src/config/config.ts index fb802776f0..b6e1a7378a 100644 --- a/packages/framework-integration-tests/src/config/config.ts +++ b/packages/framework-integration-tests/src/config/config.ts @@ -85,6 +85,7 @@ Booster.configure('development', (config: BoosterConfig): void => { enabled: false, } config.injectable = injectable + config.enableAutomaticGraphQLQueryGenerationFromReadModels = true configureInvocationsHandler(config) configureBoosterSensorHealth(config) }) From aaf5ebfe1f8e89bd275b49575ac0a360cfae23b4 Mon Sep 17 00:00:00 2001 From: Nicolas Scheers Date: Tue, 8 Oct 2024 15:03:00 +0200 Subject: [PATCH 7/7] fix: config --- packages/framework-integration-tests/src/config/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/framework-integration-tests/src/config/config.ts b/packages/framework-integration-tests/src/config/config.ts index b6e1a7378a..2dc6ebb71d 100644 --- a/packages/framework-integration-tests/src/config/config.ts +++ b/packages/framework-integration-tests/src/config/config.ts @@ -121,6 +121,7 @@ Booster.configure('production', (config: BoosterConfig): void => { ), ] config.injectable = injectable + config.enableAutomaticGraphQLQueryGenerationFromReadModels = true configureInvocationsHandler(config) configureBoosterSensorHealth(config) })