graphql-anycable 1.3.1 → 1.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eebb61e4181214e0f86bdf0cc33e7153e0a02c6c8de5e37996ca9457e6231385
4
- data.tar.gz: 6fa0f37c132f8c22454bcdf9d995ac9b0c3f40c3913b0047f7be3b91eb68d07f
3
+ metadata.gz: ba22f385767198acc338809a6b60237248dfc15ecdd2f279a31628e6dad013e8
4
+ data.tar.gz: 6e59e6c8119969ee8358219b080abecb05df38d6ffe5549adbc58869f214872d
5
5
  SHA512:
6
- metadata.gz: 06b6b55507a369100a71e9a30b93cf0fccef1d82c948b71693e43a4629116749453577aa0919d5de12c3bad8f6703d9652b0378c6ad90c4775ab355fe9b4ce7d
7
- data.tar.gz: d0ed4156e37e936fad396659517245b1f4540451e4b4012ec8d1478cc4c1b1d244c254b690b168f4d8d2d36a3aadf1c22643c5cde639b08c119c8ec801d97152
6
+ metadata.gz: 3479edd8cf0548d649ca3a0aaaeea728a8030c22342b50cb41beacca14b2ab7391bc9fc1cd2783ca861a07c8049b4ef1256e735fc97468469372213a822c9201
7
+ data.tar.gz: f6595e5f63dd71d375ec8d90816ae102839a8fda29bfc3f3400af10dedbcbf0cd4fb587d1462233b1a1b733801dfa5af8f172fc1f0906c4b4ba4db6e5250eebf
@@ -24,10 +24,10 @@ jobs:
24
24
  env:
25
25
  BUNDLE_GEMFILE: "gemfiles/rubocop.gemfile"
26
26
  steps:
27
- - uses: actions/checkout@v4
27
+ - uses: actions/checkout@v6
28
28
  - uses: ruby/setup-ruby@v1
29
29
  with:
30
- ruby-version: 3.1
30
+ ruby-version: 3.4
31
31
  bundler-cache: true
32
32
  - name: Lint Ruby code with RuboCop
33
33
  run: |
@@ -13,7 +13,7 @@ jobs:
13
13
  id-token: write
14
14
  packages: write
15
15
  steps:
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v6
17
17
  with:
18
18
  fetch-depth: 0 # Fetch current tag as annotated. See https://github.com/actions/checkout/issues/290
19
19
  - uses: ruby/setup-ruby@v1
@@ -18,6 +18,10 @@ jobs:
18
18
  fail-fast: false
19
19
  matrix:
20
20
  include:
21
+ - ruby: "4.0"
22
+ graphql: '~> 2.3'
23
+ anycable: '~> 1.6'
24
+ redis_version: latest
21
25
  - ruby: "3.3"
22
26
  graphql: '~> 2.3'
23
27
  anycable: '~> 1.5'
@@ -52,7 +56,7 @@ jobs:
52
56
  ports:
53
57
  - 6379:6379
54
58
  steps:
55
- - uses: actions/checkout@v4
59
+ - uses: actions/checkout@v6
56
60
  - uses: ruby/setup-ruby@v1
57
61
  with:
58
62
  ruby-version: ${{ matrix.ruby }}
data/.rubocop/rspec.yml CHANGED
@@ -1,4 +1,4 @@
1
- require:
1
+ plugins:
2
2
  - rubocop-rspec
3
3
 
4
4
  # Disable all cops by default,
data/.rubocop.yml CHANGED
@@ -2,8 +2,7 @@ inherit_mode:
2
2
  merge:
3
3
  - Exclude
4
4
 
5
- require:
6
- - standard
5
+ plugins:
7
6
  - standard-custom
8
7
  - standard-performance
9
8
  - rubocop-performance
data/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 1.3.3 - 2026-08-10
11
+
12
+ ### Fixed
13
+
14
+ - Subscriptions are no longer leaked when a channel carries more than one of them. The first subscription's id now names the channel and later subscriptions are added to that same set. On disconnect, the channel's set is read and all subscriptions in it are deleted instead of only the most recent one as before. [@jjb] ([#58](https://github.com/anycable/graphql-anycable/pull/58))
15
+
16
+ - Further optimize cleanup tasks with pipelining their reads and removals across keys. [@jjb] ([#57](https://github.com/anycable/graphql-anycable/pull/57))
17
+
18
+ ## 1.3.2 - 2026-08-05
19
+
20
+ ### Fixed
21
+
22
+ - Cleanup tasks are much faster and use bounded memory: `SCAN`/`SSCAN`/`ZSCAN` are called with a configurable `COUNT` (new `redis_scan_count` setting, default `1000`) instead of Redis' default of 10, members are read incrementally with `SSCAN`/`ZSCAN` instead of `SMEMBERS`/`ZRANGE` (which materialized entire sets in the rake process), and the per-member `EXISTS`, `OBJECT IDLETIME`, `SREM`, `ZREM` and `DEL` calls are pipelined in batches instead of being issued one round trip at a time. [@jjb] ([#56](https://github.com/anycable/graphql-anycable/pull/56))
23
+
24
+ ## 1.3.1 - 2025-03-29
25
+
26
+ ### Fixed
27
+
28
+ - Remove `clean:events` from `clean_expired_subscriptions.rake`. [@Geesu] ([#46](https://github.com/anycable/graphql-anycable/pull/46))
29
+
10
30
  ## 1.3.0 - 2024-08-13
11
31
 
12
32
  ### Changed
@@ -211,6 +231,7 @@ Technical release to test publishing via GitHub Actions.
211
231
 
212
232
  Initial version: store subscriptions on redis, re-execute queries in sync. [@Envek]
213
233
 
234
+ [@jjb]: https://github.com/jjb "John Bachir"
214
235
  [@prog-supdex]: https://github.com/prog-supdex "Igor Platonov"
215
236
  [@ilyasgaraev]: https://github.com/ilyasgaraev "Ilyas Garaev"
216
237
  [@smasry]: https://github.com/smasry "Samer Masry"
data/README.md CHANGED
@@ -5,9 +5,9 @@ A (mostly) drop-in replacement for default ActionCable subscriptions adapter shi
5
5
  [![Gem Version](https://badge.fury.io/rb/graphql-anycable.svg)](https://badge.fury.io/rb/graphql-anycable)
6
6
  [![Tests](https://github.com/anycable/graphql-anycable/actions/workflows/test.yml/badge.svg)](https://github.com/anycable/graphql-anycable/actions/workflows/test.yml)
7
7
 
8
- <a href="https://evilmartians.com/?utm_source=graphql-anycable&utm_campaign=project_page">
9
- <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54">
10
- </a>
8
+ <br/>
9
+
10
+ <img src="https://cdn.evilmartians.com/badges/logo-no-label.svg" alt="Evil Martians logo" width="22" height="16" /> <b>GraphQL-AnyCable</b> is built by <b><a href="https://evilmartians.com/">Evil Martians</a></b>, an American design and engineering consultancy for <b>developer tools, AI, and cybersecurity startups</b>.
11
11
 
12
12
  ## Why?
13
13
 
@@ -134,6 +134,10 @@ To avoid filling Redis storage with stale subscription data:
134
134
 
135
135
  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).
136
136
 
137
+ The task is also available as granular sub-tasks, so you can run only some of them, or run them on different schedules: `rake graphql:anycable:clean:channels`, `rake graphql:anycable:clean:subscriptions`, `rake graphql:anycable:clean:fingerprint_subscriptions`, `rake graphql:anycable:clean:topic_fingerprints`.
138
+
139
+ Cleanup iterates over Redis with the `SCAN` family of commands and pipelines the checks it performs on each batch. Use the `redis_scan_count` setting to tune the batch size: larger values mean fewer round trips (so faster cleanup), at the cost of doing more work per Redis call.
140
+
137
141
  ## Configuration
138
142
 
139
143
  GraphQL-AnyCable uses [anyway_config] to configure itself. There are several possibilities to configure this gem:
@@ -144,6 +148,7 @@ GraphQL-AnyCable uses [anyway_config] to configure itself. There are several pos
144
148
  GRAPHQL_ANYCABLE_SUBSCRIPTION_EXPIRATION_SECONDS=604800
145
149
  GRAPHQL_ANYCABLE_USE_REDIS_OBJECT_ON_CLEANUP=true
146
150
  GRAPHQL_ANYCABLE_REDIS_PREFIX=graphql
151
+ GRAPHQL_ANYCABLE_REDIS_SCAN_COUNT=1000
147
152
  ```
148
153
 
149
154
  2. YAML configuration files (note that this is `config/graphql_anycable.yml`, *not* `config/anycable.yml`):
@@ -154,6 +159,7 @@ GraphQL-AnyCable uses [anyway_config] to configure itself. There are several pos
154
159
  subscription_expiration_seconds: 300 # 5 minutes
155
160
  use_redis_object_on_cleanup: false # For restricted redis installations
156
161
  redis_prefix: graphql # You can configure redis_prefix for anycable-graphql subscription prefixes. Default value "graphql"
162
+ redis_scan_count: 1000 # Batch size used by the cleanup rake tasks. Default value 1000
157
163
  ```
158
164
 
159
165
  3. Configuration from your application code:
@@ -240,11 +246,12 @@ As in AnyCable there is no place to store subscription data in-memory, it should
240
246
  }
241
247
  ```
242
248
 
243
- 4. Channel subscriptions: `graphql-channel:#{subscription_id}` set containing identifiers for subscriptions created in ActionCable channel to delete them on client disconnect.
249
+ 4. Channel subscriptions: `graphql-channel:#{channel_id}` set containing identifiers for all subscriptions created on one ActionCable channel, used to delete them on client disconnect. The channel is named after its first subscription's id (stored in the channel's AnyCable state); every later subscription on the same channel is added to that same set.
244
250
 
245
251
  ```sh
246
- SMEMBERS graphql-channel:17420c6ed9e
252
+ SMEMBERS graphql-channel:52ee8d65-275e-4d22-94af-313129116388
247
253
  => 52ee8d65-275e-4d22-94af-313129116388
254
+ a1b2c3d4-5678-90ab-cdef-111213141516
248
255
  ```
249
256
 
250
257
  ## Stats
@@ -1,5 +1,4 @@
1
- source "https://rubygems.org" do
2
- gem "standard", "~> 1.28"
1
+ source "https://rubygems.org"
3
2
 
4
- gem "rubocop-rspec"
5
- end
3
+ gem "standard"
4
+ gem "rubocop-rspec"
@@ -17,6 +17,10 @@ Gem::Specification.new do |spec|
17
17
  spec.homepage = "https://github.com/Envek/graphql-anycable"
18
18
  spec.license = "MIT"
19
19
 
20
+ spec.metadata = {
21
+ "rubygems_mfa_required" => "true"
22
+ }
23
+
20
24
  # Specify which files should be added to the gem when it is released.
21
25
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
26
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
@@ -34,7 +38,7 @@ Gem::Specification.new do |spec|
34
38
  spec.add_dependency "redis", ">= 4.2.0"
35
39
 
36
40
  spec.add_development_dependency "anycable-rails"
37
- spec.add_development_dependency "bundler", "~> 2.0"
41
+ spec.add_development_dependency "bundler", ">= 2.0"
38
42
  spec.add_development_dependency "rack"
39
43
  spec.add_development_dependency "railties"
40
44
  spec.add_development_dependency "rake", ">= 12.3.3"
@@ -16,37 +16,30 @@ module GraphQL
16
16
  return unless config.subscription_expiration_seconds
17
17
  return unless config.use_redis_object_on_cleanup
18
18
 
19
- AnyCable.with_redis do |redis|
20
- redis.scan_each(match: "#{redis_key(adapter::CHANNEL_PREFIX)}*") do |key|
21
- idle = redis.object("IDLETIME", key)
22
- next if idle&.<= config.subscription_expiration_seconds
23
-
24
- redis.del(key)
25
- end
26
- end
19
+ clean_idle_keys(adapter::CHANNEL_PREFIX)
27
20
  end
28
21
 
29
22
  def clean_subscriptions
30
23
  return unless config.subscription_expiration_seconds
31
24
  return unless config.use_redis_object_on_cleanup
32
25
 
33
- AnyCable.with_redis do |redis|
34
- redis.scan_each(match: "#{redis_key(adapter::SUBSCRIPTION_PREFIX)}*") do |key|
35
- idle = redis.object("IDLETIME", key)
36
- next if idle&.<= config.subscription_expiration_seconds
37
-
38
- redis.del(key)
39
- end
40
- end
26
+ clean_idle_keys(adapter::SUBSCRIPTION_PREFIX)
41
27
  end
42
28
 
43
29
  def clean_fingerprint_subscriptions
44
30
  AnyCable.with_redis do |redis|
45
- redis.scan_each(match: "#{redis_key(adapter::SUBSCRIPTIONS_PREFIX)}*") do |key|
46
- redis.smembers(key).each do |subscription_id|
47
- next if redis.exists?(redis_key(adapter::SUBSCRIPTION_PREFIX) + subscription_id)
31
+ each_key_batch(redis, adapter::SUBSCRIPTIONS_PREFIX) do |keys|
32
+ bulk_readable, oversized = partition_by_size(redis, keys, :scard)
33
+
34
+ each_capped_chunk(bulk_readable) do |chunk|
35
+ members = redis.pipelined { |pipeline| chunk.each { |key| pipeline.smembers(key) } }
36
+ remove_stale(redis, adapter::SUBSCRIPTION_PREFIX, chunk.zip(members), :srem)
37
+ end
48
38
 
49
- redis.srem(key, subscription_id)
39
+ oversized.each do |key|
40
+ each_batch(redis.sscan_each(key, count: redis_scan_count)) do |subscription_ids|
41
+ remove_stale(redis, adapter::SUBSCRIPTION_PREFIX, [[key, subscription_ids]], :srem)
42
+ end
50
43
  end
51
44
  end
52
45
  end
@@ -54,12 +47,20 @@ module GraphQL
54
47
 
55
48
  def clean_topic_fingerprints
56
49
  AnyCable.with_redis do |redis|
57
- redis.scan_each(match: "#{redis_key(adapter::FINGERPRINTS_PREFIX)}*") do |key|
58
- redis.zremrangebyscore(key, "-inf", "0")
59
- redis.zrange(key, 0, -1).each do |fingerprint|
60
- next if redis.exists?(redis_key(adapter::SUBSCRIPTIONS_PREFIX) + fingerprint)
50
+ each_key_batch(redis, adapter::FINGERPRINTS_PREFIX) do |keys|
51
+ redis.pipelined { |pipeline| keys.each { |key| pipeline.zremrangebyscore(key, "-inf", "0") } }
52
+
53
+ bulk_readable, oversized = partition_by_size(redis, keys, :zcard)
54
+
55
+ each_capped_chunk(bulk_readable) do |chunk|
56
+ fingerprints = redis.pipelined { |pipeline| chunk.each { |key| pipeline.zrange(key, 0, -1) } }
57
+ remove_stale(redis, adapter::SUBSCRIPTIONS_PREFIX, chunk.zip(fingerprints), :zrem)
58
+ end
61
59
 
62
- redis.zrem(key, fingerprint)
60
+ oversized.each do |key|
61
+ each_batch(redis.zscan_each(key, count: redis_scan_count)) do |members|
62
+ remove_stale(redis, adapter::SUBSCRIPTIONS_PREFIX, [[key, members.map(&:first)]], :zrem)
63
+ end
63
64
  end
64
65
  end
65
66
  end
@@ -67,6 +68,88 @@ module GraphQL
67
68
 
68
69
  private
69
70
 
71
+ def clean_idle_keys(prefix)
72
+ AnyCable.with_redis do |redis|
73
+ each_key_batch(redis, prefix) do |keys|
74
+ idle_times = redis.pipelined do |pipeline|
75
+ keys.each { |key| pipeline.object("IDLETIME", key) }
76
+ end
77
+
78
+ expired = keys.reject.with_index { |_key, index| idle_times[index]&.<= config.subscription_expiration_seconds }
79
+ redis.del(*expired) unless expired.empty?
80
+ end
81
+ end
82
+ end
83
+
84
+ # Splits keys into those small enough to read in one go and those that have to be iterated
85
+ # with a cursor. Most collections hold only a handful of members, and reading many of them
86
+ # per round trip is what keeps cleanup bearable when there are a lot of keys; the rare huge
87
+ # ones still have to be iterated, or they would blow up memory in the process.
88
+ def partition_by_size(redis, keys, size_command)
89
+ sizes = redis.pipelined do |pipeline|
90
+ keys.each { |key| pipeline.public_send(size_command, key) }
91
+ end
92
+
93
+ bulk_readable, oversized = keys.zip(sizes).partition { |_key, size| size <= redis_scan_count }
94
+ [bulk_readable, oversized.map(&:first)]
95
+ end
96
+
97
+ # Yields key chunks whose combined size stays within redis_scan_count.
98
+ def each_capped_chunk(sized_keys)
99
+ chunk, total = [], 0
100
+
101
+ sized_keys.each do |key, size|
102
+ if chunk.any? && total + size > redis_scan_count
103
+ yield chunk
104
+ chunk, total = [], 0
105
+ end
106
+
107
+ chunk << key
108
+ total += size
109
+ end
110
+
111
+ yield chunk if chunk.any?
112
+ end
113
+
114
+ # Removes the members which no longer have a corresponding key in redis, given `[key, members]`
115
+ # pairs for one or more collections. Takes two round trips for the whole lot, however many
116
+ # collections and members it is handed: one for the existence checks and one for the removals.
117
+ def remove_stale(redis, prefix, members_by_key, remove_command)
118
+ pairs = members_by_key.flat_map { |key, members| members.map { |member| [key, member] } }
119
+ return if pairs.empty?
120
+
121
+ stale = missing_key_pairs(redis, prefix, pairs)
122
+ return if stale.empty?
123
+
124
+ redis.pipelined do |pipeline|
125
+ stale.group_by(&:first).each do |key, key_pairs|
126
+ pipeline.public_send(remove_command, key, key_pairs.map(&:last))
127
+ end
128
+ end
129
+ end
130
+
131
+ # Iterates over the keys matching the given prefix in batches, to allow pipelining.
132
+ def each_key_batch(redis, prefix, &block)
133
+ each_batch(redis.scan_each(match: "#{redis_key(prefix)}*", count: redis_scan_count), &block)
134
+ end
135
+
136
+ # Consumes a lazy enumerator (SCAN family) in batches to keep memory usage bounded.
137
+ def each_batch(enumerator, &block)
138
+ enumerator.each_slice(redis_scan_count, &block)
139
+ end
140
+
141
+ # Returns the `[key, member]` pairs whose member has no corresponding key in redis anymore,
142
+ # checking all of them in a single round trip. The member cannot be looked up on its own,
143
+ # since the same member may well appear in more than one collection.
144
+ def missing_key_pairs(redis, prefix, pairs)
145
+ exists = redis.pipelined do |pipeline|
146
+ pairs.each { |_key, member| pipeline.exists?(redis_key(prefix) + member) }
147
+ end
148
+
149
+ # Reject pairs whose member has a corresponding key in Redis in `exists` array of pipelined responses.
150
+ pairs.reject.with_index { |_pair, index| exists[index] }
151
+ end
152
+
70
153
  def adapter
71
154
  GraphQL::Subscriptions::AnyCableSubscriptions
72
155
  end
@@ -75,6 +158,10 @@ module GraphQL
75
158
  GraphQL::AnyCable.config
76
159
  end
77
160
 
161
+ def redis_scan_count
162
+ config.redis_scan_count.to_i
163
+ end
164
+
78
165
  def redis_key(prefix)
79
166
  "#{config.redis_prefix}-#{prefix}"
80
167
  end
@@ -11,6 +11,7 @@ module GraphQL
11
11
  attr_config subscription_expiration_seconds: nil
12
12
  attr_config use_redis_object_on_cleanup: true
13
13
  attr_config redis_prefix: "graphql" # Here, we set clear redis_prefix without any hyphen. The hyphen is added at the end of this value on our side.
14
+ attr_config redis_scan_count: 1_000 # COUNT for SCAN commands and batch size for pipelined commands during cleanup.
14
15
  end
15
16
  end
16
17
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module AnyCable
5
- VERSION = "1.3.1"
5
+ VERSION = "1.3.3"
6
6
  end
7
7
  end
@@ -4,8 +4,6 @@ require "anycable"
4
4
  require "graphql/subscriptions"
5
5
  require "graphql/anycable/errors"
6
6
 
7
- # rubocop: disable Metrics/AbcSize, Metrics/LineLength, Metrics/MethodLength
8
-
9
7
  # A subscriptions implementation that sends data as AnyCable broadcastings.
10
8
  #
11
9
  # Since AnyCable is aimed to be compatible with ActionCable, this adapter
@@ -129,8 +127,12 @@ module GraphQL
129
127
 
130
128
  raise GraphQL::AnyCable::ChannelConfigurationError unless channel
131
129
 
132
- # Store subscription_id in the channel state to cleanup on disconnect
133
- write_subscription_id(channel, subscription_id)
130
+ # Store the channel's id in its state to cleanup on disconnect. A channel may carry more
131
+ # than one subscription, so the first subscription's id names the channel and every later
132
+ # subscription is added to that same set: otherwise each new subscription would overwrite
133
+ # the stored id, and #delete_channel_subscriptions could only ever find the last one.
134
+ channel_id = read_channel_id(channel) || subscription_id
135
+ write_channel_id(channel, channel_id)
134
136
 
135
137
  events.each do |event|
136
138
  channel.stream_from(redis_key(SUBSCRIPTIONS_PREFIX) + event.fingerprint)
@@ -146,14 +148,14 @@ module GraphQL
146
148
 
147
149
  with_redis do |redis|
148
150
  redis.multi do |pipeline|
149
- pipeline.sadd(redis_key(CHANNEL_PREFIX) + subscription_id, [subscription_id])
151
+ pipeline.sadd(redis_key(CHANNEL_PREFIX) + channel_id, [subscription_id])
150
152
  pipeline.mapped_hmset(redis_key(SUBSCRIPTION_PREFIX) + subscription_id, data)
151
153
  events.each do |event|
152
154
  pipeline.zincrby(redis_key(FINGERPRINTS_PREFIX) + event.topic, 1, event.fingerprint)
153
155
  pipeline.sadd(redis_key(SUBSCRIPTIONS_PREFIX) + event.fingerprint, [subscription_id])
154
156
  end
155
157
  next unless config.subscription_expiration_seconds
156
- pipeline.expire(redis_key(CHANNEL_PREFIX) + subscription_id, config.subscription_expiration_seconds)
158
+ pipeline.expire(redis_key(CHANNEL_PREFIX) + channel_id, config.subscription_expiration_seconds)
157
159
  pipeline.expire(redis_key(SUBSCRIPTION_PREFIX) + subscription_id, config.subscription_expiration_seconds)
158
160
  end
159
161
  end
@@ -179,7 +181,7 @@ module GraphQL
179
181
  def delete_channel_subscriptions(channel)
180
182
  raise(ArgumentError, "Please pass channel instance to #{__method__} in your #unsubscribed method") if channel.is_a?(String)
181
183
 
182
- channel_id = read_subscription_id(channel)
184
+ channel_id = read_channel_id(channel)
183
185
 
184
186
  # Missing in case disconnect happens before #execute
185
187
  return unless channel_id
@@ -215,7 +217,8 @@ module GraphQL
215
217
 
216
218
  private
217
219
 
218
- def read_subscription_id(channel)
220
+ # Channel cleanup key kept in AnyCable istate as "sid"
221
+ def read_channel_id(channel)
219
222
  return channel.instance_variable_get(:@__sid__) if channel.instance_variable_defined?(:@__sid__)
220
223
 
221
224
  istate = fetch_channel_istate(channel)
@@ -225,7 +228,7 @@ module GraphQL
225
228
  channel.instance_variable_set(:@__sid__, istate["sid"])
226
229
  end
227
230
 
228
- def write_subscription_id(channel, val)
231
+ def write_channel_id(channel, val)
229
232
  channel.connection.anycable_socket.istate["sid"] = val
230
233
  channel.instance_variable_set(:@__sid__, val)
231
234
  end
@@ -249,4 +252,3 @@ module GraphQL
249
252
  end
250
253
  end
251
254
  end
252
- # rubocop: enable Metrics/AbcSize, Metrics/LineLength, Metrics/MethodLength
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: 1.3.1
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Novikov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-28 00:00:00.000000000 Z
11
+ date: 2026-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anycable-core
@@ -96,14 +96,14 @@ dependencies:
96
96
  name: bundler
97
97
  requirement: !ruby/object:Gem::Requirement
98
98
  requirements:
99
- - - "~>"
99
+ - - ">="
100
100
  - !ruby/object:Gem::Version
101
101
  version: '2.0'
102
102
  type: :development
103
103
  prerelease: false
104
104
  version_requirements: !ruby/object:Gem::Requirement
105
105
  requirements:
106
- - - "~>"
106
+ - - ">="
107
107
  - !ruby/object:Gem::Version
108
108
  version: '2.0'
109
109
  - !ruby/object:Gem::Dependency
@@ -202,7 +202,8 @@ files:
202
202
  homepage: https://github.com/Envek/graphql-anycable
203
203
  licenses:
204
204
  - MIT
205
- metadata: {}
205
+ metadata:
206
+ rubygems_mfa_required: 'true'
206
207
  post_install_message:
207
208
  rdoc_options: []
208
209
  require_paths: