1
1
import { GraphQLResolveInfo } from "graphql" ;
2
- import { withFilter } from "graphql-subscriptions" ;
3
2
4
3
import GraphContext from "../../context" ;
5
4
import { SUBSCRIPTION_CHANNELS , SubscriptionPayload } from "./types" ;
6
5
6
+ type ResolverFn < TParent , TArgs , TContext > = (
7
+ parent : TParent ,
8
+ args : TArgs ,
9
+ context : TContext ,
10
+ info : GraphQLResolveInfo
11
+ ) => AsyncIterable < any > ;
12
+
7
13
type FilterFn < TParent , TArgs , TContext > = (
8
14
parent : TParent ,
9
15
args : TArgs ,
@@ -23,13 +29,51 @@ interface SubscriptionResolver<TParent, TArgs, TResult> {
23
29
resolve : Resolver < TParent , TArgs , TParent > ;
24
30
}
25
31
26
- export function createTenantAsyncIterator < TParent , TArgs , TResult > (
32
+ /**
33
+ * withFilter applies a filter to a async iterator.
34
+ *
35
+ * This duplicates the functionality of the withFilter function provided by the
36
+ * `graphql-subscriptions` package without the memory leak as it uses native
37
+ * async iterators instead.
38
+ *
39
+ * Solution provided by @brettjashford.
40
+ *
41
+ * https://github.com/apollographql/graphql-subscriptions/pull/209#issuecomment-713906710
42
+ *
43
+ * @param asyncIteratorFn the async iterator to use that's provided by the transport
44
+ * @param filterFn the filter to apply for each iteration to check to see if we should sent it
45
+ */
46
+ function withFilter < TParent , TArgs > (
47
+ asyncIteratorFn : ResolverFn < TParent , TArgs , GraphContext > ,
48
+ filterFn : FilterFn < TParent , TArgs , GraphContext >
49
+ ) {
50
+ return async function * (
51
+ source : TParent ,
52
+ args : TArgs ,
53
+ ctx : GraphContext ,
54
+ info : GraphQLResolveInfo
55
+ ) {
56
+ const asyncIterator = asyncIteratorFn ( source , args , ctx , info ) ;
57
+ for await ( const payload of asyncIterator ) {
58
+ if ( await filterFn ( payload , args , ctx , info ) ) {
59
+ yield payload ;
60
+ }
61
+ }
62
+ } ;
63
+ }
64
+
65
+ function createTenantAsyncIterator < TParent , TArgs , TResult > (
27
66
channel : SUBSCRIPTION_CHANNELS
28
- ) : Resolver < TParent , TArgs , AsyncIterator < TResult > > {
67
+ ) : Resolver < TParent , TArgs , AsyncIterable < TResult > > {
29
68
return ( source , args , ctx ) =>
30
- ctx . pubsub . asyncIterator < TResult > (
69
+ // This is already technically returning an AsyncIterable, the Typescript
70
+ // types are in fact wrong:
71
+ //
72
+ // https://github.com/davidyaha/graphql-redis-subscriptions/pull/255
73
+ //
74
+ ( ctx . pubsub . asyncIterator < TResult > (
31
75
createSubscriptionChannelName ( ctx . tenant . id , channel )
32
- ) ;
76
+ ) as unknown ) as AsyncIterable < TResult > ;
33
77
}
34
78
35
79
export function createSubscriptionChannelName (
@@ -40,7 +84,7 @@ export function createSubscriptionChannelName(
40
84
}
41
85
42
86
/**
43
- * defaultFilterFn will perform filtering operations on the subscription
87
+ * clientIDFilterFn will perform filtering operations on the subscription
44
88
* responses to ensure that mutations issued by one user is not sent back as a
45
89
* subscription to the same requesting User, as they already implement the
46
90
* update via the mutation response.
@@ -53,7 +97,7 @@ export function createSubscriptionChannelName(
53
97
* need to determine eligibility to send the subscription back or
54
98
* not.
55
99
*/
56
- export function defaultFilterFn < TParent extends SubscriptionPayload , TArgs > (
100
+ export function clientIDFilterFn < TParent extends SubscriptionPayload , TArgs > (
57
101
source : TParent ,
58
102
args : TArgs ,
59
103
ctx : GraphContext
@@ -65,28 +109,15 @@ export function defaultFilterFn<TParent extends SubscriptionPayload, TArgs>(
65
109
return true ;
66
110
}
67
111
68
- /**
69
- * Ensure that even when we're provided with a domain specific filtering
70
- * function we respect the subscription id that is sent back with the request to
71
- * prevent double responses.
72
- */
73
- export function createFilterFn < TParent , TArgs > (
74
- filter ?: FilterFn < TParent , TArgs , GraphContext >
112
+ function composeFilters < TParent , TArgs > (
113
+ ...filters : Array < FilterFn < TParent , TArgs , GraphContext > >
75
114
) : FilterFn < TParent , TArgs , GraphContext > {
76
- return filter
77
- ? // Combine the filters, preferring the defaultFilterFn first.
78
- ( source , args , ctx , info ) => {
79
- if ( ! defaultFilterFn ( source , args , ctx ) ) {
80
- return false ;
81
- }
82
-
83
- return filter ( source , args , ctx , info ) ;
84
- }
85
- : defaultFilterFn ;
115
+ return ( source , args , ctx , info ) =>
116
+ filters . every ( ( filter ) => filter ( source , args , ctx , info ) ) ;
86
117
}
87
118
88
119
export interface CreateIteratorInput < TParent , TArgs , TResult > {
89
- filter ? : FilterFn < TParent , TArgs , GraphContext > ;
120
+ filter : FilterFn < TParent , TArgs , GraphContext > ;
90
121
}
91
122
92
123
export function createIterator <
@@ -95,12 +126,12 @@ export function createIterator<
95
126
TResult
96
127
> (
97
128
channel : SUBSCRIPTION_CHANNELS ,
98
- { filter } : CreateIteratorInput < TParent , TArgs , TResult > = { }
129
+ { filter } : CreateIteratorInput < TParent , TArgs , TResult >
99
130
) : SubscriptionResolver < TParent , TArgs , TResult > {
100
131
return {
101
132
subscribe : withFilter (
102
133
createTenantAsyncIterator ( channel ) ,
103
- createFilterFn ( filter )
134
+ composeFilters ( clientIDFilterFn , filter )
104
135
) ,
105
136
resolve : ( payload ) => payload ,
106
137
} ;
0 commit comments