diff --git a/src/graphqlProtocol/ProtocolGraphQLConfiguration.ts b/src/graphqlProtocol/ProtocolGraphQLConfiguration.ts index 32d72a9d..5cbeea7f 100644 --- a/src/graphqlProtocol/ProtocolGraphQLConfiguration.ts +++ b/src/graphqlProtocol/ProtocolGraphQLConfiguration.ts @@ -93,6 +93,18 @@ export class ProtocolGraphQLConfiguration extends ProtocolConfiguration { ); } + generateSubscriptionI18nAttributeInputTypeName( + entity, + subscription, + attribute, + ) { + const typeName = this.generateEntityTypeName(entity); + const fieldName = this.generateFieldName(attribute); + return generateTypeNamePascalCase( + `${subscription.name}-${typeName}-${fieldName}-i18n-input`, + ); + } + generateListQueryTypeName(entity) { const typeNamePlural = this.generateEntityTypeNamePlural(entity); return generateTypeName(`all-${typeNamePlural}`); @@ -262,6 +274,71 @@ export class ProtocolGraphQLConfiguration extends ProtocolConfiguration { const typeName = this.generateEntityTypeName(entity); return generateTypeNamePascalCase(`${typeName}-connection`); } + + generateSubscriptionInstanceInputTypeName(entity, subscription) { + const typeName = this.generateEntityTypeName(entity); + return generateTypeNamePascalCase( + `${subscription.name}-${typeName}-instance-input`, + ); + } + + generateSubscriptionInputTypeName(entity, subscription) { + const typeName = this.generateEntityTypeName(entity); + return generateTypeNamePascalCase(`${subscription.name}-${typeName}-input`); + } + + generateSubscriptionByPrimaryAttributeInputTypeName( + entity, + subscription, + attribute, + ) { + const typeName = this.generateEntityTypeName(entity); + const fieldName = this.generateFieldName(attribute); + return generateTypeNamePascalCase( + `${subscription.name}-${typeName}-by-${fieldName}-input`, + ); + } + + generateSubscriptionInstanceNestedInputTypeName(entity, subscription) { + const typeName = this.generateEntityTypeName(entity); + return generateTypeNamePascalCase( + `${subscription.name}-${typeName}-instance-nested-input`, + ); + } + + generateSubscriptionNestedInputTypeName(entity, subscription) { + const typeName = this.generateEntityTypeName(entity); + return generateTypeNamePascalCase( + `${subscription.name}-${typeName}-nested-input`, + ); + } + + generateSubscriptionOutputTypeName(entity, subscription) { + const typeName = this.generateEntityTypeName(entity); + return generateTypeNamePascalCase( + `${subscription.name}-${typeName}-output`, + ); + } + + generateSubscriptionTypeName(entity, subscription) { + const typeName = this.generateEntityTypeName(entity); + return generateTypeName(`${subscription.name}-${typeName}`); + } + + generateSubscriptionNestedTypeName(entity, subscription) { + const typeName = this.generateEntityTypeName(entity); + return generateTypeName(`${subscription.name}-${typeName}-nested`); + } + + generateSubscriptionByPrimaryAttributeTypeName( + entity, + subscription, + attribute, + ) { + const typeName = this.generateEntityTypeName(entity); + const fieldName = this.generateFieldName(attribute); + return generateTypeName(`${subscription.name}-${typeName}-by-${fieldName}`); + } } export const isProtocolGraphQLConfiguration = (obj: any): boolean => { diff --git a/src/graphqlProtocol/__snapshots__/generator.spec.ts.snap b/src/graphqlProtocol/__snapshots__/generator.spec.ts.snap index 154b1ce4..351a0b03 100644 --- a/src/graphqlProtocol/__snapshots__/generator.spec.ts.snap +++ b/src/graphqlProtocol/__snapshots__/generator.spec.ts.snap @@ -17,7 +17,7 @@ GraphQLSchema { "_mutationType": "Mutation", "_possibleTypeMap": Object {}, "_queryType": "Query", - "_subscriptionType": undefined, + "_subscriptionType": "Subscription", "_typeMap": Object { "Boolean": "Boolean", "CreateTestEntityNameInput": "CreateTestEntityNameInput", @@ -33,9 +33,24 @@ GraphQLSchema { "Int": "Int", "Mutation": "Mutation", "Node": "Node", + "OnCreateTestEntityNameInput": "OnCreateTestEntityNameInput", + "OnCreateTestEntityNameInstanceInput": "OnCreateTestEntityNameInstanceInput", + "OnCreateTestEntityNameInstanceNestedInput": "OnCreateTestEntityNameInstanceNestedInput", + "OnCreateTestEntityNameNestedInput": "OnCreateTestEntityNameNestedInput", + "OnCreateTestEntityNameOutput": "OnCreateTestEntityNameOutput", + "OnDeleteTestEntityNameByIdInput": "OnDeleteTestEntityNameByIdInput", + "OnDeleteTestEntityNameInput": "OnDeleteTestEntityNameInput", + "OnDeleteTestEntityNameOutput": "OnDeleteTestEntityNameOutput", + "OnUpdateTestEntityNameByIdInput": "OnUpdateTestEntityNameByIdInput", + "OnUpdateTestEntityNameInput": "OnUpdateTestEntityNameInput", + "OnUpdateTestEntityNameInstanceInput": "OnUpdateTestEntityNameInstanceInput", + "OnUpdateTestEntityNameInstanceNestedInput": "OnUpdateTestEntityNameInstanceNestedInput", + "OnUpdateTestEntityNameNestedInput": "OnUpdateTestEntityNameNestedInput", + "OnUpdateTestEntityNameOutput": "OnUpdateTestEntityNameOutput", "PageInfo": "PageInfo", "Query": "Query", "String": "String", + "Subscription": "Subscription", "TestEntityName": "TestEntityName", "TestEntityNameConnection": "TestEntityNameConnection", "TestEntityNameEdge": "TestEntityNameEdge", diff --git a/src/graphqlProtocol/generator.ts b/src/graphqlProtocol/generator.ts index 8a87fac6..545de163 100644 --- a/src/graphqlProtocol/generator.ts +++ b/src/graphqlProtocol/generator.ts @@ -23,6 +23,8 @@ import { generateMutations } from './mutation'; import { generateActions } from './action'; +import { generateSubscriptions } from './subscription'; + import { resolveByFindOne } from './resolver'; import { isConfiguration } from '../engine/configuration/Configuration'; import { isEntity } from '../engine/entity/Entity'; @@ -372,6 +374,7 @@ export const generateGraphQLSchema = configuration => { fields: () => { const mutations = generateMutations(graphRegistry); const actions = generateActions(graphRegistry, ACTION_TYPE_MUTATION); + // console.log('generate mutations', { mutations }); return { ...mutations, @@ -380,9 +383,22 @@ export const generateGraphQLSchema = configuration => { }, }); + const subscriptionType = new GraphQLObjectType({ + name: 'Subscription', + // root: 'The root subscription type', + + fields: () => { + const subscriptions = generateSubscriptions(graphRegistry); + console.log('generate subscriptions', { subscriptions }); + + return subscriptions; + }, + }); + // put it all together into a graphQL schema return new GraphQLSchema({ query: queryType, mutation: mutationType, + subscription: subscriptionType, }); }; diff --git a/src/graphqlProtocol/resolver.ts b/src/graphqlProtocol/resolver.ts index 6afd0120..349a6191 100644 --- a/src/graphqlProtocol/resolver.ts +++ b/src/graphqlProtocol/resolver.ts @@ -30,6 +30,11 @@ import { MUTATION_TYPE_UPDATE, MUTATION_TYPE_DELETE, } from '../engine/mutation/Mutation'; +import { + SUBSCRIPTION_TYPE_CREATE, + SUBSCRIPTION_TYPE_UPDATE, + // SUBSCRIPTION_TYPE_DELETE, +} from '../engine/subscription/Subscription'; import { CustomError } from '../engine/CustomError'; import { fillSystemAttributesDefaultValues, @@ -453,3 +458,143 @@ export const getMutationResolver = ( } }; }; + +export const getSubscriptionPayloadResolver = ( + entity, + entitySubscription, + typeName, + // nested, + // idResolver, +) => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + return async (source, args, context) => { + // checkRequiredI18nInputs( + // entity, + // entitySubscription, + // args.input[typeName], + // // context, + // ); + + // if (nested) { + // args.input[typeName] = await nestedPayloadResolver( + // source, + // args.input[typeName], + // context, + // info, + // ); + // } + + // const id = idResolver({ args }); + let result; + if (entitySubscription.type !== MUTATION_TYPE_DELETE) { + result = entity.graphql.dataShaper( + addRelayTypePromoterToInstance( + protocolConfiguration.generateEntityTypeName(entity), + source, + ), + ); + + result = translateInstance(entity, result, context); + } + + let ret = { + clientSubscriptionId: args.input.clientSubscriptionId, + }; + + if (entitySubscription.type === MUTATION_TYPE_DELETE) { + ret = { + ...ret, + ...source, + }; + } else { + ret[typeName] = result; + } + + return ret; + }; +}; + +export const getSubscriptionResolver = ( + entity, + entitySubscription, + typeName, + nested, + // idResolver, +) => { + const storageType = entity.storageType; + // const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + const nestedPayloadResolver = getNestedPayloadResolver( + entity, + entitySubscription.attributes, + storageType, + ); + + return async (source, args, context, info) => { + checkRequiredI18nInputs( + entity, + entitySubscription, + args.input[typeName], + // context, + ); + + if (nested) { + args.input[typeName] = await nestedPayloadResolver( + source, + args.input[typeName], + context, + info, + ); + } + + // const id = idResolver({ args }); + + if (entitySubscription.type === SUBSCRIPTION_TYPE_CREATE) { + args.input[typeName] = await fillDefaultValues( + entity, + entitySubscription, + args.input[typeName], + context, + ); + } + + if ( + entitySubscription.type === SUBSCRIPTION_TYPE_CREATE || + entitySubscription.type === SUBSCRIPTION_TYPE_UPDATE + ) { + args.input[typeName] = fillSystemAttributesDefaultValues( + entity, + entitySubscription, + args.input[typeName], + context, + ); + } + + // await validateSubscriptionPayload( + // entity, + // entitySubscription, + // args.input[typeName], + // context, + // ); + + // if (entitySubscription.type !== SUBSCRIPTION_TYPE_DELETE) { + // // + // // this function might be wrong when we look serializeValues args + // // unless we add typeName ? + // args.input[typeName] = serializeValues( + // entity, + // entitySubscription, + // args.input[typeName], + // typeName, + // context, + // ); + // } + + // use entity.name and args.input to compose topic + const topic = ''; + + return context.pubsub ? context.pubsub.asyncIterator(topic) : null; + // : pubsub.asyncIterator(topic); + }; +}; diff --git a/src/graphqlProtocol/subscription.ts b/src/graphqlProtocol/subscription.ts new file mode 100644 index 00000000..45c537ee --- /dev/null +++ b/src/graphqlProtocol/subscription.ts @@ -0,0 +1,792 @@ +import { + GraphQLString, + GraphQLID, + GraphQLNonNull, + GraphQLInputObjectType, + GraphQLObjectType, + GraphQLInt, + GraphQLInputFieldConfigMap, + GraphQLFieldConfigMap, +} from 'graphql'; + +// import { fromGlobalId } from 'graphql-relay'; +import * as _ from 'lodash'; + +import { ProtocolGraphQL } from './ProtocolGraphQL'; +import { ProtocolGraphQLConfiguration } from './ProtocolGraphQLConfiguration'; +import { getEntityUniquenessAttributes } from './helper'; +import { + getSubscriptionResolver, + getSubscriptionPayloadResolver, +} from './resolver'; +import { isEntity } from '../engine/entity/Entity'; + +const i18nInputFieldTypesCache = {}; + +const generateI18nInputFieldType = (entity, entitySubscription, attribute) => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + const i18nFieldTypeName = protocolConfiguration.generateSubscriptionI18nAttributeInputTypeName( + entity, + entitySubscription, + attribute, + ); + + if (i18nInputFieldTypesCache[i18nFieldTypeName]) { + return i18nInputFieldTypesCache[i18nFieldTypeName]; + } + + const attributeType = attribute.type; + const typeNamePascalCase = entity.graphql.typeNamePascalCase; + const languages = protocolConfiguration + .getParentConfiguration() + .getLanguages(); + const fieldType = ProtocolGraphQL.convertToProtocolDataType( + attributeType, + entity.name, + true, + ); + + const i18nFieldType = new GraphQLInputObjectType({ + name: i18nFieldTypeName, + description: `**\`${entitySubscription.name}\`** subscription translations input type for **\`${typeNamePascalCase}.${attribute.gqlFieldName}\`**`, + + fields: () => { + const i18nFields = {}; + + languages.map((language, langIdx) => { + const type = + langIdx === 0 && + attribute.required && + !entitySubscription.ignoreRequired + ? new GraphQLNonNull(fieldType) + : fieldType; + + i18nFields[language] = { + type, + }; + }); + + return i18nFields; + }, + }); + + i18nInputFieldTypesCache[i18nFieldTypeName] = i18nFieldType; + + return i18nFieldType; +}; + +export const generateSubscriptionInstanceInput = ( + entity, + entitySubscription, +) => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + const typeNamePascalCase = entity.graphql.typeNamePascalCase; + + const entitySubscriptionInstanceInputType = new GraphQLInputObjectType({ + name: protocolConfiguration.generateSubscriptionInstanceInputTypeName( + entity, + entitySubscription, + ), + description: `**\`${entitySubscription.name}\`** subscription input type for **\`${typeNamePascalCase}\`**`, + + fields: () => { + const fields: GraphQLInputFieldConfigMap = {}; + + const entityAttributes = entity.getAttributes(); + + _.forEach(entitySubscription.attributes, attributeName => { + const attribute = entityAttributes[attributeName]; + + let attributeType = attribute.type; + + // it's a reference + if (isEntity(attributeType)) { + const targetEntity = attributeType; + const primaryAttribute = targetEntity.getPrimaryAttribute(); + attributeType = primaryAttribute.type; + } + + const fieldType = ProtocolGraphQL.convertToProtocolDataType( + attributeType, + entity.name, + true, + ); + + fields[attribute.gqlFieldName] = { + type: + attribute.required && + !entitySubscription.ignoreRequired && + !attribute.i18n && + !attribute.defaultValue + ? new GraphQLNonNull(fieldType) + : fieldType, + }; + + if (attribute.i18n) { + const i18nFieldType = generateI18nInputFieldType( + entity, + entitySubscription, + attribute, + ); + + fields[attribute.gqlFieldNameI18n] = { + type: i18nFieldType, + }; + } + }); + + return fields; + }, + }); + + return entitySubscriptionInstanceInputType; +}; + +export const generateSubscriptionInput = ( + entity, + typeName, + entitySubscription, + entitySubscriptionInstanceInputType, +) => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + const typeNamePascalCase = entity.graphql.typeNamePascalCase; + + const entitySubscriptionInputType = new GraphQLInputObjectType({ + name: protocolConfiguration.generateSubscriptionInputTypeName( + entity, + entitySubscription, + ), + description: `Subscription input type for **\`${typeNamePascalCase}\`**`, + + fields: () => { + const fields: GraphQLInputFieldConfigMap = { + clientSubscriptionId: { + type: GraphQLString, + }, + }; + + if (entitySubscription.needsInstance) { + fields.nodeId = { + type: new GraphQLNonNull(GraphQLID), + }; + } + + if (entitySubscriptionInstanceInputType) { + fields[typeName] = { + type: new GraphQLNonNull(entitySubscriptionInstanceInputType), + }; + } + + return fields; + }, + }); + + return entitySubscriptionInputType; +}; + +export const generateSubscriptionByPrimaryAttributeInput = ( + entity, + typeName, + entitySubscription, + entitySubscriptionInstanceInputType, + primaryAttribute, +) => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + const fieldName = primaryAttribute.gqlFieldName; + const fieldType = ProtocolGraphQL.convertToProtocolDataType( + primaryAttribute.type, + entity.name, + true, + ); + const typeNamePascalCase = entity.graphql.typeNamePascalCase; + + const entitySubscriptionInputType = new GraphQLInputObjectType({ + name: protocolConfiguration.generateMutationByPrimaryAttributeInputTypeName( + entity, + entitySubscription, + primaryAttribute, + ), + description: `Subscription input type for **\`${typeNamePascalCase}\`** using the **\`${fieldName}\`**`, + + fields: () => { + const fields: GraphQLInputFieldConfigMap = { + clientSubscriptionId: { + type: GraphQLString, + }, + }; + + if (entitySubscription.needsInstance) { + fields[fieldName] = { + type: new GraphQLNonNull(fieldType), + }; + } + + if (entitySubscriptionInstanceInputType) { + fields[typeName] = { + type: new GraphQLNonNull(entitySubscriptionInstanceInputType), + }; + } + + return fields; + }, + }); + + return entitySubscriptionInputType; +}; + +export const generateInstanceUniquenessInput = ( + entity, + uniquenessAttributes, + graphRegistry, +) => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + const typeNamePascalCase = entity.graphql.typeNamePascalCase; + + const entityInstanceInputType = new GraphQLInputObjectType({ + name: protocolConfiguration.generateInstanceUniquenessInputTypeName( + entity, + uniquenessAttributes.uniquenessName, + ), + description: `Input type for **\`${typeNamePascalCase}\`** using data uniqueness (${uniquenessAttributes.attributes}) to resolve the ID`, + + fields: () => { + const fields: GraphQLInputFieldConfigMap = {}; + + const entityAttributes = entity.getAttributes(); + + _.forEach(uniquenessAttributes.attributes, attributeName => { + const attribute = entityAttributes[attributeName]; + + let attributeType = attribute.type; + + if (isEntity(attributeType)) { + const targetEntity = attributeType; + const primaryAttribute = targetEntity.getPrimaryAttribute(); + const targetTypeName = targetEntity.graphql.typeName; + + attributeType = primaryAttribute.type; + const fieldType = ProtocolGraphQL.convertToProtocolDataType( + attributeType, + entity.name, + true, + ); + + const uniquenessAttributesList = getEntityUniquenessAttributes( + targetEntity, + ); + + if (uniquenessAttributesList.length === 0) { + fields[attribute.gqlFieldName] = { + type: attribute.required + ? new GraphQLNonNull(fieldType) + : fieldType, + }; + } else { + fields[attribute.gqlFieldName] = { + type: fieldType, + }; + + const registryType = graphRegistry.types[targetTypeName]; + registryType.instanceUniquenessInputs = + registryType.instanceUniquenessInputs || {}; + + uniquenessAttributesList.map(({ uniquenessName }) => { + const fieldName = protocolConfiguration.generateUniquenessAttributesFieldName( + entity, + attribute, + uniquenessName, + ); + fields[fieldName] = { + type: registryType.instanceUniquenessInputs[uniquenessName], + }; + }); + } + } else { + const fieldType = ProtocolGraphQL.convertToProtocolDataType( + attributeType, + entity.name, + true, + ); + + fields[attribute.gqlFieldName] = { + type: new GraphQLNonNull(fieldType), + }; + } + }); + + return fields; + }, + }); + + return entityInstanceInputType; +}; + +export const generateInstanceUniquenessInputs = graphRegistry => { + _.forEach(graphRegistry.types, ({ entity }, typeName) => { + const uniquenessAttributesList = getEntityUniquenessAttributes(entity); + + const registryType = graphRegistry.types[typeName]; + registryType.instanceUniquenessInputs = + registryType.instanceUniquenessInputs || {}; + + uniquenessAttributesList.map(uniquenessAttributes => { + const instanceUniquenessInput = generateInstanceUniquenessInput( + entity, + uniquenessAttributes, + graphRegistry, + ); + registryType.instanceUniquenessInputs[ + uniquenessAttributes.uniquenessName + ] = instanceUniquenessInput; + }); + }); +}; + +export const generateSubscriptionInstanceNestedInput = ( + entity, + entitySubscription, + graphRegistry, +) => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + const typeNamePascalCase = entity.graphql.typeNamePascalCase; + + const entitySubscriptionInstanceInputType = new GraphQLInputObjectType({ + name: protocolConfiguration.generateSubscriptionInstanceNestedInputTypeName( + entity, + entitySubscription, + ), + description: `**\`${entitySubscription.name}\`** subscription input type for **\`${typeNamePascalCase}\`** using data uniqueness to resolve references`, + + fields: () => { + const fields: GraphQLInputFieldConfigMap = {}; + + const entityAttributes = entity.getAttributes(); + + _.forEach(entitySubscription.attributes, attributeName => { + const attribute = entityAttributes[attributeName]; + + let attributeType = attribute.type; + + if (isEntity(attributeType)) { + const targetEntity = attributeType; + const primaryAttribute = targetEntity.getPrimaryAttribute(); + const targetTypeName = targetEntity.graphql.typeName; + + attributeType = primaryAttribute.type; + const fieldType = ProtocolGraphQL.convertToProtocolDataType( + attributeType, + entity.name, + true, + ); + + const uniquenessAttributesList = getEntityUniquenessAttributes( + targetEntity, + ); + + if (uniquenessAttributesList.length === 0) { + fields[attribute.gqlFieldName] = { + type: + attribute.required && + !entitySubscription.ignoreRequired && + !attribute.defaultValue + ? new GraphQLNonNull(fieldType) + : fieldType, + }; + } else { + fields[attribute.gqlFieldName] = { + type: fieldType, + }; + + const registryType = graphRegistry.types[targetTypeName]; + registryType.instanceUniquenessInputs = + registryType.instanceUniquenessInputs || {}; + + uniquenessAttributesList.map(({ uniquenessName }) => { + const fieldName = protocolConfiguration.generateUniquenessAttributesFieldName( + entity, + attribute, + uniquenessName, + ); + fields[fieldName] = { + type: registryType.instanceUniquenessInputs[uniquenessName], + }; + }); + } + } else { + const fieldType = ProtocolGraphQL.convertToProtocolDataType( + attributeType, + entity.name, + true, + ); + + fields[attribute.gqlFieldName] = { + type: + attribute.required && + !entitySubscription.ignoreRequired && + !attribute.i18n && + !attribute.defaultValue + ? new GraphQLNonNull(fieldType) + : fieldType, + }; + + if (attribute.i18n) { + const i18nFieldType = generateI18nInputFieldType( + entity, + entitySubscription, + attribute, + ); + + fields[attribute.gqlFieldNameI18n] = { + type: i18nFieldType, + }; + } + } + }); + + return fields; + }, + }); + + return entitySubscriptionInstanceInputType; +}; + +export const generateSubscriptionNestedInput = ( + entity, + typeName, + entitySubscription, + entitySubscriptionInstanceUniquenessInputType, +) => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + const typeNamePascalCase = entity.graphql.typeNamePascalCase; + + const entitySubscriptionInputType = new GraphQLInputObjectType({ + name: protocolConfiguration.generateSubscriptionNestedInputTypeName( + entity, + entitySubscription, + ), + description: `Mutation input type for **\`${typeNamePascalCase}\`** using data uniqueness to resolve references`, + + fields: () => { + const fields: GraphQLInputFieldConfigMap = { + clientSubscriptionId: { + type: GraphQLString, + }, + }; + + if (entitySubscription.needsInstance) { + fields.nodeId = { + type: new GraphQLNonNull(GraphQLID), + }; + } + + if (entitySubscriptionInstanceUniquenessInputType) { + fields[typeName] = { + type: new GraphQLNonNull( + entitySubscriptionInstanceUniquenessInputType, + ), + }; + } + + return fields; + }, + }); + + return entitySubscriptionInputType; +}; + +export const generateSubscriptionOutput = ( + entity, + typeName, + type, + entitySubscription, +) => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + + const typeNamePascalCase = entity.graphql.typeNamePascalCase; + + const entitySubscriptionOutputType = new GraphQLObjectType({ + name: protocolConfiguration.generateSubscriptionOutputTypeName( + entity, + entitySubscription, + ), + description: `Subscription output type for **\`${typeNamePascalCase}\`**`, + + fields: () => { + const fields: GraphQLFieldConfigMap = { + clientSubscriptionId: { + type: GraphQLString, + }, + }; + + if (entitySubscription.isTypeDelete) { + fields.deleteRowCount = { + type: new GraphQLNonNull(GraphQLInt), + description: 'Number of deleted rows', + }; + + const primaryAttribute = entity.getPrimaryAttribute(); + + if (primaryAttribute) { + const fieldName = primaryAttribute.gqlFieldName; + const fieldType = ProtocolGraphQL.convertToProtocolDataType( + primaryAttribute.type, + entity.name, + false, + ); + + fields[fieldName] = { + type: new GraphQLNonNull(fieldType), + description: primaryAttribute.description, + }; + } + } else { + fields[typeName] = { + type: new GraphQLNonNull(type), + }; + } + + return fields; + }, + }); + + return entitySubscriptionOutputType; +}; + +// const extractIdFromNodeId = (graphRegistry, sourceEntityName, nodeId) => { +// let instanceId; + +// if (nodeId) { +// const { type, id } = fromGlobalId(nodeId); + +// instanceId = id; + +// const entity = graphRegistry.types[type] +// ? graphRegistry.types[type].entity +// : null; + +// if (!entity || entity.name !== sourceEntityName) { +// throw new Error('Incompatible nodeId used with this mutation'); +// } +// } + +// return instanceId; +// }; + +export const generateSubscriptions = graphRegistry => { + const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; + const subscriptions = {}; + + generateInstanceUniquenessInputs(graphRegistry); + + // console.log('generateSubscriptions', { graphRegistry }); + + _.forEach(graphRegistry.types, ({ type, entity }, typeName) => { + if (!entity.getSubscriptions) { + return; + } + + const entitySubscriptions = entity.getSubscriptions(); + + // console.log('generateSubscriptions', { entitySubscriptions }); + + if (!entitySubscriptions || entitySubscriptions.length < 1) { + return; + } + + entitySubscriptions.map(entitySubscription => { + const subscriptionName = protocolConfiguration.generateSubscriptionTypeName( + entity, + entitySubscription, + ); + + let entitySubscriptionInstanceInputType; + + if ( + entitySubscription.attributes && + entitySubscription.attributes.length + ) { + entitySubscriptionInstanceInputType = generateSubscriptionInstanceInput( + entity, + entitySubscription, + ); + } + + const subscriptionInputType = generateSubscriptionInput( + entity, + typeName, + entitySubscription, + entitySubscriptionInstanceInputType, + ); + const subscriptionOutputType = generateSubscriptionOutput( + entity, + typeName, + type, + entitySubscription, + ); + + subscriptions[subscriptionName] = { + type: subscriptionOutputType, + description: entitySubscription.description, + args: { + input: { + description: 'Input argument for this subscription', + type: new GraphQLNonNull(subscriptionInputType), + }, + }, + // use input for subscribe + subscribe: getSubscriptionResolver( + entity, + entitySubscription, + typeName, + false, + // ({ args }) => { + // return extractIdFromNodeId( + // graphRegistry, + // entity.name, + // args.input.nodeId, + // ); + // }, + ), + // use output for resolve + resolve: getSubscriptionPayloadResolver( + entity, + entitySubscription, + typeName, + // false, + // ({ args }) => { + // return extractIdFromNodeId( + // graphRegistry, + // entity.name, + // args.input.nodeId, + // ); + // }, + ), + }; + + if (entitySubscription.isTypeCreate || entitySubscription.isTypeUpdate) { + const subscriptionNestedName = protocolConfiguration.generateSubscriptionNestedTypeName( + entity, + entitySubscription, + ); + + let entitySubscriptionInstanceNestedInputType; + + if ( + entitySubscription.attributes && + entitySubscription.attributes.length + ) { + entitySubscriptionInstanceNestedInputType = generateSubscriptionInstanceNestedInput( + entity, + entitySubscription, + graphRegistry, + ); + } + + const subscriptionInputNestedType = generateSubscriptionNestedInput( + entity, + typeName, + entitySubscription, + entitySubscriptionInstanceNestedInputType, + ); + subscriptions[subscriptionNestedName] = { + type: subscriptionOutputType, + description: entitySubscription.description, + args: { + input: { + description: 'Input argument for this subscription', + type: new GraphQLNonNull(subscriptionInputNestedType), + }, + }, + // use input for subscribe + subscribe: getSubscriptionResolver( + entity, + entitySubscription, + typeName, + true, + // ({ args }) => { + // return extractIdFromNodeId( + // graphRegistry, + // entity.name, + // args.input.nodeId, + // ); + // }, + ), + // use output for resolve + resolve: getSubscriptionPayloadResolver( + entity, + entitySubscription, + typeName, + // true, + // ({ args }) => { + // return extractIdFromNodeId( + // graphRegistry, + // entity.name, + // args.input.nodeId, + // ); + // }, + ), + }; + } + + if (entitySubscription.needsInstance) { + const primaryAttribute = entity.getPrimaryAttribute(); + + if (primaryAttribute) { + // const fieldName = primaryAttribute.gqlFieldName; + const subscriptionByPrimaryAttributeInputType = generateSubscriptionByPrimaryAttributeInput( + entity, + typeName, + entitySubscription, + entitySubscriptionInstanceInputType, + primaryAttribute, + ); + const subscriptionByPrimaryAttributeName = protocolConfiguration.generateSubscriptionByPrimaryAttributeTypeName( + entity, + entitySubscription, + primaryAttribute, + ); + + subscriptions[subscriptionByPrimaryAttributeName] = { + type: subscriptionOutputType, + description: entitySubscription.description, + args: { + input: { + description: 'Input argument for this subscription', + type: new GraphQLNonNull( + subscriptionByPrimaryAttributeInputType, + ), + }, + }, + subscribe: getSubscriptionResolver( + entity, + entitySubscription, + typeName, + false, + // ({ args }) => { + // return args.input[fieldName]; + // }, + ), + // use output for resolve + resolve: getSubscriptionPayloadResolver( + entity, + entitySubscription, + typeName, + // false, + ), + }; + } + } + }); + }); + + // console.log('generateSubscriptions', { subscriptions }); + + return subscriptions; +};