graphql-anycable 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|