graphql-anycable 0.3.1 → 0.4.2
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/.github/ISSUE_TEMPLATE/bug_report.md +63 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/build-release.yml +64 -0
- data/.github/workflows/test.yml +69 -0
- data/.gitignore +1 -2
- data/CHANGELOG.md +59 -0
- data/Gemfile +6 -0
- data/README.md +72 -2
- data/graphql-anycable.gemspec +5 -4
- data/lib/graphql-anycable.rb +11 -2
- data/lib/graphql/anycable/cleaner.rb +66 -0
- data/lib/graphql/anycable/config.rb +1 -0
- data/lib/graphql/anycable/tasks/clean_expired_subscriptions.rake +16 -28
- data/lib/graphql/anycable/version.rb +1 -1
- data/lib/graphql/subscriptions/anycable_subscriptions.rb +8 -12
- metadata +45 -14
- data/.travis.yml +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f464f4d333c2d59173af97549967def49f8843ae53882c4ab455fa48e1b62ab
|
4
|
+
data.tar.gz: f19ba6bfd926d4038ce6c11bc40c499f60f5d426daeec59f9b2f28304d08d0bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b35ce8815a7ffebed1162b35d21cf3addb5f6677df3dd00311827cdac7d371277703c271447631a143d9b6e3e4865ea17a141ce2664ea7948fe9c52ac6bc6f5
|
7
|
+
data.tar.gz: 7696f55e6a71cc8e1c14d08473a86da9b950317d88224afb8beaa2f0484d96886bf1d0d5a51cc53d3026a8598f4f4f7ecc57db9106b4aec80bcd81a0670b07e1
|
@@ -0,0 +1,63 @@
|
|
1
|
+
---
|
2
|
+
name: Bug report
|
3
|
+
about: Create a report to help us improve graphql-anycable
|
4
|
+
title: ''
|
5
|
+
labels: ''
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
**Describe the bug**
|
11
|
+
A clear and concise description of what the bug is.
|
12
|
+
|
13
|
+
**Versions**
|
14
|
+
ruby:
|
15
|
+
rails (or other framework):
|
16
|
+
graphql:
|
17
|
+
graphql-anycable:
|
18
|
+
anycable:
|
19
|
+
|
20
|
+
**GraphQL schema**
|
21
|
+
Provide relevant details. Are you using [subscription classes](https://graphql-ruby.org/subscriptions/subscription_classes.html) or not (graphql-ruby behavior differs there)?
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class Product < GraphQL::Schema::Object
|
25
|
+
field :id, ID, null: false, hash_key: :id
|
26
|
+
field :title, String, null: true, hash_key: :title
|
27
|
+
end
|
28
|
+
|
29
|
+
class SubscriptionType < GraphQL::Schema::Object
|
30
|
+
field :product_created, Product, null: false
|
31
|
+
field :product_updated, Product, null: false
|
32
|
+
|
33
|
+
def product_created; end
|
34
|
+
def product_updated; end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ApplicationSchema < GraphQL::Schema
|
38
|
+
subscription SubscriptionType
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
**GraphQL query**
|
43
|
+
|
44
|
+
How do you subscribe to subscriptions?
|
45
|
+
|
46
|
+
```graphql
|
47
|
+
subscription {
|
48
|
+
productCreated { id title }
|
49
|
+
productUpdated { id }
|
50
|
+
}
|
51
|
+
```
|
52
|
+
|
53
|
+
**Steps to reproduce**
|
54
|
+
Steps to reproduce the behavior
|
55
|
+
|
56
|
+
**Expected behavior**
|
57
|
+
A clear and concise description of what you expected to happen.
|
58
|
+
|
59
|
+
**Actual behavior**
|
60
|
+
What specifically went wrong?
|
61
|
+
|
62
|
+
**Additional context**
|
63
|
+
Add any other context about the problem here. Tracebacks, your thoughts. anything that may be useful.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
---
|
2
|
+
name: Feature request
|
3
|
+
about: Suggest an idea for this project
|
4
|
+
title: ''
|
5
|
+
labels: ''
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
**Is your feature request related to a problem? Please describe.**
|
11
|
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
12
|
+
|
13
|
+
**Describe the solution you'd like**
|
14
|
+
A clear and concise description of what you want to happen.
|
15
|
+
|
16
|
+
**Describe alternatives you've considered**
|
17
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
18
|
+
|
19
|
+
**Additional context**
|
20
|
+
Add any other context or screenshots about the feature request here.
|
@@ -0,0 +1,64 @@
|
|
1
|
+
name: Build and release gem to RubyGems
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
tags:
|
6
|
+
- v*
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
release:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@v2
|
13
|
+
with:
|
14
|
+
fetch-depth: 0 # Fetch current tag as annotated. See https://github.com/actions/checkout/issues/290
|
15
|
+
- uses: ruby/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: 2.7
|
18
|
+
- name: "Extract data from tag: version, message, body"
|
19
|
+
id: tag
|
20
|
+
run: |
|
21
|
+
git fetch --tags --force # Really fetch annotated tag. See https://github.com/actions/checkout/issues/290#issuecomment-680260080
|
22
|
+
echo ::set-output name=version::${GITHUB_REF#refs/tags/v}
|
23
|
+
echo ::set-output name=subject::$(git for-each-ref $GITHUB_REF --format='%(contents:subject)')
|
24
|
+
# Multiline body for release. See https://github.community/t/set-output-truncates-multiline-strings/16852/5
|
25
|
+
BODY="$(git for-each-ref $GITHUB_REF --format='%(contents:body)')"
|
26
|
+
BODY="${BODY//'%'/'%25'}"
|
27
|
+
BODY="${BODY//$'\n'/'%0A'}"
|
28
|
+
BODY="${BODY//$'\r'/'%0D'}"
|
29
|
+
echo "::set-output name=body::$BODY"
|
30
|
+
- name: Build gem
|
31
|
+
run: gem build
|
32
|
+
- name: Check version
|
33
|
+
run: ls graphql-anycable-${{ steps.tag.outputs.version }}.gem
|
34
|
+
- name: Create Release
|
35
|
+
id: create_release
|
36
|
+
uses: actions/create-release@v1
|
37
|
+
env:
|
38
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
39
|
+
with:
|
40
|
+
tag_name: ${{ github.ref }}
|
41
|
+
release_name: ${{ steps.tag.outputs.subject }}
|
42
|
+
body: ${{ steps.tag.outputs.body }}
|
43
|
+
draft: false
|
44
|
+
prerelease: false
|
45
|
+
- name: Upload Release Asset
|
46
|
+
id: upload-release-asset
|
47
|
+
uses: actions/upload-release-asset@v1
|
48
|
+
env:
|
49
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
50
|
+
with:
|
51
|
+
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
52
|
+
asset_path: graphql-anycable-${{ steps.tag.outputs.version }}.gem
|
53
|
+
asset_name: graphql-anycable-${{ steps.tag.outputs.version }}.gem
|
54
|
+
asset_content_type: application/x-tar
|
55
|
+
- name: Install publish prerequisites
|
56
|
+
run: |
|
57
|
+
sudo apt-get update
|
58
|
+
sudo apt-get install oathtool
|
59
|
+
- name: Publish to RubyGems
|
60
|
+
env:
|
61
|
+
GEM_HOST_API_KEY: "${{ secrets.RUBYGEMS_API_KEY }}"
|
62
|
+
RUBYGEMS_OTP_KEY: "${{ secrets.RUBYGEMS_OTP_KEY }}"
|
63
|
+
run: |
|
64
|
+
gem push graphql-anycable-${{ steps.tag.outputs.version }}.gem --otp=$(oathtool --totp --base32 $RUBYGEMS_OTP_KEY)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
name: Run tests
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request:
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- '**'
|
8
|
+
tags-ignore:
|
9
|
+
- 'v*'
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
test:
|
13
|
+
name: "Run tests"
|
14
|
+
if: "! contains(toJSON(github.event.commits.latest.message), '[ci skip]')"
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
strategy:
|
17
|
+
fail-fast: false
|
18
|
+
matrix:
|
19
|
+
include:
|
20
|
+
- ruby: 2.7
|
21
|
+
graphql: '~> 1.11.0'
|
22
|
+
anycable: '~> 1.0.0'
|
23
|
+
interpreter: yes
|
24
|
+
- ruby: 2.7
|
25
|
+
graphql: '~> 1.11.0'
|
26
|
+
anycable: '~> 1.0.0'
|
27
|
+
interpreter: no
|
28
|
+
- ruby: 2.6
|
29
|
+
graphql: '~> 1.10.0'
|
30
|
+
anycable: '~> 1.0.0'
|
31
|
+
interpreter: yes
|
32
|
+
- ruby: 2.6
|
33
|
+
graphql: '~> 1.10.0'
|
34
|
+
anycable: '~> 1.0.0'
|
35
|
+
interpreter: no
|
36
|
+
- ruby: 2.5
|
37
|
+
graphql: '~> 1.9.0'
|
38
|
+
anycable: '~> 0.6.0'
|
39
|
+
interpreter: yes
|
40
|
+
- ruby: 2.5
|
41
|
+
graphql: '~> 1.9.0'
|
42
|
+
anycable: '~> 0.6.0'
|
43
|
+
interpreter: no
|
44
|
+
container:
|
45
|
+
image: ruby:${{ matrix.ruby }}
|
46
|
+
env:
|
47
|
+
CI: true
|
48
|
+
GRAPHQL_RUBY_VERSION: ${{ matrix.graphql }}
|
49
|
+
ANYCABLE_VERSION: ${{ matrix.anycable }}
|
50
|
+
steps:
|
51
|
+
- uses: actions/checkout@v2
|
52
|
+
- uses: actions/cache@v2
|
53
|
+
with:
|
54
|
+
path: vendor/bundle
|
55
|
+
key: bundle-${{ matrix.ruby }}-${{ matrix.graphql }}-${{ matrix.anycable }}-${{ hashFiles('**/*.gemspec') }}-${{ hashFiles('**/Gemfile') }}
|
56
|
+
restore-keys: |
|
57
|
+
bundle-${{ matrix.ruby }}-${{ matrix.graphql }}-${{ matrix.anycable }}-
|
58
|
+
bundle-${{ matrix.ruby }}-
|
59
|
+
- name: Upgrade Bundler to 2.0 (for older Rubies)
|
60
|
+
run: gem install bundler -v '~> 2.0'
|
61
|
+
- name: Bundle install
|
62
|
+
run: |
|
63
|
+
bundle config path vendor/bundle
|
64
|
+
bundle install
|
65
|
+
bundle update
|
66
|
+
- name: Run RSpec
|
67
|
+
env:
|
68
|
+
GRAPHQL_RUBY_INTERPRETER: ${{ matrix.interpreter }}
|
69
|
+
run: bundle exec rspec
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## 0.4.1 - 2020-08-21
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
- Deprecation warning for `Redis#exist` usage on Redis Ruby client 4.2+. Switch to `exists?` method and require Redis 4.2+ (see [#14](https://github.com/anycable/graphql-anycable/issues/14)). [@Envek]
|
13
|
+
|
14
|
+
## 0.4.0 - 2020-03-19
|
15
|
+
|
16
|
+
### Added
|
17
|
+
|
18
|
+
- Ability to configure the gem via `configure` block, in addition to enironment variables and yaml files. [@gsamokovarov] ([#11](https://github.com/Envek/graphql-anycable/pull/11))
|
19
|
+
- Ability to run Redis cleaning operations outside of Rake. [@gsamokovarov] ([#11](https://github.com/Envek/graphql-anycable/pull/11))
|
20
|
+
- AnyCable 1.0 compatibility. [@bibendi], [@Envek] ([#10](https://github.com/Envek/graphql-anycable/pull/10))
|
21
|
+
|
22
|
+
## 0.3.3 - 2020-03-03
|
23
|
+
|
24
|
+
### Fixed
|
25
|
+
|
26
|
+
- Redis command error on subscription query with multiple fields. [@Envek] ([#9](https://github.com/Envek/graphql-anycable/issues/9))
|
27
|
+
|
28
|
+
## 0.3.2 - 2020-03-02
|
29
|
+
|
30
|
+
### Added
|
31
|
+
|
32
|
+
- Ability to skip some cleanup on restricted Redis instances (like Heroku). [@Envek] ([#8](https://github.com/Envek/graphql-anycable/issues/8))
|
33
|
+
|
34
|
+
## 0.3.1 - 2019-06-13
|
35
|
+
|
36
|
+
### Fixed
|
37
|
+
|
38
|
+
- Empty operation name handling. [@FX-HAO] ([#3](https://github.com/Envek/graphql-anycable/pull/3))
|
39
|
+
|
40
|
+
## 0.3.0 - 2018-11-16
|
41
|
+
|
42
|
+
### Added
|
43
|
+
|
44
|
+
- AnyCable 0.6 compatibility. [@Envek]
|
45
|
+
|
46
|
+
## 0.2.0 - 2018-09-17
|
47
|
+
|
48
|
+
### Added
|
49
|
+
|
50
|
+
- Subscription expiration and rake task for stale subscriptions cleanup. [@Envek]
|
51
|
+
|
52
|
+
### 0.1.0 - 2018-08-26
|
53
|
+
|
54
|
+
Initial version: store subscriptions on redis, re-execute queries in sync. [@Envek]
|
55
|
+
|
56
|
+
[@gsamokovarov]: https://github.com/gsamokovarov "Genadi Samokovarov"
|
57
|
+
[@bibendi]: https://github.com/bibendi "Misha Merkushin"
|
58
|
+
[@FX-HAO]: https://github.com/FX-HAO "Fuxin Hao"
|
59
|
+
[@Envek]: https://github.com/Envek "Andrey Novikov"
|
data/Gemfile
CHANGED
@@ -7,10 +7,16 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
|
7
7
|
# Specify your gem's dependencies in graphql-anycable.gemspec
|
8
8
|
gemspec
|
9
9
|
|
10
|
+
gem "graphql", ENV.fetch("GRAPHQL_RUBY_VERSION", "~> 1.11")
|
11
|
+
gem "anycable", ENV.fetch("ANYCABLE_VERSION", "~> 1.0")
|
12
|
+
|
10
13
|
group :development, :test do
|
11
14
|
gem "pry"
|
12
15
|
gem "pry-byebug", platform: :mri
|
13
16
|
|
14
17
|
gem "rubocop"
|
15
18
|
gem "rubocop-rspec"
|
19
|
+
|
20
|
+
# See https://github.com/guilleiguaran/fakeredis/pull/247
|
21
|
+
gem "fakeredis", github: 'guilleiguaran/fakeredis'
|
16
22
|
end
|
data/README.md
CHANGED
@@ -103,8 +103,78 @@ Or install it yourself as:
|
|
103
103
|
|
104
104
|
To avoid filling Redis storage with stale subscription data:
|
105
105
|
|
106
|
-
1. Set `
|
107
|
-
|
106
|
+
1. Set `subscription_expiration_seconds` setting to number of seconds (e.g. `604800` for 1 week). See [configuration](#Configuration) section below for details.
|
107
|
+
|
108
|
+
2. Execute `rake graphql:anycable:clean` once in a while to clean up stale subscription data.
|
109
|
+
|
110
|
+
Heroku users should set up `use_redis_object_on_cleanup` setting to `false` due to [limitations in Heroku Redis](https://devcenter.heroku.com/articles/heroku-redis#connection-permissions).
|
111
|
+
|
112
|
+
## Configuration
|
113
|
+
|
114
|
+
GraphQL-Anycable uses [anyway_config] to configure itself. There are several possibilities to configure this gem:
|
115
|
+
|
116
|
+
1. Environment variables:
|
117
|
+
|
118
|
+
```.env
|
119
|
+
GRAPHQL_ANYCABLE_SUBSCRIPTION_EXPIRATION_SECONDS=604800
|
120
|
+
GRAPHQL_ANYCABLE_USE_REDIS_OBJECT_ON_CLEANUP=true
|
121
|
+
```
|
122
|
+
|
123
|
+
2. YAML configuration files:
|
124
|
+
|
125
|
+
```yaml
|
126
|
+
# config/graphql_anycable.yml
|
127
|
+
production:
|
128
|
+
subscription_expiration_seconds: 300 # 5 minutes
|
129
|
+
use_redis_object_on_cleanup: false # For restricted redis installations
|
130
|
+
```
|
131
|
+
|
132
|
+
3. Configuration from your application code:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
GraphQL::Anycable.configure do |config|
|
136
|
+
config.subscription_expiration_seconds = 3600 # 1 hour
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
And any other way provided by [anyway_config]. Check its documentation!
|
141
|
+
|
142
|
+
## Data model
|
143
|
+
|
144
|
+
As in AnyCable there is no place to store subscription data in-memory, it should be persisted somewhere to be retrieved on `GraphQLSchema.subscriptions.trigger` and sent to subscribed clients. `graphql-anycable` uses the same Redis database as AnyCable itself.
|
145
|
+
|
146
|
+
1. Event subscriptions: `graphql-event:#{event.topic}` set containing identifiers for all subscriptions for given operation with certain context and arguments (serialized in _topic_). Used to find all subscriptions on `GraphQLSchema.subscriptions.trigger`.
|
147
|
+
|
148
|
+
```
|
149
|
+
SMEMBERS graphql-event:1:myStats:
|
150
|
+
=> 52ee8d65-275e-4d22-94af-313129116388
|
151
|
+
```
|
152
|
+
|
153
|
+
2. Subscription data: `graphql-subscription:#{subscription_id}` hash contains everything required to evaluate subscription on trigger and create data for client.
|
154
|
+
|
155
|
+
```
|
156
|
+
HGETALL graphql-subscription:52ee8d65-275e-4d22-94af-313129116388
|
157
|
+
=> {
|
158
|
+
context: '{"user_id":1,"user":{"__gid__":"Z2lkOi8vZWJheS1tYWcyL1VzZXIvMQ"},"subscription_id":"52ee8d65-275e-4d22-94af-313129116388\","action_cable_stream":"graphql-subscription:52ee8d65-275e-4d22-94af-313129116388",}',
|
159
|
+
variables: '{}',
|
160
|
+
operation_name: 'MyStats'
|
161
|
+
query_string: 'subscription MyStats { myStatsUpdated { completed total processed __typename } }',
|
162
|
+
}
|
163
|
+
```
|
164
|
+
|
165
|
+
3. Channel subscriptions: `graphql-channel:#{channel_id}` set containing identifiers for subscriptions created in ActionCable channel to delete them on client disconnect.
|
166
|
+
|
167
|
+
```
|
168
|
+
SMEMBERS graphql-channel:17420c6ed9e
|
169
|
+
=> 52ee8d65-275e-4d22-94af-313129116388
|
170
|
+
```
|
171
|
+
|
172
|
+
4. Subscription events: `graphql-subscription-events:#{subscription_id}` set containing event topics to delete subscription identifier from event subscriptions set on unsubscribe (or client disconnect).
|
173
|
+
|
174
|
+
```
|
175
|
+
SMEMBERS graphql-subscription-events:52ee8d65-275e-4d22-94af-313129116388
|
176
|
+
=> 1:myStats:
|
177
|
+
```
|
108
178
|
|
109
179
|
## Development
|
110
180
|
|
data/graphql-anycable.gemspec
CHANGED
@@ -26,12 +26,13 @@ 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 "anyway_config", "
|
29
|
+
spec.add_dependency "anycable", ">= 0.6.0", "< 2"
|
30
|
+
spec.add_dependency "anyway_config", ">= 1.3", "< 3"
|
31
31
|
spec.add_dependency "graphql", "~> 1.8"
|
32
|
+
spec.add_dependency "redis", ">= 4.2.0"
|
32
33
|
|
33
|
-
spec.add_development_dependency "bundler", "~>
|
34
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
34
35
|
spec.add_development_dependency "fakeredis"
|
35
|
-
spec.add_development_dependency "rake", "
|
36
|
+
spec.add_development_dependency "rake", ">= 12.3.3"
|
36
37
|
spec.add_development_dependency "rspec", "~> 3.0"
|
37
38
|
end
|
data/lib/graphql-anycable.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "graphql"
|
4
4
|
|
5
5
|
require_relative "graphql/anycable/version"
|
6
|
+
require_relative "graphql/anycable/cleaner"
|
6
7
|
require_relative "graphql/anycable/config"
|
7
8
|
require_relative "graphql/anycable/railtie" if defined?(Rails)
|
8
9
|
require_relative "graphql/subscriptions/anycable_subscriptions"
|
@@ -13,13 +14,21 @@ module GraphQL
|
|
13
14
|
|
14
15
|
def redis
|
15
16
|
@redis ||= begin
|
16
|
-
adapter = ::
|
17
|
+
adapter = ::AnyCable.broadcast_adapter
|
17
18
|
unless adapter.is_a?(::AnyCable::BroadcastAdapters::Redis)
|
18
19
|
raise "Unsupported AnyCable adapter: #{adapter.class}. " \
|
19
20
|
"graphql-anycable works only with redis broadcast adapter."
|
20
21
|
end
|
21
|
-
::
|
22
|
+
::AnyCable.broadcast_adapter.redis_conn
|
22
23
|
end
|
23
24
|
end
|
25
|
+
|
26
|
+
def config
|
27
|
+
@config ||= GraphQL::Anycable::Config.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def configure
|
31
|
+
yield(config) if block_given?
|
32
|
+
end
|
24
33
|
end
|
25
34
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Anycable
|
5
|
+
module Cleaner
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def clean
|
9
|
+
clean_channels
|
10
|
+
clean_subscriptions
|
11
|
+
clean_events
|
12
|
+
end
|
13
|
+
|
14
|
+
def clean_channels
|
15
|
+
return unless config.subscription_expiration_seconds
|
16
|
+
return unless config.use_redis_object_on_cleanup
|
17
|
+
|
18
|
+
redis.scan_each(match: "#{adapter::CHANNEL_PREFIX}*") do |key|
|
19
|
+
idle = redis.object("IDLETIME", key)
|
20
|
+
next if idle&.<= config.subscription_expiration_seconds
|
21
|
+
|
22
|
+
redis.del(key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def clean_subscriptions
|
27
|
+
return unless config.subscription_expiration_seconds
|
28
|
+
return unless config.use_redis_object_on_cleanup
|
29
|
+
|
30
|
+
redis.scan_each(match: "#{adapter::SUBSCRIPTION_PREFIX}*") do |key|
|
31
|
+
idle = redis.object("IDLETIME", key)
|
32
|
+
next if idle&.<= config.subscription_expiration_seconds
|
33
|
+
|
34
|
+
redis.del(key)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def clean_events
|
39
|
+
redis.scan_each(match: "#{adapter::SUBSCRIPTION_EVENTS_PREFIX}*") do |key|
|
40
|
+
subscription_id = key.sub(/\A#{adapter::SUBSCRIPTION_EVENTS_PREFIX}/, "")
|
41
|
+
next if redis.exists?(adapter::SUBSCRIPTION_PREFIX + subscription_id)
|
42
|
+
|
43
|
+
redis.smembers(key).each do |event_topic|
|
44
|
+
redis.srem(adapter::EVENT_PREFIX + event_topic, subscription_id)
|
45
|
+
end
|
46
|
+
|
47
|
+
redis.del(key)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def adapter
|
54
|
+
GraphQL::Subscriptions::AnyCableSubscriptions
|
55
|
+
end
|
56
|
+
|
57
|
+
def redis
|
58
|
+
GraphQL::Anycable.redis
|
59
|
+
end
|
60
|
+
|
61
|
+
def config
|
62
|
+
GraphQL::Anycable.config
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -1,41 +1,29 @@
|
|
1
|
-
require "graphql-anycable"
|
2
|
-
|
3
1
|
# frozen_string_literal: true
|
4
2
|
|
3
|
+
require "graphql-anycable"
|
4
|
+
|
5
5
|
namespace :graphql do
|
6
6
|
namespace :anycable do
|
7
|
-
|
8
|
-
|
9
|
-
unless config.subscription_expiration_seconds
|
10
|
-
warn "GraphQL::Anycable: No expiration set for subscriptions!"
|
11
|
-
next
|
12
|
-
end
|
7
|
+
desc "Clean up stale graphql channels, subscriptions, and events from redis"
|
8
|
+
task clean: %i[clean:channels clean:subscriptions clean:events]
|
13
9
|
|
14
|
-
|
15
|
-
|
10
|
+
# Old name that was used earlier
|
11
|
+
task clean_expired_subscriptions: :clean
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
redis.del(key)
|
13
|
+
namespace :clean do
|
14
|
+
# Clean up old channels
|
15
|
+
task :channels do
|
16
|
+
GraphQL::Anycable::Cleaner.clean_channels
|
22
17
|
end
|
23
18
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
next if idle&.<= config.subscription_expiration_seconds
|
28
|
-
redis.del(key)
|
19
|
+
# Clean up old subscriptions (they should have expired by themselves)
|
20
|
+
task :subscriptions do
|
21
|
+
GraphQL::Anycable::Cleaner.clean_subscriptions
|
29
22
|
end
|
30
23
|
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
next if redis.exists(klass::SUBSCRIPTION_PREFIX + subscription_id)
|
35
|
-
redis.smembers(key).each do |event_topic|
|
36
|
-
redis.srem(klass::EVENT_PREFIX + event_topic, subscription_id)
|
37
|
-
end
|
38
|
-
redis.del(key)
|
24
|
+
# Clean up subscription_ids from events for expired subscriptions
|
25
|
+
task :events do
|
26
|
+
GraphQL::Anycable::Cleaner.clean_events
|
39
27
|
end
|
40
28
|
end
|
41
29
|
end
|
@@ -54,7 +54,7 @@ module GraphQL
|
|
54
54
|
class AnyCableSubscriptions < GraphQL::Subscriptions
|
55
55
|
extend Forwardable
|
56
56
|
|
57
|
-
def_delegators :"GraphQL::Anycable", :redis
|
57
|
+
def_delegators :"GraphQL::Anycable", :redis, :config
|
58
58
|
|
59
59
|
SUBSCRIPTION_PREFIX = "graphql-subscription:"
|
60
60
|
SUBSCRIPTION_EVENTS_PREFIX = "graphql-subscription-events:"
|
@@ -71,7 +71,7 @@ module GraphQL
|
|
71
71
|
# Re-evaluate all subscribed queries and push the data over ActionCable.
|
72
72
|
def execute_all(event, object)
|
73
73
|
redis.smembers(EVENT_PREFIX + event.topic).each do |subscription_id|
|
74
|
-
next unless redis.exists(SUBSCRIPTION_PREFIX + subscription_id)
|
74
|
+
next unless redis.exists?(SUBSCRIPTION_PREFIX + subscription_id)
|
75
75
|
execute(subscription_id, event, object)
|
76
76
|
end
|
77
77
|
end
|
@@ -79,7 +79,7 @@ module GraphQL
|
|
79
79
|
# This subscription was re-evaluated.
|
80
80
|
# Send it to the specific stream where this client was waiting.
|
81
81
|
def deliver(subscription_id, result)
|
82
|
-
payload = {
|
82
|
+
payload = {result: result.to_h, more: true}
|
83
83
|
anycable.broadcast(SUBSCRIPTION_PREFIX + subscription_id, payload.to_json)
|
84
84
|
end
|
85
85
|
|
@@ -95,13 +95,13 @@ module GraphQL
|
|
95
95
|
query_string: query.query_string,
|
96
96
|
variables: query.provided_variables.to_json,
|
97
97
|
context: @serializer.dump(context.to_h),
|
98
|
-
operation_name: query.operation_name
|
98
|
+
operation_name: query.operation_name
|
99
99
|
}
|
100
100
|
|
101
101
|
redis.multi do
|
102
102
|
redis.sadd(CHANNEL_PREFIX + channel.params["channelId"], subscription_id)
|
103
103
|
redis.mapped_hmset(SUBSCRIPTION_PREFIX + subscription_id, data)
|
104
|
-
redis.sadd(SUBSCRIPTION_EVENTS_PREFIX + subscription_id,
|
104
|
+
redis.sadd(SUBSCRIPTION_EVENTS_PREFIX + subscription_id, events.map(&:topic))
|
105
105
|
events.each do |event|
|
106
106
|
redis.sadd(EVENT_PREFIX + event.topic, subscription_id)
|
107
107
|
end
|
@@ -115,9 +115,9 @@ module GraphQL
|
|
115
115
|
def read_subscription(subscription_id)
|
116
116
|
redis.mapped_hmget(
|
117
117
|
"#{SUBSCRIPTION_PREFIX}#{subscription_id}",
|
118
|
-
:query_string, :variables, :context, :operation_name
|
118
|
+
:query_string, :variables, :context, :operation_name
|
119
119
|
).tap do |subscription|
|
120
|
-
subscription[:context]
|
120
|
+
subscription[:context] = @serializer.load(subscription[:context])
|
121
121
|
subscription[:variables] = JSON.parse(subscription[:variables])
|
122
122
|
subscription[:operation_name] = nil if subscription[:operation_name].strip == ""
|
123
123
|
end
|
@@ -145,11 +145,7 @@ module GraphQL
|
|
145
145
|
private
|
146
146
|
|
147
147
|
def anycable
|
148
|
-
@anycable ||= ::
|
149
|
-
end
|
150
|
-
|
151
|
-
def config
|
152
|
-
@config ||= GraphQL::Anycable::Config.new
|
148
|
+
@anycable ||= ::AnyCable.broadcast_adapter
|
153
149
|
end
|
154
150
|
end
|
155
151
|
end
|
metadata
CHANGED
@@ -1,43 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-anycable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrey Novikov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: anycable
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 0.6.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '2'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- - "
|
27
|
+
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: 0.6.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: anyway_config
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
|
-
- - "
|
37
|
+
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
39
|
version: '1.3'
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '3'
|
34
43
|
type: :runtime
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
37
46
|
requirements:
|
38
|
-
- - "
|
47
|
+
- - ">="
|
39
48
|
- !ruby/object:Gem::Version
|
40
49
|
version: '1.3'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '3'
|
41
53
|
- !ruby/object:Gem::Dependency
|
42
54
|
name: graphql
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,20 +64,34 @@ dependencies:
|
|
52
64
|
- - "~>"
|
53
65
|
- !ruby/object:Gem::Version
|
54
66
|
version: '1.8'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: redis
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 4.2.0
|
74
|
+
type: :runtime
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 4.2.0
|
55
81
|
- !ruby/object:Gem::Dependency
|
56
82
|
name: bundler
|
57
83
|
requirement: !ruby/object:Gem::Requirement
|
58
84
|
requirements:
|
59
85
|
- - "~>"
|
60
86
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
87
|
+
version: '2.0'
|
62
88
|
type: :development
|
63
89
|
prerelease: false
|
64
90
|
version_requirements: !ruby/object:Gem::Requirement
|
65
91
|
requirements:
|
66
92
|
- - "~>"
|
67
93
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
94
|
+
version: '2.0'
|
69
95
|
- !ruby/object:Gem::Dependency
|
70
96
|
name: fakeredis
|
71
97
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,16 +110,16 @@ dependencies:
|
|
84
110
|
name: rake
|
85
111
|
requirement: !ruby/object:Gem::Requirement
|
86
112
|
requirements:
|
87
|
-
- - "
|
113
|
+
- - ">="
|
88
114
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
115
|
+
version: 12.3.3
|
90
116
|
type: :development
|
91
117
|
prerelease: false
|
92
118
|
version_requirements: !ruby/object:Gem::Requirement
|
93
119
|
requirements:
|
94
|
-
- - "
|
120
|
+
- - ">="
|
95
121
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
122
|
+
version: 12.3.3
|
97
123
|
- !ruby/object:Gem::Dependency
|
98
124
|
name: rspec
|
99
125
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,10 +141,14 @@ executables: []
|
|
115
141
|
extensions: []
|
116
142
|
extra_rdoc_files: []
|
117
143
|
files:
|
144
|
+
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
145
|
+
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
146
|
+
- ".github/workflows/build-release.yml"
|
147
|
+
- ".github/workflows/test.yml"
|
118
148
|
- ".gitignore"
|
119
149
|
- ".rspec"
|
120
150
|
- ".rubocop.yml"
|
121
|
-
-
|
151
|
+
- CHANGELOG.md
|
122
152
|
- Gemfile
|
123
153
|
- LICENSE.txt
|
124
154
|
- README.md
|
@@ -129,6 +159,7 @@ files:
|
|
129
159
|
- lib/Rakefile
|
130
160
|
- lib/graphql-anycable.rb
|
131
161
|
- lib/graphql/anycable.rb
|
162
|
+
- lib/graphql/anycable/cleaner.rb
|
132
163
|
- lib/graphql/anycable/config.rb
|
133
164
|
- lib/graphql/anycable/railtie.rb
|
134
165
|
- lib/graphql/anycable/tasks/clean_expired_subscriptions.rake
|
@@ -153,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
184
|
- !ruby/object:Gem::Version
|
154
185
|
version: '0'
|
155
186
|
requirements: []
|
156
|
-
rubygems_version: 3.
|
187
|
+
rubygems_version: 3.1.2
|
157
188
|
signing_key:
|
158
189
|
specification_version: 4
|
159
190
|
summary: A drop-in replacement for GraphQL ActionCable subscriptions for AnyCable.
|