Skip to content
Open
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
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"devDependencies": {
"@types/mocha": "^10.0.9"
}
}
42 changes: 21 additions & 21 deletions packages/framework-core/src/decorators/read-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import {
Class,
ReadModelAuthorizer,
ReadModelFilterHooks,
ReadModelGraphqlQueryGeneratorConfig,
ReadModelGraphqlSubscriptionGeneratorConfig,
GenerationStrategy,
ReadModelInterface,
ReadModelRoleAccess,
} from '@boostercloud/framework-types'
Expand All @@ -16,7 +15,11 @@ import { getClassMetadata } from './metadata'
* @param attributes
*/
export function ReadModel(
attributes: ReadModelRoleAccess & ReadModelFilterHooks, graphQLQueryConfiguration: ReadModelGraphqlQueryGeneratorConfig, graphQLSubscriptionConfiguration: ReadModelGraphqlSubscriptionGeneratorConfig
attributes: ReadModelRoleAccess & ReadModelFilterHooks,
graphQlGeneration?: {
queryGeneration: GenerationStrategy[]
subscriptionGeneration: GenerationStrategy[]
}
): (readModelClass: Class<ReadModelInterface>, context?: ClassDecoratorContext) => void {
return (readModelClass) => {
Booster.configureCurrentEnv((config): void => {
Expand All @@ -25,32 +28,29 @@ export function ReadModel(
If you think that this is an error, try performing a clean build.`)
}

const enableAutomaticGraphQLQueryGenerationFromReadModels = config.enableAutomaticGraphQLQueryGenerationFromReadModels;
if (enableAutomaticGraphQLQueryGenerationFromReadModels) {
if (!graphQLQueryConfiguration) {
graphQLQueryConfiguration = 'GRAPHQL_LIST_AND_SINGLE_QUERIES';
}
if (!graphQLSubscriptionConfiguration) {
graphQLSubscriptionConfiguration = 'GRAPHQL_LIST_AND_SINGLE_SUBSCRIPTION';
}
}
if (!enableAutomaticGraphQLQueryGenerationFromReadModels) {
if (!graphQLQueryConfiguration) {
graphQLQueryConfiguration = 'NO_GRAPHQL_QUERIES';
}
if (!graphQLSubscriptionConfiguration) {
graphQLSubscriptionConfiguration = 'NO_GRAPHQL_SUBSCRIPTIONS';
const enableAutomaticGraphQLQueryGenerationFromReadModels =
config.enableAutomaticGraphQLQueryGenerationFromReadModels
let queryGeneration: GenerationStrategy[] = []
let subscriptionGeneration: GenerationStrategy[] = []

if (graphQlGeneration) {
queryGeneration = graphQlGeneration.queryGeneration
subscriptionGeneration = graphQlGeneration.subscriptionGeneration
} else {
if (enableAutomaticGraphQLQueryGenerationFromReadModels) {
queryGeneration = [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE]
subscriptionGeneration = [GenerationStrategy.GRAPHQL_LIST, GenerationStrategy.GRAPHQL_SINGLE]
}
}

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,
queryGeneration,
subscriptionGeneration,
}
})
}
Expand Down
24 changes: 24 additions & 0 deletions packages/framework-core/src/services/graphql/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
GraphQLOperation,
ReadModelInterface,
ContextEnvelope,
BoosterConfig,
GenerationStrategy,
} from '@boostercloud/framework-types'
import {
GraphQLEnumType,
Expand Down Expand Up @@ -88,6 +90,28 @@ export const buildGraphqlSimpleEnumFor = (enumName: string, values: Array<string
})
}

export const filterReadModelsThatRequireGraphQLQueries = (
readModels: AnyClass[],
config: BoosterConfig
): AnyClass[] => {
return readModels.filter((readModel) => {
const graphqlQueryGenerationConfig = config.readModels[readModel.name]?.queryGeneration
if (!graphqlQueryGenerationConfig) return config.enableAutomaticGraphQLQueryGenerationFromReadModels
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
if (!graphqlSubscriptionGenerationConfig) return config.enableAutomaticGraphQLQueryGenerationFromReadModels
return graphqlSubscriptionGenerationConfig.includes(GenerationStrategy.GRAPHQL_LIST)
})
}

export function nonExcludedFields(
fields: Array<PropertyMetadata>,
excludeProps?: Array<string>
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand All @@ -36,9 +40,9 @@ export class GraphQLSubscriptionGenerator {
})
}

private generateByIDSubscriptions(): GraphQLFieldConfigMap<any, any> {
private generateByIDSubscriptions(readModels: AnyClass[]): GraphQLFieldConfigMap<any, any> {
const subscriptions: GraphQLFieldConfigMap<any, any> = {}
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] = {
Expand All @@ -53,9 +57,9 @@ export class GraphQLSubscriptionGenerator {
return subscriptions
}

private generateFilterSubscriptions(): GraphQLFieldConfigMap<any, any> {
private generateFilterSubscriptions(readModels: AnyClass[]): GraphQLFieldConfigMap<any, any> {
const subscriptions: GraphQLFieldConfigMap<any, any> = {}
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)] = {
Expand Down
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -13,7 +13,7 @@ export class GraphqlQueryByKeysGenerator {

public generateByKeysQueries(): GraphQLFieldConfigMap<unknown, GraphQLResolverContext> {
const queries: GraphQLFieldConfigMap<unknown, GraphQLResolverContext> = {}
const readModelsThatRequireGraphQLQueries = this.filterReadModelsThatRequireGraphQLQueries(this.readModels, this.config);
const readModelsThatRequireGraphQLQueries = filterReadModelsThatRequireGraphQLQueries(this.readModels, this.config)

for (const readModel of readModelsThatRequireGraphQLQueries) {
const readModelName = readModel.name
Expand All @@ -27,13 +27,6 @@ export class GraphqlQueryByKeysGenerator {
return queries
}

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';
});
}

private generateByIdQuery(readModel: AnyClass): GraphQLFieldConfig<unknown, GraphQLResolverContext> {
const graphQLType = this.typeInformer.generateGraphQLTypeForClass(
readModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -37,7 +37,7 @@ export class GraphqlQueryListedGenerator {

public generateListedQueries(): GraphQLFieldConfigMap<unknown, GraphQLResolverContext> {
const queries: GraphQLFieldConfigMap<unknown, GraphQLResolverContext> = {}
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)
Expand All @@ -59,13 +59,6 @@ export class GraphqlQueryListedGenerator {
return queries
}

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';
});
}

private generateListedQueriesFields(
name: string,
type: AnyClass,
Expand Down
17 changes: 17 additions & 0 deletions packages/framework-core/test/booster-read-models-reader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { expect } from './expect'
import {
BoosterConfig,
FilterFor,
GenerationStrategy,
GraphQLOperation,
InvalidParameterError,
NotAuthorizedError,
Expand Down Expand Up @@ -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],
}
})

Expand Down Expand Up @@ -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],
}
})

Expand Down Expand Up @@ -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')
})
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions packages/framework-core/test/decorators/read-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ describe('the `ReadModel` decorator', () => {
},
},
],
queryGeneration: [0, 1],
subscriptionGeneration: [0, 1],
})
})
})
Expand Down Expand Up @@ -156,6 +158,8 @@ describe('the `ReadModel` decorator', () => {
},
},
],
queryGeneration: [0, 1],
subscriptionGeneration: [0, 1],
})
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createInstance } from '@boostercloud/framework-common-helpers'
import {
BoosterConfig,
EntitySnapshotEnvelope,
GenerationStrategy,
Level,
OptimisticConcurrencyUnexpectedVersionError,
ProjectionMetadata,
Expand Down Expand Up @@ -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] = [
{
Expand Down
Loading