graphql-anycable 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -0
- data/graphql-anycable.gemspec +3 -2
- data/lib/Rakefile +5 -0
- data/lib/graphql/anycable/config.rb +14 -0
- data/lib/graphql/anycable/railtie.rb +14 -0
- data/lib/graphql/anycable/tasks/clean_expired_subscriptions.rake +40 -0
- data/lib/graphql/anycable/version.rb +1 -1
- data/lib/graphql/subscriptions/anycable_subscriptions.rb +13 -3
- data/lib/graphql-anycable.rb +2 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc4ead83b0cc39fdf211c97e4664932a6125ca48b6e30ea889ab4692c075bb23
|
4
|
+
data.tar.gz: 89ff31b340ed13fbe0493245556a587692f3f6211a72422c0dbee750c8e48135
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d705c044762c1bbf2baa763ac8fcfee2a45f3bfbe0c9ec7ab8a94710af255769793eb9e2932b84edc800ee00fbcaf37ecf3b4acba1e6d475b172d5773c2b4ae0
|
7
|
+
data.tar.gz: be40d20ca4ce9b35f37501646029c8bceafba7617a8d236ac66aa963f4b4f22196ea753597bfae124f357a91441ed937016766b454cc56ff649c6dbb25a40fe6
|
data/README.md
CHANGED
@@ -97,6 +97,13 @@ Or install it yourself as:
|
|
97
97
|
MySchema.subscriptions.trigger(:product_updated, {}, Product.first!, scope: account.id)
|
98
98
|
```
|
99
99
|
|
100
|
+
## Operations
|
101
|
+
|
102
|
+
To avoid filling Redis storage with stale subscription data:
|
103
|
+
|
104
|
+
1. Set `GRAPHQL_ANYCABLE_SUBSCRIPTION_EXPIRATION_SECONDS` environment variable to number of seconds (e.g. `604800` for 1 week). See [anyway_config] documentation to other ways of configuring this gem.
|
105
|
+
2. Execute `rake graphql:anycable:clean_expired_subscriptions` once in a while to clean up stale subscription data
|
106
|
+
|
100
107
|
## Development
|
101
108
|
|
102
109
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -114,3 +121,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
114
121
|
[graphql gem]: https://github.com/rmosolgo/graphql-ruby "Ruby implementation of GraphQL"
|
115
122
|
[AnyCable]: https://github.com/anycable/anycable "Polyglot replacement for Ruby WebSocket servers with Action Cable protocol"
|
116
123
|
[LiteCable]: https://github.com/palkan/litecable "Lightweight Action Cable implementation (Rails-free)"
|
124
|
+
[anyway_config]: https://github.com/palkan/anyway_config "Ruby libraries and applications configuration on steroids!"
|
data/graphql-anycable.gemspec
CHANGED
@@ -26,8 +26,9 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.require_paths = ["lib"]
|
28
28
|
|
29
|
-
spec.add_dependency "anycable",
|
30
|
-
spec.add_dependency "
|
29
|
+
spec.add_dependency "anycable", "~> 0.5"
|
30
|
+
spec.add_dependency "anyway_config", "~> 1.3"
|
31
|
+
spec.add_dependency "graphql", "~> 1.8"
|
31
32
|
|
32
33
|
spec.add_development_dependency "bundler", "~> 1.16"
|
33
34
|
spec.add_development_dependency "fakeredis"
|
data/lib/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "anyway"
|
4
|
+
|
5
|
+
module Graphql
|
6
|
+
module Anycable
|
7
|
+
class Config < Anyway::Config
|
8
|
+
config_name :graphql_anycable
|
9
|
+
env_prefix :graphql_anycable
|
10
|
+
|
11
|
+
attr_config subscription_expiration_seconds: nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
|
5
|
+
module Graphql
|
6
|
+
module Anycable
|
7
|
+
class Railtie < ::Rails::Railtie
|
8
|
+
rake_tasks do
|
9
|
+
path = File.expand_path(__dir__)
|
10
|
+
Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :graphql do
|
4
|
+
namespace :anycable do
|
5
|
+
task :clean_expired_subscriptions do
|
6
|
+
config = Graphql::Anycable::Config.new
|
7
|
+
unless config.subscription_expiration_seconds
|
8
|
+
warn "GraphQL::Anycable: No expiration set for subscriptions!"
|
9
|
+
next
|
10
|
+
end
|
11
|
+
|
12
|
+
redis = Anycable::PubSub.new.redis_conn
|
13
|
+
klass = GraphQL::Subscriptions::AnyCableSubscriptions
|
14
|
+
|
15
|
+
# 1. Clean up old channels
|
16
|
+
redis.scan_each(match: "#{klass::CHANNEL_PREFIX}*") do |key|
|
17
|
+
idle = redis.object("IDLETIME", key)
|
18
|
+
next if idle&.<= config.subscription_expiration_seconds
|
19
|
+
redis.del(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
# 2. Clean up old subscriptions (they should have expired by themselves)
|
23
|
+
redis.scan_each(match: "#{klass::SUBSCRIPTION_PREFIX}*") do |key|
|
24
|
+
idle = redis.object("IDLETIME", key)
|
25
|
+
next if idle&.<= config.subscription_expiration_seconds
|
26
|
+
redis.del(key)
|
27
|
+
end
|
28
|
+
|
29
|
+
# 3. Clean up subscription_ids from events for expired subscriptions
|
30
|
+
redis.scan_each(match: "#{klass::SUBSCRIPTION_EVENTS_PREFIX}*") do |key|
|
31
|
+
subscription_id = key.sub(/\A#{klass::SUBSCRIPTION_EVENTS_PREFIX}/, "")
|
32
|
+
next if redis.exists(klass::SUBSCRIPTION_PREFIX + subscription_id)
|
33
|
+
redis.smembers(key).each do |event_topic|
|
34
|
+
redis.srem(klass::EVENT_PREFIX + event_topic, subscription_id)
|
35
|
+
end
|
36
|
+
redis.del(key)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -53,6 +53,7 @@ module GraphQL
|
|
53
53
|
class Subscriptions
|
54
54
|
class AnyCableSubscriptions < GraphQL::Subscriptions
|
55
55
|
SUBSCRIPTION_PREFIX = "graphql-subscription:"
|
56
|
+
SUBSCRIPTION_EVENTS_PREFIX = "graphql-subscription-events:"
|
56
57
|
EVENT_PREFIX = "graphql-event:"
|
57
58
|
CHANNEL_PREFIX = "graphql-channel:"
|
58
59
|
|
@@ -66,6 +67,7 @@ module GraphQL
|
|
66
67
|
# Re-evaluate all subscribed queries and push the data over ActionCable.
|
67
68
|
def execute_all(event, object)
|
68
69
|
redis.smembers(EVENT_PREFIX + event.topic).each do |subscription_id|
|
70
|
+
next unless redis.exists(SUBSCRIPTION_PREFIX + subscription_id)
|
69
71
|
execute(subscription_id, event, object)
|
70
72
|
end
|
71
73
|
end
|
@@ -90,15 +92,18 @@ module GraphQL
|
|
90
92
|
variables: query.provided_variables.to_json,
|
91
93
|
context: @serializer.dump(context.to_h),
|
92
94
|
operation_name: query.operation_name,
|
93
|
-
events: events.map(&:topic).to_json,
|
94
95
|
}
|
95
96
|
|
96
97
|
redis.multi do
|
97
98
|
redis.sadd(CHANNEL_PREFIX + channel.params["channelId"], subscription_id)
|
98
99
|
redis.mapped_hmset(SUBSCRIPTION_PREFIX + subscription_id, data)
|
100
|
+
redis.sadd(SUBSCRIPTION_EVENTS_PREFIX + subscription_id, *events.map(&:topic))
|
99
101
|
events.each do |event|
|
100
102
|
redis.sadd(EVENT_PREFIX + event.topic, subscription_id)
|
101
103
|
end
|
104
|
+
next unless config.subscription_expiration_seconds
|
105
|
+
redis.expire(CHANNEL_PREFIX + channel.params["channelId"], config.subscription_expiration_seconds)
|
106
|
+
redis.expire(SUBSCRIPTION_PREFIX + subscription_id, config.subscription_expiration_seconds)
|
102
107
|
end
|
103
108
|
end
|
104
109
|
|
@@ -116,11 +121,12 @@ module GraphQL
|
|
116
121
|
# The channel was closed, forget about it.
|
117
122
|
def delete_subscription(subscription_id)
|
118
123
|
# Remove subscription ids from all events
|
119
|
-
|
120
|
-
|
124
|
+
events = redis.smembers(SUBSCRIPTION_EVENTS_PREFIX + subscription_id)
|
125
|
+
events.each do |event_topic|
|
121
126
|
redis.srem(EVENT_PREFIX + event_topic, subscription_id)
|
122
127
|
end
|
123
128
|
# Delete subscription itself
|
129
|
+
redis.del(SUBSCRIPTION_EVENTS_PREFIX + subscription_id)
|
124
130
|
redis.del(SUBSCRIPTION_PREFIX + subscription_id)
|
125
131
|
end
|
126
132
|
|
@@ -140,6 +146,10 @@ module GraphQL
|
|
140
146
|
def redis
|
141
147
|
@redis ||= anycable.redis_conn
|
142
148
|
end
|
149
|
+
|
150
|
+
def config
|
151
|
+
@config ||= Graphql::Anycable::Config.new
|
152
|
+
end
|
143
153
|
end
|
144
154
|
end
|
145
155
|
end
|
data/lib/graphql-anycable.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-anycable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrey Novikov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: anycable
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: anyway_config
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: graphql
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -112,8 +126,12 @@ files:
|
|
112
126
|
- bin/console
|
113
127
|
- bin/setup
|
114
128
|
- graphql-anycable.gemspec
|
129
|
+
- lib/Rakefile
|
115
130
|
- lib/graphql-anycable.rb
|
116
131
|
- lib/graphql/anycable.rb
|
132
|
+
- lib/graphql/anycable/config.rb
|
133
|
+
- lib/graphql/anycable/railtie.rb
|
134
|
+
- lib/graphql/anycable/tasks/clean_expired_subscriptions.rake
|
117
135
|
- lib/graphql/anycable/version.rb
|
118
136
|
- lib/graphql/subscriptions/anycable_subscriptions.rb
|
119
137
|
homepage: https://github.com/Envek/graphql-anycable
|