diff --git a/src/engine/subscription/Subscription.ts b/src/engine/subscription/Subscription.ts index 1271cd7b..1f234b71 100644 --- a/src/engine/subscription/Subscription.ts +++ b/src/engine/subscription/Subscription.ts @@ -1,10 +1,5 @@ import { uniq } from 'lodash'; -import { - passOrThrow, - isArray, - // isFunction, - mapOverProperties, -} from '../util'; +import { passOrThrow, isArray, isFunction, mapOverProperties } from '../util'; import { Entity } from '../entity/Entity'; @@ -46,10 +41,29 @@ export type SubscriptionSetup = { type?: string; description?: string; attributes?: string[]; - // preProcessor?: Function; - // postProcessor?: Function; - // fromState?: string | string[]; - // toState?: string | string[]; + delimiter?: string; + wildCard?: string; + pattern?: string; + preProcessor?: ( + entity?: Entity, + id?: string | number, + source?: any, + input?: any, + typeName?: string, + entitySubscription?: Subscription, + context?: object, + info?: any, + ) => Promise; + postProcessor?: ( + entity?: Entity, + // id, + source?: any, + input?: any, + typeName?: string, + entitySubscription?: Subscription, + context?: object, + info?: any, + ) => Promise; }; export class Subscription { @@ -57,11 +71,12 @@ export class Subscription { type: string; description: string; attributes: string[]; - // fromState: string | string[]; - // toState: string | string[]; + delimiter?: string; + wildCard?: string; + pattern?: string; - // preProcessor: Function; - // postProcessor: Function; + preProcessor: Function; + postProcessor: Function; isTypeCreate?: boolean; isTypeDelete?: boolean; @@ -75,10 +90,8 @@ export class Subscription { type, description, attributes, - // preProcessor, - // postProcessor, - // fromState, - // toState, + preProcessor, + postProcessor, } = setup; passOrThrow(name, () => 'Missing subscription name'); @@ -122,72 +135,25 @@ export class Subscription { this.isTypeDelete = true; } - // if (preProcessor) { - // passOrThrow( - // isFunction(preProcessor), - // () => `preProcessor of subscription '${name}' needs to be a valid function`, - // ); - - // this.preProcessor = preProcessor; - // } - - // if (postProcessor) { - // passOrThrow( - // isFunction(postProcessor), - // () => - // `postProcessor of subscription '${name}' needs to be a valid function`, - // ); - - // this.postProcessor = postProcessor; - // } - - // if (fromState) { - // passOrThrow( - // this.type !== SUBSCRIPTION_TYPE_CREATE, - // () => - // `Subscription '${this.name}' cannot define fromState as it is a 'onCreate' type subscription`, - // ); - - // passOrThrow( - // typeof fromState === 'string' || isArray(fromState), - // () => - // `fromState in subscription '${name}' needs to be the name of a state or a list of state names as a precondition to the subscription`, - // ); - - // if (this.type !== SUBSCRIPTION_TYPE_DELETE) { - // passOrThrow( - // toState, - // () => - // `Subscription '${this.name}' has a fromState defined but misses a toState definition`, - // ); - // } - - // this.fromState = fromState; - // } - - // if (toState) { - // passOrThrow( - // this.type !== SUBSCRIPTION_TYPE_DELETE, - // () => - // `Subscription '${this.name}' cannot define toState as it is a 'onDelete' type subscription`, - // ); - - // passOrThrow( - // typeof toState === 'string' || isArray(toState), - // () => - // `toState in subscription '${this.name}' needs to be the name of a state or a list of state names the subscription can transition to`, - // ); - - // if (this.type !== SUBSCRIPTION_TYPE_CREATE) { - // passOrThrow( - // fromState, - // () => - // `Subscription '${this.name}' has a toState defined but misses a fromState definition`, - // ); - // } - - // this.toState = toState; - // } + if (preProcessor) { + passOrThrow( + isFunction(preProcessor), + () => + `preProcessor of subscription '${name}' needs to be a valid function`, + ); + + this.preProcessor = preProcessor; + } + + if (postProcessor) { + passOrThrow( + isFunction(postProcessor), + () => + `postProcessor of subscription '${name}' needs to be a valid function`, + ); + + this.postProcessor = postProcessor; + } } toString() { @@ -304,39 +270,37 @@ export const processEntitySubscriptions = ( subscription.attributes = nonSystemAttributeNames; } - // const checkSubscriptionStates = stateStringOrArray => { - // const stateNames = isArray(stateStringOrArray) - // ? stateStringOrArray - // : [stateStringOrArray]; - - // stateNames.map(stateName => { - // passOrThrow( - // entityStates[stateName], - // () => - // `Unknown state '${stateName}' used in subscription '${entity.name}.${subscription.name}'`, - // ); - // }); - // }; - - // if (subscription.fromState) { - // passOrThrow( - // entity.hasStates(), - // () => - // `Mutation '${entity.name}.${subscription.name}' cannot define fromState as the entity is stateless`, - // ); - - // checkSubscriptionStates(subscription.fromState); - // } - - // if (subscription.toState) { - // passOrThrow( - // entity.hasStates(), - // () => - // `Subscription '${entity.name}.${subscription.name}' cannot define toState as the entity is stateless`, - // ); - - // checkSubscriptionStates(subscription.toState); - // } + if (subscription.delimiter) { + passOrThrow( + typeof subscription.delimiter === 'string', + () => + `Subscription '${entity.name}.${subscription.name}' delimiter should be a string`, + ); + } else { + subscription.delimiter = '/'; + } + + if (subscription.wildCard) { + // todo make a list of valid wildCards (#, +, *) + passOrThrow( + typeof subscription.wildCard === 'string', + () => + `Subscription '${entity.name}.${subscription.name}' wildCard should be a string`, + ); + } + + if (subscription.pattern) { + // todo validate pattern based on entityAttributes + passOrThrow( + typeof subscription.pattern === 'string', + () => + `Subscription '${entity.name}.${subscription.name}' pattern should be a string`, + ); + } else { + subscription.pattern = Object.keys(entityAttributes).join( + subscription.delimiter, + ); + } }); return subscriptions; diff --git a/src/graphqlProtocol/generator.ts b/src/graphqlProtocol/generator.ts index 545de163..61950c19 100644 --- a/src/graphqlProtocol/generator.ts +++ b/src/graphqlProtocol/generator.ts @@ -389,8 +389,6 @@ export const generateGraphQLSchema = configuration => { fields: () => { const subscriptions = generateSubscriptions(graphRegistry); - console.log('generate subscriptions', { subscriptions }); - return subscriptions; }, }); diff --git a/src/graphqlProtocol/resolver.ts b/src/graphqlProtocol/resolver.ts index 349a6191..e08abaad 100644 --- a/src/graphqlProtocol/resolver.ts +++ b/src/graphqlProtocol/resolver.ts @@ -459,68 +459,12 @@ 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, + idResolver, ) => { const storageType = entity.storageType; // const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; @@ -548,14 +492,18 @@ export const getSubscriptionResolver = ( ); } - // const id = idResolver({ args }); + const id = idResolver({ args }); - if (entitySubscription.type === SUBSCRIPTION_TYPE_CREATE) { - args.input[typeName] = await fillDefaultValues( + if (entitySubscription.preProcessor) { + args.input[typeName] = await entitySubscription.preProcessor( entity, - entitySubscription, + id, + source, args.input[typeName], + typeName, + entitySubscription, context, + info, ); } @@ -591,10 +539,58 @@ export const getSubscriptionResolver = ( // ); // } - // use entity.name and args.input to compose topic - const topic = ''; + const delimiter = entitySubscription.delimiter || '/'; + + const params = entitySubscription.pattern + .split(delimiter) + .reduce((acc, curr) => (acc[curr] = args.input[typeName][curr]), {}); + + const filled = Object.values(params).join(entitySubscription.delimiter); + + const topic = `${entitySubscription.name}/${filled}${ + entitySubscription ? delimiter + entitySubscription.wildCard : '' + }`; return context.pubsub ? context.pubsub.asyncIterator(topic) : null; // : pubsub.asyncIterator(topic); }; }; + +export const getSubscriptionPayloadResolver = ( + entity, + entitySubscription, + typeName, +) => { + return async (source, args, context, info) => { + let ret = { + clientSubscriptionId: args.input.clientSubscriptionId, + }; + + let result; + if (entitySubscription.postProcessor) { + result = await entitySubscription.postProcessor( + entity, + // id, + source, + args.input[typeName], + typeName, + entitySubscription, + context, + info, + ); + } else { + result = source; + } + + if (entitySubscription.type === MUTATION_TYPE_DELETE) { + ret = { + ...ret, + ...result, + }; + } else { + ret[typeName] = result; + } + + return ret; + }; +}; diff --git a/src/graphqlProtocol/subscription.ts b/src/graphqlProtocol/subscription.ts index 45c537ee..932bebd5 100644 --- a/src/graphqlProtocol/subscription.ts +++ b/src/graphqlProtocol/subscription.ts @@ -9,7 +9,7 @@ import { GraphQLFieldConfigMap, } from 'graphql'; -// import { fromGlobalId } from 'graphql-relay'; +import { fromGlobalId } from 'graphql-relay'; import * as _ from 'lodash'; import { ProtocolGraphQL } from './ProtocolGraphQL'; @@ -559,25 +559,25 @@ export const generateSubscriptionOutput = ( return entitySubscriptionOutputType; }; -// const extractIdFromNodeId = (graphRegistry, sourceEntityName, nodeId) => { -// let instanceId; +const extractIdFromNodeId = (graphRegistry, sourceEntityName, nodeId) => { + let instanceId; -// if (nodeId) { -// const { type, id } = fromGlobalId(nodeId); + if (nodeId) { + const { type, id } = fromGlobalId(nodeId); -// instanceId = id; + instanceId = id; -// const entity = graphRegistry.types[type] -// ? graphRegistry.types[type].entity -// : null; + const entity = graphRegistry.types[type] + ? graphRegistry.types[type].entity + : null; -// if (!entity || entity.name !== sourceEntityName) { -// throw new Error('Incompatible nodeId used with this mutation'); -// } -// } + if (!entity || entity.name !== sourceEntityName) { + throw new Error('Incompatible nodeId used with this mutation'); + } + } -// return instanceId; -// }; + return instanceId; +}; export const generateSubscriptions = graphRegistry => { const protocolConfiguration = ProtocolGraphQL.getProtocolConfiguration() as ProtocolGraphQLConfiguration; @@ -585,16 +585,14 @@ export const generateSubscriptions = graphRegistry => { generateInstanceUniquenessInputs(graphRegistry); - // console.log('generateSubscriptions', { graphRegistry }); - _.forEach(graphRegistry.types, ({ type, entity }, typeName) => { if (!entity.getSubscriptions) { return; } - const entitySubscriptions = entity.getSubscriptions(); + // console.log('generateSubscriptions', { type, typeName }); - // console.log('generateSubscriptions', { entitySubscriptions }); + const entitySubscriptions = entity.getSubscriptions(); if (!entitySubscriptions || entitySubscriptions.length < 1) { return; @@ -640,33 +638,18 @@ export const generateSubscriptions = graphRegistry => { type: new GraphQLNonNull(subscriptionInputType), }, }, - // use input for subscribe subscribe: getSubscriptionResolver( entity, entitySubscription, typeName, false, - // ({ args }) => { - // return extractIdFromNodeId( - // graphRegistry, - // entity.name, - // args.input.nodeId, - // ); - // }, + ({ args }) => + 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, - // ); - // }, ), }; @@ -704,33 +687,22 @@ export const generateSubscriptions = graphRegistry => { type: new GraphQLNonNull(subscriptionInputNestedType), }, }, - // use input for subscribe subscribe: getSubscriptionResolver( entity, entitySubscription, typeName, true, - // ({ args }) => { - // return extractIdFromNodeId( - // graphRegistry, - // entity.name, - // args.input.nodeId, - // ); - // }, + ({ args }) => + 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, - // ); - // }, ), }; } @@ -739,7 +711,7 @@ export const generateSubscriptions = graphRegistry => { const primaryAttribute = entity.getPrimaryAttribute(); if (primaryAttribute) { - // const fieldName = primaryAttribute.gqlFieldName; + const fieldName = primaryAttribute.gqlFieldName; const subscriptionByPrimaryAttributeInputType = generateSubscriptionByPrimaryAttributeInput( entity, typeName, @@ -769,16 +741,12 @@ export const generateSubscriptions = graphRegistry => { entitySubscription, typeName, false, - // ({ args }) => { - // return args.input[fieldName]; - // }, + ({ args }) => args.input[fieldName], ), - // use output for resolve resolve: getSubscriptionPayloadResolver( entity, entitySubscription, typeName, - // false, ), }; } @@ -786,7 +754,5 @@ export const generateSubscriptions = graphRegistry => { }); }); - // console.log('generateSubscriptions', { subscriptions }); - return subscriptions; };